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

feat: command bar feedback (#7485)

![Screenshot from 2024-07-01
13-04-57](https://github.com/Unleash/unleash/assets/964450/4d5c96a2-0cfc-47a9-9323-f7ce1b27da3d)
![Screenshot from 2024-07-01
13-05-03](https://github.com/Unleash/unleash/assets/964450/79f9c289-4c62-4a3e-b612-c88ff8ca434d)
This commit is contained in:
Jaanus Sellin 2024-07-01 14:15:51 +03:00 committed by GitHub
parent 2706a09b8b
commit c907199d23
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 145 additions and 31 deletions

View File

@ -1,4 +1,4 @@
import { useRef, useState } from 'react';
import { useEffect, useRef, useState } from 'react';
import {
Box,
IconButton,
@ -13,7 +13,6 @@ import { ConditionallyRender } from 'component/common/ConditionallyRender/Condit
import { useKeyboardShortcut } from 'hooks/useKeyboardShortcut';
import { SEARCH_INPUT } from 'utils/testIds';
import { useOnClickOutside } from 'hooks/useOnClickOutside';
import { useOnBlur } from 'hooks/useOnBlur';
import {
CommandResultGroup,
type CommandResultGroupItem,
@ -26,6 +25,7 @@ import { CommandFeatures } from './CommandFeatures';
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
import { CommandRecent } from './CommandRecent';
import { CommandPages } from './CommandPages';
import { CommandBarFeedback } from './CommandBarFeedback';
import { RecentlyVisitedRecorder } from './RecentlyVisitedRecorder';
export const CommandResultsPaper = styled(Paper)(({ theme }) => ({
@ -35,7 +35,7 @@ export const CommandResultsPaper = styled(Paper)(({ theme }) => ({
top: '39px',
zIndex: 4,
borderTop: theme.spacing(0),
padding: theme.spacing(4, 0, 1.5),
padding: theme.spacing(1.5, 0, 1.5),
borderRadius: 0,
borderBottomLeftRadius: theme.spacing(1),
borderBottomRightRadius: theme.spacing(1),
@ -109,6 +109,7 @@ export const CommandBar = () => {
CommandResultGroupItem[]
>([]);
const [searchedFlagCount, setSearchedFlagCount] = useState(0);
const [hasNoResults, setHasNoResults] = useState(false);
const [value, setValue] = useState<string>('');
const { routes } = useRoutes();
const allRoutes: Record<string, IPageRouteInfo> = {};
@ -166,8 +167,13 @@ export const CommandBar = () => {
},
});
}
setHasNoResults(noResultsFound);
}, 200);
useEffect(() => {
debouncedSetSearchState(value);
}, [searchedFlagCount]);
const onSearchChange = (value: string) => {
debouncedSetSearchState(value);
setValue(value);
@ -195,8 +201,6 @@ export const CommandBar = () => {
const placeholder = `Command bar (${hotkey})`;
useOnClickOutside([searchContainerRef], hideSuggestions);
useOnBlur(searchContainerRef, hideSuggestions);
return (
<StyledContainer ref={searchContainerRef} active={showSuggestions}>
<RecentlyVisitedRecorder />
@ -261,6 +265,14 @@ export const CommandBar = () => {
items={searchedProjects}
/>
<CommandPages items={searchedPages} />
<ConditionallyRender
condition={hasNoResults}
show={
<CommandBarFeedback
onSubmit={hideSuggestions}
/>
}
/>
</CommandResultsPaper>
}
elseShow={

View File

@ -0,0 +1,96 @@
import { Button, styled, TextField } from '@mui/material';
import type React from 'react';
import { useState } from 'react';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { useUserFeedbackApi } from 'hooks/api/actions/useUserFeedbackApi/useUserFeedbackApi';
import useToast from 'hooks/useToast';
import useUserType from '../feedbackNew/useUserType';
const StyledContainer = styled('div')(({ theme }) => ({
display: 'flex',
flexDirection: 'column',
padding: theme.spacing(2),
gap: theme.spacing(1),
}));
const StyledText = styled('span')(({ theme }) => ({
fontSize: theme.spacing(1.5),
}));
const StyledButton = styled(Button)(({ theme }) => ({
fontSize: theme.spacing(1.5),
}));
interface ICommandBarFeedbackProps {
onSubmit: () => void;
}
export const CommandBarFeedback = ({ onSubmit }: ICommandBarFeedbackProps) => {
const userType = useUserType();
const { addFeedback } = useUserFeedbackApi();
const { setToastData } = useToast();
const [suggesting, setSuggesting] = useState(false);
const [feedback, setFeedback] = useState<string | undefined>(undefined);
const changeFeedback = (event: React.ChangeEvent<HTMLInputElement>) => {
setFeedback(event.target.value.trim());
};
const sendFeedback = async () => {
await addFeedback({
areasForImprovement: feedback,
category: 'commandBar',
userType: userType,
});
onSubmit();
setToastData({
title: 'Feedback sent',
type: 'success',
});
};
return (
<StyledContainer>
<ConditionallyRender
condition={suggesting}
show={
<>
<StyledText>Describe the capability</StyledText>
<TextField
multiline={true}
minRows={2}
onChange={changeFeedback}
/>
<StyledButton
type='submit'
variant='contained'
color='primary'
onClick={sendFeedback}
>
Send to Unleash
</StyledButton>
</>
}
elseShow={
<>
<StyledText>
We couldnt find anything matching your search
criteria. If you think this is a missing capability,
feel free to make a suggestion.
</StyledText>
<StyledButton
type='submit'
variant='contained'
color='primary'
onClick={(e) => {
e.stopPropagation();
setSuggesting(true);
}}
>
Suggest capability
</StyledButton>
</>
}
/>
</StyledContainer>
);
};

View File

@ -17,6 +17,7 @@ import {
import { TooltipResolver } from 'component/common/TooltipResolver/TooltipResolver';
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
import useProjectOverview from 'hooks/api/getters/useProjectOverview/useProjectOverview';
import { Children } from 'react';
export const listItemButtonStyle = (theme: Theme) => ({
border: `1px solid transparent`,
@ -26,10 +27,6 @@ export const listItemButtonStyle = (theme: Theme) => ({
borderLeft: `${theme.spacing(0.5)} solid ${theme.palette.primary.main}`,
},
});
const StyledContainer = styled('div')(({ theme }) => ({
marginBottom: theme.spacing(3),
}));
export const StyledTypography = styled(Typography)(({ theme }) => ({
fontSize: theme.fontSizes.bodySize,
padding: theme.spacing(0, 2.5),
@ -193,7 +190,10 @@ export const CommandResultGroup = ({
children,
}: CommandResultGroupProps) => {
const { trackEvent } = usePlausibleTracker();
if (!children && (!items || items.length === 0)) {
if (
(!children || Children.count(children) === 0) &&
(!items || items.length === 0)
) {
return null;
}
@ -211,7 +211,7 @@ export const CommandResultGroup = ({
};
return (
<StyledContainer>
<div>
<StyledTypography color='textSecondary'>
{groupName}
</StyledTypography>
@ -249,6 +249,6 @@ export const CommandResultGroup = ({
</ListItemButton>
))}
</List>
</StyledContainer>
</div>
);
};

View File

@ -16,12 +16,12 @@ import useToast from 'hooks/useToast';
import type { 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 type { IToast } from 'interfaces/toast';
import { useTheme } from '@mui/material/styles';
import type { FeedbackData, FeedbackMode } from './FeedbackContext';
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
import { useUiFlag } from 'hooks/useUiFlag';
import useUserType from './useUserType';
export const ParentContainer = styled('div')(({ theme }) => ({
position: 'relative',
@ -201,9 +201,10 @@ export const FeedbackComponent = ({
feedbackMode,
}: IFeedbackComponent) => {
const { setToastData } = useToast();
const userType = useUserType();
const { trackEvent } = usePlausibleTracker();
const theme = useTheme();
const { isPro, isOss, isEnterprise } = useUiConfig();
const { addFeedback } = useUserFeedbackApi();
const { setHasSubmittedFeedback } = useUserSubmittedFeedback(
feedbackData.category,
@ -276,22 +277,6 @@ 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}
@ -320,7 +305,7 @@ export const FeedbackComponent = ({
<input
type='hidden'
name='userType'
value={getUserType()}
value={userType}
/>
<FormTitle>{feedbackData.title}</FormTitle>
<StyledScoreContainer>

View File

@ -0,0 +1,21 @@
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
const useUserType = () => {
const { isPro, isOss, isEnterprise } = useUiConfig();
if (isPro()) {
return 'pro';
}
if (isOss()) {
return 'oss';
}
if (isEnterprise()) {
return 'enterprise';
}
return 'unknown';
};
export default useUserType;