1
0
mirror of https://github.com/Unleash/unleash.git synced 2024-12-22 19:07:54 +01:00

Feat/add feedback to new strategy form (#5745)

This PR adds the feedback form to the new create / edit strategy form
behind a feature flag.

* Add feedback form
* Minor refactor to useFeedback
This commit is contained in:
Fredrik Strand Oseberg 2024-01-03 15:43:22 +01:00 committed by GitHub
parent 31124e4a90
commit 70600552d2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 109 additions and 44 deletions

View File

@ -44,6 +44,9 @@ import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
import { formatStrategyName } from 'utils/strategyNames';
import { Badge } from 'component/common/Badge/Badge';
import EnvironmentIcon from 'component/common/EnvironmentIcon/EnvironmentIcon';
import { useFeedback } from 'component/feedbackNew/useFeedback';
import { useUserSubmittedFeedback } from 'hooks/useSubmittedFeedback';
import { useUiFlag } from 'hooks/useUiFlag';
interface IFeatureStrategyFormProps {
feature: IFeatureToggle;
@ -167,6 +170,8 @@ const EnvironmentTypographyHeader = styled(Typography)(({ theme }) => ({
color: theme.palette.text.secondary,
}));
const feedbackCategory = 'newStrategyForm';
export const NewFeatureStrategyForm = ({
projectId,
feature,
@ -185,6 +190,8 @@ export const NewFeatureStrategyForm = ({
setTab,
StrategyVariants,
}: IFeatureStrategyFormProps) => {
const { openFeedback, hasSubmittedFeedback } =
useFeedback(feedbackCategory);
const { trackEvent } = usePlausibleTracker();
const [showProdGuard, setShowProdGuard] = useState(false);
const hasValidConstraints = useConstraintsValidation(strategy.constraints);
@ -195,6 +202,9 @@ export const NewFeatureStrategyForm = ({
environmentId,
);
const { strategyDefinition } = useStrategy(strategy?.name);
const newStrategyConfigurationFeedback = useUiFlag(
'newStrategyConfigurationFeedback',
);
useEffect(() => {
trackEvent('new-strategy-form', {
@ -265,6 +275,15 @@ export const NewFeatureStrategyForm = ({
navigate(formatFeaturePath(feature.project, feature.name));
};
const createFeedbackContext = () => {
openFeedback({
title: 'How easy was it to work with the new strategy form?',
positiveLabel: 'What do you like most about the new strategy form?',
areasForImprovementsLabel:
'What should be improved the new strategy form?',
});
};
const onSubmitWithValidation = async (event: React.FormEvent) => {
if (Array.isArray(strategy.variants) && strategy.variants?.length > 0) {
trackEvent('strategy-variants', {
@ -287,7 +306,15 @@ export const NewFeatureStrategyForm = ({
if (enableProdGuard && !isChangeRequest) {
setShowProdGuard(true);
} else {
onSubmit();
await onSubmitWithFeedback();
}
};
const onSubmitWithFeedback = async () => {
await onSubmit();
if (newStrategyConfigurationFeedback && !hasSubmittedFeedback) {
createFeedbackContext();
}
};
@ -488,7 +515,7 @@ export const NewFeatureStrategyForm = ({
<FeatureStrategyProdGuard
open={showProdGuard}
onClose={() => setShowProdGuard(false)}
onClick={onSubmit}
onClick={onSubmitWithFeedback}
loading={loading}
label='Save strategy'
/>

View File

@ -71,9 +71,8 @@ const feedbackCategory = 'search';
const FeatureToggleListTableComponent: VFC = () => {
const theme = useTheme();
const { openFeedback } = useFeedback();
const { hasSubmittedFeedback, setHasSubmittedFeedback } =
useUserSubmittedFeedback(feedbackCategory);
const { openFeedback, hasSubmittedFeedback } =
useFeedback(feedbackCategory);
const { trackEvent } = usePlausibleTracker();
const { environments } = useEnvironments();
const enabledEnvironments = environments
@ -276,7 +275,6 @@ const FeatureToggleListTableComponent: VFC = () => {
const createFeedbackContext = () => {
openFeedback({
category: feedbackCategory,
title: 'How easy was it to use search and filters?',
positiveLabel: 'What do you like most about search and filters?',
areasForImprovementsLabel:

View File

@ -7,7 +7,7 @@ import {
Tooltip,
} from '@mui/material';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { useFeedback } from './useFeedback';
import { useFeedbackContext } from './useFeedback';
import React, { useState } from 'react';
import CloseIcon from '@mui/icons-material/Close';
import useToast from 'hooks/useToast';
@ -93,6 +93,7 @@ export const FormSubTitle = styled(Box)(({ theme }) => ({
color: theme.palette.text.primary,
fontSize: theme.spacing(1.75),
lineHeight: theme.spacing(2.5),
marginBottom: theme.spacing(0.5),
}));
export const StyledButton = styled(Button)(() => ({
@ -160,7 +161,7 @@ const StyledCloseButton = styled(IconButton)(({ theme }) => ({
}));
export const FeedbackComponentWrapper = () => {
const { feedbackData, showFeedback, closeFeedback } = useFeedback();
const { feedbackData, showFeedback, closeFeedback } = useFeedbackContext();
if (!feedbackData) return null;
@ -309,7 +310,7 @@ export const FeedbackComponent = ({
{feedbackData.positiveLabel}
</FormSubTitle>
<TextField
label='Your answer here'
placeholder='Your answer here'
style={{ width: '100%' }}
name='positive'
multiline
@ -329,7 +330,7 @@ export const FeedbackComponent = ({
{feedbackData.areasForImprovementsLabel}
</FormSubTitle>
<TextField
label='Your answer here'
placeholder='Your answer here'
style={{ width: '100%' }}
multiline
name='areasForImprovement'
@ -344,18 +345,15 @@ export const FeedbackComponent = ({
size='small'
/>
</Box>
<ConditionallyRender
condition={Boolean(selectedScore)}
show={
<StyledButton
variant='contained'
color='primary'
type='submit'
>
Send Feedback
</StyledButton>
}
/>
<StyledButton
disabled={!selectedScore}
variant='contained'
color='primary'
type='submit'
>
Send Feedback
</StyledButton>
</StyledForm>
</StyledContent>
</StyledContainer>

View File

@ -1,8 +1,7 @@
import { createContext } from 'react';
import { ProvideFeedbackSchema } from '../../openapi';
import { IFeedbackCategory } from 'hooks/useSubmittedFeedback';
interface IFeedbackContext {
export interface IFeedbackContext {
feedbackData: FeedbackData | undefined;
openFeedback: (data: FeedbackData) => void;
closeFeedback: () => void;

View File

@ -1,10 +1,41 @@
import { FeedbackContext } from './FeedbackContext';
import {
IFeedbackCategory,
useUserSubmittedFeedback,
} from 'hooks/useSubmittedFeedback';
import { FeedbackContext, IFeedbackContext } from './FeedbackContext';
import { useContext } from 'react';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
export const useFeedback = () => {
type OpenFeedbackParams = {
title: string;
positiveLabel: string;
areasForImprovementsLabel: string;
};
export const useFeedbackContext = (): IFeedbackContext => {
const context = useContext(FeedbackContext);
if (!context) {
throw new Error('useFeedback must be used within a FeedbackProvider');
throw new Error(
'useFeedbackContext must be used within a FeedbackProvider',
);
}
return context;
};
export const useFeedback = (feedbackCategory: IFeedbackCategory) => {
const context = useFeedbackContext();
const { hasSubmittedFeedback } = useUserSubmittedFeedback(feedbackCategory);
return {
...context,
hasSubmittedFeedback,
openFeedback: (parameters: OpenFeedbackParams) => {
context.openFeedback({
...parameters,
category: feedbackCategory,
});
},
};
};

View File

@ -3,7 +3,7 @@ import { getLocalStorageItem, setLocalStorageItem } from '../utils/storage';
import { basePath } from 'utils/formatPath';
import { createLocalStorage } from '../utils/createLocalStorage';
export type IFeedbackCategory = 'search';
export type IFeedbackCategory = 'search' | 'newStrategyForm';
export const useUserSubmittedFeedback = (category: IFeedbackCategory) => {
const key = `${basePath}:unleash-userSubmittedFeedback:${category}`;

View File

@ -72,6 +72,7 @@ export type UiFlags = {
increaseUnleashWidth?: boolean;
featureSearchFeedback?: boolean;
enableLicense?: boolean;
newStrategyConfigurationFeedback?: boolean;
};
export interface IVersionInfo {

View File

@ -9,6 +9,7 @@ import { AccessProviderMock } from 'component/providers/AccessProvider/AccessPro
import { UIProviderContainer } from '../component/providers/UIProvider/UIProviderContainer';
import { ReactRouter6Adapter } from 'use-query-params/adapters/react-router-6';
import { QueryParamProvider } from 'use-query-params';
import { FeedbackProvider } from 'component/feedbackNew/FeedbackProvider';
export const render = (
ui: JSX.Element,
@ -29,21 +30,23 @@ export const render = (
const Wrapper: FC = ({ children }) => (
<UIProviderContainer>
<SWRConfig
value={{ provider: () => new Map(), dedupingInterval: 0 }}
>
<AccessProviderMock permissions={permissions}>
<BrowserRouter>
<QueryParamProvider adapter={ReactRouter6Adapter}>
<ThemeProvider>
<AnnouncerProvider>
{children}
</AnnouncerProvider>
</ThemeProvider>
</QueryParamProvider>
</BrowserRouter>
</AccessProviderMock>
</SWRConfig>
<FeedbackProvider>
<SWRConfig
value={{ provider: () => new Map(), dedupingInterval: 0 }}
>
<AccessProviderMock permissions={permissions}>
<BrowserRouter>
<QueryParamProvider adapter={ReactRouter6Adapter}>
<ThemeProvider>
<AnnouncerProvider>
{children}
</AnnouncerProvider>
</ThemeProvider>
</QueryParamProvider>
</BrowserRouter>
</AccessProviderMock>
</SWRConfig>
</FeedbackProvider>
</UIProviderContainer>
);

View File

@ -104,6 +104,7 @@ exports[`should create default config 1`] = `
},
"migrationLock": true,
"newStrategyConfiguration": false,
"newStrategyConfigurationFeedback": false,
"personalAccessTokensKillSwitch": false,
"proPlanAutoCharge": false,
"responseTimeWithAppNameKillSwitch": false,

View File

@ -36,7 +36,8 @@ export type IFlagKey =
| 'celebrateUnleash'
| 'increaseUnleashWidth'
| 'featureSearchFeedback'
| 'featureSearchFeedbackPosting';
| 'featureSearchFeedbackPosting'
| 'newStrategyConfigurationFeedback';
export type IFlags = Partial<{ [key in IFlagKey]: boolean | Variant }>;
@ -163,6 +164,10 @@ const flags: IFlags = {
process.env.UNLEASH_EXPERIMENTAL_FEATURE_SEARCH_FEEDBACK_POSTING,
false,
),
newStrategyConfigurationFeedback: parseEnvVarBoolean(
process.env.UNLEASH_EXPERIMENTAL_NEW_STRATEGY_CONFIGURATION_FEEDBACK,
false,
),
};
export const defaultExperimentalOptions: IExperimentalOptions = {

View File

@ -47,6 +47,8 @@ process.nextTick(async () => {
stripHeadersOnAPI: true,
celebrateUnleash: true,
increaseUnleashWidth: true,
featureSearchFeedback: true,
newStrategyConfigurationFeedback: true,
},
},
authentication: {