Skip to content

Commit bd1d844

Browse files
authored
Add helper method to ensure benchmark folder existency (#2011)
Fixes OPS-3752 ## Additional Notes This PR implements minimal service for create benchmark endpoint that ensures Benchmark folder exists for a given provider. Also add a new method in folder service that checks for a folder and creates one.
1 parent 35f728e commit bd1d844

3 files changed

Lines changed: 191 additions & 24 deletions

File tree

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import {
2+
BenchmarkCreationResult,
3+
BenchmarkProviders,
4+
ContentType,
5+
Folder,
6+
openOpsId,
7+
} from '@openops/shared';
8+
import { flowFolderService } from '../flows/folder/folder.service';
9+
import { throwValidationError } from './errors';
10+
11+
function getBenchmarkFolderDisplayName(provider: string): string {
12+
const normalizedProvider = provider.toLowerCase();
13+
switch (normalizedProvider) {
14+
case BenchmarkProviders.AWS:
15+
return 'AWS Benchmark';
16+
default:
17+
throwValidationError(`Unknown provider: ${provider}`);
18+
}
19+
}
20+
21+
async function ensureBenchmarkFolder(
22+
projectId: string,
23+
displayName: string,
24+
): Promise<Folder> {
25+
return flowFolderService.getOrCreate({
26+
projectId,
27+
request: {
28+
displayName,
29+
contentType: ContentType.WORKFLOW,
30+
},
31+
});
32+
}
33+
34+
export async function createBenchmark(params: {
35+
provider: string;
36+
projectId: string;
37+
}): Promise<BenchmarkCreationResult> {
38+
const { provider, projectId } = params;
39+
40+
const benchmarkFolder = await ensureBenchmarkFolder(
41+
projectId,
42+
getBenchmarkFolderDisplayName(provider),
43+
);
44+
45+
return {
46+
benchmarkId: openOpsId(),
47+
folderId: benchmarkFolder.id,
48+
provider,
49+
workflows: [],
50+
webhookPayload: {
51+
webhookBaseUrl: '',
52+
workflows: [],
53+
cleanupWorkflows: [],
54+
accounts: [],
55+
regions: [],
56+
},
57+
};
58+
}

packages/server/api/src/app/flows/folder/folder.service.ts

Lines changed: 45 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -86,36 +86,22 @@ export const flowFolderService = {
8686
params: { folderName: request.displayName },
8787
});
8888
}
89-
90-
const folderId = openOpsId();
91-
const parentFolder = await flowFolderService.getParentFolder(
92-
projectId,
93-
request.parentFolderId,
94-
);
95-
96-
await folderRepo().upsert(
89+
return createFolder(params);
90+
},
91+
async getOrCreate(params: UpsertParams): Promise<Folder> {
92+
const { projectId, request } = params;
93+
const requestContentType = request.contentType ?? ContentType.WORKFLOW;
94+
const folderWithDisplayName = await this.getOneByDisplayNameCaseInsensitive(
9795
{
98-
id: folderId,
9996
projectId,
100-
parentFolder,
10197
displayName: request.displayName,
10298
contentType: requestContentType,
10399
},
104-
['projectId', 'contentType', 'displayName'],
105100
);
106-
107-
const folder = await folderRepo().findOneByOrFail({
108-
projectId,
109-
id: folderId,
110-
});
111-
112-
return {
113-
...folder,
114-
numberOfFlows: 0,
115-
flows: undefined,
116-
subfolders: undefined,
117-
parentFolderId: request.parentFolderId,
118-
};
101+
if (!isNil(folderWithDisplayName)) {
102+
return folderWithDisplayName;
103+
}
104+
return createFolder(params);
119105
},
120106
async getParentFolder(
121107
projectId: string,
@@ -225,6 +211,41 @@ export const flowFolderService = {
225211
},
226212
};
227213

