Skip to content

Commit 4ff41fa

Browse files
authored
chore: add warning about release plans in import-export (#10805)
https://linear.app/unleash/issue/2-3965/add-a-note-if-were-exporting-that-we-dont-understand-release-plans-in Adds a warning about release plans in import/export. It's not trivial to know every flag that will be exported in every scenario, and whether they have release plans, so our logic here is "have you configured release templates?" <img width="706" height="516" alt="image" src="https://github.com/user-attachments/assets/68ba8618-9887-491c-b46e-256b45700d74" /> <img width="732" height="503" alt="image" src="https://github.com/user-attachments/assets/086e37d4-78ae-4647-93a2-5d1845c2758a" />
1 parent f3ab70a commit 4ff41fa

File tree

5 files changed

+65
-36
lines changed

5 files changed

+65
-36
lines changed

frontend/src/component/feature/FeatureToggleList/ExportDialog.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { createRef, useState } from 'react';
2-
import { styled, Typography, Box } from '@mui/material';
2+
import { styled, Typography, Box, Alert } from '@mui/material';
33
import { Dialogue } from 'component/common/Dialogue/Dialogue';
44
import GeneralSelect from 'component/common/GeneralSelect/GeneralSelect';
55
import { useExportApi } from 'hooks/api/actions/useExportApi/useExportApi';
@@ -8,10 +8,11 @@ import type { FeatureSchema } from 'openapi';
88

99
import { formatUnknownError } from 'utils/formatUnknownError';
1010
import { ConditionallyRender } from '../../common/ConditionallyRender/ConditionallyRender.tsx';
11+
import { useReleasePlanTemplates } from 'hooks/api/getters/useReleasePlanTemplates/useReleasePlanTemplates.ts';
1112

1213
interface IExportDialogProps {
1314
showExportDialog: boolean;
14-
data: Pick<FeatureSchema, 'name'>[];
15+
data: Pick<FeatureSchema, 'name' | 'environments'>[];
1516
project?: string;
1617
onClose: () => void;
1718
onConfirm?: () => void;
@@ -35,6 +36,8 @@ export const ExportDialog = ({
3536
const { createExport } = useExportApi();
3637
const ref = createRef<HTMLDivElement>();
3738
const { setToastApiError } = useToast();
39+
const { templates } = useReleasePlanTemplates();
40+
const hasReleaseTemplates = Boolean(templates.length);
3841

3942
const getOptions = () =>
4043
environments.map((env) => ({
@@ -88,6 +91,13 @@ export const ExportDialog = ({
8891
secondaryButtonText='Cancel'
8992
>
9093
<Box ref={ref}>
94+
{hasReleaseTemplates && (
95+
<Alert severity='warning' sx={{ mb: 4 }}>
96+
Exporting does not include release plans. You may need
97+
to set up new release plans for the imported feature
98+
flags.
99+
</Alert>
100+
)}
91101
<ConditionallyRender
92102
condition={data.length > 0}
93103
show={
Lines changed: 43 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { FC } from 'react';
22
import { Box, styled, Typography } from '@mui/material';
3+
import { useReleasePlanTemplates } from 'hooks/api/getters/useReleasePlanTemplates/useReleasePlanTemplates';
34

45
const ImportExplanationContainer = styled(Box)(({ theme }) => ({
56
backgroundColor: theme.palette.background.elevation2,
@@ -17,33 +18,45 @@ const ImportExplanationDescription = styled(Box)(({ theme }) => ({
1718
fontSize: theme.fontSizes.smallBody,
1819
}));
1920

20-
export const ImportExplanation: FC = () => (
21-
<ImportExplanationContainer>
22-
<ImportExplanationHeader>
23-
What is being imported?
24-
</ImportExplanationHeader>
25-
<ImportExplanationDescription>
26-
Feature flags will be imported with full configuration:
27-
<ul>
28-
<li>strategies</li>
29-
<li>context fields</li>
30-
<li>variants</li>
31-
<li>tags</li>
32-
<li>feature flag status</li>
33-
<li>feature dependencies</li>
34-
<li>feature links</li>
35-
</ul>
36-
</ImportExplanationDescription>
37-
<ImportExplanationHeader>Exceptions?</ImportExplanationHeader>
38-
<ImportExplanationDescription>
39-
If the feature flag already exists in the new instance, it will be
40-
overwritten
41-
</ImportExplanationDescription>
42-
<ImportExplanationHeader>What is not imported?</ImportExplanationHeader>
43-
<ImportExplanationDescription sx={{ marginBottom: 0 }}>
44-
If we detect segments or custom strategies in your imported file, we
45-
will stop the import. You need to create them first in the new
46-
instance and run the import again
47-
</ImportExplanationDescription>
48-
</ImportExplanationContainer>
49-
);
21+
export const ImportExplanation: FC = () => {
22+
const { templates } = useReleasePlanTemplates();
23+
const hasReleaseTemplates = Boolean(templates.length);
24+
return (
25+
<ImportExplanationContainer>
26+
<ImportExplanationHeader>
27+
What is being imported?
28+
</ImportExplanationHeader>
29+
<ImportExplanationDescription>
30+
Feature flags will be imported with full configuration:
31+
<ul>
32+
<li>strategies</li>
33+
<li>context fields</li>
34+
<li>variants</li>
35+
<li>tags</li>
36+
<li>feature flag status</li>
37+
<li>feature dependencies</li>
38+
<li>feature links</li>
39+
</ul>
40+
</ImportExplanationDescription>
41+
<ImportExplanationHeader>Exceptions?</ImportExplanationHeader>
42+
<ImportExplanationDescription>
43+
If the feature flag already exists in the new instance, it will
44+
be overwritten
45+
</ImportExplanationDescription>
46+
<ImportExplanationHeader>
47+
What is not imported?
48+
</ImportExplanationHeader>
49+
<ImportExplanationDescription sx={{ marginBottom: 0 }}>
50+
If we detect segments or custom strategies in your imported
51+
file, we will stop the import. You need to create them first in
52+
the new instance and run the import again
53+
</ImportExplanationDescription>
54+
{hasReleaseTemplates && (
55+
<ImportExplanationDescription sx={{ marginTop: 2 }}>
56+
Release plans are not included in the import. You may need
57+
to set up new release plans for the imported feature flags
58+
</ImportExplanationDescription>
59+
)}
60+
</ImportExplanationContainer>
61+
);
62+
};

frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectFeatureTogglesHeader/ProjectFeatureTogglesHeader.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { type FC, type ReactNode, useState } from 'react';
2-
import { Box, IconButton, Tooltip } from '@mui/material';
2+
import { Box, IconButton, styled, Tooltip } from '@mui/material';
33
import useLoading from 'hooks/useLoading';
44
import { PageHeader } from 'component/common/PageHeader/PageHeader';
55
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
@@ -8,6 +8,10 @@ import IosShare from '@mui/icons-material/IosShare';
88
import { FlagCreationButton } from './FlagCreationButton/FlagCreationButton.tsx';
99
import { ImportButton } from './ImportButton/ImportButton.tsx';
1010

11+
const StyledIconButton = styled(IconButton)(({ theme }) => ({
12+
padding: theme.spacing(1.5),
13+
}));
14+
1115
type ProjectFeatureTogglesHeaderProps = {
1216
isLoading?: boolean;
1317
totalItems?: number;
@@ -39,12 +43,12 @@ export const ProjectFeatureTogglesHeader: FC<
3943
<>
4044
{actions}
4145
<Tooltip title='Export all project flags' arrow>
42-
<IconButton
46+
<StyledIconButton
4347
data-loading
4448
onClick={() => setShowExportDialog(true)}
4549
>
4650
<IosShare />
47-
</IconButton>
51+
</StyledIconButton>
4852
</Tooltip>
4953
<ImportButton />
5054

src/lib/features/feature-toggle/fakes/fake-feature-strategies-store.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -335,7 +335,8 @@ export default class FakeFeatureStrategiesStore
335335
this.featureStrategies.filter(
336336
(strategy) =>
337337
features.includes(strategy.featureName) &&
338-
strategy.environment === environment,
338+
strategy.environment === environment &&
339+
!strategy.milestoneId,
339340
),
340341
);
341342
}

src/lib/features/feature-toggle/feature-toggle-strategies-store.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,7 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore {
278278
.select(COLUMNS)
279279
.from<IFeatureStrategiesTable>(T.featureStrategies)
280280
.whereIn('feature_name', features)
281+
.andWhere('milestone_id', null)
281282
.orderBy('feature_name', 'asc');
282283
if (environment) {
283284
query.where('environment', environment);

0 commit comments

Comments
 (0)