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:
parent
31124e4a90
commit
70600552d2
@ -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'
|
||||
/>
|
||||
|
@ -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:
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
});
|
||||
},
|
||||
};
|
||||
};
|
||||
|
@ -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}`;
|
||||
|
@ -72,6 +72,7 @@ export type UiFlags = {
|
||||
increaseUnleashWidth?: boolean;
|
||||
featureSearchFeedback?: boolean;
|
||||
enableLicense?: boolean;
|
||||
newStrategyConfigurationFeedback?: boolean;
|
||||
};
|
||||
|
||||
export interface IVersionInfo {
|
||||
|
@ -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>
|
||||
);
|
||||
|
||||
|
@ -104,6 +104,7 @@ exports[`should create default config 1`] = `
|
||||
},
|
||||
"migrationLock": true,
|
||||
"newStrategyConfiguration": false,
|
||||
"newStrategyConfigurationFeedback": false,
|
||||
"personalAccessTokensKillSwitch": false,
|
||||
"proPlanAutoCharge": false,
|
||||
"responseTimeWithAppNameKillSwitch": false,
|
||||
|
@ -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 = {
|
||||
|
@ -47,6 +47,8 @@ process.nextTick(async () => {
|
||||
stripHeadersOnAPI: true,
|
||||
celebrateUnleash: true,
|
||||
increaseUnleashWidth: true,
|
||||
featureSearchFeedback: true,
|
||||
newStrategyConfigurationFeedback: true,
|
||||
},
|
||||
},
|
||||
authentication: {
|
||||
|
Loading…
Reference in New Issue
Block a user