mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01: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 { useFeedback } from '../../feedbackNew/useFeedback';
 | 
			
		||||
import { ReviewsOutlined } from '@mui/icons-material';
 | 
			
		||||
import { useUserSubmittedFeedback } from 'hooks/useSubmittedFeedback';
 | 
			
		||||
 | 
			
		||||
export const featuresPlaceholder = Array(15).fill({
 | 
			
		||||
    name: 'Name of the feature',
 | 
			
		||||
@ -66,10 +67,13 @@ export const featuresPlaceholder = Array(15).fill({
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const columnHelper = createColumnHelper<FeatureSearchResponseSchema>();
 | 
			
		||||
const feedbackCategory = 'search';
 | 
			
		||||
 | 
			
		||||
const FeatureToggleListTableComponent: VFC = () => {
 | 
			
		||||
    const theme = useTheme();
 | 
			
		||||
    const { openFeedback } = useFeedback();
 | 
			
		||||
    const { hasSubmittedFeedback, setHasSubmittedFeedback } =
 | 
			
		||||
        useUserSubmittedFeedback(feedbackCategory);
 | 
			
		||||
    const { trackEvent } = usePlausibleTracker();
 | 
			
		||||
    const { environments } = useEnvironments();
 | 
			
		||||
    const enabledEnvironments = environments
 | 
			
		||||
@ -81,6 +85,7 @@ const FeatureToggleListTableComponent: VFC = () => {
 | 
			
		||||
 | 
			
		||||
    const { setToastApiError } = useToast();
 | 
			
		||||
    const { uiConfig, isPro, isOss, isEnterprise } = useUiConfig();
 | 
			
		||||
    const featureSearchFeedback = useUiFlag('featureSearchFeedback');
 | 
			
		||||
 | 
			
		||||
    const stateConfig = {
 | 
			
		||||
        offset: withDefault(NumberParam, 0),
 | 
			
		||||
@ -278,7 +283,7 @@ const FeatureToggleListTableComponent: VFC = () => {
 | 
			
		||||
                  ? 'enterprise'
 | 
			
		||||
                  : 'unknown';
 | 
			
		||||
        openFeedback({
 | 
			
		||||
            category: 'search',
 | 
			
		||||
            category: feedbackCategory,
 | 
			
		||||
            userType,
 | 
			
		||||
            title: 'How easy was it to use search and filters?',
 | 
			
		||||
            positiveLabel: 'What do you like most about search and filters?',
 | 
			
		||||
@ -328,6 +333,13 @@ const FeatureToggleListTableComponent: VFC = () => {
 | 
			
		||||
                            <FeatureToggleListActions
 | 
			
		||||
                                onExportClick={() => setShowExportDialog(true)}
 | 
			
		||||
                            />
 | 
			
		||||
                            <ConditionallyRender
 | 
			
		||||
                                condition={
 | 
			
		||||
                                    !isOss() &&
 | 
			
		||||
                                    !hasSubmittedFeedback &&
 | 
			
		||||
                                    featureSearchFeedback
 | 
			
		||||
                                }
 | 
			
		||||
                                show={
 | 
			
		||||
                                    <Tooltip title='Provide feedback' arrow>
 | 
			
		||||
                                        <IconButton
 | 
			
		||||
                                            onClick={createFeedbackContext}
 | 
			
		||||
@ -336,6 +348,8 @@ const FeatureToggleListTableComponent: VFC = () => {
 | 
			
		||||
                                            <ReviewsOutlined />
 | 
			
		||||
                                        </IconButton>
 | 
			
		||||
                                    </Tooltip>
 | 
			
		||||
                                }
 | 
			
		||||
                            />
 | 
			
		||||
                        </>
 | 
			
		||||
                    }
 | 
			
		||||
                >
 | 
			
		||||
 | 
			
		||||
@ -10,9 +10,10 @@ import { ConditionallyRender } from 'component/common/ConditionallyRender/Condit
 | 
			
		||||
import { useFeedback } from './useFeedback';
 | 
			
		||||
import React, { useState } from 'react';
 | 
			
		||||
import CloseIcon from '@mui/icons-material/Close';
 | 
			
		||||
import { useUserFeedbackApi } from 'hooks/api/actions/useUserFeedbackApi/useUserFeedbackApi';
 | 
			
		||||
import useToast from 'hooks/useToast';
 | 
			
		||||
import { ProvideFeedbackSchema } from '../../openapi';
 | 
			
		||||
import { useUserFeedbackApi } from 'hooks/api/actions/useUserFeedbackApi/useUserFeedbackApi';
 | 
			
		||||
import { useUserSubmittedFeedback } from 'hooks/useSubmittedFeedback';
 | 
			
		||||
 | 
			
		||||
export const ParentContainer = styled('div')(({ theme }) => ({
 | 
			
		||||
    position: 'relative',
 | 
			
		||||
@ -69,7 +70,7 @@ export const StyledForm = styled('form')(({ theme }) => ({
 | 
			
		||||
    border: `1px solid ${theme.palette.divider}`,
 | 
			
		||||
    borderRadius: theme.spacing(1.5),
 | 
			
		||||
    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)',
 | 
			
		||||
 | 
			
		||||
    '& > *': {
 | 
			
		||||
@ -161,6 +162,9 @@ export const FeedbackComponent = () => {
 | 
			
		||||
 | 
			
		||||
    const { setToastData } = useToast();
 | 
			
		||||
    const { addFeedback } = useUserFeedbackApi();
 | 
			
		||||
    const { setHasSubmittedFeedback } = useUserSubmittedFeedback(
 | 
			
		||||
        feedbackData.category,
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    function isProvideFeedbackSchema(data: any): data is ProvideFeedbackSchema {
 | 
			
		||||
        data.difficultyScore = data.difficultyScore
 | 
			
		||||
@ -186,6 +190,7 @@ export const FeedbackComponent = () => {
 | 
			
		||||
                title: 'Feedback sent',
 | 
			
		||||
                type: 'success',
 | 
			
		||||
            });
 | 
			
		||||
            setHasSubmittedFeedback(true);
 | 
			
		||||
        } else {
 | 
			
		||||
            setToastData({
 | 
			
		||||
                title: 'Feedback not sent',
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,6 @@
 | 
			
		||||
import { createContext } from 'react';
 | 
			
		||||
import { ProvideFeedbackSchema } from '../../openapi';
 | 
			
		||||
import { IFeedbackCategory } from 'hooks/useSubmittedFeedback';
 | 
			
		||||
 | 
			
		||||
interface IFeedbackContext {
 | 
			
		||||
    feedbackData: IFeedbackData | undefined;
 | 
			
		||||
@ -15,11 +16,8 @@ type IFeedbackText = {
 | 
			
		||||
    areasForImprovementsLabel: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type IFeedbackData = Pick<
 | 
			
		||||
    ProvideFeedbackSchema,
 | 
			
		||||
    'category' | 'userType'
 | 
			
		||||
> &
 | 
			
		||||
    IFeedbackText;
 | 
			
		||||
export type IFeedbackData = Pick<ProvideFeedbackSchema, 'userType'> &
 | 
			
		||||
    IFeedbackText & { category: IFeedbackCategory };
 | 
			
		||||
 | 
			
		||||
export const FeedbackContext = createContext<IFeedbackContext | undefined>(
 | 
			
		||||
    undefined,
 | 
			
		||||
 | 
			
		||||
@ -2,15 +2,23 @@ import { IInternalBanner } from 'interfaces/banner';
 | 
			
		||||
import useAPI from '../useApi/useApi';
 | 
			
		||||
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 = () => {
 | 
			
		||||
    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({
 | 
			
		||||
        propagateErrors: true,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    const addFeedback = async (feedbackSchema: ProvideFeedbackSchema) => {
 | 
			
		||||
        const requestId = 'addBanner';
 | 
			
		||||
        const requestId = 'addFeedback';
 | 
			
		||||
        const req = createRequest(
 | 
			
		||||
            ENDPOINT,
 | 
			
		||||
            {
 | 
			
		||||
@ -26,6 +34,7 @@ export const useUserFeedbackApi = () => {
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
        addFeedback,
 | 
			
		||||
        addDirectFeedback,
 | 
			
		||||
        errors,
 | 
			
		||||
        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;
 | 
			
		||||
    celebrateUnleash?: boolean;
 | 
			
		||||
    increaseUnleashWidth?: boolean;
 | 
			
		||||
    featureSearchFeedback?: boolean;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export interface IVersionInfo {
 | 
			
		||||
 | 
			
		||||
@ -83,6 +83,7 @@ exports[`should create default config 1`] = `
 | 
			
		||||
      "embedProxy": true,
 | 
			
		||||
      "embedProxyFrontend": true,
 | 
			
		||||
      "featureSearchAPI": false,
 | 
			
		||||
      "featureSearchFeedback": false,
 | 
			
		||||
      "featureSearchFrontend": false,
 | 
			
		||||
      "featuresExportImport": true,
 | 
			
		||||
      "filterInvalidClientMetrics": false,
 | 
			
		||||
 | 
			
		||||
@ -34,7 +34,8 @@ export type IFlagKey =
 | 
			
		||||
    | 'stripHeadersOnAPI'
 | 
			
		||||
    | 'incomingWebhooks'
 | 
			
		||||
    | 'celebrateUnleash'
 | 
			
		||||
    | 'increaseUnleashWidth';
 | 
			
		||||
    | 'increaseUnleashWidth'
 | 
			
		||||
    | 'featureSearchFeedback';
 | 
			
		||||
 | 
			
		||||
export type IFlags = Partial<{ [key in IFlagKey]: boolean | Variant }>;
 | 
			
		||||
 | 
			
		||||
@ -156,6 +157,10 @@ const flags: IFlags = {
 | 
			
		||||
        process.env.UNLEASH_EXPERIMENTAL_INCREASE_UNLEASH_WIDTH,
 | 
			
		||||
        false,
 | 
			
		||||
    ),
 | 
			
		||||
    featureSearchFeedback: parseEnvVarBoolean(
 | 
			
		||||
        process.env.UNLEASH_EXPERIMENTAL_FEATURE_SEARCH_FEEDBACK,
 | 
			
		||||
        false,
 | 
			
		||||
    ),
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const defaultExperimentalOptions: IExperimentalOptions = {
 | 
			
		||||
 | 
			
		||||
@ -48,6 +48,7 @@ process.nextTick(async () => {
 | 
			
		||||
                        stripHeadersOnAPI: true,
 | 
			
		||||
                        celebrateUnleash: true,
 | 
			
		||||
                        increaseUnleashWidth: true,
 | 
			
		||||
                        featureSearchFeedback: true,
 | 
			
		||||
                    },
 | 
			
		||||
                },
 | 
			
		||||
                authentication: {
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user