214+
async function createFolder(params: UpsertParams): Promise<FolderDto> {
215+
const { projectId, request } = params;
216+
const requestContentType = request.contentType ?? ContentType.WORKFLOW;
217+
218+
const folderId = openOpsId();
219+
const parentFolder = await flowFolderService.getParentFolder(
220+
projectId,
221+
request.parentFolderId,
222+
);
223+
224+
await folderRepo().upsert(
225+
{
226+
id: folderId,
227+
projectId,
228+
parentFolder,
229+
displayName: request.displayName,
230+
contentType: requestContentType,
231+
},
232+
['projectId', 'contentType', 'displayName'],
233+
);
234+
235+
const folder = await folderRepo().findOneByOrFail({
236+
projectId,
237+
id: folderId,
238+
});
239+
240+
return {
241+
...folder,
242+
numberOfFlows: 0,
243+
flows: undefined,
244+
subfolders: undefined,
245+
parentFolderId: request.parentFolderId,
246+
};
247+
}
248+
228249
type DeleteParams = {
229250
projectId: ProjectId;
230251
folderId: FolderId;
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import { ContentType, type Folder } from '@openops/shared';
2+
import { createBenchmark } from '../../../src/app/benchmark/create-benchmark.service';
3+
import { flowFolderService } from '../../../src/app/flows/folder/folder.service';
4+
5+
jest.mock('../../../src/app/flows/folder/folder.service', () => ({
6+
flowFolderService: {
7+
getOrCreate: jest.fn(),
8+
},
9+
}));
10+
11+
const flowFolderServiceMock = flowFolderService as jest.Mocked<
12+
typeof flowFolderService
13+
>;
14+
15+
describe('create-benchmark.service', () => {
16+
beforeEach(() => {
17+
jest.clearAllMocks();
18+
});
19+
20+
it('createBenchmark with provider aws calls getOrCreate with displayName AWS Benchmark', async () => {
21+
const projectId = 'project-1';
22+
const folder: Folder = {
23+
id: 'folder-1',
24+
projectId,
25+
displayName: 'AWS Benchmark',
26+
created: '',
27+
updated: '',
28+
contentType: ContentType.WORKFLOW,
29+
};
30+
flowFolderServiceMock.getOrCreate.mockResolvedValue(folder);
31+
32+
await createBenchmark({ provider: 'aws', projectId });
33+
34+
expect(flowFolderServiceMock.getOrCreate).toHaveBeenCalledWith({
35+
projectId,
36+
request: {
37+
displayName: 'AWS Benchmark',
38+
contentType: ContentType.WORKFLOW,
39+
},
40+
});
41+
});
42+
43+
it('createBenchmark throws for unknown provider', async () => {
44+
const projectId = 'project-1';
45+
await expect(
46+
createBenchmark({ provider: 'gcp', projectId }),
47+
).rejects.toThrow('Unknown provider: gcp');
48+
expect(flowFolderServiceMock.getOrCreate).not.toHaveBeenCalled();
49+
});
50+
51+
it('createBenchmark returns BenchmarkCreationResult', async () => {
52+
const projectId = 'project-1';
53+
const folder: Folder = {
54+
id: 'folder-2',
55+
projectId,
56+
displayName: 'AWS Benchmark',
57+
created: '',
58+
updated: '',
59+
contentType: ContentType.WORKFLOW,
60+
};
61+
62+
flowFolderServiceMock.getOrCreate.mockResolvedValue(folder);
63+
64+
const result = await createBenchmark({
65+
provider: 'aws',
66+
projectId,
67+
});
68+
69+
expect(flowFolderServiceMock.getOrCreate).toHaveBeenCalledWith({
70+
projectId,
71+
request: {
72+
displayName: 'AWS Benchmark',
73+
contentType: ContentType.WORKFLOW,
74+
},
75+
});
76+
expect(result.folderId).toBe(folder.id);
77+
expect(result.workflows).toEqual([]);
78+
expect(result.benchmarkId).toBeDefined();
79+
expect(result.provider).toBe('aws');
80+
expect(result.webhookPayload).toEqual({
81+
webhookBaseUrl: '',
82+
workflows: [],
83+
cleanupWorkflows: [],
84+
accounts: [],
85+
regions: [],
86+
});
87+
});
88+
});

0 commit comments

Comments
 (0)