1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-04-19 01:17:18 +02:00

feat: make feedback available for OSS (#5748)

This commit is contained in:
Jaanus Sellin 2024-01-03 15:08:01 +02:00 committed by GitHub
parent 1556a51e37
commit a73d87a943
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 87 additions and 66 deletions

View File

@ -84,7 +84,7 @@ const FeatureToggleListTableComponent: VFC = () => {
const [showExportDialog, setShowExportDialog] = useState(false); const [showExportDialog, setShowExportDialog] = useState(false);
const { setToastApiError } = useToast(); const { setToastApiError } = useToast();
const { uiConfig, isPro, isOss, isEnterprise } = useUiConfig(); const { uiConfig } = useUiConfig();
const featureSearchFeedback = useUiFlag('featureSearchFeedback'); const featureSearchFeedback = useUiFlag('featureSearchFeedback');
const stateConfig = { const stateConfig = {
@ -275,16 +275,8 @@ const FeatureToggleListTableComponent: VFC = () => {
} }
const createFeedbackContext = () => { const createFeedbackContext = () => {
const userType = isPro()
? 'pro'
: isOss()
? 'oss'
: isEnterprise()
? 'enterprise'
: 'unknown';
openFeedback({ openFeedback({
category: feedbackCategory, category: feedbackCategory,
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?',
areasForImprovementsLabel: areasForImprovementsLabel:
@ -335,7 +327,6 @@ const FeatureToggleListTableComponent: VFC = () => {
/> />
<ConditionallyRender <ConditionallyRender
condition={ condition={
!isOss() &&
!hasSubmittedFeedback && !hasSubmittedFeedback &&
featureSearchFeedback featureSearchFeedback
} }

View File

@ -14,6 +14,10 @@ import useToast from 'hooks/useToast';
import { ProvideFeedbackSchema } from '../../openapi'; import { ProvideFeedbackSchema } from '../../openapi';
import { useUserFeedbackApi } from 'hooks/api/actions/useUserFeedbackApi/useUserFeedbackApi'; import { useUserFeedbackApi } from 'hooks/api/actions/useUserFeedbackApi/useUserFeedbackApi';
import { useUserSubmittedFeedback } from 'hooks/useSubmittedFeedback'; 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 }) => ({ export const ParentContainer = styled('div')(({ theme }) => ({
position: 'relative', position: 'relative',
@ -155,12 +159,34 @@ const StyledCloseButton = styled(IconButton)(({ theme }) => ({
color: theme.palette.background.paper, color: theme.palette.background.paper,
})); }));
export const FeedbackComponent = () => { export const FeedbackComponentWrapper = () => {
const { feedbackData, showFeedback, closeFeedback } = useFeedback(); const { feedbackData, showFeedback, closeFeedback } = useFeedback();
if (!feedbackData) return null; 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 { setToastData } = useToast();
const theme = useTheme();
const { isPro, isOss, isEnterprise } = useUiConfig();
const { addFeedback } = useUserFeedbackApi(); const { addFeedback } = useUserFeedbackApi();
const { setHasSubmittedFeedback } = useUserSubmittedFeedback( const { setHasSubmittedFeedback } = useUserSubmittedFeedback(
feedbackData.category, feedbackData.category,
@ -184,19 +210,22 @@ export const FeedbackComponent = () => {
const formData = new FormData(event.currentTarget); const formData = new FormData(event.currentTarget);
const data = Object.fromEntries(formData); const data = Object.fromEntries(formData);
let toastType: IToast['type'] = 'error';
let toastTitle = 'Feedback not sent';
if (isProvideFeedbackSchema(data)) { if (isProvideFeedbackSchema(data)) {
try {
await addFeedback(data as ProvideFeedbackSchema); await addFeedback(data as ProvideFeedbackSchema);
setToastData({ toastTitle = 'Feedback sent';
title: 'Feedback sent', toastType = 'success';
type: 'success',
});
setHasSubmittedFeedback(true); setHasSubmittedFeedback(true);
} else { } catch (e) {}
setToastData({
title: 'Feedback not sent',
type: 'error',
});
} }
setToastData({
title: toastTitle,
type: toastType,
});
closeFeedback(); closeFeedback();
}; };
@ -206,6 +235,22 @@ export const FeedbackComponent = () => {
setSelectedScore(event.target.value); setSelectedScore(event.target.value);
}; };
const getUserType = () => {
if (isPro()) {
return 'pro';
}
if (isOss()) {
return 'oss';
}
if (isEnterprise()) {
return 'enterprise';
}
return 'unknown';
};
return ( return (
<ConditionallyRender <ConditionallyRender
condition={showFeedback} condition={showFeedback}
@ -233,7 +278,7 @@ export const FeedbackComponent = () => {
<input <input
type='hidden' type='hidden'
name='userType' name='userType'
value={feedbackData.userType} value={getUserType()}
/> />
<FormTitle>{feedbackData.title}</FormTitle> <FormTitle>{feedbackData.title}</FormTitle>
<StyledScoreContainer> <StyledScoreContainer>
@ -273,7 +318,8 @@ export const FeedbackComponent = () => {
size='small' size='small'
InputLabelProps={{ InputLabelProps={{
style: { style: {
fontSize: '14px', fontSize:
theme.fontSizes.smallBody,
}, },
}} }}
/> />
@ -290,7 +336,8 @@ export const FeedbackComponent = () => {
rows={3} rows={3}
InputLabelProps={{ InputLabelProps={{
style: { style: {
fontSize: '14px', fontSize:
theme.fontSizes.smallBody,
}, },
}} }}
variant='outlined' variant='outlined'

View File

@ -3,21 +3,22 @@ import { ProvideFeedbackSchema } from '../../openapi';
import { IFeedbackCategory } from 'hooks/useSubmittedFeedback'; import { IFeedbackCategory } from 'hooks/useSubmittedFeedback';
interface IFeedbackContext { interface IFeedbackContext {
feedbackData: IFeedbackData | undefined; feedbackData: FeedbackData | undefined;
openFeedback: (data: IFeedbackData) => void; openFeedback: (data: FeedbackData) => void;
closeFeedback: () => void; closeFeedback: () => void;
showFeedback: boolean; showFeedback: boolean;
setShowFeedback: (visible: boolean) => void; setShowFeedback: (visible: boolean) => void;
} }
type IFeedbackText = { interface IFeedbackText {
title: string; title: string;
positiveLabel: string; positiveLabel: string;
areasForImprovementsLabel: string; areasForImprovementsLabel: string;
}; }
export type IFeedbackData = Pick<ProvideFeedbackSchema, 'userType'> & export type FeedbackData = IFeedbackText & {
IFeedbackText & { category: IFeedbackCategory }; category: IFeedbackCategory;
};
export const FeedbackContext = createContext<IFeedbackContext | undefined>( export const FeedbackContext = createContext<IFeedbackContext | undefined>(
undefined, undefined,

View File

@ -1,14 +1,17 @@
import { FeedbackComponent } from './FeedbackComponent'; import {
import { FeedbackContext, IFeedbackData } from './FeedbackContext'; FeedbackComponent,
FeedbackComponentWrapper,
} from './FeedbackComponent';
import { FeedbackContext, FeedbackData } from './FeedbackContext';
import { FC, useState } from 'react'; import { FC, useState } from 'react';
export const FeedbackProvider: FC = ({ children }) => { export const FeedbackProvider: FC = ({ children }) => {
const [feedbackData, setFeedbackData] = useState<IFeedbackData | undefined>( const [feedbackData, setFeedbackData] = useState<FeedbackData | undefined>(
undefined, undefined,
); );
const [showFeedback, setShowFeedback] = useState(false); const [showFeedback, setShowFeedback] = useState(false);
const openFeedback = (data: IFeedbackData) => { const openFeedback = (data: FeedbackData) => {
setFeedbackData(data); setFeedbackData(data);
setShowFeedback(true); setShowFeedback(true);
}; };
@ -29,7 +32,7 @@ export const FeedbackProvider: FC = ({ children }) => {
}} }}
> >
{children} {children}
<FeedbackComponent /> <FeedbackComponentWrapper />
</FeedbackContext.Provider> </FeedbackContext.Provider>
); );
}; };

View File

@ -2,41 +2,20 @@ import useAPI from '../useApi/useApi';
import { ProvideFeedbackSchema } from '../../../../openapi'; import { ProvideFeedbackSchema } from '../../../../openapi';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
const ENDPOINT = 'feedback'; const ENDPOINT = 'https://app.unleash-hosted.com/hosted/feedback';
export const useUserFeedbackApi = () => { export const useUserFeedbackApi = () => {
const { uiConfig } = useUiConfig(); const { uiConfig } = useUiConfig();
const { loading, makeRequest, createRequest, errors } = useAPI({
propagateErrors: true,
});
const addFeedback = async (feedbackSchema: ProvideFeedbackSchema) => { const addFeedback = async (feedbackSchema: ProvideFeedbackSchema) => {
if (uiConfig.feedbackUriPath !== undefined) { await fetch(uiConfig.feedbackUriPath || ENDPOINT, {
await fetch(uiConfig.feedbackUriPath, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(feedbackSchema), 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();
}
}; };
return { return {
addFeedback, addFeedback,
errors,
loading,
}; };
}; };

View File

@ -84,7 +84,7 @@ exports[`should create default config 1`] = `
"embedProxy": true, "embedProxy": true,
"embedProxyFrontend": true, "embedProxyFrontend": true,
"featureSearchAPI": false, "featureSearchAPI": false,
"featureSearchFeedback": false, "featureSearchFeedback": true,
"featureSearchFeedbackPosting": false, "featureSearchFeedbackPosting": false,
"featureSearchFrontend": false, "featureSearchFrontend": false,
"featuresExportImport": true, "featuresExportImport": true,

View File

@ -53,6 +53,7 @@ const secureHeaders: (config: IUnleashConfig) => RequestHandler = (config) => {
'plausible.getunleash.io', 'plausible.getunleash.io',
'gravatar.com', 'gravatar.com',
'europe-west3-metrics-304612.cloudfunctions.net', 'europe-west3-metrics-304612.cloudfunctions.net',
'app.unleash-hosted.com',
...config.additionalCspAllowedDomains.connectSrc, ...config.additionalCspAllowedDomains.connectSrc,
], ],
mediaSrc: [ mediaSrc: [

View File

@ -155,7 +155,7 @@ const flags: IFlags = {
), ),
featureSearchFeedback: parseEnvVarBoolean( featureSearchFeedback: parseEnvVarBoolean(
process.env.UNLEASH_EXPERIMENTAL_FEATURE_SEARCH_FEEDBACK, process.env.UNLEASH_EXPERIMENTAL_FEATURE_SEARCH_FEEDBACK,
false, true,
), ),
featureSearchFeedbackPosting: parseEnvVarBoolean( featureSearchFeedbackPosting: parseEnvVarBoolean(
process.env.UNLEASH_EXPERIMENTAL_FEATURE_SEARCH_FEEDBACK_POSTING, process.env.UNLEASH_EXPERIMENTAL_FEATURE_SEARCH_FEEDBACK_POSTING,

View File

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