Skip to content

Commit 9a48ca1

Browse files
authored
chore: ld flags not evaluated (#1254)
## Problem LD dashboard did not reflect feature flag evaluations. Cannot tell which user each flag was evaluated for. ## Solution * use `variation()` to get the flag value instead of reading from all flags: * returns the flag value * registers evaluation with LaunchDarkly servers * shows up in LaunchDarkly dashboard * lightweight and fast as it reads from the cache; does not make an additional API call * use Datadog RUM's feature flag feature to check evaluations by user ## Tests Test that flags are being evaluated correctly: - [ ] Should only see Apps that you are allowed to see (e.g., some users should not see GatherSG / AISAY etc) - [ ] Only Plumbers should see the bulk retry button in the Executions page - [ ] Only should see the SSO login on OGP wifi - [ ] Test that banner still works as intended
1 parent e2772ae commit 9a48ca1

File tree

12 files changed

+100
-83
lines changed

12 files changed

+100
-83
lines changed

packages/frontend/src/components/ExecutionStep/components/RetryAllButton.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ interface RetryAllButtonProps {
1616

1717
export const RetryAllButton = ({ execution }: RetryAllButtonProps) => {
1818
const flowId = execution.flow?.id
19-
const { flags } = useContext(LaunchDarklyContext)
19+
const { getFlagValue } = useContext(LaunchDarklyContext)
2020
const toast = useToast()
2121
const [isBulkRetrying, setIsBulkRetrying] = useState(false)
2222
const [hasBulkRetried, setHasBulkRetried] = useState(false)
@@ -53,7 +53,7 @@ export const RetryAllButton = ({ execution }: RetryAllButtonProps) => {
5353
}
5454
}, [flowId, bulkRetryExecutions, toast])
5555

56-
if (!flags?.[BULK_RETRY_EXECUTIONS_FLAG]) {
56+
if (!getFlagValue(BULK_RETRY_EXECUTIONS_FLAG, false)) {
5757
return null
5858
}
5959

packages/frontend/src/components/ExecutionStep/components/RetryAllIterationsButton.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export const RetryAllIterationsButton = ({
1818
execution,
1919
}: RetryAllIterationsButtonProps) => {
2020
const executionId = execution.id
21-
const { flags } = useContext(LaunchDarklyContext)
21+
const { getFlagValue } = useContext(LaunchDarklyContext)
2222
const toast = useToast()
2323
const [isBulkRetrying, setIsBulkRetrying] = useState(false)
2424
const [hasBulkRetried, setHasBulkRetried] = useState(false)
@@ -61,7 +61,7 @@ export const RetryAllIterationsButton = ({
6161
}
6262
}, [toast, executionId, bulkRetryIterations])
6363

64-
if (!flags?.[BULK_RETRY_EXECUTIONS_FLAG]) {
64+
if (!getFlagValue(BULK_RETRY_EXECUTIONS_FLAG, false)) {
6565
return null
6666
}
6767

packages/frontend/src/components/FlowStepConfigurationModal/ChooseAppAndEvent/ChooseApp.tsx

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ interface ChooseAppProps {
4444

4545
export default function ChooseApp(props: ChooseAppProps) {
4646
const { apps, onSelectAppEvent } = props
47-
const launchDarkly = useContext(LaunchDarklyContext)
47+
const { getFlagValue } = useContext(LaunchDarklyContext)
4848
const { patchModalState, isTrigger, isLastStep, step, prevStepId } =
4949
useContext(FlowStepConfigurationContext)
5050

@@ -55,7 +55,7 @@ export default function ChooseApp(props: ChooseAppProps) {
5555
})
5656

5757
const [_, isInitializingIfThen] = useIfThenInitializer()
58-
const isLoading = launchDarkly.isLoading || isInitializingIfThen
58+
const isLoading = isInitializingIfThen
5959

6060
const [searchQuery, setSearchQuery] = useState('')
6161

@@ -81,25 +81,25 @@ export default function ChooseApp(props: ChooseAppProps) {
8181
)
8282

8383
const toolboxActionsToDisplay = useMemo(() => {
84-
if (isLoading || !launchDarkly.flags) {
84+
if (isLoading) {
8585
return []
8686
}
8787

8888
const ldToolboxAppFlag = getAppFlag(TOOLBOX_APP_KEY)
89-
if (!launchDarkly.flags[ldToolboxAppFlag]) {
89+
if (!getFlagValue(ldToolboxAppFlag, false)) {
9090
return []
9191
}
9292

9393
const toolboxActions =
9494
apps?.find((app) => app.key === TOOLBOX_APP_KEY)?.actions ?? []
9595
const filteredToolboxActions = toolboxActions.filter((action) => {
9696
// Filter away actions hidden behind feature flags
97-
if (isLoading || !launchDarkly.flags) {
97+
if (isLoading) {
9898
return true
9999
}
100100

101101
const ldToolboxActionFlag = getAppActionFlag(TOOLBOX_APP_KEY, action.key)
102-
return launchDarkly.flags[ldToolboxActionFlag] ?? true
102+
return getFlagValue(ldToolboxActionFlag, true)
103103
})
104104

105105
const fuzzySearchToolboxActions = fuzzysort
@@ -111,17 +111,17 @@ export default function ChooseApp(props: ChooseAppProps) {
111111
.map((result) => result.obj)
112112

113113
return fuzzySearchToolboxActions
114-
}, [apps, isLoading, launchDarkly.flags, searchQuery])
114+
}, [apps, isLoading, getFlagValue, searchQuery])
115115

116116
// Combine filtering and grouping logic into a single operation
117117
const groupedApps = useMemo(() => {
118118
const filteredApps = apps?.filter((app) => {
119119
// Filter away apps hidden behind feature flags
120-
if (isLoading || !launchDarkly.flags || !app?.key) {
120+
if (isLoading || !app?.key) {
121121
return true
122122
}
123123
const ldAppFlag = getAppFlag(app.key)
124-
return launchDarkly.flags[ldAppFlag] ?? true
124+
return getFlagValue(ldAppFlag, true)
125125
})
126126

127127
// Note: Separate toolbox app from other apps because we filter toolbox actions separately
@@ -160,13 +160,7 @@ export default function ChooseApp(props: ChooseAppProps) {
160160
}
161161
return a[0].localeCompare(b[0])
162162
})
163-
}, [
164-
apps,
165-
launchDarkly.flags,
166-
isLoading,
167-
searchQuery,
168-
toolboxActionsToDisplay,
169-
])
163+
}, [apps, getFlagValue, isLoading, searchQuery, toolboxActionsToDisplay])
170164

171165
return (
172166
<>

packages/frontend/src/components/FlowStepConfigurationModal/ChooseAppAndEvent/ChooseEvent.tsx

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,13 @@ interface ChooseEventProps {
2121
export default function ChooseEvent(props: ChooseEventProps): JSX.Element {
2222
const { onSelectAppEvent } = props
2323

24-
const launchDarkly = useContext(LaunchDarklyContext)
24+
const { getFlagValue } = useContext(LaunchDarklyContext)
2525

2626
const { modalState, isTrigger, patchModalState } = useContext(
2727
FlowStepConfigurationContext,
2828
)
2929
const { selectedApp } = modalState
3030

31-
const isLoading = launchDarkly.isLoading
32-
3331
const filteredTriggersOrActions = useMemo(() => {
3432
if (!selectedApp) {
3533
return []
@@ -40,15 +38,15 @@ export default function ChooseEvent(props: ChooseEventProps): JSX.Element {
4038
: selectedApp.actions ?? []
4139
return triggersOrActions?.filter((triggerOrAction: ITrigger | IAction) => {
4240
// Filter away triggers or actions hidden behind feature flags
43-
if (isLoading || !launchDarkly.flags || !selectedApp.key) {
41+
if (!selectedApp.key) {
4442
return true
4543
}
4644
const launchDarklyKey = isTrigger
4745
? getAppTriggerFlag(selectedApp.key, triggerOrAction.key)
4846
: getAppActionFlag(selectedApp.key, triggerOrAction.key)
49-
return launchDarkly.flags[launchDarklyKey] ?? true
47+
return getFlagValue(launchDarklyKey, true)
5048
})
51-
}, [selectedApp, isTrigger, launchDarkly.flags, isLoading])
49+
}, [selectedApp, isTrigger, getFlagValue])
5250

5351
const onBack = useCallback(() => {
5452
patchModalState({

packages/frontend/src/components/FlowStepConfigurationModal/hooks/useIsAppSelectable.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { IFlow, IStep } from '@plumber/types'
22

33
import { useContext } from 'react'
4-
import { LDFlagSet } from 'launchdarkly-js-client-sdk'
54

65
import { BranchContext } from '@/components/FlowStepGroup/Content/IfThen/BranchContext'
76
import { NESTED_IFTHEN_FEATURE_FLAG } from '@/config/flags'
@@ -97,18 +96,21 @@ function isIfThenSelectable({
9796
depth,
9897
hasIfThen,
9998
isLastStep,
100-
ldFlags,
99+
getFlagValue,
101100
}: {
102101
depth: number
103102
hasIfThen: boolean
104103
isLastStep: boolean
105-
ldFlags: LDFlagSet | null
104+
getFlagValue: (
105+
flagKey: string,
106+
defaultValue?: boolean | string,
107+
) => boolean | string
106108
}) {
107109
if (!isLastStep || hasIfThen) {
108110
return false
109111
}
110112

111-
const canUseNestedBranch = ldFlags?.[NESTED_IFTHEN_FEATURE_FLAG] ?? false
113+
const canUseNestedBranch = getFlagValue(NESTED_IFTHEN_FEATURE_FLAG, false)
112114
if (canUseNestedBranch) {
113115
return true
114116
}
@@ -128,14 +130,14 @@ export const useIsAppSelectable = ({
128130
}): Record<string, boolean> => {
129131
const { depth } = useContext(BranchContext)
130132
const { flow, hasIfThen } = useContext(EditorContext)
131-
const { flags: ldFlags } = useContext(LaunchDarklyContext)
133+
const { getFlagValue } = useContext(LaunchDarklyContext)
132134

133135
return {
134136
[TOOLBOX_ACTIONS.IfThen]: isIfThenSelectable({
135137
depth,
136138
hasIfThen,
137139
isLastStep,
138-
ldFlags,
140+
getFlagValue,
139141
}),
140142
[TOOLBOX_ACTIONS.ForEach]: isForEachSelectable({
141143
flow,

packages/frontend/src/components/FlowSubstep/index.tsx

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ type FlowSubstepProps = {
2424

2525
function FlowSubstep(props: FlowSubstepProps): JSX.Element {
2626
const { isTrigger, substep, step, selectedActionOrTrigger } = props
27-
const { flags } = useContext(LaunchDarklyContext)
27+
const { getFlagValue } = useContext(LaunchDarklyContext)
2828
const formContext = useFormContext()
2929
const {
3030
executeTestStep,
@@ -63,13 +63,14 @@ function FlowSubstep(props: FlowSubstepProps): JSX.Element {
6363
const argsToDisplay = useMemo(
6464
() =>
6565
args?.filter((arg) => {
66-
if (!flags) {
67-
return true
68-
}
69-
const flag = getInputFlag(selectedActionOrTrigger?.key ?? '', arg.key)
70-
return !flags[flag] || +step.createdAt <= flags[flag]
66+
const inputFlag = getInputFlag(
67+
selectedActionOrTrigger?.key ?? '',
68+
arg.key,
69+
)
70+
const flagValue = getFlagValue(inputFlag, false)
71+
return !flagValue || +step.createdAt <= flagValue
7172
}) || [],
72-
[args, flags, step.createdAt, selectedActionOrTrigger],
73+
[args, step.createdAt, selectedActionOrTrigger, getFlagValue],
7374
)
7475

7576
useEffect(() => {

packages/frontend/src/components/LoginForm/index.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import SgidLoginSection from './SgidLoginSection'
1717
import SsoLoginSection from './SsoLoginSection'
1818

1919
export const LoginForm = (): JSX.Element => {
20-
const { flags } = useContext(LaunchDarklyContext)
20+
const { getFlagValue } = useContext(LaunchDarklyContext)
2121

2222
const headers = useResponseHeaders()
2323

@@ -55,12 +55,12 @@ export const LoginForm = (): JSX.Element => {
5555
}
5656
}
5757

58-
const shouldShowSgidLogin = flags?.[SGID_FEATURE_FLAG]
58+
const shouldShowSgidLogin = getFlagValue(SGID_FEATURE_FLAG, false)
5959
// show sso login if request is from OGP office wifi or in dev
6060
const shouldShowSsoLogin =
6161
(headers[RESPONSE_HEADERS.OGP_INTERNAL_HEADER] === 'true' ||
6262
appConfig.isDev) &&
63-
flags?.[SSO_FEATURE_FLAG]
63+
getFlagValue(SSO_FEATURE_FLAG, false)
6464

6565
return (
6666
<form onSubmit={handleSubmit}>

packages/frontend/src/components/SiteWideBanner/index.tsx

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const SESSION_STORAGE_HIDE_BANNER_KEY = 'hide-banner'
1010

1111
const SiteWideBanner = (): JSX.Element | null => {
1212
const [bannerMessage, setBannerMessage] = useState(EMPTY_BANNER_MESSAGE)
13-
const launchDarkly = useContext(LaunchDarklyContext)
13+
const { getFlagValue } = useContext(LaunchDarklyContext)
1414

1515
const closeBanner = useCallback(() => {
1616
setBannerMessage(EMPTY_BANNER_MESSAGE)
@@ -19,19 +19,17 @@ const SiteWideBanner = (): JSX.Element | null => {
1919

2020
// check for feature flag (takes time to load) to display banner
2121
useEffect(() => {
22-
if (launchDarkly.flags) {
23-
// message needs to be fetched everytime the page is re-rendered
24-
const message = launchDarkly.flags[BANNER_TEXT_FLAG]
25-
const bannerMessageStored = getItemForSession(
26-
SESSION_STORAGE_HIDE_BANNER_KEY,
27-
)
28-
if (message !== bannerMessageStored) {
29-
setBannerMessage(message)
30-
} else {
31-
setBannerMessage(EMPTY_BANNER_MESSAGE)
32-
}
22+
// message needs to be fetched everytime the page is re-rendered
23+
const message = getFlagValue(BANNER_TEXT_FLAG, '')
24+
const bannerMessageStored = getItemForSession(
25+
SESSION_STORAGE_HIDE_BANNER_KEY,
26+
)
27+
if (message !== bannerMessageStored) {
28+
setBannerMessage(message)
29+
} else {
30+
setBannerMessage(EMPTY_BANNER_MESSAGE)
3331
}
34-
}, [launchDarkly])
32+
}, [getFlagValue])
3533

3634
if (bannerMessage === EMPTY_BANNER_MESSAGE) {
3735
return null

0 commit comments

Comments
 (0)