mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-31 00:16:47 +01:00
feat: revive features (#3344)
This commit is contained in:
parent
2c2da4ad3f
commit
d28e65b94c
@ -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,
|
||||
|
@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
@ -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';
|
||||
@ -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[];
|
||||
@ -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>();
|
||||
@ -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,
|
||||
@ -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(
|
||||
@ -220,9 +252,11 @@ export const ArchiveTable = ({
|
||||
autoResetHiddenColumns: false,
|
||||
disableSortRemove: true,
|
||||
autoResetSortBy: false,
|
||||
getRowId,
|
||||
},
|
||||
useFlexLayout,
|
||||
useSortBy
|
||||
useSortBy,
|
||||
useRowSelect
|
||||
);
|
||||
|
||||
useConditionallyHiddenColumns(
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
|
@ -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';
|
||||
|
||||
|
@ -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>
|
||||
 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;
|
||||
|
@ -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';
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import PermissionButton, {
|
||||
IPermissionButtonProps,
|
||||
} from '../PermissionButton/PermissionButton';
|
||||
} from 'component/common/PermissionButton/PermissionButton';
|
||||
|
||||
export const UpdateButton = ({ ...rest }: IPermissionButtonProps) => {
|
||||
return (
|
||||
|
@ -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',
|
||||
@ -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}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
@ -1,113 +0,0 @@
|
||||
import { useMemo, useState, VFC } from 'react';
|
||||
import { Box, Button, Paper, styled, Typography } from '@mui/material';
|
||||
import { FileDownload, Label, WatchLater } 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 ISelectionActionsBarProps {
|
||||
selectedIds: string[];
|
||||
data: FeatureSchema[];
|
||||
projectId: 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 SelectionActionsBar: VFC<ISelectionActionsBarProps> = ({
|
||||
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]);
|
||||
|
||||
if (selectedIds.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledContainer>
|
||||
<StyledBar elevation={4}>
|
||||
<StyledText>
|
||||
<StyledCount>{selectedIds.length}</StyledCount>
|
||||
 selected
|
||||
</StyledText>
|
||||
<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} />
|
||||
</StyledBar>
|
||||
<ConditionallyRender
|
||||
condition={Boolean(uiConfig?.flags?.featuresExportImport)}
|
||||
show={
|
||||
<ExportDialog
|
||||
showExportDialog={showExportDialog}
|
||||
data={selectedData}
|
||||
onClose={() => setShowExportDialog(false)}
|
||||
environments={environments}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
@ -7,7 +7,7 @@ import { ProjectFeatureToggles } from './ProjectFeatureToggles/ProjectFeatureTog
|
||||
import ProjectInfo from './ProjectInfo/ProjectInfo';
|
||||
import { usePageTitle } from 'hooks/usePageTitle';
|
||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||
import { useLastViewedProject } from '../../../hooks/useLastViewedProject';
|
||||
import { useLastViewedProject } from 'hooks/useLastViewedProject';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||
import { ProjectStats } from './ProjectStats/ProjectStats';
|
||||
|
@ -227,6 +227,16 @@ const useProjectApi = () => {
|
||||
return makeRequest(req.caller, req.id);
|
||||
};
|
||||
|
||||
const reviveFeatures = async (projectId: string, featureIds: string[]) => {
|
||||
const path = `api/admin/projects/${projectId}/revive`;
|
||||
const req = createRequest(path, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ features: featureIds }),
|
||||
});
|
||||
|
||||
return makeRequest(req.caller, req.id);
|
||||
};
|
||||
|
||||
const staleFeatures = async (
|
||||
projectId: string,
|
||||
featureIds: string[],
|
||||
@ -259,6 +269,7 @@ const useProjectApi = () => {
|
||||
changeUserRole,
|
||||
changeGroupRole,
|
||||
archiveFeatures,
|
||||
reviveFeatures,
|
||||
staleFeatures,
|
||||
searchProjectUser,
|
||||
setDefaultProjectStickiness,
|
||||
|
@ -17,7 +17,8 @@ import { BatchFeaturesSchema, createRequestSchema } from '../../../openapi';
|
||||
import NotFoundError from '../../../error/notfound-error';
|
||||
import Controller from '../../controller';
|
||||
|
||||
const PATH = '/:projectId/archive';
|
||||
const PATH = '/:projectId';
|
||||
const PATH_ARCHIVE = `${PATH}/archive`;
|
||||
const PATH_DELETE = `${PATH}/delete`;
|
||||
const PATH_REVIVE = `${PATH}/revive`;
|
||||
|
||||
@ -83,7 +84,7 @@ export default class ProjectArchiveController extends Controller {
|
||||
|
||||
this.route({
|
||||
method: 'post',
|
||||
path: PATH,
|
||||
path: PATH_ARCHIVE,
|
||||
handler: this.archiveFeatures,
|
||||
permission: DELETE_FEATURE,
|
||||
middleware: [
|
||||
|
@ -221,7 +221,7 @@ test('can bulk delete features and recreate after', async () => {
|
||||
})
|
||||
.expect(202);
|
||||
await app.request
|
||||
.post('/api/admin/projects/default/archive/delete')
|
||||
.post('/api/admin/projects/default/delete')
|
||||
.send({ features })
|
||||
.expect(200);
|
||||
for (const feature of features) {
|
||||
@ -253,7 +253,7 @@ test('can bulk revive features', async () => {
|
||||
})
|
||||
.expect(202);
|
||||
await app.request
|
||||
.post('/api/admin/projects/default/archive/revive')
|
||||
.post('/api/admin/projects/default/revive')
|
||||
.send({ features })
|
||||
.expect(200);
|
||||
for (const feature of features) {
|
||||
|
@ -6130,7 +6130,7 @@ If the provided project does not exist, the list of events will be empty.",
|
||||
],
|
||||
},
|
||||
},
|
||||
"/api/admin/projects/{projectId}/archive/delete": {
|
||||
"/api/admin/projects/{projectId}/delete": {
|
||||
"post": {
|
||||
"description": "This endpoint deletes the specified features, that are in archive.",
|
||||
"operationId": "deleteFeatures",
|
||||
@ -6166,42 +6166,6 @@ If the provided project does not exist, the list of events will be empty.",
|
||||
],
|
||||
},
|
||||
},
|
||||
"/api/admin/projects/{projectId}/archive/revive": {
|
||||
"post": {
|
||||
"description": "This endpoint revives the specified features.",
|
||||
"operationId": "reviveFeatures",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "path",
|
||||
"name": "projectId",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/batchFeaturesSchema",
|
||||
},
|
||||
},
|
||||
},
|
||||
"description": "batchFeaturesSchema",
|
||||
"required": true,
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "This response has no body.",
|
||||
},
|
||||
},
|
||||
"summary": "Revives a list of features",
|
||||
"tags": [
|
||||
"Archive",
|
||||
],
|
||||
},
|
||||
},
|
||||
"/api/admin/projects/{projectId}/environments": {
|
||||
"post": {
|
||||
"operationId": "addEnvironmentToProject",
|
||||
@ -7582,6 +7546,42 @@ If the provided project does not exist, the list of events will be empty.",
|
||||
],
|
||||
},
|
||||
},
|
||||
"/api/admin/projects/{projectId}/revive": {
|
||||
"post": {
|
||||
"description": "This endpoint revives the specified features.",
|
||||
"operationId": "reviveFeatures",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "path",
|
||||
"name": "projectId",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/batchFeaturesSchema",
|
||||
},
|
||||
},
|
||||
},
|
||||
"description": "batchFeaturesSchema",
|
||||
"required": true,
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "This response has no body.",
|
||||
},
|
||||
},
|
||||
"summary": "Revives a list of features",
|
||||
"tags": [
|
||||
"Archive",
|
||||
],
|
||||
},
|
||||
},
|
||||
"/api/admin/projects/{projectId}/stale": {
|
||||
"post": {
|
||||
"description": "This endpoint stales the specified features.",
|
||||
|
Loading…
Reference in New Issue
Block a user