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');
|
||||
});
|
||||
|
||||
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 () => {
|
||||
const project = {
|
||||
id: 'test-update-legacy',
|
||||
|
@ -12,6 +12,7 @@ import { nameType } from '../../routes/util';
|
||||
import { projectSchema } from '../../services/project-schema';
|
||||
import NotFoundError from '../../error/notfound-error';
|
||||
import {
|
||||
ADMIN,
|
||||
ADMIN_TOKEN_USER,
|
||||
type CreateProject,
|
||||
DEFAULT_PROJECT,
|
||||
@ -27,6 +28,7 @@ import {
|
||||
type IProjectApplications,
|
||||
type IProjectHealth,
|
||||
type IProjectOverview,
|
||||
type IProjectOwnersReadModel,
|
||||
type IProjectRoleUsage,
|
||||
type IProjectStore,
|
||||
type IProjectUpdate,
|
||||
@ -38,6 +40,8 @@ import {
|
||||
ProjectAccessGroupRolesUpdated,
|
||||
ProjectAccessUserRolesDeleted,
|
||||
ProjectAccessUserRolesUpdated,
|
||||
ProjectArchivedEvent,
|
||||
type ProjectCreated,
|
||||
ProjectCreatedEvent,
|
||||
ProjectDeletedEvent,
|
||||
ProjectGroupAddedEvent,
|
||||
@ -49,9 +53,6 @@ import {
|
||||
ProjectUserUpdateRoleEvent,
|
||||
RoleName,
|
||||
SYSTEM_USER_ID,
|
||||
type ProjectCreated,
|
||||
type IProjectOwnersReadModel,
|
||||
ADMIN,
|
||||
} from '../../types';
|
||||
import type {
|
||||
IProjectAccessModel,
|
||||
@ -591,6 +592,30 @@ export default class ProjectService {
|
||||
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> {
|
||||
await nameType.validateAsync(id);
|
||||
await this.validateUniqueId(id);
|
||||
|
@ -134,4 +134,6 @@ export interface IProjectStore extends Store<IProject, string> {
|
||||
getApplicationsByProject(
|
||||
searchParams: IProjectApplicationsSearchParams,
|
||||
): 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(
|
||||
environments: string[],
|
||||
): Promise<IEnvironmentProjectLink[]> {
|
||||
|
@ -81,6 +81,7 @@ export const ROLE_DELETED = 'role-deleted';
|
||||
export const PROJECT_CREATED = 'project-created' as const;
|
||||
export const PROJECT_UPDATED = 'project-updated' 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_USER_ADDED = 'project-user-added' as const;
|
||||
export const PROJECT_USER_REMOVED = 'project-user-removed' as const;
|
||||
@ -249,6 +250,7 @@ export const IEventTypes = [
|
||||
PROJECT_CREATED,
|
||||
PROJECT_UPDATED,
|
||||
PROJECT_DELETED,
|
||||
PROJECT_ARCHIVED,
|
||||
PROJECT_IMPORT,
|
||||
PROJECT_USER_ADDED,
|
||||
PROJECT_USER_REMOVED,
|
||||
@ -574,7 +576,19 @@ export class ProjectDeletedEvent extends BaseEvent {
|
||||
project: string;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
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> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
async archive(id: string): Promise<void> {}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user