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;
|
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(
|
async createProject(
|
||||||
newProject: CreateProject,
|
newProject: CreateProject,
|
||||||
user: IUser,
|
user: IUser,
|
||||||
@ -314,11 +323,29 @@ export default class ProjectService {
|
|||||||
return [];
|
return [];
|
||||||
},
|
},
|
||||||
): Promise<ProjectCreated> {
|
): 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);
|
const data = this.removePropertiesForNonEnterprise(validatedData);
|
||||||
await this.validateUniqueId(data.id);
|
|
||||||
|
|
||||||
await this.projectStore.create(data);
|
await this.projectStore.create(data);
|
||||||
|
|
||||||
@ -362,7 +389,7 @@ export default class ProjectService {
|
|||||||
await this.eventService.storeEvent(
|
await this.eventService.storeEvent(
|
||||||
new ProjectCreatedEvent({
|
new ProjectCreatedEvent({
|
||||||
data,
|
data,
|
||||||
project: newProject.id,
|
project: data.id,
|
||||||
auditUser,
|
auditUser,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
@ -487,7 +487,8 @@ export interface IImportData extends ImportCommon {
|
|||||||
// Create project aligns with #/components/schemas/createProjectSchema
|
// Create project aligns with #/components/schemas/createProjectSchema
|
||||||
// joi is providing default values when the optional inputs are not provided
|
// joi is providing default values when the optional inputs are not provided
|
||||||
// const data = await projectSchema.validateAsync(newProject);
|
// const data = await projectSchema.validateAsync(newProject);
|
||||||
export type CreateProject = Pick<IProject, 'id' | 'name'> & {
|
export type CreateProject = Pick<IProject, 'name'> & {
|
||||||
|
id?: string;
|
||||||
mode?: ProjectMode;
|
mode?: ProjectMode;
|
||||||
defaultStickiness?: string;
|
defaultStickiness?: string;
|
||||||
environments?: string[];
|
environments?: string[];
|
||||||
|
Loading…
Reference in New Issue
Block a user