mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +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