diff --git a/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx b/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx index a61dcecec0..6800488d18 100644 --- a/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx +++ b/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx @@ -17,7 +17,6 @@ import { DateCell } from 'component/common/Table/cells/DateCell/DateCell'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { Search } from 'component/common/Search/Search'; import { FeatureTypeCell } from 'component/common/Table/cells/FeatureTypeCell/FeatureTypeCell'; -import { LinkCell } from 'component/common/Table/cells/LinkCell/LinkCell'; import { ArchivedFeatureActionCell } from 'component/archive/ArchiveTable/ArchivedFeatureActionCell/ArchivedFeatureActionCell'; import { featuresPlaceholder } from 'component/feature/FeatureToggleList/FeatureToggleListTable'; import theme from 'themes/theme'; @@ -32,7 +31,6 @@ import { RowSelectCell } from '../../project/Project/ProjectFeatureToggles/RowSe import { BatchSelectionActionsBar } from '../../common/BatchSelectionActionsBar/BatchSelectionActionsBar'; import { ArchiveBatchActions } from './ArchiveBatchActions'; import { FeatureEnvironmentSeenCell } from 'component/common/Table/cells/FeatureSeenCell/FeatureEnvironmentSeenCell'; -import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; import { ArchivedFeatureReviveConfirm } from './ArchivedFeatureActionCell/ArchivedFeatureReviveConfirm/ArchivedFeatureReviveConfirm'; export interface IFeaturesArchiveTableProps { @@ -46,7 +44,7 @@ export interface IFeaturesArchiveTableProps { | SortingRule | ((prev: SortingRule) => SortingRule), ) => SortingRule; - projectId?: string; + projectId: string; } export const ArchiveTable = ({ @@ -72,35 +70,27 @@ export const ArchiveTable = ({ searchParams.get('search') || '', ); - const { uiConfig } = useUiConfig(); - const columns = useMemo( () => [ - ...(projectId - ? [ - { - id: 'Select', - Header: ({ getToggleAllRowsSelectedProps }: any) => ( - - ), - Cell: ({ row }: any) => ( - - ), - maxWidth: 50, - disableSortBy: true, - hideInMenu: true, - }, - ] - : []), + { + id: 'Select', + Header: ({ getToggleAllRowsSelectedProps }: any) => ( + + ), + Cell: ({ row }: any) => ( + + ), + maxWidth: 50, + disableSortBy: true, + hideInMenu: true, + }, { Header: 'Seen', accessor: 'lastSeenAt', - Cell: ({ value, row: { original: feature } }: any) => { + Cell: ({ row: { original: feature } }: any) => { return ; }, align: 'center', @@ -139,24 +129,6 @@ export const ArchiveTable = ({ width: 150, Cell: FeatureArchivedCell, }, - ...(!projectId - ? [ - { - Header: 'Project ID', - accessor: 'project', - sortType: 'alphanumeric', - filterName: 'project', - searchable: true, - maxWidth: 170, - Cell: ({ value }: any) => ( - - ), - }, - ] - : []), { Header: 'Actions', id: 'Actions', @@ -184,8 +156,7 @@ export const ArchiveTable = ({ searchable: true, }, ], - //eslint-disable-next-line - [projectId], + [], ); const { @@ -270,7 +241,7 @@ export const ArchiveTable = ({ replace: true, }); setStoredParams({ id: sortBy[0].id, desc: sortBy[0].desc || false }); - }, [loading, sortBy, searchValue]); // eslint-disable-line react-hooks/exhaustive-deps + }, [loading, sortBy, searchValue]); return ( <> @@ -322,33 +293,28 @@ export const ArchiveTable = ({ /> - - toggleAllRowsSelected(false)} - /> - - } - /> + + toggleAllRowsSelected(false)} + /> + ); }; diff --git a/frontend/src/component/archive/FeaturesArchiveTable.tsx b/frontend/src/component/archive/FeaturesArchiveTable.tsx deleted file mode 100644 index 6320142879..0000000000 --- a/frontend/src/component/archive/FeaturesArchiveTable.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { useFeaturesArchive } from 'hooks/api/getters/useFeaturesArchive/useFeaturesArchive'; -import { ArchiveTable } from './ArchiveTable/ArchiveTable'; -import type { SortingRule } from 'react-table'; -import { usePageTitle } from 'hooks/usePageTitle'; -import { createLocalStorage } from 'utils/createLocalStorage'; - -const defaultSort: SortingRule = { id: 'createdAt' }; -const { value, setValue } = createLocalStorage( - 'FeaturesArchiveTable:v1', - defaultSort, -); - -export const FeaturesArchiveTable = () => { - usePageTitle('Archive'); - - const { - archivedFeatures = [], - loading, - refetchArchived, - } = useFeaturesArchive(); - - return ( - - ); -}; diff --git a/frontend/src/component/feature/FeatureView/FeatureNotFound/FeatureNotFound.tsx b/frontend/src/component/feature/FeatureView/FeatureNotFound/FeatureNotFound.tsx index 36209cc256..49b4bb2197 100644 --- a/frontend/src/component/feature/FeatureView/FeatureNotFound/FeatureNotFound.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureNotFound/FeatureNotFound.tsx @@ -11,7 +11,7 @@ const StyledFeatureId = styled('strong')({ export const FeatureNotFound = () => { const projectId = useRequiredPathParam('projectId'); const featureId = useRequiredPathParam('featureId'); - const { archivedFeatures } = useFeaturesArchive(); + const { archivedFeatures } = useFeaturesArchive(projectId); const createFeatureTogglePath = getCreateTogglePath(projectId, { name: featureId, diff --git a/frontend/src/component/menu/__tests__/__snapshots__/routes.test.tsx.snap b/frontend/src/component/menu/__tests__/__snapshots__/routes.test.tsx.snap index 1dd43f7153..57bb8cac40 100644 --- a/frontend/src/component/menu/__tests__/__snapshots__/routes.test.tsx.snap +++ b/frontend/src/component/menu/__tests__/__snapshots__/routes.test.tsx.snap @@ -440,13 +440,6 @@ exports[`returns all baseRoutes 1`] = ` "title": "Login history", "type": "protected", }, - { - "component": [Function], - "menu": {}, - "path": "/archive", - "title": "Archived flags", - "type": "protected", - }, { "component": { "$$typeof": Symbol(react.lazy), diff --git a/frontend/src/component/menu/routes.ts b/frontend/src/component/menu/routes.ts index 19e2f57677..510f5c83f5 100644 --- a/frontend/src/component/menu/routes.ts +++ b/frontend/src/component/menu/routes.ts @@ -31,7 +31,6 @@ import { EditSegment } from 'component/segments/EditSegment/EditSegment'; import type { INavigationMenuItem, IRoute } from 'interfaces/route'; import { EnvironmentTable } from 'component/environments/EnvironmentTable/EnvironmentTable'; import { SegmentTable } from '../segments/SegmentTable/SegmentTable'; -import { FeaturesArchiveTable } from '../archive/FeaturesArchiveTable'; import { LazyPlayground } from 'component/playground/Playground/LazyPlayground'; import { Profile } from 'component/user/Profile/Profile'; import { LazyFeatureView } from 'component/feature/FeatureView/LazyFeatureView'; @@ -456,15 +455,6 @@ export const routes: IRoute[] = [ menu: { adminSettings: true }, }, - // Archive - { - path: '/archive', - title: 'Archived flags', - component: FeaturesArchiveTable, - type: 'protected', - menu: {}, - }, - // Admin { path: '/admin/*', diff --git a/frontend/src/hooks/api/getters/useFeaturesArchive/useFeaturesArchive.ts b/frontend/src/hooks/api/getters/useFeaturesArchive/useFeaturesArchive.ts index ee5dd8d239..fe52e806ae 100644 --- a/frontend/src/hooks/api/getters/useFeaturesArchive/useFeaturesArchive.ts +++ b/frontend/src/hooks/api/getters/useFeaturesArchive/useFeaturesArchive.ts @@ -9,13 +9,9 @@ const fetcher = (path: string) => { .then((res) => res.json()); }; -export const useFeaturesArchive = (projectId?: string) => { +export const useFeaturesArchive = (projectId: string) => { const { data, error, mutate, isLoading } = useSWR( - formatApiPath( - projectId - ? `/api/admin/archive/features/${projectId}` - : 'api/admin/archive/features', - ), + formatApiPath(`/api/admin/archive/features/${projectId}`), fetcher, { refreshInterval: 15 * 1000, // ms diff --git a/src/lib/features/feature-toggle/archive-feature-toggle-controller.ts b/src/lib/features/feature-toggle/archive-feature-toggle-controller.ts index 428a603111..4e70d8304d 100644 --- a/src/lib/features/feature-toggle/archive-feature-toggle-controller.ts +++ b/src/lib/features/feature-toggle/archive-feature-toggle-controller.ts @@ -2,10 +2,7 @@ import type { Request, Response } from 'express'; import type { IUnleashConfig } from '../../types/option'; import type { IUnleashServices } from '../../types'; import Controller from '../../routes/controller'; -import { - extractUserIdFromUser, - extractUsername, -} from '../../util/extract-user'; +import { extractUsername } from '../../util/extract-user'; import { DELETE_FEATURE, NONE, UPDATE_FEATURE } from '../../types/permissions'; import type FeatureToggleService from './feature-toggle-service'; import type { IAuthRequest } from '../../routes/unleash-types'; @@ -46,28 +43,6 @@ export default class ArchiveController extends Controller { this.transactionalFeatureToggleService = transactionalFeatureToggleService; - this.route({ - method: 'get', - path: '/features', - handler: this.getArchivedFeatures, - permission: NONE, - middleware: [ - openApiService.validPath({ - tags: ['Archive'], - summary: 'Get archived features', - description: - 'Retrieve a list of all [archived feature flags](https://docs.getunleash.io/reference/feature-toggles#archive-a-feature-flag).', - operationId: 'getArchivedFeatures', - responses: { - 200: createResponseSchema('archivedFeaturesSchema'), - ...getStandardResponses(401, 403), - }, - - deprecated: true, - }), - ], - }); - this.route({ method: 'get', path: '/features/:projectId', @@ -133,45 +108,13 @@ export default class ArchiveController extends Controller { }); } - async getArchivedFeatures( - req: IAuthRequest, - res: Response, - ): Promise { - const { user } = req; - const features = await this.featureService.getAllArchivedFeatures( - true, - extractUserIdFromUser(user), - ); - - this.openApiService.respondWithValidation( - 200, - res, - archivedFeaturesSchema.$id, - { - version: 2, - features: serializeDates( - features.map((feature) => { - return { - ...feature, - stale: feature.stale || false, - archivedAt: feature.archivedAt!, - }; - }), - ), - }, - ); - } - async getArchivedFeaturesByProjectId( req: Request<{ projectId: string }, any, any, any>, res: Response, ): Promise { const { projectId } = req.params; const features = - await this.featureService.getArchivedFeaturesByProjectId( - true, - projectId, - ); + await this.featureService.getArchivedFeaturesByProjectId(projectId); this.openApiService.respondWithValidation( 200, res, diff --git a/src/lib/features/feature-toggle/feature-toggle-service.ts b/src/lib/features/feature-toggle/feature-toggle-service.ts index af8b3958c9..e8ef888d3a 100644 --- a/src/lib/features/feature-toggle/feature-toggle-service.ts +++ b/src/lib/features/feature-toggle/feature-toggle-service.ts @@ -2162,25 +2162,7 @@ class FeatureToggleService { ); } - async getAllArchivedFeatures( - archived: boolean, - userId: number, - ): Promise { - const features = await this.featureToggleStore.getArchivedFeatures(); - - const projectAccess = - await this.privateProjectChecker.getUserAccessibleProjects(userId); - if (projectAccess.mode === 'all') { - return features; - } else { - return features.filter((f) => - projectAccess.projects.includes(f.project), - ); - } - } - async getArchivedFeaturesByProjectId( - archived: boolean, project: string, ): Promise { return this.featureToggleStore.getArchivedFeatures(project); diff --git a/src/lib/features/feature-toggle/feature-toggle-store.ts b/src/lib/features/feature-toggle/feature-toggle-store.ts index 5d355469e7..57ffbc3c28 100644 --- a/src/lib/features/feature-toggle/feature-toggle-store.ts +++ b/src/lib/features/feature-toggle/feature-toggle-store.ts @@ -258,7 +258,7 @@ export default class FeatureToggleStore implements IFeatureToggleStore { return rows.map(this.rowToFeature); } - async getArchivedFeatures(project?: string): Promise { + async getArchivedFeatures(project: string): Promise { const builder = new FeatureToggleListBuilder(this.db, [ ...commonSelectColumns, 'features.archived_at as archived_at', @@ -274,18 +274,10 @@ export default class FeatureToggleStore implements IFeatureToggleStore { 'last_seen_at_metrics.environment as last_seen_at_env', ); - let rows: any[]; - - if (project) { - rows = await builder.internalQuery - .select(builder.getSelectColumns()) - .where({ project }) - .whereNotNull('archived_at'); - } else { - rows = await builder.internalQuery - .select(builder.getSelectColumns()) - .whereNotNull('archived_at'); - } + const rows = await builder.internalQuery + .select(builder.getSelectColumns()) + .where({ project }) + .whereNotNull('archived_at'); return this.featureToggleRowConverter.buildArchivedFeatureToggleListFromRows( rows, diff --git a/src/lib/features/feature-toggle/tests/archive-feature-toggles.e2e.test.ts b/src/lib/features/feature-toggle/tests/archive-feature-toggles.e2e.test.ts index 48ac55ab4c..5605157d30 100644 --- a/src/lib/features/feature-toggle/tests/archive-feature-toggles.e2e.test.ts +++ b/src/lib/features/feature-toggle/tests/archive-feature-toggles.e2e.test.ts @@ -32,16 +32,6 @@ afterAll(async () => { await db.destroy(); }); -test('Should get empty features via admin', async () => { - await app.request - .get('/api/admin/archive/features') - .expect(200) - .expect('Content-Type', /json/) - .expect((res) => { - expect(res.body.features).toHaveLength(0); - }); -}); - test('Should be allowed to reuse deleted toggle name', async () => { await app.request .post('/api/admin/projects/default/features') @@ -61,30 +51,6 @@ test('Should be allowed to reuse deleted toggle name', async () => { .expect(200); }); -test('Should get archived toggles via admin', async () => { - await app.request - .post('/api/admin/projects/default/features') - .send({ - name: 'archived.test.1', - archived: true, - }) - .expect(201); - await app.request - .post('/api/admin/projects/default/features') - .send({ - name: 'archived.test.2', - archived: true, - }) - .expect(201); - await app.request - .get('/api/admin/archive/features') - .expect(200) - .expect('Content-Type', /json/) - .expect((res) => { - expect(res.body.features).toHaveLength(2); - }); -}); - test('Should get archived toggles via project', async () => { await db.stores.featureToggleStore.deleteAll(); @@ -132,14 +98,6 @@ test('Should get archived toggles via project', async () => { .expect((res) => { expect(res.body.features).toHaveLength(2); }); - - await app.request - .get('/api/admin/archive/features') - .expect(200) - .expect('Content-Type', /json/) - .expect((res) => { - expect(res.body.features).toHaveLength(3); - }); }); test('Should be able to revive toggle', async () => { diff --git a/src/lib/features/feature-toggle/tests/feature-toggle-last-seen-at.e2e.test.ts b/src/lib/features/feature-toggle/tests/feature-toggle-last-seen-at.e2e.test.ts index c783a8c8b1..58441f73e3 100644 --- a/src/lib/features/feature-toggle/tests/feature-toggle-last-seen-at.e2e.test.ts +++ b/src/lib/features/feature-toggle/tests/feature-toggle-last-seen-at.e2e.test.ts @@ -80,26 +80,6 @@ test('response should include last seen at per environment for multiple environm expect(production.lastSeenAt).toEqual('2023-10-01T12:34:56.000Z'); }); -test('response should include last seen at per environment for multiple environments in /api/admin/archive/features', async () => { - const featureName = 'multiple-environment-last-seen-at-archived'; - await setupLastSeenAtTest(featureName); - - await app.request - .delete(`/api/admin/projects/default/features/${featureName}`) - .expect(202); - - const { body } = await app.request.get(`/api/admin/archive/features`); - - const featureEnvironments = body.features[0].environments; - const [development, production] = featureEnvironments; - - expect(development.name).toBe('development'); - expect(development.lastSeenAt).toEqual('2023-10-01T12:34:56.000Z'); - - expect(production.name).toBe('production'); - expect(production.lastSeenAt).toEqual('2023-10-01T12:34:56.000Z'); -}); - test('response should include last seen at per environment for multiple environments in /api/admin/archive/features/:projectId', async () => { const featureName = 'multiple-environment-last-seen-at-archived-project'; await setupLastSeenAtTest(featureName); diff --git a/src/lib/features/feature-toggle/tests/feature-toggles.e2e.test.ts b/src/lib/features/feature-toggle/tests/feature-toggles.e2e.test.ts index c1ca02194b..f663dd5366 100644 --- a/src/lib/features/feature-toggle/tests/feature-toggles.e2e.test.ts +++ b/src/lib/features/feature-toggle/tests/feature-toggles.e2e.test.ts @@ -1036,7 +1036,8 @@ test('Patching feature flags to active (turning stale to false) should trigger F }); test('Should archive feature flag', async () => { - const url = '/api/admin/projects/default/features'; + const projectId = 'default'; + const url = `/api/admin/projects/${projectId}/features`; const name = 'new.flag.archive'; await app.request .post(url) @@ -1046,7 +1047,7 @@ test('Should archive feature flag', async () => { await app.request.get(`${url}/${name}`).expect(404); const { body } = await app.request - .get(`/api/admin/archive/features`) + .get(`/api/admin/archive/features/${projectId}`) .expect(200); const flag = body.features.find((f) => f.name === name); diff --git a/src/lib/features/feature-toggle/types/feature-toggle-store-type.ts b/src/lib/features/feature-toggle/types/feature-toggle-store-type.ts index 89afef8020..cf46f9a563 100644 --- a/src/lib/features/feature-toggle/types/feature-toggle-store-type.ts +++ b/src/lib/features/feature-toggle/types/feature-toggle-store-type.ts @@ -54,7 +54,7 @@ export interface IFeatureToggleStore extends Store { archived?: boolean, ): Promise; - getArchivedFeatures(project?: string): Promise; + getArchivedFeatures(project: string): Promise; getPlaygroundFeatures( featureQuery?: IFeatureToggleQuery, diff --git a/src/test/e2e/api/admin/feature-archive.e2e.test.ts b/src/test/e2e/api/admin/feature-archive.e2e.test.ts index 2828a01575..ddf417387d 100644 --- a/src/test/e2e/api/admin/feature-archive.e2e.test.ts +++ b/src/test/e2e/api/admin/feature-archive.e2e.test.ts @@ -68,7 +68,7 @@ afterAll(async () => { test('returns three archived flags', async () => { expect.assertions(1); return app.request - .get('/api/admin/archive/features') + .get(`/api/admin/archive/features/${DEFAULT_PROJECT}`) .expect('Content-Type', /json/) .expect(200) .expect((res) => { @@ -79,7 +79,7 @@ test('returns three archived flags', async () => { test('returns three archived flags with archivedAt', async () => { expect.assertions(2); return app.request - .get('/api/admin/archive/features') + .get(`/api/admin/archive/features/${DEFAULT_PROJECT}`) .expect('Content-Type', /json/) .expect(200) .expect((res) => { diff --git a/website/docs/reference/api/legacy/unleash/admin/archive.md b/website/docs/reference/api/legacy/unleash/admin/archive.md index 68079f5880..bbc3c3412c 100644 --- a/website/docs/reference/api/legacy/unleash/admin/archive.md +++ b/website/docs/reference/api/legacy/unleash/admin/archive.md @@ -4,31 +4,6 @@ title: /api/admin/archive > In order to access the admin API endpoints you need to identify yourself. Unless you're using the `none` authentication method, you'll need to [create an ADMIN token](/how-to/how-to-create-api-tokens) and add an Authorization header using the token. -### Fetch archived flags {#fetch-archived-toggles} - -`GET http://unleash.host.com/api/admin/archive/features` - -Used to fetch list of archived feature flags - -**Example response:** - -```json -{ - "version": 1, - "features": [ - { - "name": "Feature.A", - "description": "lorem ipsum", - "type": "release", - "stale": false, - "variants": [], - "tags": [], - "strategy": "default", - "parameters": {} - } - ] -} -``` ### Revive feature flag {#revive-feature-toggle}