mirror of
https://github.com/Unleash/unleash.git
synced 2024-12-22 19:07:54 +01:00
feat: export all features in project (#5677)
This commit is contained in:
parent
1043efd89f
commit
7800d9d1b4
@ -7,10 +7,12 @@ import useToast from 'hooks/useToast';
|
||||
import type { FeatureSchema } from 'openapi';
|
||||
|
||||
import { formatUnknownError } from 'utils/formatUnknownError';
|
||||
import { ConditionallyRender } from '../../common/ConditionallyRender/ConditionallyRender';
|
||||
|
||||
interface IExportDialogProps {
|
||||
showExportDialog: boolean;
|
||||
data: Pick<FeatureSchema, 'name'>[];
|
||||
project?: string;
|
||||
onClose: () => void;
|
||||
onConfirm?: () => void;
|
||||
environments: string[];
|
||||
@ -24,6 +26,7 @@ const StyledSelect = styled(GeneralSelect)(({ theme }) => ({
|
||||
export const ExportDialog = ({
|
||||
showExportDialog,
|
||||
data,
|
||||
project,
|
||||
onClose,
|
||||
onConfirm,
|
||||
environments,
|
||||
@ -63,6 +66,7 @@ export const ExportDialog = ({
|
||||
const payload = {
|
||||
features: data.map((feature) => feature.name),
|
||||
environment: selected,
|
||||
project,
|
||||
};
|
||||
const res = await createExport(payload);
|
||||
const body = await res.json();
|
||||
@ -84,9 +88,23 @@ export const ExportDialog = ({
|
||||
secondaryButtonText='Cancel'
|
||||
>
|
||||
<Box ref={ref}>
|
||||
The current search filter will be used to export feature
|
||||
toggles. Currently {data.length} feature toggles will be
|
||||
exported.
|
||||
<ConditionallyRender
|
||||
condition={data.length > 0}
|
||||
show={
|
||||
<span>
|
||||
The current search filter will be used to export
|
||||
feature toggles. Currently {data.length} feature
|
||||
toggles will be exported.
|
||||
</span>
|
||||
}
|
||||
elseShow={
|
||||
<span>
|
||||
You will export all feature toggles from this
|
||||
project.
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
|
||||
<br />
|
||||
<br />
|
||||
<Typography>
|
||||
|
@ -42,7 +42,6 @@ export const ProjectFeatureTogglesHeader: VFC<
|
||||
totalItems,
|
||||
searchQuery,
|
||||
onChangeSearchQuery,
|
||||
dataToExport,
|
||||
environmentsToExport,
|
||||
actions,
|
||||
}) => {
|
||||
@ -100,7 +99,7 @@ export const ProjectFeatureTogglesHeader: VFC<
|
||||
show={
|
||||
<>
|
||||
<Tooltip
|
||||
title='Export toggles visible in the table below'
|
||||
title='Export all project toggles'
|
||||
arrow
|
||||
>
|
||||
<IconButton
|
||||
@ -123,7 +122,8 @@ export const ProjectFeatureTogglesHeader: VFC<
|
||||
showExportDialog={
|
||||
showExportDialog
|
||||
}
|
||||
data={dataToExport || []}
|
||||
project={projectId}
|
||||
data={[]}
|
||||
onClose={() =>
|
||||
setShowExportDialog(false)
|
||||
}
|
||||
|
@ -786,10 +786,21 @@ export default class ExportImportService
|
||||
userName: string,
|
||||
userId: number,
|
||||
): Promise<ExportResultSchema> {
|
||||
const featureNames =
|
||||
typeof query.tag === 'string'
|
||||
? await this.featureTagService.listFeatures(query.tag)
|
||||
: (query.features as string[]) || [];
|
||||
let featureNames: string[] = [];
|
||||
if (typeof query.tag === 'string') {
|
||||
featureNames = await this.featureTagService.listFeatures(query.tag);
|
||||
} else if (Array.isArray(query.features) && query.features.length) {
|
||||
featureNames = query.features;
|
||||
} else if (typeof query.project === 'string') {
|
||||
const allProjectFeatures = await this.toggleStore.getAll({
|
||||
project: query.project,
|
||||
});
|
||||
featureNames = allProjectFeatures.map((feature) => feature.name);
|
||||
} else {
|
||||
const allFeatures = await this.toggleStore.getAll();
|
||||
featureNames = allFeatures.map((feature) => feature.name);
|
||||
}
|
||||
|
||||
const [
|
||||
features,
|
||||
featureEnvironments,
|
||||
|
@ -548,7 +548,7 @@ test('should export tags', async () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('returns no features, when no feature was requested', async () => {
|
||||
test('returns all features, when no explicit feature was requested', async () => {
|
||||
await createProjects();
|
||||
await createToggle({
|
||||
name: defaultFeatureName,
|
||||
@ -567,7 +567,41 @@ test('returns no features, when no feature was requested', async () => {
|
||||
.set('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
expect(body.features).toHaveLength(0);
|
||||
expect(body.features).toHaveLength(2);
|
||||
});
|
||||
|
||||
test('returns all project features', async () => {
|
||||
await createProjects();
|
||||
await createToggle({
|
||||
name: defaultFeatureName,
|
||||
description: 'the #1 feature',
|
||||
});
|
||||
await createToggle({
|
||||
name: 'second_feature',
|
||||
description: 'the #1 feature',
|
||||
});
|
||||
const { body } = await app.request
|
||||
.post('/api/admin/features-batch/export')
|
||||
.send({
|
||||
environment: 'default',
|
||||
project: DEFAULT_PROJECT,
|
||||
})
|
||||
.set('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
expect(body.features).toHaveLength(2);
|
||||
|
||||
const { body: otherProject } = await app.request
|
||||
.post('/api/admin/features-batch/export')
|
||||
.send({
|
||||
environment: 'default',
|
||||
features: [], // should be ignored because we have project
|
||||
project: 'other_project',
|
||||
})
|
||||
.set('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
expect(otherProject.features).toHaveLength(0);
|
||||
});
|
||||
|
||||
const variants: VariantsSchema = [
|
||||
|
@ -305,6 +305,7 @@ export default class FeatureToggleStore implements IFeatureToggleStore {
|
||||
async getAllByNames(names: string[]): Promise<FeatureToggle[]> {
|
||||
const query = this.db<FeaturesTable>(TABLE).orderBy('name', 'asc');
|
||||
query.whereIn('name', names);
|
||||
|
||||
const rows = await query;
|
||||
return rows.map(this.rowToFeature);
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ export const exportQuerySchema = {
|
||||
type: 'object',
|
||||
description:
|
||||
'Available query parameters for the [deprecated export/import](https://docs.getunleash.io/reference/deploy/import-export) functionality.',
|
||||
oneOf: [
|
||||
anyOf: [
|
||||
{
|
||||
required: ['environment', 'features'],
|
||||
properties: {
|
||||
@ -30,7 +30,8 @@ export const exportQuerySchema = {
|
||||
type: 'string',
|
||||
minLength: 1,
|
||||
},
|
||||
description: 'Selects features to export by name.',
|
||||
description:
|
||||
'Selects features to export by name. If the list is empty all features are returned.',
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -41,8 +42,19 @@ export const exportQuerySchema = {
|
||||
tag: {
|
||||
type: 'string',
|
||||
example: 'release',
|
||||
description: 'Selects features to export by tag.',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
required: ['environment', 'project'],
|
||||
properties: {
|
||||
...commonProps,
|
||||
project: {
|
||||
type: 'string',
|
||||
example: 'my-project',
|
||||
description:
|
||||
'Selects features to export by tag. Takes precedence over the features field.',
|
||||
'Selects project to export the features from. Used when no tags or features are provided.',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user