mirror of
https://github.com/Unleash/unleash.git
synced 2025-08-04 13:48:56 +02:00
feat: keep feedback submission in local storage (#5737)
Now it will track if feedback has been submitted in local storage.
This commit is contained in:
parent
86da11015c
commit
55bd0a6760
@ -56,6 +56,7 @@ import useLoading from 'hooks/useLoading';
|
|||||||
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
|
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
|
||||||
import { useFeedback } from '../../feedbackNew/useFeedback';
|
import { useFeedback } from '../../feedbackNew/useFeedback';
|
||||||
import { ReviewsOutlined } from '@mui/icons-material';
|
import { ReviewsOutlined } from '@mui/icons-material';
|
||||||
|
import { useUserSubmittedFeedback } from 'hooks/useSubmittedFeedback';
|
||||||
|
|
||||||
export const featuresPlaceholder = Array(15).fill({
|
export const featuresPlaceholder = Array(15).fill({
|
||||||
name: 'Name of the feature',
|
name: 'Name of the feature',
|
||||||
@ -66,10 +67,13 @@ export const featuresPlaceholder = Array(15).fill({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const columnHelper = createColumnHelper<FeatureSearchResponseSchema>();
|
const columnHelper = createColumnHelper<FeatureSearchResponseSchema>();
|
||||||
|
const feedbackCategory = 'search';
|
||||||
|
|
||||||
const FeatureToggleListTableComponent: VFC = () => {
|
const FeatureToggleListTableComponent: VFC = () => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { openFeedback } = useFeedback();
|
const { openFeedback } = useFeedback();
|
||||||
|
const { hasSubmittedFeedback, setHasSubmittedFeedback } =
|
||||||
|
useUserSubmittedFeedback(feedbackCategory);
|
||||||
const { trackEvent } = usePlausibleTracker();
|
const { trackEvent } = usePlausibleTracker();
|
||||||
const { environments } = useEnvironments();
|
const { environments } = useEnvironments();
|
||||||
const enabledEnvironments = environments
|
const enabledEnvironments = environments
|
||||||
@ -81,6 +85,7 @@ const FeatureToggleListTableComponent: VFC = () => {
|
|||||||
|
|
||||||
const { setToastApiError } = useToast();
|
const { setToastApiError } = useToast();
|
||||||
const { uiConfig, isPro, isOss, isEnterprise } = useUiConfig();
|
const { uiConfig, isPro, isOss, isEnterprise } = useUiConfig();
|
||||||
|
const featureSearchFeedback = useUiFlag('featureSearchFeedback');
|
||||||
|
|
||||||
const stateConfig = {
|
const stateConfig = {
|
||||||
offset: withDefault(NumberParam, 0),
|
offset: withDefault(NumberParam, 0),
|
||||||
@ -278,7 +283,7 @@ const FeatureToggleListTableComponent: VFC = () => {
|
|||||||
? 'enterprise'
|
? 'enterprise'
|
||||||
: 'unknown';
|
: 'unknown';
|
||||||
openFeedback({
|
openFeedback({
|
||||||
category: 'search',
|
category: feedbackCategory,
|
||||||
userType,
|
userType,
|
||||||
title: 'How easy was it to use search and filters?',
|
title: 'How easy was it to use search and filters?',
|
||||||
positiveLabel: 'What do you like most about search and filters?',
|
positiveLabel: 'What do you like most about search and filters?',
|
||||||
@ -328,14 +333,23 @@ const FeatureToggleListTableComponent: VFC = () => {
|
|||||||
<FeatureToggleListActions
|
<FeatureToggleListActions
|
||||||
onExportClick={() => setShowExportDialog(true)}
|
onExportClick={() => setShowExportDialog(true)}
|
||||||
/>
|
/>
|
||||||
<Tooltip title='Provide feedback' arrow>
|
<ConditionallyRender
|
||||||
<IconButton
|
condition={
|
||||||
onClick={createFeedbackContext}
|
!isOss() &&
|
||||||
size='large'
|
!hasSubmittedFeedback &&
|
||||||
>
|
featureSearchFeedback
|
||||||
<ReviewsOutlined />
|
}
|
||||||
</IconButton>
|
show={
|
||||||
</Tooltip>
|
<Tooltip title='Provide feedback' arrow>
|
||||||
|
<IconButton
|
||||||
|
onClick={createFeedbackContext}
|
||||||
|
size='large'
|
||||||
|
>
|
||||||
|
<ReviewsOutlined />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
@ -10,9 +10,10 @@ import { ConditionallyRender } from 'component/common/ConditionallyRender/Condit
|
|||||||
import { useFeedback } from './useFeedback';
|
import { useFeedback } from './useFeedback';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import CloseIcon from '@mui/icons-material/Close';
|
import CloseIcon from '@mui/icons-material/Close';
|
||||||
import { useUserFeedbackApi } from 'hooks/api/actions/useUserFeedbackApi/useUserFeedbackApi';
|
|
||||||
import useToast from 'hooks/useToast';
|
import useToast from 'hooks/useToast';
|
||||||
import { ProvideFeedbackSchema } from '../../openapi';
|
import { ProvideFeedbackSchema } from '../../openapi';
|
||||||
|
import { useUserFeedbackApi } from 'hooks/api/actions/useUserFeedbackApi/useUserFeedbackApi';
|
||||||
|
import { useUserSubmittedFeedback } from 'hooks/useSubmittedFeedback';
|
||||||
|
|
||||||
export const ParentContainer = styled('div')(({ theme }) => ({
|
export const ParentContainer = styled('div')(({ theme }) => ({
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
@ -69,7 +70,7 @@ export const StyledForm = styled('form')(({ theme }) => ({
|
|||||||
border: `1px solid ${theme.palette.divider}`,
|
border: `1px solid ${theme.palette.divider}`,
|
||||||
borderRadius: theme.spacing(1.5),
|
borderRadius: theme.spacing(1.5),
|
||||||
borderColor: 'rgba(0, 0, 0, 0.12)',
|
borderColor: 'rgba(0, 0, 0, 0.12)',
|
||||||
backgroundColor: '#fff',
|
backgroundColor: theme.palette.background.paper,
|
||||||
boxShadow: '0px 4px 4px 0px rgba(0, 0, 0, 0.12)',
|
boxShadow: '0px 4px 4px 0px rgba(0, 0, 0, 0.12)',
|
||||||
|
|
||||||
'& > *': {
|
'& > *': {
|
||||||
@ -161,6 +162,9 @@ export const FeedbackComponent = () => {
|
|||||||
|
|
||||||
const { setToastData } = useToast();
|
const { setToastData } = useToast();
|
||||||
const { addFeedback } = useUserFeedbackApi();
|
const { addFeedback } = useUserFeedbackApi();
|
||||||
|
const { setHasSubmittedFeedback } = useUserSubmittedFeedback(
|
||||||
|
feedbackData.category,
|
||||||
|
);
|
||||||
|
|
||||||
function isProvideFeedbackSchema(data: any): data is ProvideFeedbackSchema {
|
function isProvideFeedbackSchema(data: any): data is ProvideFeedbackSchema {
|
||||||
data.difficultyScore = data.difficultyScore
|
data.difficultyScore = data.difficultyScore
|
||||||
@ -186,6 +190,7 @@ export const FeedbackComponent = () => {
|
|||||||
title: 'Feedback sent',
|
title: 'Feedback sent',
|
||||||
type: 'success',
|
type: 'success',
|
||||||
});
|
});
|
||||||
|
setHasSubmittedFeedback(true);
|
||||||
} else {
|
} else {
|
||||||
setToastData({
|
setToastData({
|
||||||
title: 'Feedback not sent',
|
title: 'Feedback not sent',
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { createContext } from 'react';
|
import { createContext } from 'react';
|
||||||
import { ProvideFeedbackSchema } from '../../openapi';
|
import { ProvideFeedbackSchema } from '../../openapi';
|
||||||
|
import { IFeedbackCategory } from 'hooks/useSubmittedFeedback';
|
||||||
|
|
||||||
interface IFeedbackContext {
|
interface IFeedbackContext {
|
||||||
feedbackData: IFeedbackData | undefined;
|
feedbackData: IFeedbackData | undefined;
|
||||||
@ -15,11 +16,8 @@ type IFeedbackText = {
|
|||||||
areasForImprovementsLabel: string;
|
areasForImprovementsLabel: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type IFeedbackData = Pick<
|
export type IFeedbackData = Pick<ProvideFeedbackSchema, 'userType'> &
|
||||||
ProvideFeedbackSchema,
|
IFeedbackText & { category: IFeedbackCategory };
|
||||||
'category' | 'userType'
|
|
||||||
> &
|
|
||||||
IFeedbackText;
|
|
||||||
|
|
||||||
export const FeedbackContext = createContext<IFeedbackContext | undefined>(
|
export const FeedbackContext = createContext<IFeedbackContext | undefined>(
|
||||||
undefined,
|
undefined,
|
||||||
|
@ -2,15 +2,23 @@ import { IInternalBanner } from 'interfaces/banner';
|
|||||||
import useAPI from '../useApi/useApi';
|
import useAPI from '../useApi/useApi';
|
||||||
import { ProvideFeedbackSchema } from '../../../../openapi';
|
import { ProvideFeedbackSchema } from '../../../../openapi';
|
||||||
|
|
||||||
const ENDPOINT = 'api/admin/user-feedback';
|
const DIRECT_ENDPOINT = 'https://sandbox.getunleash.io/enterprise/feedback';
|
||||||
|
const ENDPOINT = 'feedback';
|
||||||
|
|
||||||
export const useUserFeedbackApi = () => {
|
export const useUserFeedbackApi = () => {
|
||||||
|
const addDirectFeedback = async (feedbackSchema: ProvideFeedbackSchema) => {
|
||||||
|
await fetch(DIRECT_ENDPOINT, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(feedbackSchema),
|
||||||
|
});
|
||||||
|
};
|
||||||
const { loading, makeRequest, createRequest, errors } = useAPI({
|
const { loading, makeRequest, createRequest, errors } = useAPI({
|
||||||
propagateErrors: true,
|
propagateErrors: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const addFeedback = async (feedbackSchema: ProvideFeedbackSchema) => {
|
const addFeedback = async (feedbackSchema: ProvideFeedbackSchema) => {
|
||||||
const requestId = 'addBanner';
|
const requestId = 'addFeedback';
|
||||||
const req = createRequest(
|
const req = createRequest(
|
||||||
ENDPOINT,
|
ENDPOINT,
|
||||||
{
|
{
|
||||||
@ -26,6 +34,7 @@ export const useUserFeedbackApi = () => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
addFeedback,
|
addFeedback,
|
||||||
|
addDirectFeedback,
|
||||||
errors,
|
errors,
|
||||||
loading,
|
loading,
|
||||||
};
|
};
|
||||||
|
22
frontend/src/hooks/useSubmittedFeedback.ts
Normal file
22
frontend/src/hooks/useSubmittedFeedback.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { getLocalStorageItem, setLocalStorageItem } from '../utils/storage';
|
||||||
|
import { basePath } from 'utils/formatPath';
|
||||||
|
|
||||||
|
export type IFeedbackCategory = 'search';
|
||||||
|
|
||||||
|
export const useUserSubmittedFeedback = (category: IFeedbackCategory) => {
|
||||||
|
const key = `${basePath}:unleash-userSubmittedFeedback:${category}`;
|
||||||
|
|
||||||
|
const [hasSubmittedFeedback, setHasSubmittedFeedback] = useState(() => {
|
||||||
|
return getLocalStorageItem<boolean>(key) || false;
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setLocalStorageItem(key, hasSubmittedFeedback);
|
||||||
|
}, [hasSubmittedFeedback, key]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
hasSubmittedFeedback,
|
||||||
|
setHasSubmittedFeedback,
|
||||||
|
};
|
||||||
|
};
|
@ -70,6 +70,7 @@ export type UiFlags = {
|
|||||||
incomingWebhooks?: boolean;
|
incomingWebhooks?: boolean;
|
||||||
celebrateUnleash?: boolean;
|
celebrateUnleash?: boolean;
|
||||||
increaseUnleashWidth?: boolean;
|
increaseUnleashWidth?: boolean;
|
||||||
|
featureSearchFeedback?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface IVersionInfo {
|
export interface IVersionInfo {
|
||||||
|
@ -83,6 +83,7 @@ exports[`should create default config 1`] = `
|
|||||||
"embedProxy": true,
|
"embedProxy": true,
|
||||||
"embedProxyFrontend": true,
|
"embedProxyFrontend": true,
|
||||||
"featureSearchAPI": false,
|
"featureSearchAPI": false,
|
||||||
|
"featureSearchFeedback": false,
|
||||||
"featureSearchFrontend": false,
|
"featureSearchFrontend": false,
|
||||||
"featuresExportImport": true,
|
"featuresExportImport": true,
|
||||||
"filterInvalidClientMetrics": false,
|
"filterInvalidClientMetrics": false,
|
||||||
|
@ -34,7 +34,8 @@ export type IFlagKey =
|
|||||||
| 'stripHeadersOnAPI'
|
| 'stripHeadersOnAPI'
|
||||||
| 'incomingWebhooks'
|
| 'incomingWebhooks'
|
||||||
| 'celebrateUnleash'
|
| 'celebrateUnleash'
|
||||||
| 'increaseUnleashWidth';
|
| 'increaseUnleashWidth'
|
||||||
|
| 'featureSearchFeedback';
|
||||||
|
|
||||||
export type IFlags = Partial<{ [key in IFlagKey]: boolean | Variant }>;
|
export type IFlags = Partial<{ [key in IFlagKey]: boolean | Variant }>;
|
||||||
|
|
||||||
@ -156,6 +157,10 @@ const flags: IFlags = {
|
|||||||
process.env.UNLEASH_EXPERIMENTAL_INCREASE_UNLEASH_WIDTH,
|
process.env.UNLEASH_EXPERIMENTAL_INCREASE_UNLEASH_WIDTH,
|
||||||
false,
|
false,
|
||||||
),
|
),
|
||||||
|
featureSearchFeedback: parseEnvVarBoolean(
|
||||||
|
process.env.UNLEASH_EXPERIMENTAL_FEATURE_SEARCH_FEEDBACK,
|
||||||
|
false,
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const defaultExperimentalOptions: IExperimentalOptions = {
|
export const defaultExperimentalOptions: IExperimentalOptions = {
|
||||||
|
@ -48,6 +48,7 @@ process.nextTick(async () => {
|
|||||||
stripHeadersOnAPI: true,
|
stripHeadersOnAPI: true,
|
||||||
celebrateUnleash: true,
|
celebrateUnleash: true,
|
||||||
increaseUnleashWidth: true,
|
increaseUnleashWidth: true,
|
||||||
|
featureSearchFeedback: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
authentication: {
|
authentication: {
|
||||||
|
Loading…
Reference in New Issue
Block a user