Skip to content

Commit ab043dd

Browse files
authored
feat(toolbar): Indicate when a FF override is in place (#87106)
A little red indicator shows up in the Nav when something is overridden This change only applie to the prototype toolbar on sentry.io, not the production one (getsentry/sentry-toolbar#233) <img width="382" alt="SCR-20250314-nouq" src="https://github.com/user-attachments/assets/75ac7387-7c78-41a7-b97e-4a51332e007c" /> Related to #86991
1 parent 4f1e41d commit ab043dd

File tree

6 files changed

+122
-81
lines changed

6 files changed

+122
-81
lines changed

static/app/components/devtoolbar/components/featureFlags/featureFlagsPanel.tsx

+89-78
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ import PanelLayout from '../panelLayout';
2020

2121
import CustomOverride from './customOverride';
2222
import FeatureFlagItem from './featureFlagItem';
23-
import {FeatureFlagsContextProvider, useFeatureFlagsContext} from './featureFlagsContext';
23+
import {useFeatureFlagsContext} from './featureFlagsContext';
24+
import FlagOverridesBadge from './flagOverridesBadge';
2425

2526
type Prefilter = 'all' | 'overrides';
2627

@@ -30,90 +31,88 @@ export default function FeatureFlagsPanel() {
3031
const [isAddFlagActive, setIsAddFlagActive] = useState(false);
3132

3233
return (
33-
<FeatureFlagsContextProvider>
34-
<PanelLayout
35-
title="Feature Flags"
36-
titleRight={
37-
<button
38-
aria-label="Override Flag"
39-
css={[resetButtonCss, panelHeadingRightCss]}
40-
title="Override Flag"
41-
onClick={() => setIsAddFlagActive(!isAddFlagActive)}
42-
>
43-
<span css={buttonRightCss}>
44-
{isAddFlagActive ? (
45-
<IconChevron direction="up" size="xs" />
46-
) : (
47-
<IconChevron direction="down" size="xs" />
48-
)}
49-
Override
50-
</span>
51-
</button>
52-
}
34+
<PanelLayout
35+
title="Feature Flags"
36+
titleRight={
37+
<button
38+
aria-label="Override Flag"
39+
css={[resetButtonCss, panelHeadingRightCss]}
40+
title="Override Flag"
41+
onClick={() => setIsAddFlagActive(!isAddFlagActive)}
42+
>
43+
<span css={buttonRightCss}>
44+
{isAddFlagActive ? (
45+
<IconChevron direction="up" size="xs" />
46+
) : (
47+
<IconChevron direction="down" size="xs" />
48+
)}
49+
Override
50+
</span>
51+
</button>
52+
}
53+
>
54+
{isAddFlagActive && (
55+
<div
56+
css={[
57+
smallCss,
58+
panelSectionCss,
59+
panelInsetContentCss,
60+
css`
61+
background: var(--surface200);
62+
padding: var(--space150);
63+
`,
64+
]}
65+
>
66+
<AnalyticsProvider keyVal="custom-override" nameVal="Custom Override">
67+
<CustomOverride setComponentActive={setIsAddFlagActive} />
68+
</AnalyticsProvider>
69+
</div>
70+
)}
71+
<div
72+
css={css`
73+
display: grid;
74+
grid-template-rows: auto auto 1fr auto;
75+
flex-grow: 1;
76+
`}
5377
>
54-
{isAddFlagActive && (
55-
<div
56-
css={[
57-
smallCss,
58-
panelSectionCss,
59-
panelInsetContentCss,
60-
css`
61-
background: var(--surface200);
62-
padding: var(--space150);
63-
`,
64-
]}
65-
>
66-
<AnalyticsProvider keyVal="custom-override" nameVal="Custom Override">
67-
<CustomOverride setComponentActive={setIsAddFlagActive} />
68-
</AnalyticsProvider>
69-
</div>
70-
)}
78+
<IsDirtyMessage />
79+
<div
80+
css={[
81+
smallCss,
82+
panelSectionCssNoBorder,
83+
panelInsetContentCss,
84+
css`
85+
display: grid;
86+
grid-template-areas: 'search segments';
87+
gap: var(--space100);
88+
`,
89+
]}
90+
>
91+
<Filters
92+
setPrefilter={setPrefilter}
93+
prefilter={prefilter}
94+
setSearchTerm={setSearchTerm}
95+
/>
96+
</div>
7197
<div
7298
css={css`
73-
display: grid;
74-
grid-template-rows: auto auto 1fr auto;
75-
flex-grow: 1;
99+
contain: strict;
100+
flex-direction: column;
101+
align-items: stretch;
76102
`}
77103
>
78-
<IsDirtyMessage />
79-
<div
80-
css={[
81-
smallCss,
82-
panelSectionCssNoBorder,
83-
panelInsetContentCss,
84-
css`
85-
display: grid;
86-
grid-template-areas: 'search segments';
87-
gap: var(--space100);
88-
`,
89-
]}
90-
>
91-
<Filters
92-
setPrefilter={setPrefilter}
93-
prefilter={prefilter}
94-
setSearchTerm={setSearchTerm}
95-
/>
96-
</div>
97104
<div
98105
css={css`
99-
contain: strict;
100-
flex-direction: column;
101-
align-items: stretch;
106+
overflow: auto;
102107
`}
103108
>
104-
<div
105-
css={css`
106-
overflow: auto;
107-
`}
108-
>
109-
<AnalyticsProvider keyVal="flag-table" nameVal="Flag Table">
110-
<FlagTable searchTerm={searchTerm} prefilter={prefilter} />
111-
</AnalyticsProvider>
112-
</div>
109+
<AnalyticsProvider keyVal="flag-table" nameVal="Flag Table">
110+
<FlagTable searchTerm={searchTerm} prefilter={prefilter} />
111+
</AnalyticsProvider>
113112
</div>
114113
</div>
115-
</PanelLayout>
116-
</FeatureFlagsContextProvider>
114+
</div>
115+
</PanelLayout>
117116
);
118117
}
119118

@@ -154,10 +153,22 @@ function Filters({
154153
grid-area: segments;
155154
`}
156155
>
157-
<SegmentedControl<Prefilter> onChange={setPrefilter} size="xs" value={prefilter}>
158-
<SegmentedControl.Item key="all">All</SegmentedControl.Item>
159-
<SegmentedControl.Item key="overrides">Overrides</SegmentedControl.Item>
160-
</SegmentedControl>
156+
<div
157+
css={css`
158+
position: relative;
159+
display: grid;
160+
`}
161+
>
162+
<SegmentedControl<Prefilter>
163+
onChange={setPrefilter}
164+
size="xs"
165+
value={prefilter}
166+
>
167+
<SegmentedControl.Item key="all">All</SegmentedControl.Item>
168+
<SegmentedControl.Item key="overrides">Overrides</SegmentedControl.Item>
169+
</SegmentedControl>
170+
<FlagOverridesBadge />
171+
</div>
161172
</div>
162173
<Input
163174
css={css`
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import IndicatorBadge from 'sentry/components/devtoolbar/components/indicatorBadge';
2+
3+
import useHasCustomFlagOverrides from './useHasCustomFlagOverrides';
4+
5+
export default function FlagOverridesBadge() {
6+
const hasOverrides = useHasCustomFlagOverrides();
7+
8+
if (hasOverrides) {
9+
return <IndicatorBadge variant="red" />;
10+
}
11+
return null;
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import {useFeatureFlagsContext} from 'sentry/components/devtoolbar/components/featureFlags/featureFlagsContext';
2+
3+
export default function useHasCustomFlagOverrides(): boolean {
4+
const {featureFlagMap} = useFeatureFlagsContext();
5+
return (
6+
Object.entries(featureFlagMap)?.filter(
7+
([_name, {value, override}]) => override !== undefined && value !== override
8+
).length > 0
9+
);
10+
}

static/app/components/devtoolbar/components/indicatorBadge.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const variants = {
66
red: css`
77
background: var(--red400);
88
color: var(--gray100);
9+
z-index: 1;
910
`,
1011
};
1112

@@ -27,7 +28,7 @@ const badgeCss = css`
2728
line-height: 1rem;
2829
position: absolute;
2930
right: 2px;
30-
top: 18px;
31+
bottom: 2px;
3132
width: 0.55rem;
3233
display: flex;
3334
align-items: center;

static/app/components/devtoolbar/components/navigation.tsx

+4-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {navigationCss} from '../styles/navigation';
1919
import {resetDialogCss} from '../styles/reset';
2020

2121
import AlertBadge from './alerts/alertBadge';
22+
import FlagOverridesBadge from './featureFlags/flagOverridesBadge';
2223
import IconButton from './navigation/iconButton';
2324

2425
export default function Navigation({
@@ -57,7 +58,9 @@ export default function Navigation({
5758
<NavButton panelName="alerts" label="Active Alerts" icon={<IconSiren />}>
5859
<AlertBadge />
5960
</NavButton>
60-
<NavButton panelName="featureFlags" label="Feature Flags" icon={<IconFlag />} />
61+
<NavButton panelName="featureFlags" label="Feature Flags" icon={<IconFlag />}>
62+
<FlagOverridesBadge />
63+
</NavButton>
6164
<NavButton panelName="releases" label="Releases" icon={<IconReleases />}>
6265
<SessionStatusBadge />
6366
</NavButton>

static/app/components/devtoolbar/components/providers.tsx

+5-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import {ToolbarRouterContextProvider} from '../hooks/useToolbarRoute';
1010
import {VisibilityContextProvider} from '../hooks/useVisibility';
1111
import type {Configuration} from '../types';
1212

13+
import {FeatureFlagsContextProvider} from './featureFlags/featureFlagsContext';
14+
1315
interface Props {
1416
children: React.ReactNode;
1517
config: Configuration;
@@ -38,7 +40,9 @@ export default function Providers({children, config, container}: Props) {
3840
<ConfigurationContextProvider config={config}>
3941
<QueryClientProvider client={queryClient}>
4042
<VisibilityContextProvider>
41-
<ToolbarRouterContextProvider>{children}</ToolbarRouterContextProvider>
43+
<ToolbarRouterContextProvider>
44+
<FeatureFlagsContextProvider>{children}</FeatureFlagsContextProvider>
45+
</ToolbarRouterContextProvider>
4246
</VisibilityContextProvider>
4347
</QueryClientProvider>
4448
</ConfigurationContextProvider>

0 commit comments

Comments
 (0)