1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-04-15 01:16:22 +02:00
Tymoteusz Czech 2023-02-17 13:10:27 +01:00 committed by GitHub
parent eac5fca44c
commit c0ec6f20b2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 173 additions and 6 deletions

View File

@ -0,0 +1,76 @@
import { useState, VFC } from 'react';
import { Box, Paper, Button, styled } from '@mui/material';
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import { createLocalStorage } from 'utils/createLocalStorage';
interface IFeedbackProps {
id: string;
}
const StyledBox = styled(Box)(({ theme }) => ({
display: 'flex',
gap: theme.spacing(1),
marginTop: theme.spacing(0.5),
}));
export const Feedback: VFC<IFeedbackProps> = ({ id }) => {
const { uiConfig } = useUiConfig();
const { value: selectedValue, setValue: setSelectedValue } =
createLocalStorage<{ value?: 'yes' | 'no' }>(
`ProjectOverviewFeedback:v1:${id}`,
{}
);
const [selected, setSelected] = useState<'yes' | 'no' | undefined>(
selectedValue.value
);
const { trackEvent } = usePlausibleTracker();
if (!uiConfig?.flags?.T) {
return null;
}
const onTrackFeedback = (value: 'yes' | 'no') => {
setSelected(value);
setSelectedValue({ value });
trackEvent('project_overview', {
props: {
eventType: id,
wasHelpful: value === 'yes',
},
});
};
return (
<Paper
elevation={0}
sx={{
background: theme => theme.palette.neutral.light,
padding: theme => theme.spacing(1.5, 2),
marginTop: theme => theme.spacing(1.5),
}}
>
Was this information useful to you?
<StyledBox>
<Button
size="small"
variant={selected === 'yes' ? 'contained' : 'outlined'}
sx={{ padding: 0 }}
onClick={() => onTrackFeedback('yes')}
disabled={Boolean(selected)}
>
Yes
</Button>
<Button
size="small"
variant={selected === 'no' ? 'contained' : 'outlined'}
sx={{ padding: 0 }}
onClick={() => onTrackFeedback('no')}
disabled={Boolean(selected)}
>
No
</Button>
</StyledBox>
</Paper>
);
};

View File

@ -0,0 +1,75 @@
import { FC, useState } from 'react';
import { Close, HelpOutline } from '@mui/icons-material';
import {
Box,
IconButton,
Popper,
Paper,
ClickAwayListener,
styled,
} from '@mui/material';
import { Feedback } from './Feedback';
interface IHelpPopperProps {
id: string;
}
const StyledPaper = styled(Paper)(({ theme }) => ({
padding: theme.spacing(3, 3),
maxWidth: '350px',
borderRadius: `${theme.shape.borderRadiusMedium}px`,
border: `1px solid ${theme.palette.neutral.border}`,
fontSize: theme.typography.body2.fontSize,
}));
export const HelpPopper: FC<IHelpPopperProps> = ({ children, id }) => {
const [anchor, setAnchorEl] = useState<null | Element>(null);
const onOpen = (event: React.FormEvent<HTMLButtonElement>) =>
setAnchorEl(event.currentTarget);
const onClose = () => setAnchorEl(null);
const open = Boolean(anchor);
return (
<Box
sx={{
position: 'absolute',
top: theme => theme.spacing(0.5),
right: theme => theme.spacing(0.5),
}}
>
<IconButton onClick={onOpen} aria-describedby={id} size="small">
<HelpOutline
sx={{ fontSize: theme => theme.typography.body1.fontSize }}
/>
</IconButton>
<Popper
id={id}
open={open}
anchorEl={anchor}
sx={theme => ({ zIndex: theme.zIndex.tooltip })}
>
<ClickAwayListener onClickAway={onClose}>
<StyledPaper elevation={3}>
<IconButton
onClick={onClose}
sx={{ position: 'absolute', right: 4, top: 4 }}
>
<Close
sx={{
fontSize: theme =>
theme.typography.body1.fontSize,
}}
/>
</IconButton>
{children}
<Feedback id={id} />
</StyledPaper>
</ClickAwayListener>
</Popper>
</Box>
);
};

View File

@ -1,5 +1,6 @@
import { Box, styled, Typography } from '@mui/material';
import { ProjectStatsSchema } from 'openapi/models';
import { HelpPopper } from './HelpPopper';
import { StatusBox } from './StatusBox';
const StyledBox = styled(Box)(({ theme }) => ({
@ -20,6 +21,7 @@ const StyledBox = styled(Box)(({ theme }) => ({
}));
const StyledWidget = styled(Box)(({ theme }) => ({
position: 'relative',
padding: theme.spacing(3),
backgroundColor: theme.palette.background.paper,
flex: 1,
@ -73,7 +75,12 @@ export const ProjectStats = ({ stats }: IProjectStatsProps) => {
projectActivityPastWindow -
20
}
/>
>
<HelpPopper id="total-changes">
Sum of all configuration and state modifications in the
project.
</HelpPopper>
</StatusBox>
</StyledWidget>
<StyledWidget>
<StatusBox
@ -95,7 +102,13 @@ export const ProjectStats = ({ stats }: IProjectStatsProps) => {
avgTimeToProdPastWindow
)}
percentage
/>
>
<HelpPopper id="avg-time-to-prod">
How long did it take on average from a feature toggle
was created until it was enabled in an environment of
type production.
</HelpPopper>
</StatusBox>
</StyledWidget>
<StyledWidget>
<StatusBox

View File

@ -1,4 +1,4 @@
import type { ReactNode } from 'react';
import type { FC, ReactNode } from 'react';
import { CallMade, SouthEast } from '@mui/icons-material';
import { Box, Typography, styled } from '@mui/material';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
@ -53,17 +53,19 @@ const resolveColor = (change: number) => {
return 'warning.dark';
};
export const StatusBox = ({
export const StatusBox: FC<IStatusBoxProps> = ({
title,
boxText,
change,
percentage,
}: IStatusBoxProps) => (
children,
}) => (
<>
<ConditionallyRender
condition={Boolean(title)}
show={<StyledTypographyHeader>{title}</StyledTypographyHeader>}
/>
{children}
<Box
sx={{
...flexRow,

View File

@ -12,7 +12,7 @@ type UsePersistentGlobalState<T> = () => [
* The state is also persisted to localStorage and restored on page load.
* The localStorage state is not synced between tabs.
*
* @deprecated `hooks/useLocalStorage` -- we don't need `react-hooks-global-state`
* @deprecated `utils/createLocalStorage` -- we don't need `react-hooks-global-state`
*/
export const createPersistentGlobalStateHook = <T extends object>(
key: string,

View File

@ -16,6 +16,7 @@ type CustomEvents =
| 'maintenance'
| 'message_banner'
| 'hidden_environment'
| 'project_overview'
| 'suggest_tags'
| 'unknown_ui_error';