mirror of
https://github.com/Unleash/unleash.git
synced 2025-02-04 00:18:01 +01:00
feat: add tags to the new feature overview sidepanel (#2488)
https://linear.app/unleash/issue/2-423/update-feature-toggle-overview-sidepanel
This commit is contained in:
parent
d5fbd0b743
commit
ac16e7e3ba
@ -11,8 +11,10 @@ import { UPDATE_FEATURE } from 'component/providers/AccessProvider/permissions';
|
||||
import useTags from 'hooks/api/getters/useTags/useTags';
|
||||
import FeatureOverviewTags from './FeatureOverviewTags/FeatureOverviewTags';
|
||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||
|
||||
const FeatureOverviewMetaData = () => {
|
||||
const { uiConfig } = useUiConfig();
|
||||
const { classes: styles } = useStyles();
|
||||
const projectId = useRequiredPathParam('projectId');
|
||||
const featureId = useRequiredPathParam('featureId');
|
||||
@ -78,7 +80,10 @@ const FeatureOverviewMetaData = () => {
|
||||
</div>
|
||||
</div>
|
||||
<ConditionallyRender
|
||||
condition={tags.length > 0}
|
||||
condition={
|
||||
tags.length > 0 &&
|
||||
!Boolean(uiConfig.flags.variantsPerEnvironment)
|
||||
}
|
||||
show={
|
||||
<div className={styles.paddingContainerBottom}>
|
||||
<FeatureOverviewTags projectId={projectId} />
|
||||
|
@ -1,15 +1,15 @@
|
||||
import { styled } from '@mui/material';
|
||||
import { Divider, styled } from '@mui/material';
|
||||
import { HelpIcon } from 'component/common/HelpIcon/HelpIcon';
|
||||
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
|
||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||
import { FeatureOverviewSidePanelEnvironmentSwitches } from './FeatureOverviewSidePanelEnvironmentSwitches/FeatureOverviewSidePanelEnvironmentSwitches';
|
||||
import { FeatureOverviewSidePanelTags } from './FeatureOverviewSidePanelTags/FeatureOverviewSidePanelTags';
|
||||
|
||||
const StyledContainer = styled('div')(({ theme }) => ({
|
||||
borderRadius: theme.shape.borderRadiusLarge,
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
padding: '1.5rem',
|
||||
maxWidth: '350px',
|
||||
minWidth: '350px',
|
||||
marginRight: '1rem',
|
||||
@ -56,6 +56,15 @@ export const FeatureOverviewSidePanel = () => {
|
||||
}
|
||||
feature={feature}
|
||||
/>
|
||||
<Divider />
|
||||
<FeatureOverviewSidePanelTags
|
||||
header={
|
||||
<StyledHeader data-loading>
|
||||
Tags for this feature toggle
|
||||
</StyledHeader>
|
||||
}
|
||||
feature={feature}
|
||||
/>
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
|
@ -6,6 +6,10 @@ import { Link, styled } from '@mui/material';
|
||||
import { Link as RouterLink } from 'react-router-dom';
|
||||
|
||||
const StyledContainer = styled('div')(({ theme }) => ({
|
||||
padding: theme.spacing(3),
|
||||
}));
|
||||
|
||||
const StyledSwitchLabel = styled('div')(() => ({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
}));
|
||||
@ -38,7 +42,7 @@ export const FeatureOverviewSidePanelEnvironmentSwitches = ({
|
||||
const [environmentName, setEnvironmentName] = useState('');
|
||||
|
||||
return (
|
||||
<>
|
||||
<StyledContainer>
|
||||
{header}
|
||||
{feature.environments.map(environment => {
|
||||
const strategiesLabel =
|
||||
@ -72,13 +76,13 @@ export const FeatureOverviewSidePanelEnvironmentSwitches = ({
|
||||
setShowInfoBox(true);
|
||||
}}
|
||||
>
|
||||
<StyledContainer>
|
||||
<StyledSwitchLabel>
|
||||
<StyledLabel>{environment.name}</StyledLabel>
|
||||
<StyledSubLabel>
|
||||
{strategiesLabel}
|
||||
{variantsLink}
|
||||
</StyledSubLabel>
|
||||
</StyledContainer>
|
||||
</StyledSwitchLabel>
|
||||
</FeatureOverviewSidePanelEnvironmentSwitch>
|
||||
);
|
||||
})}
|
||||
@ -89,6 +93,6 @@ export const FeatureOverviewSidePanelEnvironmentSwitches = ({
|
||||
featureId={feature.name}
|
||||
environmentName={environmentName}
|
||||
/>
|
||||
</>
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
|
@ -0,0 +1,148 @@
|
||||
import { IFeatureToggle } from 'interfaces/featureToggle';
|
||||
import { useContext, useState } from 'react';
|
||||
import { Button, Chip, Divider, styled } from '@mui/material';
|
||||
import useTags from 'hooks/api/getters/useTags/useTags';
|
||||
import { Add, Cancel } from '@mui/icons-material';
|
||||
import AddTagDialog from 'component/feature/FeatureView/FeatureOverview/AddTagDialog/AddTagDialog';
|
||||
import { UPDATE_FEATURE } from 'component/providers/AccessProvider/permissions';
|
||||
import AccessContext from 'contexts/AccessContext';
|
||||
import { Dialogue } from 'component/common/Dialogue/Dialogue';
|
||||
import { ITag } from 'interfaces/tags';
|
||||
import useFeatureApi from 'hooks/api/actions/useFeatureApi/useFeatureApi';
|
||||
import useToast from 'hooks/useToast';
|
||||
import { formatUnknownError } from 'utils/formatUnknownError';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
|
||||
const StyledContainer = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
padding: theme.spacing(3),
|
||||
}));
|
||||
|
||||
const StyledTagContainer = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
gap: theme.spacing(1),
|
||||
flexWrap: 'wrap',
|
||||
}));
|
||||
|
||||
const StyledChip = styled(Chip)(({ theme }) => ({
|
||||
fontSize: theme.fontSizes.smallBody,
|
||||
'.MuiChip-deleteIcon': {
|
||||
color: theme.palette.neutral.main,
|
||||
},
|
||||
}));
|
||||
|
||||
const StyledDivider = styled(Divider)(({ theme }) => ({
|
||||
margin: theme.spacing(3),
|
||||
borderStyle: 'dashed',
|
||||
}));
|
||||
|
||||
const StyledButton = styled(Button)(({ theme }) => ({
|
||||
maxWidth: theme.spacing(20),
|
||||
alignSelf: 'center',
|
||||
}));
|
||||
|
||||
interface IFeatureOverviewSidePanelTagsProps {
|
||||
feature: IFeatureToggle;
|
||||
header: React.ReactNode;
|
||||
}
|
||||
|
||||
export const FeatureOverviewSidePanelTags = ({
|
||||
feature,
|
||||
header,
|
||||
}: IFeatureOverviewSidePanelTagsProps) => {
|
||||
const { tags, refetch } = useTags(feature.name);
|
||||
const { deleteTagFromFeature } = useFeatureApi();
|
||||
|
||||
const [openTagDialog, setOpenTagDialog] = useState(false);
|
||||
const [showDelDialog, setShowDelDialog] = useState(false);
|
||||
const [selectedTag, setSelectedTag] = useState<ITag>();
|
||||
|
||||
const { setToastData, setToastApiError } = useToast();
|
||||
const { hasAccess } = useContext(AccessContext);
|
||||
const canUpdateTags = hasAccess(UPDATE_FEATURE, feature.project);
|
||||
|
||||
const handleDelete = async () => {
|
||||
if (!selectedTag) return;
|
||||
try {
|
||||
await deleteTagFromFeature(
|
||||
feature.name,
|
||||
selectedTag.type,
|
||||
selectedTag.value
|
||||
);
|
||||
refetch();
|
||||
setToastData({
|
||||
type: 'success',
|
||||
title: 'Tag deleted',
|
||||
text: 'Successfully deleted tag',
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
setToastApiError(formatUnknownError(error));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledContainer>
|
||||
{header}
|
||||
<StyledTagContainer>
|
||||
{tags.map(tag => {
|
||||
const tagLabel = `${tag.type}:${tag.value}`;
|
||||
return (
|
||||
<StyledChip
|
||||
key={tagLabel}
|
||||
label={tagLabel}
|
||||
deleteIcon={<Cancel titleAccess="Remove" />}
|
||||
onDelete={
|
||||
canUpdateTags
|
||||
? () => {
|
||||
setShowDelDialog(true);
|
||||
setSelectedTag(tag);
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</StyledTagContainer>
|
||||
<ConditionallyRender
|
||||
condition={canUpdateTags}
|
||||
show={
|
||||
<>
|
||||
<ConditionallyRender
|
||||
condition={tags.length > 0}
|
||||
show={<StyledDivider />}
|
||||
/>
|
||||
<StyledButton
|
||||
variant="outlined"
|
||||
startIcon={<Add />}
|
||||
onClick={() => setOpenTagDialog(true)}
|
||||
>
|
||||
Add new tag
|
||||
</StyledButton>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<AddTagDialog open={openTagDialog} setOpen={setOpenTagDialog} />
|
||||
<Dialogue
|
||||
open={showDelDialog}
|
||||
primaryButtonText="Delete tag"
|
||||
secondaryButtonText="Cancel"
|
||||
onClose={() => {
|
||||
setShowDelDialog(false);
|
||||
setSelectedTag(undefined);
|
||||
}}
|
||||
onClick={() => {
|
||||
setShowDelDialog(false);
|
||||
handleDelete();
|
||||
setSelectedTag(undefined);
|
||||
}}
|
||||
title="Delete tag?"
|
||||
>
|
||||
You are about to delete tag:{' '}
|
||||
<strong>
|
||||
{selectedTag?.type}:{selectedTag?.value}
|
||||
</strong>
|
||||
</Dialogue>
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
Loading…
Reference in New Issue
Block a user