Skip to content

Commit

Permalink
feat: revive features (#3344)
Browse files Browse the repository at this point in the history
  • Loading branch information
sjaanus committed Mar 17, 2023
1 parent 2c2da4a commit d28e65b
Show file tree
Hide file tree
Showing 17 changed files with 307 additions and 175 deletions.
8 changes: 4 additions & 4 deletions frontend/src/component/addons/AddonForm/AddonForm.tsx
Expand Up @@ -16,12 +16,12 @@ import { useNavigate } from 'react-router-dom';
import useAddonsApi from 'hooks/api/actions/useAddonsApi/useAddonsApi';
import useToast from 'hooks/useToast';
import { formatUnknownError } from 'utils/formatUnknownError';
import useProjects from '../../../hooks/api/getters/useProjects/useProjects';
import { useEnvironments } from '../../../hooks/api/getters/useEnvironments/useEnvironments';
import useProjects from 'hooks/api/getters/useProjects/useProjects';
import { useEnvironments } from 'hooks/api/getters/useEnvironments/useEnvironments';
import { AddonMultiSelector } from './AddonMultiSelector/AddonMultiSelector';
import FormTemplate from 'component/common/FormTemplate/FormTemplate';
import useUiConfig from '../../../hooks/api/getters/useUiConfig/useUiConfig';
import PermissionButton from '../../common/PermissionButton/PermissionButton';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import PermissionButton from 'component/common/PermissionButton/PermissionButton';
import {
CREATE_ADDON,
UPDATE_ADDON,
Expand Down
@@ -0,0 +1,54 @@
import { FC } from 'react';
import { Button } from '@mui/material';
import { Undo } from '@mui/icons-material';
import { UPDATE_FEATURE } from 'component/providers/AccessProvider/permissions';
import { PermissionHOC } from 'component/common/PermissionHOC/PermissionHOC';
import useProjectApi from 'hooks/api/actions/useProjectApi/useProjectApi';
import { formatUnknownError } from 'utils/formatUnknownError';
import { useFeaturesArchive } from 'hooks/api/getters/useFeaturesArchive/useFeaturesArchive';
import useToast from 'hooks/useToast';

interface IArchiveBatchActionsProps {
selectedIds: string[];
projectId: string;
}

export const ArchiveBatchActions: FC<IArchiveBatchActionsProps> = ({
selectedIds,
projectId,
}) => {
const { reviveFeatures } = useProjectApi();
const { setToastData, setToastApiError } = useToast();
const { refetchArchived } = useFeaturesArchive(projectId);

const onRevive = async () => {
try {
await reviveFeatures(projectId, selectedIds);
await refetchArchived();
setToastData({
type: 'success',
title: "And we're back!",
text: 'The feature toggles have been revived.',
});
} catch (error: unknown) {
setToastApiError(formatUnknownError(error));
}
};
return (
<>
<PermissionHOC projectId={projectId} permission={UPDATE_FEATURE}>
{({ hasAccess }) => (
<Button
disabled={!hasAccess}
startIcon={<Undo />}
variant="outlined"
size="small"
onClick={onRevive}
>
Revive
</Button>
)}
</PermissionHOC>
</>
);
};
55 changes: 51 additions & 4 deletions frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx
@@ -1,9 +1,15 @@
import { PageContent } from 'component/common/PageContent/PageContent';
import { PageHeader } from 'component/common/PageHeader/PageHeader';
import { TablePlaceholder, VirtualizedTable } from 'component/common/Table';
import { SortingRule, useFlexLayout, useSortBy, useTable } from 'react-table';
import {
SortingRule,
useFlexLayout,
useRowSelect,
useSortBy,
useTable,
} from 'react-table';
import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext';
import { useMediaQuery } from '@mui/material';
import { Checkbox, useMediaQuery } from '@mui/material';
import { sortTypes } from 'utils/sortTypes';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { HighlightCell } from 'component/common/Table/cells/HighlightCell/HighlightCell';
Expand All @@ -27,6 +33,10 @@ import { useSearchParams } from 'react-router-dom';
import { ArchivedFeatureDeleteConfirm } from './ArchivedFeatureActionCell/ArchivedFeatureDeleteConfirm/ArchivedFeatureDeleteConfirm';
import { IFeatureToggle } from 'interfaces/featureToggle';
import { useConditionallyHiddenColumns } from 'hooks/useConditionallyHiddenColumns';
import { RowSelectCell } from '../../project/Project/ProjectFeatureToggles/RowSelectCell/RowSelectCell';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import { BatchSelectionActionsBar } from '../../common/BatchSelectionActionsBar/BatchSelectionActionsBar';
import { ArchiveBatchActions } from './ArchiveBatchActions';

export interface IFeaturesArchiveTableProps {
archivedFeatures: FeatureSchema[];
Expand Down Expand Up @@ -54,6 +64,7 @@ export const ArchiveTable = ({
const isSmallScreen = useMediaQuery(theme.breakpoints.down('md'));
const isMediumScreen = useMediaQuery(theme.breakpoints.down('lg'));
const { setToastData, setToastApiError } = useToast();
const { uiConfig } = useUiConfig();

const [deleteModalOpen, setDeleteModalOpen] = useState(false);
const [deletedFeature, setDeletedFeature] = useState<IFeatureToggle>();
Expand Down Expand Up @@ -84,6 +95,24 @@ export const ArchiveTable = ({

const columns = useMemo(
() => [
...(uiConfig?.flags?.bulkOperations
? [
{
id: 'Select',
Header: ({ getToggleAllRowsSelectedProps }: any) => (
<Checkbox {...getToggleAllRowsSelectedProps()} />
),
Cell: ({ row }: any) => (
<RowSelectCell
{...row?.getToggleRowSelectedProps?.()}
/>
),
maxWidth: 50,
disableSortBy: true,
hideInMenu: true,
},
]
: []),
{
Header: 'Seen',
width: 85,
Expand Down Expand Up @@ -203,12 +232,15 @@ export const ArchiveTable = ({
},
],
hiddenColumns: ['description'],
selectedRowIds: {},
}));

const getRowId = useCallback((row: any) => row.name, []);

const {
headerGroups,
rows,
state: { sortBy },
state: { sortBy, selectedRowIds },
prepareRow,
setHiddenColumns,
} = useTable(
Expand All @@ -220,9 +252,11 @@ export const ArchiveTable = ({
autoResetHiddenColumns: false,
disableSortRemove: true,
autoResetSortBy: false,
getRowId,
},
useFlexLayout,
useSortBy
useSortBy,
useRowSelect
);

useConditionallyHiddenColumns(
Expand Down Expand Up @@ -312,6 +346,19 @@ export const ArchiveTable = ({
setOpen={setDeleteModalOpen}
refetch={refetch}
/>
<ConditionallyRender
condition={Boolean(projectId)}
show={
<BatchSelectionActionsBar
selectedIds={Object.keys(selectedRowIds)}
>
<ArchiveBatchActions
selectedIds={Object.keys(selectedRowIds)}
projectId={projectId!}
/>
</BatchSelectionActionsBar>
}
/>
</PageContent>
);
};
Expand Up @@ -25,7 +25,7 @@ import AccessContext from 'contexts/AccessContext';
import { ChangeRequestComment } from './ChangeRequestComments/ChangeRequestComment';
import { AddCommentField } from './ChangeRequestComments/AddCommentField';
import { usePendingChangeRequests } from 'hooks/api/getters/usePendingChangeRequests/usePendingChangeRequests';
import { useChangeRequestsEnabled } from '../../../hooks/useChangeRequestsEnabled';
import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled';
import { Dialogue } from 'component/common/Dialogue/Dialogue';
import { changesCount } from '../changesCount';

Expand Down
@@ -0,0 +1,61 @@
import { FC } from 'react';
import { Box, Paper, styled, Typography } from '@mui/material';

interface IBatchSelectionActionsBarProps {
selectedIds: string[];
}

const StyledContainer = styled(Box)(() => ({
display: 'flex',
justifyContent: 'center',
width: '100%',
flexWrap: 'wrap',
}));

const StyledBar = styled(Paper)(({ theme }) => ({
display: 'flex',
alignItems: 'center',
justifyContent: 'flex-end',
marginTop: theme.spacing(2),
marginLeft: 'auto',
marginRight: 'auto',
padding: theme.spacing(2, 3),
backgroundColor: theme.palette.background.paper,
border: `1px solid ${theme.palette.secondary.main}`,
borderRadius: theme.shape.borderRadiusLarge,
gap: theme.spacing(1),
flexWrap: 'wrap',
}));

const StyledCount = styled('span')(({ theme }) => ({
background: theme.palette.secondary.main,
color: theme.palette.background.paper,
padding: theme.spacing(0.5, 1),
borderRadius: theme.shape.borderRadius,
}));

const StyledText = styled(Typography)(({ theme }) => ({
paddingRight: theme.spacing(2),
marginRight: 'auto',
}));

export const BatchSelectionActionsBar: FC<IBatchSelectionActionsBarProps> = ({
selectedIds,
children,
}) => {
if (selectedIds.length === 0) {
return null;
}

return (
<StyledContainer>
<StyledBar elevation={4}>
<StyledText>
<StyledCount>{selectedIds.length}</StyledCount>
&ensp;selected
</StyledText>
{children}
</StyledBar>
</StyledContainer>
);
};
@@ -1,6 +1,6 @@
import PermissionButton, {
IPermissionButtonProps,
} from '../PermissionButton/PermissionButton';
} from 'component/common/PermissionButton/PermissionButton';

interface ICreateButtonProps extends IPermissionButtonProps {
name: string;
Expand Down
@@ -1,7 +1,7 @@
import { useNavigate } from 'react-router-dom';
import { CREATE_FEATURE_STRATEGY } from 'component/providers/AccessProvider/permissions';
import { Dialogue } from 'component/common/Dialogue/Dialogue';
import PermissionButton from '../PermissionButton/PermissionButton';
import PermissionButton from 'component/common/PermissionButton/PermissionButton';
import { formatCreateStrategyPath } from 'component/feature/FeatureStrategy/FeatureStrategyCreate/FeatureStrategyCreate';
import { styled } from '@mui/material';

Expand Down
@@ -1,8 +1,8 @@
import React from 'react';
import { useMediaQuery } from '@mui/material';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import PermissionButton from '../PermissionButton/PermissionButton';
import PermissionIconButton from '../PermissionIconButton/PermissionIconButton';
import PermissionButton from 'component/common/PermissionButton/PermissionButton';
import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton';
import { ITooltipResolverProps } from '../TooltipResolver/TooltipResolver';

interface IResponsiveButtonProps {
Expand Down
@@ -1,6 +1,6 @@
import PermissionButton, {
IPermissionButtonProps,
} from '../PermissionButton/PermissionButton';
} from 'component/common/PermissionButton/PermissionButton';

export const UpdateButton = ({ ...rest }: IPermissionButtonProps) => {
return (
Expand Down
Expand Up @@ -64,7 +64,8 @@ import FileDownload from '@mui/icons-material/FileDownload';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import { ExportDialog } from 'component/feature/FeatureToggleList/ExportDialog';
import { RowSelectCell } from './RowSelectCell/RowSelectCell';
import { SelectionActionsBar } from './SelectionActionsBar/SelectionActionsBar';
import { BatchSelectionActionsBar } from '../../../common/BatchSelectionActionsBar/BatchSelectionActionsBar';
import { ProjectFeaturesBatchActions } from './SelectionActionsBar/ProjectFeaturesBatchActions';

const StyledResponsiveButton = styled(ResponsiveButton)(() => ({
whiteSpace: 'nowrap',
Expand Down Expand Up @@ -714,11 +715,13 @@ export const ProjectFeatureToggles = ({
/>
}
/>
<SelectionActionsBar
selectedIds={Object.keys(selectedRowIds)}
data={features}
projectId={projectId}
/>
<BatchSelectionActionsBar selectedIds={Object.keys(selectedRowIds)}>
<ProjectFeaturesBatchActions
selectedIds={Object.keys(selectedRowIds)}
data={features}
projectId={projectId}
/>
</BatchSelectionActionsBar>
</PageContent>
);
};
@@ -0,0 +1,68 @@
import { FC, useMemo, useState } from 'react';
import { Button } from '@mui/material';
import { FileDownload, Label } from '@mui/icons-material';
import type { FeatureSchema } from 'openapi';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import { ExportDialog } from 'component/feature/FeatureToggleList/ExportDialog';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { ArchiveButton } from './ArchiveButton/ArchiveButton';
import { MoreActions } from './MoreActions/MoreActions';

interface IProjectFeaturesBatchActionsProps {
selectedIds: string[];
data: FeatureSchema[];
projectId: string;
}

export const ProjectFeaturesBatchActions: FC<
IProjectFeaturesBatchActionsProps
> = ({ selectedIds, data, projectId }) => {
const { uiConfig } = useUiConfig();
const [showExportDialog, setShowExportDialog] = useState(false);
const selectedData = useMemo(
() => data.filter(d => selectedIds.includes(d.name)),
[data, selectedIds]
);

const environments = useMemo(() => {
const envs = selectedData
.flatMap(d => d.environments)
.map(env => env?.name)
.filter(env => env !== undefined) as string[];
return Array.from(new Set(envs));
}, [selectedData]);

return (
<>
<ArchiveButton projectId={projectId} features={selectedIds} />
<Button
startIcon={<FileDownload />}
variant="outlined"
size="small"
onClick={() => setShowExportDialog(true)}
>
Export
</Button>
<Button
disabled
startIcon={<Label />}
variant="outlined"
size="small"
>
Tags
</Button>
<MoreActions projectId={projectId} data={selectedData} />
<ConditionallyRender
condition={Boolean(uiConfig?.flags?.featuresExportImport)}
show={
<ExportDialog
showExportDialog={showExportDialog}
data={selectedData}
onClose={() => setShowExportDialog(false)}
environments={environments}
/>
}
/>
</>
);
};

0 comments on commit d28e65b

Please sign in to comment.