Skip to content

feat: Confirm flag changes modal #31031

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Open

Conversation

ordehi
Copy link
Contributor

@ordehi ordehi commented Apr 9, 2025

Problem

Users often report issues with others changing flags when they shouldn't, which has an impact in their app. They'd like a way to prevent or make it harder to make such changes. A quick win is to have an optional modal that asks for confirmation (listing the changes made) before changing things like flag status, variants and release conditions.

Changes

This adds a modal that shows when the user makes changes to:

  • Flag status (enable/disable)
  • Flag type (AB, multivariate, etc)
  • Release conditions
  • Variants

The modal lists the changes the user is trying to save to the flag:

image

The user has to click "Save changes" before the changes are applied.

The modal is optional, they need to enable it in settings for the project:

image

Does this work well for both Cloud and self-hosted?

Yes

How did you test this code?

Tested locally.

@ordehi ordehi changed the title feat(flags):Confirm changes modal feat: Confirm changes modal Apr 9, 2025
@ordehi ordehi changed the title feat: Confirm changes modal feat: Confirm flag changes modal Apr 9, 2025
@posthog-bot
Copy link
Contributor

📸 UI snapshots have been updated

19 snapshot changes in total. 0 added, 19 modified, 0 deleted:

Triggered by this commit.

👉 Review this PR's diff of snapshots.

@posthog-bot
Copy link
Contributor

📸 UI snapshots have been updated

1 snapshot changes in total. 0 added, 1 modified, 0 deleted:

  • chromium: 0 added, 1 modified, 0 deleted (diff for shard 2)
  • webkit: 0 added, 0 modified, 0 deleted

Triggered by this commit.

👉 Review this PR's diff of snapshots.

@ordehi ordehi marked this pull request as ready for review April 11, 2025 15:24
Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR Summary

Added a feature flag confirmation system to prevent accidental changes to feature flags, with a modal that displays pending changes before they're applied.

  • Added FeatureFlagConfirmationModal.tsx that shows specific changes being made to flags (status, variants, conditions) before saving
  • Added FeatureFlagRolloutConfirmationModal.tsx specifically for confirming rollout condition changes
  • Added flags_require_confirmation boolean field to the Team model with a migration (0708)
  • Added team setting in FeatureFlagSettings.tsx to toggle confirmation requirement
  • Implemented changesRequireConfirmation() in featureFlagLogic.ts to detect significant changes requiring confirmation

12 file(s) reviewed, 2 comment(s)
Edit PR Review Bot Settings | Greptile

Comment on lines +144 to +148
// Wrapper for any function that modifies rollout conditions
const withConfirmation = (callback: () => void): (() => void) => {
// Don't require confirmation for rollout changes - these are relatively minor adjustments
return callback
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: The withConfirmation function doesn't actually show the confirmation modal - it just returns the callback directly. This makes the confirmation feature non-functional. The function should check flagsRequireConfirmation and either show the modal or execute the callback directly.

Suggested change
// Wrapper for any function that modifies rollout conditions
const withConfirmation = (callback: () => void): (() => void) => {
// Don't require confirmation for rollout changes - these are relatively minor adjustments
return callback
}
// Wrapper for any function that modifies rollout conditions
const withConfirmation = (callback: () => void): (() => void) => {
// Don't require confirmation for rollout changes - these are relatively minor adjustments
return () => {
if (flagsRequireConfirmation && id && id !== 'new') {
setConfirmRolloutModalVisible(true)
setPendingRolloutUpdate({ callback })
} else {
callback()
}
}
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

^ this is good feedback IMO

Comment on lines +706 to +708
document
.getElementById('feature-flag')
?.dispatchEvent(new Event('submit', { cancelable: true, bubbles: true }))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Consider adding error handling in case the form element isn't found. If the form ID changes or the element isn't in the DOM, this would silently fail.

@posthog-bot
Copy link
Contributor

📸 UI snapshots have been updated

16 snapshot changes in total. 0 added, 16 modified, 0 deleted:

  • chromium: 0 added, 16 modified, 0 deleted (diff for shard 1)
  • webkit: 0 added, 0 modified, 0 deleted

Triggered by this commit.

👉 Review this PR's diff of snapshots.

Comment on lines +133 to +135
// State for feature flag confirmation modal
const [flagChanges, setFlagChanges] = useState<string[]>([])
const [showSaveConfirmModal, setShowSaveConfirmModal] = useState(false)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we try to avoid mixing react state management (i.e. useState, useEffect) with our existing state management via Kea. see https://posthog.com/handbook/engineering/conventions/frontend-coding#dos-and-donts.

Try making these into a kea logic instead! I think it would be appropriate to define them in featureFlagLogic.ts.

Comment on lines +144 to +148
// Wrapper for any function that modifies rollout conditions
const withConfirmation = (callback: () => void): (() => void) => {
// Don't require confirmation for rollout changes - these are relatively minor adjustments
return callback
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

^ this is good feedback IMO

Comment on lines +138 to +142
// State for rollout condition confirmation modal
const [confirmRolloutModalVisible, setConfirmRolloutModalVisible] = useState(false)
const [pendingRolloutUpdate, setPendingRolloutUpdate] = useState<{
callback: () => void
} | null>(null)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as above; these should be logics in FeatureFlagReleaseConditionLogic.ts

@@ -0,0 +1,47 @@
import { LemonButton, LemonModal } from '@posthog/lemon-ui'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this modal is super similar to the FeatureFlagConfirmationModal – why not combine them both into a ConfirmationModal.tsx that lives in /scenes/feature-flags/ and serves both purposes? That feels more in the spirit of DRY (Don't Repeat Yourself) to me.

@@ -211,20 +229,20 @@ export function FeatureFlagReleaseConditions({
icon={<IconCopy />}
noPadding
tooltip="Duplicate condition set"
onClick={() => duplicateConditionSet(index)}
onClick={withConfirmation(() => duplicateConditionSet(index))}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why are these methods all wrapped in withConfirmation() if that method just returns the callback?

}

return (
<LemonModal
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

given that this is a component with which we expect users to interact, this should be LemonDialog over LemonModal.

}

// Check for release condition changes
if (JSON.stringify(originalFlag.filters.groups) !== JSON.stringify(updatedFlag.filters.groups)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can use lodash.deepEqual here instead of stringifying


class Migration(migrations.Migration):
dependencies = [
("posthog", "0707_file_system_shortcut"),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

migrations need to be updated – this should be generated and not hand-rolled.

@dmarticus
Copy link
Contributor

@ordehi This is a great start; but there's a few things we need to clean up here to make this production-ready! As discussed in Slack, I'm happy to run with them, and call out on the subsequent PR what I did differently than your original approach. Thanks for getting the ball rolling on this!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants