diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewMetaData/FeatureOverviewMetaData.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewMetaData/FeatureOverviewMetaData.tsx index a98b29091c..2645ccb7e7 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewMetaData/FeatureOverviewMetaData.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewMetaData/FeatureOverviewMetaData.tsx @@ -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 = () => { 0} + condition={ + tags.length > 0 && + !Boolean(uiConfig.flags.variantsPerEnvironment) + } show={
diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanel.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanel.tsx index 7c31209f16..f6dfcc3ca2 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanel.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanel.tsx @@ -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} /> + + + Tags for this feature toggle + + } + feature={feature} + /> ); }; diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelEnvironmentSwitches/FeatureOverviewSidePanelEnvironmentSwitches.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelEnvironmentSwitches/FeatureOverviewSidePanelEnvironmentSwitches.tsx index e64f5c82d1..b2531aa6fa 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelEnvironmentSwitches/FeatureOverviewSidePanelEnvironmentSwitches.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelEnvironmentSwitches/FeatureOverviewSidePanelEnvironmentSwitches.tsx @@ -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 ( - <> + {header} {feature.environments.map(environment => { const strategiesLabel = @@ -72,13 +76,13 @@ export const FeatureOverviewSidePanelEnvironmentSwitches = ({ setShowInfoBox(true); }} > - + {environment.name} {strategiesLabel} {variantsLink} - + ); })} @@ -89,6 +93,6 @@ export const FeatureOverviewSidePanelEnvironmentSwitches = ({ featureId={feature.name} environmentName={environmentName} /> - + ); }; diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelTags/FeatureOverviewSidePanelTags.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelTags/FeatureOverviewSidePanelTags.tsx new file mode 100644 index 0000000000..12f5e51e83 --- /dev/null +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelTags/FeatureOverviewSidePanelTags.tsx @@ -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(); + + 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 ( + + {header} + + {tags.map(tag => { + const tagLabel = `${tag.type}:${tag.value}`; + return ( + } + onDelete={ + canUpdateTags + ? () => { + setShowDelDialog(true); + setSelectedTag(tag); + } + : undefined + } + /> + ); + })} + + + 0} + show={} + /> + } + onClick={() => setOpenTagDialog(true)} + > + Add new tag + + + } + /> + + { + setShowDelDialog(false); + setSelectedTag(undefined); + }} + onClick={() => { + setShowDelDialog(false); + handleDelete(); + setSelectedTag(undefined); + }} + title="Delete tag?" + > + You are about to delete tag:{' '} + + {selectedTag?.type}:{selectedTag?.value} + + + + ); +};