From bb02ffd8c4cea7d4e1b94657f03bf1f7db7511ef Mon Sep 17 00:00:00 2001 From: Jaanus Sellin Date: Wed, 31 Jan 2024 14:32:23 +0200 Subject: [PATCH] feat: A/B test search feedback variants (#6085) Search was not getting any feedback. We introduced 3 different variants to compare conversion rate. ![image](https://github.com/Unleash/unleash/assets/964450/9c4fbcd6-c6d9-4570-9a08-9321087f609a) ![image](https://github.com/Unleash/unleash/assets/964450/6d643d48-1dcb-4a67-9951-7f0c6865f31d) ![image](https://github.com/Unleash/unleash/assets/964450/423dbd54-5dd1-409c-9cd5-295edb9453d9) --- .../FeatureToggleListTable.tsx | 86 +++++++++++++++---- .../component/feedbackNew/FeedbackContext.ts | 6 +- .../feedbackNew/FeedbackProvider.tsx | 7 +- .../src/component/feedbackNew/useFeedback.tsx | 2 + frontend/src/interfaces/uiConfig.ts | 2 +- .../__snapshots__/create-config.test.ts.snap | 9 +- src/lib/types/experimental.ts | 17 +++- src/server-dev.ts | 1 - 8 files changed, 106 insertions(+), 24 deletions(-) diff --git a/frontend/src/component/feature/FeatureToggleList/FeatureToggleListTable.tsx b/frontend/src/component/feature/FeatureToggleList/FeatureToggleListTable.tsx index 6ac43c77ef..5230b86b31 100644 --- a/frontend/src/component/feature/FeatureToggleList/FeatureToggleListTable.tsx +++ b/frontend/src/component/feature/FeatureToggleList/FeatureToggleListTable.tsx @@ -1,6 +1,7 @@ import { useCallback, useEffect, useMemo, useState, VFC } from 'react'; import { Box, + Button, IconButton, Link, Tooltip, @@ -69,7 +70,7 @@ const feedbackCategory = 'search'; export const FeatureToggleListTable: VFC = () => { const theme = useTheme(); - const { openFeedback } = useFeedback(feedbackCategory, 'automatic'); + const featureSearchFeedback = useUiFlag('featureSearchFeedback'); const { trackEvent } = usePlausibleTracker(); const { environments } = useEnvironments(); const enabledEnvironments = environments @@ -81,7 +82,17 @@ export const FeatureToggleListTable: VFC = () => { const { setToastApiError } = useToast(); const { uiConfig } = useUiConfig(); - const featureSearchFeedback = useUiFlag('featureSearchFeedback'); + + const variant = + featureSearchFeedback !== false + ? featureSearchFeedback?.name ?? '' + : ''; + + const { openFeedback } = useFeedback( + feedbackCategory, + 'automatic', + variant, + ); const stateConfig = { offset: withDefault(NumberParam, 0), @@ -352,19 +363,64 @@ export const FeatureToggleListTable: VFC = () => { setShowExportDialog(true)} /> - - - - - - } - /> + {featureSearchFeedback !== false && + featureSearchFeedback?.enabled && ( + <> + + + + + + } + /> + + } + onClick={ + createFeedbackContext + } + > + Provide feedback + + } + />{' '} + + } + onClick={ + createFeedbackContext + } + variant='outlined' + > + Provide feedback + + } + /> + + )} } > diff --git a/frontend/src/component/feedbackNew/FeedbackContext.ts b/frontend/src/component/feedbackNew/FeedbackContext.ts index 2b8a2e9148..b6302bd605 100644 --- a/frontend/src/component/feedbackNew/FeedbackContext.ts +++ b/frontend/src/component/feedbackNew/FeedbackContext.ts @@ -4,7 +4,11 @@ import { IFeedbackCategory } from 'hooks/useSubmittedFeedback'; export type FeedbackMode = 'automatic' | 'manual'; export interface IFeedbackContext { feedbackData: FeedbackData | undefined; - openFeedback: (data: FeedbackData, mode: FeedbackMode) => void; + openFeedback: ( + data: FeedbackData, + mode: FeedbackMode, + variant?: string, + ) => void; closeFeedback: () => void; showFeedback: boolean; setShowFeedback: (visible: boolean) => void; diff --git a/frontend/src/component/feedbackNew/FeedbackProvider.tsx b/frontend/src/component/feedbackNew/FeedbackProvider.tsx index 965758bd4a..b5cc7fdba1 100644 --- a/frontend/src/component/feedbackNew/FeedbackProvider.tsx +++ b/frontend/src/component/feedbackNew/FeedbackProvider.tsx @@ -13,7 +13,11 @@ export const FeedbackProvider: FC = ({ children }) => { const [feedbackMode, setFeedbackMode] = useState< FeedbackMode | undefined >(); - const openFeedback = (data: FeedbackData, mode: FeedbackMode) => { + const openFeedback = ( + data: FeedbackData, + mode: FeedbackMode, + variant: string = '', + ) => { setFeedbackData(data); setShowFeedback(true); setFeedbackMode(mode); @@ -22,6 +26,7 @@ export const FeedbackProvider: FC = ({ children }) => { props: { eventType: `feedback opened - ${data.category}`, category: data.category, + variant: variant, }, }); }; diff --git a/frontend/src/component/feedbackNew/useFeedback.tsx b/frontend/src/component/feedbackNew/useFeedback.tsx index 8f90174a11..614a5db00e 100644 --- a/frontend/src/component/feedbackNew/useFeedback.tsx +++ b/frontend/src/component/feedbackNew/useFeedback.tsx @@ -30,6 +30,7 @@ export const useFeedbackContext = (): IFeedbackContext => { export const useFeedback = ( feedbackCategory: IFeedbackCategory, mode: FeedbackMode, + variant: string = '', ) => { const context = useFeedbackContext(); const { hasSubmittedFeedback } = useUserSubmittedFeedback(feedbackCategory); @@ -44,6 +45,7 @@ export const useFeedback = ( category: feedbackCategory, }, mode, + variant, ); }, }; diff --git a/frontend/src/interfaces/uiConfig.ts b/frontend/src/interfaces/uiConfig.ts index dbcb2a2dff..04cf9988de 100644 --- a/frontend/src/interfaces/uiConfig.ts +++ b/frontend/src/interfaces/uiConfig.ts @@ -69,7 +69,7 @@ export type UiFlags = { automatedActions?: boolean; celebrateUnleash?: boolean; increaseUnleashWidth?: boolean; - featureSearchFeedback?: boolean; + featureSearchFeedback?: Variant; enableLicense?: boolean; newStrategyConfigurationFeedback?: boolean; extendedUsageMetricsUI?: boolean; diff --git a/src/lib/__snapshots__/create-config.test.ts.snap b/src/lib/__snapshots__/create-config.test.ts.snap index 7b3e0e78be..3caeb81afa 100644 --- a/src/lib/__snapshots__/create-config.test.ts.snap +++ b/src/lib/__snapshots__/create-config.test.ts.snap @@ -94,7 +94,14 @@ exports[`should create default config 1`] = ` "executiveDashboard": false, "extendedUsageMetrics": false, "extendedUsageMetricsUI": false, - "featureSearchFeedback": false, + "featureSearchFeedback": { + "enabled": true, + "name": "withText", + "payload": { + "type": "json", + "value": "", + }, + }, "featureSearchFeedbackPosting": false, "featuresExportImport": true, "feedbackComments": { diff --git a/src/lib/types/experimental.ts b/src/lib/types/experimental.ts index e9ec99c153..dc324c563c 100644 --- a/src/lib/types/experimental.ts +++ b/src/lib/types/experimental.ts @@ -157,10 +157,19 @@ const flags: IFlags = { process.env.UNLEASH_EXPERIMENTAL_INCREASE_UNLEASH_WIDTH, false, ), - featureSearchFeedback: parseEnvVarBoolean( - process.env.UNLEASH_EXPERIMENTAL_FEATURE_SEARCH_FEEDBACK, - false, - ), + featureSearchFeedback: { + name: 'withText', + enabled: parseEnvVarBoolean( + process.env.UNLEASH_EXPERIMENTAL_FEATURE_SEARCH_FEEDBACK, + true, + ), + payload: { + type: PayloadType.JSON, + value: + process.env + .UNLEASH_EXPERIMENTAL_FEATURE_SEARCH_FEEDBACK_PAYLOAD ?? '', + }, + }, featureSearchFeedbackPosting: parseEnvVarBoolean( process.env.UNLEASH_EXPERIMENTAL_FEATURE_SEARCH_FEEDBACK_POSTING, false, diff --git a/src/server-dev.ts b/src/server-dev.ts index 71104d363b..7cedcb8278 100644 --- a/src/server-dev.ts +++ b/src/server-dev.ts @@ -45,7 +45,6 @@ process.nextTick(async () => { stripHeadersOnAPI: true, celebrateUnleash: true, increaseUnleashWidth: true, - featureSearchFeedback: true, newStrategyConfigurationFeedback: true, extendedUsageMetricsUI: true, executiveDashboard: true,