Skip to content

Commit

Permalink
fix(flags): strict type validation for event flags in EventFeatureFla…
Browse files Browse the repository at this point in the history
…gList (#81781)

Relates to
- getsentry/team-replay#512
- #81770
- #81601

Forces the types of f.flag and f.result to be string and boolean,
respectively. Also a refactor/reordering, so if there are no **valid**
flags, we'd show CTA.

---------

Co-authored-by: Michelle Zhang <[email protected]>
Co-authored-by: getsantry[bot] <66042841+getsantry[bot]@users.noreply.github.com>
  • Loading branch information
3 people authored Dec 6, 2024
1 parent d3f0fda commit 6b848ee
Showing 1 changed file with 24 additions and 20 deletions.
44 changes: 24 additions & 20 deletions static/app/components/events/featureFlags/eventFeatureFlagList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,39 +98,43 @@ export function EventFeatureFlagList({
event,
});

const suspectFlagNames: Set<string> = useMemo(() => {
return isSuspectError || isSuspectPending
? new Set()
: new Set(suspectFlags.map(f => f.flag));
}, [isSuspectError, isSuspectPending, suspectFlags]);

const hasFlagContext = Boolean(event.contexts?.flags?.values);
const anyEventHasContext =
isRelatedEventsPending || isRelatedEventsError
? false
: relatedEvents.filter(e => Boolean(e.contexts?.flags?.values)).length > 0;
const flagValues = useMemo(() => {
return event.contexts?.flags?.values ?? [];

const eventFlags: Required<FeatureFlag>[] = useMemo(() => {
// At runtime there's no type guarantees on the event flags. So we have to
// explicitly validate against SDK developer error or user-provided contexts.
const rawFlags = event.contexts?.flags?.values ?? [];
return rawFlags.filter(
(f): f is Required<FeatureFlag> =>
f &&
typeof f === 'object' &&
typeof f.flag === 'string' &&
typeof f.result === 'boolean'
);
}, [event]);
const hasFlags = hasFlagContext && flagValues.length > 0;

const hasFlags = hasFlagContext && eventFlags.length > 0;

const showCTA =
!hasFlagContext &&
!anyEventHasContext &&
featureFlagOnboardingPlatforms.includes(project.platform ?? 'other') &&
organization.features.includes('feature-flag-cta');

const suspectFlagNames: Set<string> = useMemo(() => {
return isSuspectError || isSuspectPending
? new Set()
: new Set(suspectFlags.map(f => f.flag));
}, [isSuspectError, isSuspectPending, suspectFlags]);

const hydratedFlags = useMemo(() => {
// Transform the flags array into something readable by the key-value component
// Reverse the flags to show newest at the top by default
const rawFlags: FeatureFlag[] = flagValues.toReversed() ?? [];

// Filter out ill-formatted flags, which come from SDK developer error or user-provided contexts.
const flags = rawFlags.filter(
(f): f is Required<FeatureFlag> => f && 'flag' in f && 'result' in f && !!f.flag
);

return flags.map(f => {
// Transform the flags array into something readable by the key-value component.
// Reverse the flags to show newest at the top by default.
return eventFlags.toReversed().map(f => {
return {
item: {
key: f.flag,
Expand All @@ -147,7 +151,7 @@ export function EventFeatureFlagList({
isSuspectFlag: suspectFlagNames.has(f.flag),
};
});
}, [suspectFlagNames, flagValues]);
}, [suspectFlagNames, eventFlags]);

const onViewAllFlags = useCallback(
(focusControl?: FlagControlOptions) => {
Expand Down

0 comments on commit 6b848ee

Please sign in to comment.