Skip to content

Commit

Permalink
Add button to duplicate feature flag
Browse files Browse the repository at this point in the history
Rather than actually duplicating the feature flag, it navigates to the new feature flag page and pre-populates the fields with the values from the source flag. This way, the user is required to pick a unique feature flag key.

Fixes #21788
  • Loading branch information
haacked committed Mar 1, 2025
1 parent cbf88b2 commit 1a33a98
Show file tree
Hide file tree
Showing 4 changed files with 42 additions and 0 deletions.
7 changes: 7 additions & 0 deletions frontend/src/scenes/feature-flags/FeatureFlag.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -546,6 +546,13 @@ export function FeatureFlag({ id }: { id?: string } = {}): JSX.Element {
</>
)}

<LemonButton
to={urls.featureFlagDuplicate(featureFlag.id)}
fullWidth
>
<span>Duplicate feature flag</span>
</LemonButton>
<LemonDivider />
<AccessControlledLemonButton
userAccessLevel={featureFlag.user_access_level}
minAccessLevel="editor"
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/scenes/feature-flags/FeatureFlags.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,10 @@ export function OverViewTab({
</AccessControlledLemonButton>
)}

<LemonButton to={urls.featureFlagDuplicate(featureFlag.id)} data-attr="usage" fullWidth>
Duplicate feature flag
</LemonButton>

<LemonButton to={tryInInsightsUrl(featureFlag)} data-attr="usage" fullWidth>
Try out in Insights
</LemonButton>
Expand Down
30 changes: 30 additions & 0 deletions frontend/src/scenes/feature-flags/featureFlagLogic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -574,6 +574,31 @@ export const featureFlagLogic = kea<featureFlagLogicType>([
loaders(({ values, props, actions }) => ({
featureFlag: {
loadFeatureFlag: async () => {
const sourceId = router.values.searchParams.sourceId
if (props.id === 'new' && sourceId) {
// Used when "duplicating a feature flag". This populates the form with the source flag's data.
const sourceFlag = await api.featureFlags.get(sourceId)
const {
id,
created_at,
created_by,
is_simple_flag,
experiment_set,
features,
surveys,
...flagToKeep
} = sourceFlag

// Remove sourceId from URL
router.actions.replace(router.values.location.pathname)

return {
...NEW_FLAG,
...flagToKeep,
key: '',
} as FeatureFlagType
}

if (props.id && props.id !== 'new' && props.id !== 'link') {
try {
const retrievedFlag: FeatureFlagType = await api.featureFlags.get(props.id)
Expand Down Expand Up @@ -1205,6 +1230,11 @@ export const featureFlagLogic = kea<featureFlagLogicType>([
},
})),
afterMount(({ props, actions }) => {
if (props.id === 'new' && router.values.searchParams.sourceId) {
actions.loadFeatureFlag()
return
}

const foundFlag = featureFlagsLogic
.findMounted()
?.values.featureFlags.results.find((flag) => flag.id === props.id)
Expand Down
1 change: 1 addition & 0 deletions frontend/src/scenes/urls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ export const urls = {
experimentsSharedMetric: (id: string | number): string => `/experiments/shared-metrics/${id}`,
featureFlags: (tab?: string): string => `/feature_flags${tab ? `?tab=${tab}` : ''}`,
featureFlag: (id: string | number): string => `/feature_flags/${id}`,
featureFlagDuplicate: (sourceId: number | string | null): string => `/feature_flags/new?sourceId=${sourceId}`,
featureManagement: (id?: string | number): string => `/features${id ? `/${id}` : ''}`,
errorTracking: (): string => '/error_tracking',
errorTrackingConfiguration: (): string => '/error_tracking/configuration',
Expand Down

0 comments on commit 1a33a98

Please sign in to comment.