Skip to content

Commit

Permalink
feat: export all features in project (#5677)
Browse files Browse the repository at this point in the history
  • Loading branch information
kwasniew authored Dec 19, 2023
1 parent 1043efd commit 7800d9d
Show file tree
Hide file tree
Showing 6 changed files with 91 additions and 15 deletions.
24 changes: 21 additions & 3 deletions frontend/src/component/feature/FeatureToggleList/ExportDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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[];
Expand All @@ -24,6 +26,7 @@ const StyledSelect = styled(GeneralSelect)(({ theme }) => ({
export const ExportDialog = ({
showExportDialog,
data,
project,
onClose,
onConfirm,
environments,
Expand Down Expand Up @@ -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();
Expand All @@ -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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ export const ProjectFeatureTogglesHeader: VFC<
totalItems,
searchQuery,
onChangeSearchQuery,
dataToExport,
environmentsToExport,
actions,
}) => {
Expand Down Expand Up @@ -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
Expand All @@ -123,7 +122,8 @@ export const ProjectFeatureTogglesHeader: VFC<
showExportDialog={
showExportDialog
}
data={dataToExport || []}
project={projectId}
data={[]}
onClose={() =>
setShowExportDialog(false)
}
Expand Down
19 changes: 15 additions & 4 deletions src/lib/features/export-import-toggles/export-import-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
38 changes: 36 additions & 2 deletions src/lib/features/export-import-toggles/export-import.e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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 = [
Expand Down
1 change: 1 addition & 0 deletions src/lib/features/feature-toggle/feature-toggle-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
18 changes: 15 additions & 3 deletions src/lib/openapi/spec/export-query-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand All @@ -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.',
},
},
},
Expand All @@ -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.',
},
},
},
Expand Down

0 comments on commit 7800d9d

Please sign in to comment.