Skip to content

✨ [FFL-2596] Feature Flags tab — overrides (stacked PR 3 of 3) - #4912

Draft
kellyw1806 wants to merge 1 commit into
kelly.wang/ffl-2858from
kelly.wang/ffl-2596
Draft

✨ [FFL-2596] Feature Flags tab — overrides (stacked PR 3 of 3)#4912
kellyw1806 wants to merge 1 commit into
kelly.wang/ffl-2858from
kelly.wang/ffl-2596

Conversation

@kellyw1806

@kellyw1806 kellyw1806 commented Jul 24, 2026

Copy link
Copy Markdown

Motivation

Stacked on #4916 (catalog fetch). Allows users to create overrides for a flag's value (allows for switching between variables) locally and have it take effect on the inspected page.

Stack (review bottom-up)

PR Base What it adds
1 of 3 #4913 · FFL-2597 main OAuth sign-in
2 of 3 #4916 · FFL-2858 ffl-2597 catalog browsing
3 of 3 #4912 (this) · FFL-2596 ffl-2858 flag overrides

⚠️ Base is kelly.wang/ffl-2858 — merge #4913 then #4916 first.

Changes

  • Override engine (useFlagOverrides) writing to the inspected page's localStorage per the DatadogDevtools wrapper contract; mutations serialized to avoid lost updates.
  • Per-row variant buttons + revert, a manual override-by-key form, clear-all / save-and-reload.

Demo

This is a recording of what the three stacked PRs look like working together.

Screen.Recording.2026-07-24.at.5.14.31.PM.mov

Checklist

  • Tested locally
  • Tested on staging
  • Added unit tests for this change (useFlagOverrides.spec.ts).
  • Added e2e/integration tests for this change.
  • Updated documentation and/or relevant AGENTS.md file

@github-actions

github-actions Bot commented Jul 24, 2026

Copy link
Copy Markdown

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

@datadog-prod-us1-5

datadog-prod-us1-5 Bot commented Jul 24, 2026

Copy link
Copy Markdown

Pipelines  Tests

⚠️ Warnings

🚦 2 Pipeline jobs failed

DataDog/browser-sdk | e2e: [firefox]   View in Datadog   GitLab

DataDog/browser-sdk | e2e: [firefox-pinned]   View in Datadog   GitLab

ℹ️ Info

No other issues found (see more)

🧪 All tests passed
❄️ No new flaky tests detected

🎯 Code Coverage (details)
Patch Coverage: 26.00%
Overall Coverage: 77.02% (-0.23%)

Useful? React with 👍 / 👎

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: cf6c4db | Docs | Datadog PR Page | Give us feedback!

@kellyw1806 kellyw1806 changed the title Kelly.wang/ffl 2596 ✨ [FFL-2596] Feature Flags tab — overrides (stacked PR 2 of 2) Jul 24, 2026
@kellyw1806
kellyw1806 changed the base branch from main to kelly.wang/ffl-2597 July 24, 2026 20:30
@kellyw1806
kellyw1806 force-pushed the kelly.wang/ffl-2597 branch from f6766af to 5375214 Compare July 24, 2026 20:42
@kellyw1806
kellyw1806 force-pushed the kelly.wang/ffl-2596 branch from 17cca80 to 5b4061e Compare July 24, 2026 20:42
@cit-pr-commenter-54b7da

cit-pr-commenter-54b7da Bot commented Jul 24, 2026

Copy link
Copy Markdown

Bundles Sizes Evolution

📦 Bundle Name Base Size Local Size 𝚫 𝚫% Status
Rum 179.09 KiB 179.09 KiB 0 B 0.00%
Rum Profiler 8.43 KiB 8.43 KiB 0 B 0.00%
Rum Recorder 21.12 KiB 21.12 KiB 0 B 0.00%
Logs 55.71 KiB 55.71 KiB 0 B 0.00%
Rum Salesforce N/A 137.07 KiB N/A N/A N/A
Rum Slim 137.06 KiB 137.06 KiB 0 B 0.00%
Worker 22.96 KiB 22.96 KiB 0 B 0.00%

@kellyw1806

Copy link
Copy Markdown
Author

I have read the CLA Document and I hereby sign the CLA

This comment was marked as resolved.

