diff --git a/frontend/src/component/feature/FeatureToggleList/ExportDialog.tsx b/frontend/src/component/feature/FeatureToggleList/ExportDialog.tsx index d7a5df3e9e..4efc30f4ab 100644 --- a/frontend/src/component/feature/FeatureToggleList/ExportDialog.tsx +++ b/frontend/src/component/feature/FeatureToggleList/ExportDialog.tsx @@ -1,5 +1,5 @@ import { createRef, useState } from 'react'; -import { styled, Typography, Box } from '@mui/material'; +import { styled, Typography, Box, Alert } from '@mui/material'; import { Dialogue } from 'component/common/Dialogue/Dialogue'; import GeneralSelect from 'component/common/GeneralSelect/GeneralSelect'; import { useExportApi } from 'hooks/api/actions/useExportApi/useExportApi'; @@ -8,10 +8,11 @@ import type { FeatureSchema } from 'openapi'; import { formatUnknownError } from 'utils/formatUnknownError'; import { ConditionallyRender } from '../../common/ConditionallyRender/ConditionallyRender.tsx'; +import { useReleasePlanTemplates } from 'hooks/api/getters/useReleasePlanTemplates/useReleasePlanTemplates.ts'; interface IExportDialogProps { showExportDialog: boolean; - data: Pick[]; + data: Pick[]; project?: string; onClose: () => void; onConfirm?: () => void; @@ -35,6 +36,8 @@ export const ExportDialog = ({ const { createExport } = useExportApi(); const ref = createRef(); const { setToastApiError } = useToast(); + const { templates } = useReleasePlanTemplates(); + const hasReleaseTemplates = Boolean(templates.length); const getOptions = () => environments.map((env) => ({ @@ -88,6 +91,13 @@ export const ExportDialog = ({ secondaryButtonText='Cancel' > + {hasReleaseTemplates && ( + + Exporting does not include release plans. You may need + to set up new release plans for the imported feature + flags. + + )} 0} show={ diff --git a/frontend/src/component/project/Project/Import/configure/ImportExplanation.tsx b/frontend/src/component/project/Project/Import/configure/ImportExplanation.tsx index c114bf81c9..e920275cd8 100644 --- a/frontend/src/component/project/Project/Import/configure/ImportExplanation.tsx +++ b/frontend/src/component/project/Project/Import/configure/ImportExplanation.tsx @@ -1,5 +1,6 @@ import type { FC } from 'react'; import { Box, styled, Typography } from '@mui/material'; +import { useReleasePlanTemplates } from 'hooks/api/getters/useReleasePlanTemplates/useReleasePlanTemplates'; const ImportExplanationContainer = styled(Box)(({ theme }) => ({ backgroundColor: theme.palette.background.elevation2, @@ -17,33 +18,45 @@ const ImportExplanationDescription = styled(Box)(({ theme }) => ({ fontSize: theme.fontSizes.smallBody, })); -export const ImportExplanation: FC = () => ( - - - What is being imported? - - - Feature flags will be imported with full configuration: -
    -
  • strategies
  • -
  • context fields
  • -
  • variants
  • -
  • tags
  • -
  • feature flag status
  • -
  • feature dependencies
  • -
  • feature links
  • -
-
- Exceptions? - - If the feature flag already exists in the new instance, it will be - overwritten - - What is not imported? - - If we detect segments or custom strategies in your imported file, we - will stop the import. You need to create them first in the new - instance and run the import again - -
-); +export const ImportExplanation: FC = () => { + const { templates } = useReleasePlanTemplates(); + const hasReleaseTemplates = Boolean(templates.length); + return ( + + + What is being imported? + + + Feature flags will be imported with full configuration: +
    +
  • strategies
  • +
  • context fields
  • +
  • variants
  • +
  • tags
  • +
  • feature flag status
  • +
  • feature dependencies
  • +
  • feature links
  • +
+
+ Exceptions? + + If the feature flag already exists in the new instance, it will + be overwritten + + + What is not imported? + + + If we detect segments or custom strategies in your imported + file, we will stop the import. You need to create them first in + the new instance and run the import again + + {hasReleaseTemplates && ( + + Release plans are not included in the import. You may need + to set up new release plans for the imported feature flags + + )} +
+ ); +}; diff --git a/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectFeatureTogglesHeader/ProjectFeatureTogglesHeader.tsx b/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectFeatureTogglesHeader/ProjectFeatureTogglesHeader.tsx index 90989551dd..98496f56b7 100644 --- a/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectFeatureTogglesHeader/ProjectFeatureTogglesHeader.tsx +++ b/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectFeatureTogglesHeader/ProjectFeatureTogglesHeader.tsx @@ -1,5 +1,5 @@ import { type FC, type ReactNode, useState } from 'react'; -import { Box, IconButton, Tooltip } from '@mui/material'; +import { Box, IconButton, styled, Tooltip } from '@mui/material'; import useLoading from 'hooks/useLoading'; import { PageHeader } from 'component/common/PageHeader/PageHeader'; import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; @@ -8,6 +8,10 @@ import IosShare from '@mui/icons-material/IosShare'; import { FlagCreationButton } from './FlagCreationButton/FlagCreationButton.tsx'; import { ImportButton } from './ImportButton/ImportButton.tsx'; +const StyledIconButton = styled(IconButton)(({ theme }) => ({ + padding: theme.spacing(1.5), +})); + type ProjectFeatureTogglesHeaderProps = { isLoading?: boolean; totalItems?: number; @@ -39,12 +43,12 @@ export const ProjectFeatureTogglesHeader: FC< <> {actions} - setShowExportDialog(true)} > - + diff --git a/src/lib/features/feature-toggle/fakes/fake-feature-strategies-store.ts b/src/lib/features/feature-toggle/fakes/fake-feature-strategies-store.ts index fb50a844e9..555709bf7e 100644 --- a/src/lib/features/feature-toggle/fakes/fake-feature-strategies-store.ts +++ b/src/lib/features/feature-toggle/fakes/fake-feature-strategies-store.ts @@ -335,7 +335,8 @@ export default class FakeFeatureStrategiesStore this.featureStrategies.filter( (strategy) => features.includes(strategy.featureName) && - strategy.environment === environment, + strategy.environment === environment && + !strategy.milestoneId, ), ); } diff --git a/src/lib/features/feature-toggle/feature-toggle-strategies-store.ts b/src/lib/features/feature-toggle/feature-toggle-strategies-store.ts index 1663d25c1d..a0027f54ff 100644 --- a/src/lib/features/feature-toggle/feature-toggle-strategies-store.ts +++ b/src/lib/features/feature-toggle/feature-toggle-strategies-store.ts @@ -278,6 +278,7 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore { .select(COLUMNS) .from(T.featureStrategies) .whereIn('feature_name', features) + .andWhere('milestone_id', null) .orderBy('feature_name', 'asc'); if (environment) { query.where('environment', environment);