mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	feat: make feedback available for OSS (#5748)
This commit is contained in:
		
							parent
							
								
									1556a51e37
								
							
						
					
					
						commit
						a73d87a943
					
				@ -84,7 +84,7 @@ const FeatureToggleListTableComponent: VFC = () => {
 | 
			
		||||
    const [showExportDialog, setShowExportDialog] = useState(false);
 | 
			
		||||
 | 
			
		||||
    const { setToastApiError } = useToast();
 | 
			
		||||
    const { uiConfig, isPro, isOss, isEnterprise } = useUiConfig();
 | 
			
		||||
    const { uiConfig } = useUiConfig();
 | 
			
		||||
    const featureSearchFeedback = useUiFlag('featureSearchFeedback');
 | 
			
		||||
 | 
			
		||||
    const stateConfig = {
 | 
			
		||||
@ -275,16 +275,8 @@ const FeatureToggleListTableComponent: VFC = () => {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const createFeedbackContext = () => {
 | 
			
		||||
        const userType = isPro()
 | 
			
		||||
            ? 'pro'
 | 
			
		||||
            : isOss()
 | 
			
		||||
              ? 'oss'
 | 
			
		||||
              : isEnterprise()
 | 
			
		||||
                  ? 'enterprise'
 | 
			
		||||
                  : 'unknown';
 | 
			
		||||
        openFeedback({
 | 
			
		||||
            category: feedbackCategory,
 | 
			
		||||
            userType,
 | 
			
		||||
            title: 'How easy was it to use search and filters?',
 | 
			
		||||
            positiveLabel: 'What do you like most about search and filters?',
 | 
			
		||||
            areasForImprovementsLabel:
 | 
			
		||||
@ -335,7 +327,6 @@ const FeatureToggleListTableComponent: VFC = () => {
 | 
			
		||||
                            />
 | 
			
		||||
                            <ConditionallyRender
 | 
			
		||||
                                condition={
 | 
			
		||||
                                    !isOss() &&
 | 
			
		||||
                                    !hasSubmittedFeedback &&
 | 
			
		||||
                                    featureSearchFeedback
 | 
			
		||||
                                }
 | 
			
		||||
 | 
			
		||||
@ -14,6 +14,10 @@ import useToast from 'hooks/useToast';
 | 
			
		||||
import { ProvideFeedbackSchema } from '../../openapi';
 | 
			
		||||
import { useUserFeedbackApi } from 'hooks/api/actions/useUserFeedbackApi/useUserFeedbackApi';
 | 
			
		||||
import { useUserSubmittedFeedback } from 'hooks/useSubmittedFeedback';
 | 
			
		||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
 | 
			
		||||
import { IToast } from 'interfaces/toast';
 | 
			
		||||
import { useTheme } from '@mui/material/styles';
 | 
			
		||||
import { FeedbackData } from './FeedbackContext';
 | 
			
		||||
 | 
			
		||||
export const ParentContainer = styled('div')(({ theme }) => ({
 | 
			
		||||
    position: 'relative',
 | 
			
		||||
@ -155,12 +159,34 @@ const StyledCloseButton = styled(IconButton)(({ theme }) => ({
 | 
			
		||||
    color: theme.palette.background.paper,
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
export const FeedbackComponent = () => {
 | 
			
		||||
export const FeedbackComponentWrapper = () => {
 | 
			
		||||
    const { feedbackData, showFeedback, closeFeedback } = useFeedback();
 | 
			
		||||
 | 
			
		||||
    if (!feedbackData) return null;
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <FeedbackComponent
 | 
			
		||||
            feedbackData={feedbackData}
 | 
			
		||||
            showFeedback={showFeedback}
 | 
			
		||||
            closeFeedback={closeFeedback}
 | 
			
		||||
        />
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
interface IFeedbackComponent {
 | 
			
		||||
    feedbackData: FeedbackData;
 | 
			
		||||
    showFeedback: boolean;
 | 
			
		||||
    closeFeedback: () => void;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const FeedbackComponent = ({
 | 
			
		||||
    feedbackData,
 | 
			
		||||
    showFeedback,
 | 
			
		||||
    closeFeedback,
 | 
			
		||||
}: IFeedbackComponent) => {
 | 
			
		||||
    const { setToastData } = useToast();
 | 
			
		||||
    const theme = useTheme();
 | 
			
		||||
    const { isPro, isOss, isEnterprise } = useUiConfig();
 | 
			
		||||
    const { addFeedback } = useUserFeedbackApi();
 | 
			
		||||
    const { setHasSubmittedFeedback } = useUserSubmittedFeedback(
 | 
			
		||||
        feedbackData.category,
 | 
			
		||||
@ -184,19 +210,22 @@ export const FeedbackComponent = () => {
 | 
			
		||||
        const formData = new FormData(event.currentTarget);
 | 
			
		||||
        const data = Object.fromEntries(formData);
 | 
			
		||||
 | 
			
		||||
        let toastType: IToast['type'] = 'error';
 | 
			
		||||
        let toastTitle = 'Feedback not sent';
 | 
			
		||||
 | 
			
		||||
        if (isProvideFeedbackSchema(data)) {
 | 
			
		||||
            await addFeedback(data as ProvideFeedbackSchema);
 | 
			
		||||
            setToastData({
 | 
			
		||||
                title: 'Feedback sent',
 | 
			
		||||
                type: 'success',
 | 
			
		||||
            });
 | 
			
		||||
            setHasSubmittedFeedback(true);
 | 
			
		||||
        } else {
 | 
			
		||||
            setToastData({
 | 
			
		||||
                title: 'Feedback not sent',
 | 
			
		||||
                type: 'error',
 | 
			
		||||
            });
 | 
			
		||||
            try {
 | 
			
		||||
                await addFeedback(data as ProvideFeedbackSchema);
 | 
			
		||||
                toastTitle = 'Feedback sent';
 | 
			
		||||
                toastType = 'success';
 | 
			
		||||
                setHasSubmittedFeedback(true);
 | 
			
		||||
            } catch (e) {}
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        setToastData({
 | 
			
		||||
            title: toastTitle,
 | 
			
		||||
            type: toastType,
 | 
			
		||||
        });
 | 
			
		||||
        closeFeedback();
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
@ -206,6 +235,22 @@ export const FeedbackComponent = () => {
 | 
			
		||||
        setSelectedScore(event.target.value);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const getUserType = () => {
 | 
			
		||||
        if (isPro()) {
 | 
			
		||||
            return 'pro';
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (isOss()) {
 | 
			
		||||
            return 'oss';
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (isEnterprise()) {
 | 
			
		||||
            return 'enterprise';
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return 'unknown';
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <ConditionallyRender
 | 
			
		||||
            condition={showFeedback}
 | 
			
		||||
@ -233,7 +278,7 @@ export const FeedbackComponent = () => {
 | 
			
		||||
                                <input
 | 
			
		||||
                                    type='hidden'
 | 
			
		||||
                                    name='userType'
 | 
			
		||||
                                    value={feedbackData.userType}
 | 
			
		||||
                                    value={getUserType()}
 | 
			
		||||
                                />
 | 
			
		||||
                                <FormTitle>{feedbackData.title}</FormTitle>
 | 
			
		||||
                                <StyledScoreContainer>
 | 
			
		||||
@ -273,7 +318,8 @@ export const FeedbackComponent = () => {
 | 
			
		||||
                                        size='small'
 | 
			
		||||
                                        InputLabelProps={{
 | 
			
		||||
                                            style: {
 | 
			
		||||
                                                fontSize: '14px',
 | 
			
		||||
                                                fontSize:
 | 
			
		||||
                                                    theme.fontSizes.smallBody,
 | 
			
		||||
                                            },
 | 
			
		||||
                                        }}
 | 
			
		||||
                                    />
 | 
			
		||||
@ -290,7 +336,8 @@ export const FeedbackComponent = () => {
 | 
			
		||||
                                        rows={3}
 | 
			
		||||
                                        InputLabelProps={{
 | 
			
		||||
                                            style: {
 | 
			
		||||
                                                fontSize: '14px',
 | 
			
		||||
                                                fontSize:
 | 
			
		||||
                                                    theme.fontSizes.smallBody,
 | 
			
		||||
                                            },
 | 
			
		||||
                                        }}
 | 
			
		||||
                                        variant='outlined'
 | 
			
		||||
 | 
			
		||||
@ -3,21 +3,22 @@ import { ProvideFeedbackSchema } from '../../openapi';
 | 
			
		||||
import { IFeedbackCategory } from 'hooks/useSubmittedFeedback';
 | 
			
		||||
 | 
			
		||||
interface IFeedbackContext {
 | 
			
		||||
    feedbackData: IFeedbackData | undefined;
 | 
			
		||||
    openFeedback: (data: IFeedbackData) => void;
 | 
			
		||||
    feedbackData: FeedbackData | undefined;
 | 
			
		||||
    openFeedback: (data: FeedbackData) => void;
 | 
			
		||||
    closeFeedback: () => void;
 | 
			
		||||
    showFeedback: boolean;
 | 
			
		||||
    setShowFeedback: (visible: boolean) => void;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type IFeedbackText = {
 | 
			
		||||
interface IFeedbackText {
 | 
			
		||||
    title: string;
 | 
			
		||||
    positiveLabel: string;
 | 
			
		||||
    areasForImprovementsLabel: string;
 | 
			
		||||
};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type IFeedbackData = Pick<ProvideFeedbackSchema, 'userType'> &
 | 
			
		||||
    IFeedbackText & { category: IFeedbackCategory };
 | 
			
		||||
export type FeedbackData = IFeedbackText & {
 | 
			
		||||
    category: IFeedbackCategory;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const FeedbackContext = createContext<IFeedbackContext | undefined>(
 | 
			
		||||
    undefined,
 | 
			
		||||
 | 
			
		||||
@ -1,14 +1,17 @@
 | 
			
		||||
import { FeedbackComponent } from './FeedbackComponent';
 | 
			
		||||
import { FeedbackContext, IFeedbackData } from './FeedbackContext';
 | 
			
		||||
import {
 | 
			
		||||
    FeedbackComponent,
 | 
			
		||||
    FeedbackComponentWrapper,
 | 
			
		||||
} from './FeedbackComponent';
 | 
			
		||||
import { FeedbackContext, FeedbackData } from './FeedbackContext';
 | 
			
		||||
import { FC, useState } from 'react';
 | 
			
		||||
 | 
			
		||||
export const FeedbackProvider: FC = ({ children }) => {
 | 
			
		||||
    const [feedbackData, setFeedbackData] = useState<IFeedbackData | undefined>(
 | 
			
		||||
    const [feedbackData, setFeedbackData] = useState<FeedbackData | undefined>(
 | 
			
		||||
        undefined,
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    const [showFeedback, setShowFeedback] = useState(false);
 | 
			
		||||
    const openFeedback = (data: IFeedbackData) => {
 | 
			
		||||
    const openFeedback = (data: FeedbackData) => {
 | 
			
		||||
        setFeedbackData(data);
 | 
			
		||||
        setShowFeedback(true);
 | 
			
		||||
    };
 | 
			
		||||
@ -29,7 +32,7 @@ export const FeedbackProvider: FC = ({ children }) => {
 | 
			
		||||
            }}
 | 
			
		||||
        >
 | 
			
		||||
            {children}
 | 
			
		||||
            <FeedbackComponent />
 | 
			
		||||
            <FeedbackComponentWrapper />
 | 
			
		||||
        </FeedbackContext.Provider>
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -2,41 +2,20 @@ import useAPI from '../useApi/useApi';
 | 
			
		||||
import { ProvideFeedbackSchema } from '../../../../openapi';
 | 
			
		||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
 | 
			
		||||
 | 
			
		||||
const ENDPOINT = 'feedback';
 | 
			
		||||
const ENDPOINT = 'https://app.unleash-hosted.com/hosted/feedback';
 | 
			
		||||
 | 
			
		||||
export const useUserFeedbackApi = () => {
 | 
			
		||||
    const { uiConfig } = useUiConfig();
 | 
			
		||||
 | 
			
		||||
    const { loading, makeRequest, createRequest, errors } = useAPI({
 | 
			
		||||
        propagateErrors: true,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    const addFeedback = async (feedbackSchema: ProvideFeedbackSchema) => {
 | 
			
		||||
        if (uiConfig.feedbackUriPath !== undefined) {
 | 
			
		||||
            await fetch(uiConfig.feedbackUriPath, {
 | 
			
		||||
                method: 'POST',
 | 
			
		||||
                headers: { 'Content-Type': 'application/json' },
 | 
			
		||||
                body: JSON.stringify(feedbackSchema),
 | 
			
		||||
            });
 | 
			
		||||
        } else {
 | 
			
		||||
            const requestId = 'addFeedback';
 | 
			
		||||
            const req = createRequest(
 | 
			
		||||
                ENDPOINT,
 | 
			
		||||
                {
 | 
			
		||||
                    method: 'POST',
 | 
			
		||||
                    body: JSON.stringify(feedbackSchema),
 | 
			
		||||
                },
 | 
			
		||||
                requestId,
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            const response = await makeRequest(req.caller, req.id);
 | 
			
		||||
            return response.json();
 | 
			
		||||
        }
 | 
			
		||||
        await fetch(uiConfig.feedbackUriPath || ENDPOINT, {
 | 
			
		||||
            method: 'POST',
 | 
			
		||||
            headers: { 'Content-Type': 'application/json' },
 | 
			
		||||
            body: JSON.stringify(feedbackSchema),
 | 
			
		||||
        });
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
        addFeedback,
 | 
			
		||||
        errors,
 | 
			
		||||
        loading,
 | 
			
		||||
    };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -84,7 +84,7 @@ exports[`should create default config 1`] = `
 | 
			
		||||
      "embedProxy": true,
 | 
			
		||||
      "embedProxyFrontend": true,
 | 
			
		||||
      "featureSearchAPI": false,
 | 
			
		||||
      "featureSearchFeedback": false,
 | 
			
		||||
      "featureSearchFeedback": true,
 | 
			
		||||
      "featureSearchFeedbackPosting": false,
 | 
			
		||||
      "featureSearchFrontend": false,
 | 
			
		||||
      "featuresExportImport": true,
 | 
			
		||||
 | 
			
		||||
@ -53,6 +53,7 @@ const secureHeaders: (config: IUnleashConfig) => RequestHandler = (config) => {
 | 
			
		||||
                        'plausible.getunleash.io',
 | 
			
		||||
                        'gravatar.com',
 | 
			
		||||
                        'europe-west3-metrics-304612.cloudfunctions.net',
 | 
			
		||||
                        'app.unleash-hosted.com',
 | 
			
		||||
                        ...config.additionalCspAllowedDomains.connectSrc,
 | 
			
		||||
                    ],
 | 
			
		||||
                    mediaSrc: [
 | 
			
		||||
 | 
			
		||||
@ -155,7 +155,7 @@ const flags: IFlags = {
 | 
			
		||||
    ),
 | 
			
		||||
    featureSearchFeedback: parseEnvVarBoolean(
 | 
			
		||||
        process.env.UNLEASH_EXPERIMENTAL_FEATURE_SEARCH_FEEDBACK,
 | 
			
		||||
        false,
 | 
			
		||||
        true,
 | 
			
		||||
    ),
 | 
			
		||||
    featureSearchFeedbackPosting: parseEnvVarBoolean(
 | 
			
		||||
        process.env.UNLEASH_EXPERIMENTAL_FEATURE_SEARCH_FEEDBACK_POSTING,
 | 
			
		||||
 | 
			
		||||
@ -47,7 +47,6 @@ process.nextTick(async () => {
 | 
			
		||||
                        stripHeadersOnAPI: true,
 | 
			
		||||
                        celebrateUnleash: true,
 | 
			
		||||
                        increaseUnleashWidth: true,
 | 
			
		||||
                        featureSearchFeedback: true,
 | 
			
		||||
                    },
 | 
			
		||||
                },
 | 
			
		||||
                authentication: {
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user