@DataDog DataDog deleted a comment from chatgpt-codex-connector Bot Jul 24, 2026
@kellyw1806
kellyw1806 force-pushed the kelly.wang/ffl-2596 branch from 5b4061e to 31f7bb0 Compare July 24, 2026 21:02
@kellyw1806 kellyw1806 changed the title ✨ [FFL-2596] Feature Flags tab — overrides (stacked PR 2 of 2) [FFL-2596] Feature Flags tab with overrides (stacked PR 2 of 2) Jul 24, 2026
@kellyw1806
kellyw1806 force-pushed the kelly.wang/ffl-2597 branch from 5375214 to bc5e1d9 Compare July 24, 2026 21:08
@kellyw1806
kellyw1806 force-pushed the kelly.wang/ffl-2596 branch from 31f7bb0 to c9960d2 Compare July 24, 2026 21:08
@kellyw1806 kellyw1806 changed the title [FFL-2596] Feature Flags tab with overrides (stacked PR 2 of 2) ✨ [FFL-2596] Feature Flags tab with overrides (stacked PR 2 of 2) Jul 24, 2026
// Contract shared with @datadog/openfeature-browser's DatadogDevtools wrapper.
// Keep these in sync with that package: the wrapper reads OVERRIDES_KEY once on
// initialize() and writes DEVTOOLS_MARKER_KEY when it is composed into the provider stack.
export const OVERRIDES_KEY = 'dd.dd_flag.overrides'

@kellyw1806 kellyw1806 Jul 24, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

These keys are a contract with @datadog/openfeature-browser's DatadogDevtools wrapper so renaming them requires a coordinated change in that package, not a free rename.

type: FlagOverrideType
// Any JSON value — objects/arrays are `object`; `null` is allowed (a flag/variant value can be
// null). The manual-entry form still rejects null via validateOverrideValue.
value: boolean | string | number | object | null

@kellyw1806 kellyw1806 Jul 24, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Widened the override value to allow null (a variant value can legitimately be null). The manual-entry form still rejects null via validateOverrideValue. Is this okay or should variant-click also block null?


