1
0
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:
Jaanus Sellin 2023-12-29 10:08:19 +02:00 committed by GitHub
parent 86da11015c
commit 55bd0a6760
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 75 additions and 19 deletions

View File

@ -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>
}
/>
</> </>
} }
> >

View File

@ -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',

View File

@ -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,

View File

@ -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,
}; };

View 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,
};
};

View File

@ -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 {

View File

@ -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,

View File

@ -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 = {

View File

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