mirror of
https://github.com/Unleash/unleash.git
synced 2024-12-28 00:06:53 +01:00
feat: generate project ids if they're missing (#7003)
This PR updates the project service to automatically create a project id if it is not provided. The feature is behind a flag. If an ID is provided, it will still attempt to use that ID instead.
This commit is contained in:
parent
02440dfed2
commit
95ac2e6b8d
@ -2616,3 +2616,100 @@ describe('create project with environments', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('automatic ID generation for create project', () => {
|
||||
test('if no ID is included in the creation argument, it gets generated based on the project name', async () => {
|
||||
const project = await projectService.createProject(
|
||||
{
|
||||
name: 'New name',
|
||||
},
|
||||
user,
|
||||
auditUser,
|
||||
);
|
||||
|
||||
expect(project.id).toMatch(/^new-name-/);
|
||||
});
|
||||
|
||||
test('two projects with the same name get different ids', async () => {
|
||||
const createProject = async () =>
|
||||
projectService.createProject(
|
||||
{ name: 'some name' },
|
||||
user,
|
||||
auditUser,
|
||||
);
|
||||
|
||||
const project1 = await createProject();
|
||||
const project2 = await createProject();
|
||||
|
||||
expect(project1.id).toMatch(/^some-name-/);
|
||||
expect(project2.id).toMatch(/^some-name-/);
|
||||
expect(project1.id).not.toBe(project2.id);
|
||||
});
|
||||
|
||||
test.each(['', undefined, ' '])(
|
||||
'An id with the value `%s` is treated as missing (and the id is based on the name)',
|
||||
async (id) => {
|
||||
const name = randomId();
|
||||
const project = await projectService.createProject(
|
||||
{ name, id },
|
||||
user,
|
||||
auditUser,
|
||||
);
|
||||
|
||||
expect(project.id).toMatch(new RegExp(`^${name}-`));
|
||||
},
|
||||
);
|
||||
|
||||
describe('backwards compatibility', () => {
|
||||
const featureFlag = 'createProjectWithEnvironmentConfig';
|
||||
|
||||
test.each([true, false])(
|
||||
'if the ID is present in the input, it is used as the ID regardless of the feature flag states. Flag state: %s',
|
||||
async (flagState) => {
|
||||
const id = randomId();
|
||||
// @ts-expect-error - we're just checking that the same
|
||||
// thing happens regardless of flag state
|
||||
projectService.flagResolver.isEnabled = (
|
||||
flagToCheck: string,
|
||||
) => {
|
||||
if (flagToCheck === featureFlag) {
|
||||
return flagState;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
const project = await projectService.createProject(
|
||||
{
|
||||
name: id,
|
||||
id,
|
||||
},
|
||||
user,
|
||||
auditUser,
|
||||
);
|
||||
|
||||
expect(project.id).toBe(id);
|
||||
},
|
||||
);
|
||||
|
||||
test.each(['', undefined, ' '])(
|
||||
'if the flag to enable auto ID generation is off, not providing a valid ID (testing `%s`) throws an error',
|
||||
async (id) => {
|
||||
// @ts-expect-error
|
||||
projectService.flagResolver.isEnabled = () => {
|
||||
return false;
|
||||
};
|
||||
|
||||
const createProject = () =>
|
||||
projectService.createProject(
|
||||
{
|
||||
name: randomId(),
|
||||
id,
|
||||
},
|
||||
user,
|
||||
auditUser,
|
||||
);
|
||||
expect(createProject).rejects.toThrow();
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@ -302,6 +302,15 @@ export default class ProjectService {
|
||||
return id;
|
||||
}
|
||||
|
||||
async generateUniqueProjectId(name: string): Promise<string> {
|
||||
const id = this.generateProjectId(name);
|
||||
if (await this.projectStore.hasProject(id)) {
|
||||
return await this.generateUniqueProjectId(name);
|
||||
} else {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
async createProject(
|
||||
newProject: CreateProject,
|
||||
user: IUser,
|
||||
@ -314,11 +323,29 @@ export default class ProjectService {
|
||||
return [];
|
||||
},
|
||||
): Promise<ProjectCreated> {
|
||||
await this.validateProjectEnvironments(newProject.environments);
|
||||
const validateData = async () => {
|
||||
await this.validateProjectEnvironments(newProject.environments);
|
||||
|
||||
const validatedData = await projectSchema.validateAsync(newProject);
|
||||
if (
|
||||
!newProject.id?.trim() &&
|
||||
this.flagResolver.isEnabled(
|
||||
'createProjectWithEnvironmentConfig',
|
||||
)
|
||||
) {
|
||||
newProject.id = await this.generateUniqueProjectId(
|
||||
newProject.name,
|
||||
);
|
||||
return await projectSchema.validateAsync(newProject);
|
||||
} else {
|
||||
const validatedData =
|
||||
await projectSchema.validateAsync(newProject);
|
||||
await this.validateUniqueId(validatedData.id);
|
||||
return validatedData;
|
||||
}
|
||||
};
|
||||
|
||||
const validatedData = await validateData();
|
||||
const data = this.removePropertiesForNonEnterprise(validatedData);
|
||||
await this.validateUniqueId(data.id);
|
||||
|
||||
await this.projectStore.create(data);
|
||||
|
||||
@ -362,7 +389,7 @@ export default class ProjectService {
|
||||
await this.eventService.storeEvent(
|
||||
new ProjectCreatedEvent({
|
||||
data,
|
||||
project: newProject.id,
|
||||
project: data.id,
|
||||
auditUser,
|
||||
}),
|
||||
);
|
||||
|
@ -487,7 +487,8 @@ export interface IImportData extends ImportCommon {
|
||||
// Create project aligns with #/components/schemas/createProjectSchema
|
||||
// joi is providing default values when the optional inputs are not provided
|
||||
// const data = await projectSchema.validateAsync(newProject);
|
||||
export type CreateProject = Pick<IProject, 'id' | 'name'> & {
|
||||
export type CreateProject = Pick<IProject, 'name'> & {
|
||||
id?: string;
|
||||
mode?: ProjectMode;
|
||||
defaultStickiness?: string;
|
||||
environments?: string[];
|
||||
|
Loading…
Reference in New Issue
Block a user