useEffect(() => {
refresh()
const id = setInterval(refresh, REFRESH_INTERVAL)

@kellyw1806 kellyw1806 Jul 24, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

This polls the inspected page every 2s while the tab is mounted. Fine for a devtools panel IMO, but flagging in case prefer gating it on tab visibility.

@kellyw1806
kellyw1806 force-pushed the kelly.wang/ffl-2597 branch from bc5e1d9 to 2e87cbc Compare July 24, 2026 21:42
@kellyw1806
kellyw1806 force-pushed the kelly.wang/ffl-2596 branch from c9960d2 to fc8bff4 Compare July 24, 2026 21:42
@kellyw1806
kellyw1806 force-pushed the kelly.wang/ffl-2597 branch from 2e87cbc to 7b347cd Compare July 27, 2026 04:58
@kellyw1806
kellyw1806 force-pushed the kelly.wang/ffl-2596 branch from fc8bff4 to 5d030ab Compare July 27, 2026 04:58
@kellyw1806 kellyw1806 changed the title ✨ [FFL-2596] Feature Flags tab with overrides (stacked PR 2 of 2) ✨ [FFL-2596] Feature Flags tab — overrides (stacked PR 3 of 3) Jul 27, 2026
@kellyw1806
kellyw1806 changed the base branch from kelly.wang/ffl-2597 to kelly.wang/ffl-2858 July 27, 2026 04:59
Comment thread developer-extension/src/panel/components/tabs/flagsTab/flagsTab.tsx Outdated
@kellyw1806
kellyw1806 force-pushed the kelly.wang/ffl-2858 branch from 17d898b to 919073f Compare July 27, 2026 18:56
@kellyw1806
kellyw1806 marked this pull request as ready for review July 27, 2026 20:08
@kellyw1806
kellyw1806 requested a review from a team as a code owner July 27, 2026 20:08

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e13445b763

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

case 'INTEGER':
case 'NUMERIC': {
const text = String(raw).trim()
if (text === '' || Number.isNaN(Number(text))) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reject non-finite numeric overrides

issue: For a manual NUMERIC override, inputs such as Infinity or 1e9999 pass this Number.isNaN check and become a non-finite number. writeOverrides subsequently serializes that value with JSON.stringify, which converts it to null; after refreshing, the provider receives an invalid null override instead of the number the UI accepted. Validate with Number.isFinite before applying the override.

Useful? React with 👍 / 👎.

await writeOverrides(mutate(overrides))
refresh()
})
.catch((error) => logger.error('Error while updating flag overrides:', error))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Surface override write failures before offering refresh

issue: When localStorage.setItem fails—for example because storage is unavailable or over quota—this catch logs the error and converts the mutation into a fulfilled promise. FlagsTab.runMutation then considers the write settled while leaving pendingReload true, so the UI enables “Save Overrides and Refresh Page” even though nothing was saved; refreshing silently applies the old overrides. Preserve a failure signal and keep the refresh action from representing the failed mutation as saved.

Useful? React with 👍 / 👎.

</Badge>
))
flag.variants.map((variant) => {
const isActive = overridden && valuesEqual(override.value, variant.value)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Validate stored override entries before rendering them

issue: If the shared local-storage key contains syntactically valid JSON but an invalid entry such as {"my-flag": null}, readFlagState accepts the map and getOverride returns that null entry. This row then treats it as overridden and dereferences override.value, crashing the entire extension panel through its error boundary. Validate or discard each stored entry rather than trusting only the top-level object shape.

Useful? React with 👍 / 👎.

Comment on lines +130 to +131
const { overrides } = await readFlagState()
await writeOverrides(mutate(overrides))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Make each override mutation atomic in the inspected page

issue: When the inspected page navigates between these two asynchronous evalInWindow round trips, the read can capture overrides from the old page while the write runs against the new page, copying the old origin's override map into the new origin along with the requested change. The same split read/write also overwrites changes made by another page-side writer between the calls. Perform the read-modify-write in one inspected-window evaluation so it is bound to a single page context.

Useful? React with 👍 / 👎.

const totalPages = Math.max(1, Math.ceil(sorted.length / CATALOG_PAGE_SIZE))
const currentPage = Math.min(page, totalPages)
const paginated = filtered.slice((currentPage - 1) * CATALOG_PAGE_SIZE, currentPage * CATALOG_PAGE_SIZE)
const paginated = sorted.slice((currentPage - 1) * CATALOG_PAGE_SIZE, currentPage * CATALOG_PAGE_SIZE)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep the edited flag visible after override sorting

issue: In catalogs with more than one page, applying an override while viewing page 2 or later immediately moves that flag into the overridden group at the start of the sorted array, but the current page is retained. The clicked row therefore disappears before the user can see its active state and is replaced by a different flag shifted from the previous page. Reset pagination when override membership changes or avoid moving the edited row away from the current page.

Useful? React with 👍 / 👎.


<FlagCatalogBody
catalog={catalog}
flags={view.paginated}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Expose individual controls for custom overrides

issue: When ManualOverrideForm adds a key that is absent from the fetched catalog—the primary custom-override case—the new entry can never appear in this catalog-only list. The UI shows only an increased “Clear all” count, so users cannot inspect the persisted type/value or remove that custom entry without deleting every other override. Render non-catalog entries with their own revert control.

Useful? React with 👍 / 👎.

@kellyw1806
kellyw1806 force-pushed the kelly.wang/ffl-2858 branch from 5683248 to 4af8227 Compare July 27, 2026 20:30
@kellyw1806
kellyw1806 force-pushed the kelly.wang/ffl-2596 branch from e13445b to d3669d4 Compare July 27, 2026 20:30

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d3669d4516

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +117 to +120
useEffect(() => {
refresh()
const id = setInterval(refresh, REFRESH_INTERVAL)
return () => clearInterval(id)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Refresh override state immediately after navigation

issue: When the inspected tab navigates to a different origin while this panel remains open, the previous origin's overrides and marker remain rendered until the two-second poll completes. During that window, clicking a displayed revert or “Clear all” performs a fresh read against the new origin and can delete overrides there that were never shown to the user. Listen for top-frame navigation and clear or refresh the state immediately before mutation controls remain usable.

Useful? React with 👍 / 👎.

const view = useFlagCatalogView(catalog.flags)
const view = useFlagCatalogView(catalog.flags, overrides)

const [pendingReload, setPendingReload] = useState(false)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve the pending-reload state across panel remounts

issue: If the panel is destroyed and reopened after an override mutation but before refreshing the inspected page, this transient state resets to false even though the wrapper still has its pre-mutation values loaded. The persisted overrides are displayed again, but “Save Overrides and Refresh Page” is disabled; after clearing overrides this can leave the page running an old override while the panel shows none. Do not gate the required refresh solely on component-local state, or persist the dirty state until the inspected page reloads.

Useful? React with 👍 / 👎.

size="compact-xs"
variant={isActive ? 'filled' : 'default'}
color={isActive ? 'violet' : 'gray'}
onClick={() => onSelectVariant(flag.key, { type: flag.type, value: variant.value })}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reject malformed catalog variants before persisting

issue: When the catalog API supplies an unparseable variant, parseVariantValue deliberately preserves its raw string (the existing catalog spec covers malformed JSON and INTEGER variants), but this click handler now persists that fallback under the declared non-string type. Such an override violates validateOverrideValue and can make the provider throw when resolving the flag after refresh. Validate catalog values before applying them and disable or report malformed variants instead of writing them.

Useful? React with 👍 / 👎.

@kellyw1806
kellyw1806 force-pushed the kelly.wang/ffl-2858 branch from 4af8227 to 7376fbf Compare July 27, 2026 20:40
@kellyw1806
kellyw1806 force-pushed the kelly.wang/ffl-2596 branch from d3669d4 to bad0da0 Compare July 27, 2026 20:40
@kellyw1806
kellyw1806 force-pushed the kelly.wang/ffl-2858 branch from 7376fbf to fb4faa6 Compare July 27, 2026 20:54
@kellyw1806
kellyw1806 force-pushed the kelly.wang/ffl-2596 branch from bad0da0 to 683bd8b Compare July 27, 2026 20:54

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 683bd8bf66

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

setError(validationError)
return
}
onApply(flagKey.trim(), { type, value })

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Enforce the catalog type for manual overrides

issue: When this form is used with a key that already exists in catalog.flags, it still allows an unrelated type to be selected and persists that type without comparing it with the known CatalogFlag.type. For example, a STRING override for a catalogued BOOLEAN flag passes validateOverrideValue, but the DatadogDevtools wrapper resolves that key through the boolean path and rejects the mismatched override instead of applying it after refresh. Use the catalog type, or reject a mismatch, whenever the entered key is known.

Useful? React with 👍 / 👎.

overrides = parsed
}
} catch (e) {}
const devtoolsEnabled = localStorage.getItem(${JSON.stringify(DEVTOOLS_MARKER_KEY)}) === 'enabled'

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Do not treat the persistent marker as current-page detection

issue: When an origin previously ran a build containing DatadogDevtools but the current build no longer composes it, this localStorage marker remains set indefinitely because nothing here clears or scopes it to a document load. The panel therefore suppresses the “not detected” warning and presents overrides as supported even though refreshing cannot apply them. Use a marker tied to the current page lifecycle, or clear and re-establish it during reload, before reporting the wrapper as detected.

Useful? React with 👍 / 👎.

@sbarrio
sbarrio requested a review from BenoitZugmeyer July 28, 2026 07:06
@kellyw1806
kellyw1806 marked this pull request as draft July 28, 2026 13:39
@kellyw1806
kellyw1806 force-pushed the kelly.wang/ffl-2858 branch from fb4faa6 to 230ae07 Compare July 28, 2026 15:32
@kellyw1806
kellyw1806 force-pushed the kelly.wang/ffl-2596 branch from 683bd8b to 1f5d159 Compare July 28, 2026 15:32
@kellyw1806
kellyw1806 force-pushed the kelly.wang/ffl-2858 branch from 230ae07 to 673a2b1 Compare July 28, 2026 16:29
@kellyw1806
kellyw1806 force-pushed the kelly.wang/ffl-2596 branch from 1f5d159 to 37d6b9b Compare July 28, 2026 16:29
@kellyw1806
kellyw1806 force-pushed the kelly.wang/ffl-2858 branch from 673a2b1 to 2f033a5 Compare July 28, 2026 17:19
@kellyw1806
kellyw1806 force-pushed the kelly.wang/ffl-2596 branch from 37d6b9b to b1988f2 Compare July 28, 2026 17:19
@kellyw1806
kellyw1806 force-pushed the kelly.wang/ffl-2858 branch from 2f033a5 to 98058ac Compare July 28, 2026 17:39
@kellyw1806
kellyw1806 force-pushed the kelly.wang/ffl-2596 branch from b1988f2 to c9cefb8 Compare July 28, 2026 17:40
@kellyw1806
kellyw1806 force-pushed the kelly.wang/ffl-2858 branch from 98058ac to e55866f Compare July 28, 2026 18:46
@kellyw1806
kellyw1806 force-pushed the kelly.wang/ffl-2596 branch from c9cefb8 to 3ef64a4 Compare July 28, 2026 18:46
@kellyw1806
kellyw1806 force-pushed the kelly.wang/ffl-2858 branch from e55866f to 322814a Compare July 28, 2026 20:01
@kellyw1806
kellyw1806 force-pushed the kelly.wang/ffl-2596 branch from 3ef64a4 to e97cfa9 Compare July 28, 2026 20:01
@kellyw1806
kellyw1806 force-pushed the kelly.wang/ffl-2858 branch from 322814a to 87a1c1a Compare July 28, 2026 21:30
@kellyw1806
kellyw1806 force-pushed the kelly.wang/ffl-2596 branch from e97cfa9 to 6692837 Compare July 28, 2026 21:30
Builds on FFL-2597: adds the override engine (writes to the inspected
page's localStorage via the DatadogDevtools contract), per-variant
override buttons + revert on each catalog row, a manual override-by-key
form, and clear-all / save-and-reload controls.
@kellyw1806
kellyw1806 force-pushed the kelly.wang/ffl-2858 branch from 87a1c1a to 875de0b Compare July 29, 2026 15:32
@kellyw1806
kellyw1806 force-pushed the kelly.wang/ffl-2596 branch from 6692837 to cf6c4db Compare July 29, 2026 15:32
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