mirror of
https://github.com/Unleash/unleash.git
synced 2025-05-26 01:17:00 +02:00
feat: archive project service (#7794)
This commit is contained in:
parent
c36ead4b74
commit
0450bfe6f9
@ -281,6 +281,49 @@ test('should update project', async () => {
|
|||||||
expect(updatedProject.defaultStickiness).toBe('userId');
|
expect(updatedProject.defaultStickiness).toBe('userId');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should archive project', async () => {
|
||||||
|
const project = {
|
||||||
|
id: 'test-archive',
|
||||||
|
name: 'New project',
|
||||||
|
description: 'Blah',
|
||||||
|
mode: 'open' as const,
|
||||||
|
defaultStickiness: 'default',
|
||||||
|
};
|
||||||
|
|
||||||
|
await projectService.createProject(project, user, TEST_AUDIT_USER);
|
||||||
|
await projectService.archiveProject(project.id, TEST_AUDIT_USER);
|
||||||
|
|
||||||
|
const events = await stores.eventStore.getEvents();
|
||||||
|
|
||||||
|
expect(events[0]).toMatchObject({
|
||||||
|
type: 'project-archived',
|
||||||
|
createdBy: TEST_AUDIT_USER.username,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not be able to archive project with flags', async () => {
|
||||||
|
const project = {
|
||||||
|
id: 'test-archive-with-flags',
|
||||||
|
name: 'New project',
|
||||||
|
description: 'Blah',
|
||||||
|
mode: 'open' as const,
|
||||||
|
defaultStickiness: 'default',
|
||||||
|
};
|
||||||
|
await projectService.createProject(project, user, auditUser);
|
||||||
|
await stores.featureToggleStore.create(project.id, {
|
||||||
|
name: 'test-project-archive',
|
||||||
|
createdByUserId: 9999,
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await projectService.archiveProject(project.id, auditUser);
|
||||||
|
} catch (err) {
|
||||||
|
expect(err.message).toBe(
|
||||||
|
'You can not archive a project with active feature flags',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
test('should update project without existing settings', async () => {
|
test('should update project without existing settings', async () => {
|
||||||
const project = {
|
const project = {
|
||||||
id: 'test-update-legacy',
|
id: 'test-update-legacy',
|
||||||
|
@ -12,6 +12,7 @@ import { nameType } from '../../routes/util';
|
|||||||
import { projectSchema } from '../../services/project-schema';
|
import { projectSchema } from '../../services/project-schema';
|
||||||
import NotFoundError from '../../error/notfound-error';
|
import NotFoundError from '../../error/notfound-error';
|
||||||
import {
|
import {
|
||||||
|
ADMIN,
|
||||||
ADMIN_TOKEN_USER,
|
ADMIN_TOKEN_USER,
|
||||||
type CreateProject,
|
type CreateProject,
|
||||||
DEFAULT_PROJECT,
|
DEFAULT_PROJECT,
|
||||||
@ -27,6 +28,7 @@ import {
|
|||||||
type IProjectApplications,
|
type IProjectApplications,
|
||||||
type IProjectHealth,
|
type IProjectHealth,
|
||||||
type IProjectOverview,
|
type IProjectOverview,
|
||||||
|
type IProjectOwnersReadModel,
|
||||||
type IProjectRoleUsage,
|
type IProjectRoleUsage,
|
||||||
type IProjectStore,
|
type IProjectStore,
|
||||||
type IProjectUpdate,
|
type IProjectUpdate,
|
||||||
@ -38,6 +40,8 @@ import {
|
|||||||
ProjectAccessGroupRolesUpdated,
|
ProjectAccessGroupRolesUpdated,
|
||||||
ProjectAccessUserRolesDeleted,
|
ProjectAccessUserRolesDeleted,
|
||||||
ProjectAccessUserRolesUpdated,
|
ProjectAccessUserRolesUpdated,
|
||||||
|
ProjectArchivedEvent,
|
||||||
|
type ProjectCreated,
|
||||||
ProjectCreatedEvent,
|
ProjectCreatedEvent,
|
||||||
ProjectDeletedEvent,
|
ProjectDeletedEvent,
|
||||||
ProjectGroupAddedEvent,
|
ProjectGroupAddedEvent,
|
||||||
@ -49,9 +53,6 @@ import {
|
|||||||
ProjectUserUpdateRoleEvent,
|
ProjectUserUpdateRoleEvent,
|
||||||
RoleName,
|
RoleName,
|
||||||
SYSTEM_USER_ID,
|
SYSTEM_USER_ID,
|
||||||
type ProjectCreated,
|
|
||||||
type IProjectOwnersReadModel,
|
|
||||||
ADMIN,
|
|
||||||
} from '../../types';
|
} from '../../types';
|
||||||
import type {
|
import type {
|
||||||
IProjectAccessModel,
|
IProjectAccessModel,
|
||||||
@ -591,6 +592,30 @@ export default class ProjectService {
|
|||||||
await this.accessService.removeDefaultProjectRoles(user, id);
|
await this.accessService.removeDefaultProjectRoles(user, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async archiveProject(id: string, auditUser: IAuditUser): Promise<void> {
|
||||||
|
const flags = await this.featureToggleStore.getAll({
|
||||||
|
project: id,
|
||||||
|
archived: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: allow archiving project with unused flags
|
||||||
|
|
||||||
|
if (flags.length > 0) {
|
||||||
|
throw new InvalidOperationError(
|
||||||
|
'You can not archive a project with active feature flags',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.projectStore.archive(id);
|
||||||
|
|
||||||
|
await this.eventService.storeEvent(
|
||||||
|
new ProjectArchivedEvent({
|
||||||
|
project: id,
|
||||||
|
auditUser,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
async validateId(id: string): Promise<boolean> {
|
async validateId(id: string): Promise<boolean> {
|
||||||
await nameType.validateAsync(id);
|
await nameType.validateAsync(id);
|
||||||
await this.validateUniqueId(id);
|
await this.validateUniqueId(id);
|
||||||
|
@ -134,4 +134,6 @@ export interface IProjectStore extends Store<IProject, string> {
|
|||||||
getApplicationsByProject(
|
getApplicationsByProject(
|
||||||
searchParams: IProjectApplicationsSearchParams,
|
searchParams: IProjectApplicationsSearchParams,
|
||||||
): Promise<IProjectApplications>;
|
): Promise<IProjectApplications>;
|
||||||
|
|
||||||
|
archive(projectId: string): Promise<void>;
|
||||||
}
|
}
|
||||||
|
@ -396,6 +396,11 @@ class ProjectStore implements IProjectStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async archive(id: string): Promise<void> {
|
||||||
|
const now = new Date();
|
||||||
|
await this.db(TABLE).where({ id }).update({ archived_at: now });
|
||||||
|
}
|
||||||
|
|
||||||
async getProjectLinksForEnvironments(
|
async getProjectLinksForEnvironments(
|
||||||
environments: string[],
|
environments: string[],
|
||||||
): Promise<IEnvironmentProjectLink[]> {
|
): Promise<IEnvironmentProjectLink[]> {
|
||||||
|
@ -81,6 +81,7 @@ export const ROLE_DELETED = 'role-deleted';
|
|||||||
export const PROJECT_CREATED = 'project-created' as const;
|
export const PROJECT_CREATED = 'project-created' as const;
|
||||||
export const PROJECT_UPDATED = 'project-updated' as const;
|
export const PROJECT_UPDATED = 'project-updated' as const;
|
||||||
export const PROJECT_DELETED = 'project-deleted' as const;
|
export const PROJECT_DELETED = 'project-deleted' as const;
|
||||||
|
export const PROJECT_ARCHIVED = 'project-archived' as const;
|
||||||
export const PROJECT_IMPORT = 'project-import' as const;
|
export const PROJECT_IMPORT = 'project-import' as const;
|
||||||
export const PROJECT_USER_ADDED = 'project-user-added' as const;
|
export const PROJECT_USER_ADDED = 'project-user-added' as const;
|
||||||
export const PROJECT_USER_REMOVED = 'project-user-removed' as const;
|
export const PROJECT_USER_REMOVED = 'project-user-removed' as const;
|
||||||
@ -249,6 +250,7 @@ export const IEventTypes = [
|
|||||||
PROJECT_CREATED,
|
PROJECT_CREATED,
|
||||||
PROJECT_UPDATED,
|
PROJECT_UPDATED,
|
||||||
PROJECT_DELETED,
|
PROJECT_DELETED,
|
||||||
|
PROJECT_ARCHIVED,
|
||||||
PROJECT_IMPORT,
|
PROJECT_IMPORT,
|
||||||
PROJECT_USER_ADDED,
|
PROJECT_USER_ADDED,
|
||||||
PROJECT_USER_REMOVED,
|
PROJECT_USER_REMOVED,
|
||||||
@ -574,7 +576,19 @@ export class ProjectDeletedEvent extends BaseEvent {
|
|||||||
project: string;
|
project: string;
|
||||||
auditUser: IAuditUser;
|
auditUser: IAuditUser;
|
||||||
}) {
|
}) {
|
||||||
super(PROJECT_DELETED, eventData.auditUser);
|
super(PROJECT_ARCHIVED, eventData.auditUser);
|
||||||
|
this.project = eventData.project;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ProjectArchivedEvent extends BaseEvent {
|
||||||
|
readonly project: string;
|
||||||
|
|
||||||
|
constructor(eventData: {
|
||||||
|
project: string;
|
||||||
|
auditUser: IAuditUser;
|
||||||
|
}) {
|
||||||
|
super(PROJECT_ARCHIVED, eventData.auditUser);
|
||||||
this.project = eventData.project;
|
this.project = eventData.project;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
2
src/test/fixtures/fake-project-store.ts
vendored
2
src/test/fixtures/fake-project-store.ts
vendored
@ -214,4 +214,6 @@ export default class FakeProjectStore implements IProjectStore {
|
|||||||
): Promise<IProjectApplications> {
|
): Promise<IProjectApplications> {
|
||||||
throw new Error('Method not implemented.');
|
throw new Error('Method not implemented.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async archive(id: string): Promise<void> {}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user