mirror of
https://github.com/Unleash/unleash.git
synced 2025-07-17 13:46:47 +02:00
refactor: remove deprecated GET archive features endpoint (#9924)
https://linear.app/unleash/issue/2-3366/remove-get-apiadminarchivefeatures-deprecated-in-4100 Removes GET `/api/admin/archive/features` which was deprecated in v4.10. Also cleans up related code. May include some slight scouting. **P.S.** Should we merge this into main, or is there a `v7` branch we should be targeting instead?
This commit is contained in:
parent
410142cb42
commit
42b6fc810e
@ -17,7 +17,6 @@ import { DateCell } from 'component/common/Table/cells/DateCell/DateCell';
|
|||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import { Search } from 'component/common/Search/Search';
|
import { Search } from 'component/common/Search/Search';
|
||||||
import { FeatureTypeCell } from 'component/common/Table/cells/FeatureTypeCell/FeatureTypeCell';
|
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 { ArchivedFeatureActionCell } from 'component/archive/ArchiveTable/ArchivedFeatureActionCell/ArchivedFeatureActionCell';
|
||||||
import { featuresPlaceholder } from 'component/feature/FeatureToggleList/FeatureToggleListTable';
|
import { featuresPlaceholder } from 'component/feature/FeatureToggleList/FeatureToggleListTable';
|
||||||
import theme from 'themes/theme';
|
import theme from 'themes/theme';
|
||||||
@ -32,7 +31,6 @@ import { RowSelectCell } from '../../project/Project/ProjectFeatureToggles/RowSe
|
|||||||
import { BatchSelectionActionsBar } from '../../common/BatchSelectionActionsBar/BatchSelectionActionsBar';
|
import { BatchSelectionActionsBar } from '../../common/BatchSelectionActionsBar/BatchSelectionActionsBar';
|
||||||
import { ArchiveBatchActions } from './ArchiveBatchActions';
|
import { ArchiveBatchActions } from './ArchiveBatchActions';
|
||||||
import { FeatureEnvironmentSeenCell } from 'component/common/Table/cells/FeatureSeenCell/FeatureEnvironmentSeenCell';
|
import { FeatureEnvironmentSeenCell } from 'component/common/Table/cells/FeatureSeenCell/FeatureEnvironmentSeenCell';
|
||||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
|
||||||
import { ArchivedFeatureReviveConfirm } from './ArchivedFeatureActionCell/ArchivedFeatureReviveConfirm/ArchivedFeatureReviveConfirm';
|
import { ArchivedFeatureReviveConfirm } from './ArchivedFeatureActionCell/ArchivedFeatureReviveConfirm/ArchivedFeatureReviveConfirm';
|
||||||
|
|
||||||
export interface IFeaturesArchiveTableProps {
|
export interface IFeaturesArchiveTableProps {
|
||||||
@ -46,7 +44,7 @@ export interface IFeaturesArchiveTableProps {
|
|||||||
| SortingRule<string>
|
| SortingRule<string>
|
||||||
| ((prev: SortingRule<string>) => SortingRule<string>),
|
| ((prev: SortingRule<string>) => SortingRule<string>),
|
||||||
) => SortingRule<string>;
|
) => SortingRule<string>;
|
||||||
projectId?: string;
|
projectId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ArchiveTable = ({
|
export const ArchiveTable = ({
|
||||||
@ -72,12 +70,8 @@ export const ArchiveTable = ({
|
|||||||
searchParams.get('search') || '',
|
searchParams.get('search') || '',
|
||||||
);
|
);
|
||||||
|
|
||||||
const { uiConfig } = useUiConfig();
|
|
||||||
|
|
||||||
const columns = useMemo(
|
const columns = useMemo(
|
||||||
() => [
|
() => [
|
||||||
...(projectId
|
|
||||||
? [
|
|
||||||
{
|
{
|
||||||
id: 'Select',
|
id: 'Select',
|
||||||
Header: ({ getToggleAllRowsSelectedProps }: any) => (
|
Header: ({ getToggleAllRowsSelectedProps }: any) => (
|
||||||
@ -87,20 +81,16 @@ export const ArchiveTable = ({
|
|||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
Cell: ({ row }: any) => (
|
Cell: ({ row }: any) => (
|
||||||
<RowSelectCell
|
<RowSelectCell {...row?.getToggleRowSelectedProps?.()} />
|
||||||
{...row?.getToggleRowSelectedProps?.()}
|
|
||||||
/>
|
|
||||||
),
|
),
|
||||||
maxWidth: 50,
|
maxWidth: 50,
|
||||||
disableSortBy: true,
|
disableSortBy: true,
|
||||||
hideInMenu: true,
|
hideInMenu: true,
|
||||||
},
|
},
|
||||||
]
|
|
||||||
: []),
|
|
||||||
{
|
{
|
||||||
Header: 'Seen',
|
Header: 'Seen',
|
||||||
accessor: 'lastSeenAt',
|
accessor: 'lastSeenAt',
|
||||||
Cell: ({ value, row: { original: feature } }: any) => {
|
Cell: ({ row: { original: feature } }: any) => {
|
||||||
return <FeatureEnvironmentSeenCell feature={feature} />;
|
return <FeatureEnvironmentSeenCell feature={feature} />;
|
||||||
},
|
},
|
||||||
align: 'center',
|
align: 'center',
|
||||||
@ -139,24 +129,6 @@ export const ArchiveTable = ({
|
|||||||
width: 150,
|
width: 150,
|
||||||
Cell: FeatureArchivedCell,
|
Cell: FeatureArchivedCell,
|
||||||
},
|
},
|
||||||
...(!projectId
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
Header: 'Project ID',
|
|
||||||
accessor: 'project',
|
|
||||||
sortType: 'alphanumeric',
|
|
||||||
filterName: 'project',
|
|
||||||
searchable: true,
|
|
||||||
maxWidth: 170,
|
|
||||||
Cell: ({ value }: any) => (
|
|
||||||
<LinkCell
|
|
||||||
title={value}
|
|
||||||
to={`/projects/${value}`}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: []),
|
|
||||||
{
|
{
|
||||||
Header: 'Actions',
|
Header: 'Actions',
|
||||||
id: 'Actions',
|
id: 'Actions',
|
||||||
@ -184,8 +156,7 @@ export const ArchiveTable = ({
|
|||||||
searchable: true,
|
searchable: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
//eslint-disable-next-line
|
[],
|
||||||
[projectId],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -270,7 +241,7 @@ export const ArchiveTable = ({
|
|||||||
replace: true,
|
replace: true,
|
||||||
});
|
});
|
||||||
setStoredParams({ id: sortBy[0].id, desc: sortBy[0].desc || false });
|
setStoredParams({ id: sortBy[0].id, desc: sortBy[0].desc || false });
|
||||||
}, [loading, sortBy, searchValue]); // eslint-disable-line react-hooks/exhaustive-deps
|
}, [loading, sortBy, searchValue]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -322,33 +293,28 @@ export const ArchiveTable = ({
|
|||||||
/>
|
/>
|
||||||
<ArchivedFeatureDeleteConfirm
|
<ArchivedFeatureDeleteConfirm
|
||||||
deletedFeatures={[deletedFeature?.name!]}
|
deletedFeatures={[deletedFeature?.name!]}
|
||||||
projectId={projectId ?? deletedFeature?.project!}
|
projectId={projectId}
|
||||||
open={deleteModalOpen}
|
open={deleteModalOpen}
|
||||||
setOpen={setDeleteModalOpen}
|
setOpen={setDeleteModalOpen}
|
||||||
refetch={refetch}
|
refetch={refetch}
|
||||||
/>
|
/>
|
||||||
<ArchivedFeatureReviveConfirm
|
<ArchivedFeatureReviveConfirm
|
||||||
revivedFeatures={[revivedFeature?.name!]}
|
revivedFeatures={[revivedFeature?.name!]}
|
||||||
projectId={projectId ?? revivedFeature?.project!}
|
projectId={projectId}
|
||||||
open={reviveModalOpen}
|
open={reviveModalOpen}
|
||||||
setOpen={setReviveModalOpen}
|
setOpen={setReviveModalOpen}
|
||||||
refetch={refetch}
|
refetch={refetch}
|
||||||
/>
|
/>
|
||||||
</PageContent>
|
</PageContent>
|
||||||
<ConditionallyRender
|
|
||||||
condition={Boolean(projectId)}
|
|
||||||
show={
|
|
||||||
<BatchSelectionActionsBar
|
<BatchSelectionActionsBar
|
||||||
count={Object.keys(selectedRowIds).length}
|
count={Object.keys(selectedRowIds).length}
|
||||||
>
|
>
|
||||||
<ArchiveBatchActions
|
<ArchiveBatchActions
|
||||||
selectedIds={Object.keys(selectedRowIds)}
|
selectedIds={Object.keys(selectedRowIds)}
|
||||||
projectId={projectId!}
|
projectId={projectId}
|
||||||
onConfirm={() => toggleAllRowsSelected(false)}
|
onConfirm={() => toggleAllRowsSelected(false)}
|
||||||
/>
|
/>
|
||||||
</BatchSelectionActionsBar>
|
</BatchSelectionActionsBar>
|
||||||
}
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -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<string> = { id: 'createdAt' };
|
|
||||||
const { value, setValue } = createLocalStorage(
|
|
||||||
'FeaturesArchiveTable:v1',
|
|
||||||
defaultSort,
|
|
||||||
);
|
|
||||||
|
|
||||||
export const FeaturesArchiveTable = () => {
|
|
||||||
usePageTitle('Archive');
|
|
||||||
|
|
||||||
const {
|
|
||||||
archivedFeatures = [],
|
|
||||||
loading,
|
|
||||||
refetchArchived,
|
|
||||||
} = useFeaturesArchive();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ArchiveTable
|
|
||||||
title='Archive'
|
|
||||||
archivedFeatures={archivedFeatures}
|
|
||||||
loading={loading}
|
|
||||||
storedParams={value}
|
|
||||||
setStoredParams={setValue}
|
|
||||||
refetch={refetchArchived}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
@ -11,7 +11,7 @@ const StyledFeatureId = styled('strong')({
|
|||||||
export const FeatureNotFound = () => {
|
export const FeatureNotFound = () => {
|
||||||
const projectId = useRequiredPathParam('projectId');
|
const projectId = useRequiredPathParam('projectId');
|
||||||
const featureId = useRequiredPathParam('featureId');
|
const featureId = useRequiredPathParam('featureId');
|
||||||
const { archivedFeatures } = useFeaturesArchive();
|
const { archivedFeatures } = useFeaturesArchive(projectId);
|
||||||
|
|
||||||
const createFeatureTogglePath = getCreateTogglePath(projectId, {
|
const createFeatureTogglePath = getCreateTogglePath(projectId, {
|
||||||
name: featureId,
|
name: featureId,
|
||||||
|
@ -440,13 +440,6 @@ exports[`returns all baseRoutes 1`] = `
|
|||||||
"title": "Login history",
|
"title": "Login history",
|
||||||
"type": "protected",
|
"type": "protected",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"component": [Function],
|
|
||||||
"menu": {},
|
|
||||||
"path": "/archive",
|
|
||||||
"title": "Archived flags",
|
|
||||||
"type": "protected",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"component": {
|
"component": {
|
||||||
"$$typeof": Symbol(react.lazy),
|
"$$typeof": Symbol(react.lazy),
|
||||||
|
@ -31,7 +31,6 @@ import { EditSegment } from 'component/segments/EditSegment/EditSegment';
|
|||||||
import type { INavigationMenuItem, IRoute } from 'interfaces/route';
|
import type { INavigationMenuItem, IRoute } from 'interfaces/route';
|
||||||
import { EnvironmentTable } from 'component/environments/EnvironmentTable/EnvironmentTable';
|
import { EnvironmentTable } from 'component/environments/EnvironmentTable/EnvironmentTable';
|
||||||
import { SegmentTable } from '../segments/SegmentTable/SegmentTable';
|
import { SegmentTable } from '../segments/SegmentTable/SegmentTable';
|
||||||
import { FeaturesArchiveTable } from '../archive/FeaturesArchiveTable';
|
|
||||||
import { LazyPlayground } from 'component/playground/Playground/LazyPlayground';
|
import { LazyPlayground } from 'component/playground/Playground/LazyPlayground';
|
||||||
import { Profile } from 'component/user/Profile/Profile';
|
import { Profile } from 'component/user/Profile/Profile';
|
||||||
import { LazyFeatureView } from 'component/feature/FeatureView/LazyFeatureView';
|
import { LazyFeatureView } from 'component/feature/FeatureView/LazyFeatureView';
|
||||||
@ -456,15 +455,6 @@ export const routes: IRoute[] = [
|
|||||||
menu: { adminSettings: true },
|
menu: { adminSettings: true },
|
||||||
},
|
},
|
||||||
|
|
||||||
// Archive
|
|
||||||
{
|
|
||||||
path: '/archive',
|
|
||||||
title: 'Archived flags',
|
|
||||||
component: FeaturesArchiveTable,
|
|
||||||
type: 'protected',
|
|
||||||
menu: {},
|
|
||||||
},
|
|
||||||
|
|
||||||
// Admin
|
// Admin
|
||||||
{
|
{
|
||||||
path: '/admin/*',
|
path: '/admin/*',
|
||||||
|
@ -9,13 +9,9 @@ const fetcher = (path: string) => {
|
|||||||
.then((res) => res.json());
|
.then((res) => res.json());
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useFeaturesArchive = (projectId?: string) => {
|
export const useFeaturesArchive = (projectId: string) => {
|
||||||
const { data, error, mutate, isLoading } = useSWR<ArchivedFeaturesSchema>(
|
const { data, error, mutate, isLoading } = useSWR<ArchivedFeaturesSchema>(
|
||||||
formatApiPath(
|
formatApiPath(`/api/admin/archive/features/${projectId}`),
|
||||||
projectId
|
|
||||||
? `/api/admin/archive/features/${projectId}`
|
|
||||||
: 'api/admin/archive/features',
|
|
||||||
),
|
|
||||||
fetcher,
|
fetcher,
|
||||||
{
|
{
|
||||||
refreshInterval: 15 * 1000, // ms
|
refreshInterval: 15 * 1000, // ms
|
||||||
|
@ -2,10 +2,7 @@ import type { Request, Response } from 'express';
|
|||||||
import type { IUnleashConfig } from '../../types/option';
|
import type { IUnleashConfig } from '../../types/option';
|
||||||
import type { IUnleashServices } from '../../types';
|
import type { IUnleashServices } from '../../types';
|
||||||
import Controller from '../../routes/controller';
|
import Controller from '../../routes/controller';
|
||||||
import {
|
import { extractUsername } from '../../util/extract-user';
|
||||||
extractUserIdFromUser,
|
|
||||||
extractUsername,
|
|
||||||
} from '../../util/extract-user';
|
|
||||||
import { DELETE_FEATURE, NONE, UPDATE_FEATURE } from '../../types/permissions';
|
import { DELETE_FEATURE, NONE, UPDATE_FEATURE } from '../../types/permissions';
|
||||||
import type FeatureToggleService from './feature-toggle-service';
|
import type FeatureToggleService from './feature-toggle-service';
|
||||||
import type { IAuthRequest } from '../../routes/unleash-types';
|
import type { IAuthRequest } from '../../routes/unleash-types';
|
||||||
@ -46,28 +43,6 @@ export default class ArchiveController extends Controller {
|
|||||||
this.transactionalFeatureToggleService =
|
this.transactionalFeatureToggleService =
|
||||||
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({
|
this.route({
|
||||||
method: 'get',
|
method: 'get',
|
||||||
path: '/features/:projectId',
|
path: '/features/:projectId',
|
||||||
@ -133,45 +108,13 @@ export default class ArchiveController extends Controller {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getArchivedFeatures(
|
|
||||||
req: IAuthRequest,
|
|
||||||
res: Response<ArchivedFeaturesSchema>,
|
|
||||||
): Promise<void> {
|
|
||||||
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(
|
async getArchivedFeaturesByProjectId(
|
||||||
req: Request<{ projectId: string }, any, any, any>,
|
req: Request<{ projectId: string }, any, any, any>,
|
||||||
res: Response<ArchivedFeaturesSchema>,
|
res: Response<ArchivedFeaturesSchema>,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const { projectId } = req.params;
|
const { projectId } = req.params;
|
||||||
const features =
|
const features =
|
||||||
await this.featureService.getArchivedFeaturesByProjectId(
|
await this.featureService.getArchivedFeaturesByProjectId(projectId);
|
||||||
true,
|
|
||||||
projectId,
|
|
||||||
);
|
|
||||||
this.openApiService.respondWithValidation(
|
this.openApiService.respondWithValidation(
|
||||||
200,
|
200,
|
||||||
res,
|
res,
|
||||||
|
@ -2162,25 +2162,7 @@ class FeatureToggleService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAllArchivedFeatures(
|
|
||||||
archived: boolean,
|
|
||||||
userId: number,
|
|
||||||
): Promise<FeatureToggle[]> {
|
|
||||||
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(
|
async getArchivedFeaturesByProjectId(
|
||||||
archived: boolean,
|
|
||||||
project: string,
|
project: string,
|
||||||
): Promise<FeatureToggle[]> {
|
): Promise<FeatureToggle[]> {
|
||||||
return this.featureToggleStore.getArchivedFeatures(project);
|
return this.featureToggleStore.getArchivedFeatures(project);
|
||||||
|
@ -258,7 +258,7 @@ export default class FeatureToggleStore implements IFeatureToggleStore {
|
|||||||
return rows.map(this.rowToFeature);
|
return rows.map(this.rowToFeature);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getArchivedFeatures(project?: string): Promise<FeatureToggle[]> {
|
async getArchivedFeatures(project: string): Promise<FeatureToggle[]> {
|
||||||
const builder = new FeatureToggleListBuilder(this.db, [
|
const builder = new FeatureToggleListBuilder(this.db, [
|
||||||
...commonSelectColumns,
|
...commonSelectColumns,
|
||||||
'features.archived_at as archived_at',
|
'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',
|
'last_seen_at_metrics.environment as last_seen_at_env',
|
||||||
);
|
);
|
||||||
|
|
||||||
let rows: any[];
|
const rows = await builder.internalQuery
|
||||||
|
|
||||||
if (project) {
|
|
||||||
rows = await builder.internalQuery
|
|
||||||
.select(builder.getSelectColumns())
|
.select(builder.getSelectColumns())
|
||||||
.where({ project })
|
.where({ project })
|
||||||
.whereNotNull('archived_at');
|
.whereNotNull('archived_at');
|
||||||
} else {
|
|
||||||
rows = await builder.internalQuery
|
|
||||||
.select(builder.getSelectColumns())
|
|
||||||
.whereNotNull('archived_at');
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.featureToggleRowConverter.buildArchivedFeatureToggleListFromRows(
|
return this.featureToggleRowConverter.buildArchivedFeatureToggleListFromRows(
|
||||||
rows,
|
rows,
|
||||||
|
@ -32,16 +32,6 @@ afterAll(async () => {
|
|||||||
await db.destroy();
|
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 () => {
|
test('Should be allowed to reuse deleted toggle name', async () => {
|
||||||
await app.request
|
await app.request
|
||||||
.post('/api/admin/projects/default/features')
|
.post('/api/admin/projects/default/features')
|
||||||
@ -61,30 +51,6 @@ test('Should be allowed to reuse deleted toggle name', async () => {
|
|||||||
.expect(200);
|
.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 () => {
|
test('Should get archived toggles via project', async () => {
|
||||||
await db.stores.featureToggleStore.deleteAll();
|
await db.stores.featureToggleStore.deleteAll();
|
||||||
|
|
||||||
@ -132,14 +98,6 @@ test('Should get archived toggles via project', async () => {
|
|||||||
.expect((res) => {
|
.expect((res) => {
|
||||||
expect(res.body.features).toHaveLength(2);
|
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 () => {
|
test('Should be able to revive toggle', async () => {
|
||||||
|
@ -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');
|
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 () => {
|
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';
|
const featureName = 'multiple-environment-last-seen-at-archived-project';
|
||||||
await setupLastSeenAtTest(featureName);
|
await setupLastSeenAtTest(featureName);
|
||||||
|
@ -1036,7 +1036,8 @@ test('Patching feature flags to active (turning stale to false) should trigger F
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('Should archive feature flag', async () => {
|
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';
|
const name = 'new.flag.archive';
|
||||||
await app.request
|
await app.request
|
||||||
.post(url)
|
.post(url)
|
||||||
@ -1046,7 +1047,7 @@ test('Should archive feature flag', async () => {
|
|||||||
|
|
||||||
await app.request.get(`${url}/${name}`).expect(404);
|
await app.request.get(`${url}/${name}`).expect(404);
|
||||||
const { body } = await app.request
|
const { body } = await app.request
|
||||||
.get(`/api/admin/archive/features`)
|
.get(`/api/admin/archive/features/${projectId}`)
|
||||||
.expect(200);
|
.expect(200);
|
||||||
|
|
||||||
const flag = body.features.find((f) => f.name === name);
|
const flag = body.features.find((f) => f.name === name);
|
||||||
|
@ -54,7 +54,7 @@ export interface IFeatureToggleStore extends Store<FeatureToggle, string> {
|
|||||||
archived?: boolean,
|
archived?: boolean,
|
||||||
): Promise<FeatureToggle[]>;
|
): Promise<FeatureToggle[]>;
|
||||||
|
|
||||||
getArchivedFeatures(project?: string): Promise<FeatureToggle[]>;
|
getArchivedFeatures(project: string): Promise<FeatureToggle[]>;
|
||||||
|
|
||||||
getPlaygroundFeatures(
|
getPlaygroundFeatures(
|
||||||
featureQuery?: IFeatureToggleQuery,
|
featureQuery?: IFeatureToggleQuery,
|
||||||
|
@ -68,7 +68,7 @@ afterAll(async () => {
|
|||||||
test('returns three archived flags', async () => {
|
test('returns three archived flags', async () => {
|
||||||
expect.assertions(1);
|
expect.assertions(1);
|
||||||
return app.request
|
return app.request
|
||||||
.get('/api/admin/archive/features')
|
.get(`/api/admin/archive/features/${DEFAULT_PROJECT}`)
|
||||||
.expect('Content-Type', /json/)
|
.expect('Content-Type', /json/)
|
||||||
.expect(200)
|
.expect(200)
|
||||||
.expect((res) => {
|
.expect((res) => {
|
||||||
@ -79,7 +79,7 @@ test('returns three archived flags', async () => {
|
|||||||
test('returns three archived flags with archivedAt', async () => {
|
test('returns three archived flags with archivedAt', async () => {
|
||||||
expect.assertions(2);
|
expect.assertions(2);
|
||||||
return app.request
|
return app.request
|
||||||
.get('/api/admin/archive/features')
|
.get(`/api/admin/archive/features/${DEFAULT_PROJECT}`)
|
||||||
.expect('Content-Type', /json/)
|
.expect('Content-Type', /json/)
|
||||||
.expect(200)
|
.expect(200)
|
||||||
.expect((res) => {
|
.expect((res) => {
|
||||||
|
@ -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.
|
> 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}
|
### Revive feature flag {#revive-feature-toggle}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user