Skip to content
This repository was archived by the owner on Aug 6, 2025. It is now read-only.

Commit 1ddaf72

Browse files
authored
[DOP-3506]: Autobuilder generates pages using version data (#774)
* [DOP-3506]: Add apiVersion and resourceVersion * [DOP-3506]: refactor buildOpenAPIPages function * [DOP-3506]: Update getAtlasSpecUrl function to use api version and resource version * [DOP-3506]: simplify buildOpenAPIPages function * [DOP-3506]: Add comments and some error handling * [DOP-3506]: Add await statements * [DOP-3506]: Update for loop and add unit tests * [DOP-3506]: Remove trailing .0 for api version * [DOP-3506]: work on updating tests * [DOP-3506]: simplify version extension * [DOP-3506]: Update tests * [DOP-3506]: Update pageBuilder tests * [DOP-3506]: Add launch.json config for debugging unit tests * [DOP-3506]: Fixed all tests, woot! * [DOP-3506]: Fixed all tests, woot! * [DOP-3506]: Refactored page builder to only append resource version if one exists
1 parent 46f7369 commit 1ddaf72

File tree

5 files changed

+219
-32
lines changed

5 files changed

+219
-32
lines changed

.vscode/launch.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
// Use IntelliSense to learn about possible attributes.
3+
// Hover to view descriptions of existing attributes.
4+
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5+
"version": "0.2.0",
6+
"configurations": [
7+
{
8+
"type": "node",
9+
"request": "launch",
10+
"name": "Launch Program",
11+
"skipFiles": ["<node_internals>/**"],
12+
"program": "${workspaceFolder}/modules/oas-page-builder/tests/unit/services/pageBuilder.test.ts",
13+
"preLaunchTask": "tsc: build - tsconfig.json",
14+
"outFiles": ["${workspaceFolder}/build/**/*.js"]
15+
}
16+
]
17+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
// Use IntelliSense to learn about possible attributes.
3+
// Hover to view descriptions of existing attributes.
4+
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5+
"version": "0.2.0",
6+
"configurations": [
7+
{
8+
"type": "node",
9+
"request": "launch",
10+
"name": "Launch Program",
11+
"skipFiles": ["<node_internals>/**"],
12+
"program": "${workspaceFolder}/tests/unit/services/pageBuilder.test.ts",
13+
"preLaunchTask": "tsc: build - tsconfig.json",
14+
"outFiles": ["${workspaceFolder}/build/**/*.js"]
15+
}
16+
]
17+
}

modules/oas-page-builder/src/services/pageBuilder.ts

Lines changed: 76 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,30 @@ const fetchTextData = async (url: string, errMsg: string) => {
1515
return res.text();
1616
};
1717

18-
const getOASFileUrl = (gitHash: string) => `${OAS_FILE_SERVER}${gitHash}.json`;
18+
interface AtlasSpecUrlParams {
19+
apiKeyword: string;
20+
apiVersion?: string;
21+
resourceVersion?: string;
22+
}
1923

20-
const getAtlasSpecUrl = async (apiKeyword: string) => {
24+
const getAtlasSpecUrl = async ({ apiKeyword, apiVersion, resourceVersion }: AtlasSpecUrlParams) => {
2125
// Currently, the only expected API fetched programmatically is the Cloud Admin API,
2226
// but it's possible to have more in the future with varying processes.
2327
const keywords = ['cloud'];
2428
if (!keywords.includes(apiKeyword)) {
2529
throw new Error(`${apiKeyword} is not a supported API for building.`);
2630
}
2731

32+
const versionExtension = `${apiVersion ? `-v${apiVersion.split('.')[0]}` : ''}${
33+
apiVersion && resourceVersion ? `-${resourceVersion}` : ''
34+
}`;
35+
2836
let oasFileURL;
37+
2938
try {
3039
const versionURL = 'https://cloud.mongodb.com/version';
3140
const gitHash = await fetchTextData(versionURL, 'Could not find current version or git hash');
32-
oasFileURL = getOASFileUrl(gitHash);
41+
oasFileURL = `${OAS_FILE_SERVER}${gitHash}${versionExtension}.json`;
3342

3443
// Sometimes the latest git hash might not have a fully available spec file yet.
3544
// If this is the case, we should default to using the last successfully saved
@@ -40,7 +49,7 @@ const getAtlasSpecUrl = async (apiKeyword: string) => {
4049

4150
const res = await findLastSavedGitHash(apiKeyword);
4251
if (res) {
43-
oasFileURL = getOASFileUrl(res.gitHash);
52+
oasFileURL = `${OAS_FILE_SERVER}${res.gitHash}${versionExtension}.json`;
4453
console.log(`Using ${oasFileURL}`);
4554
} else {
4655
throw new Error(`Could not find a saved hash for API: ${apiKeyword}`);
@@ -50,39 +59,78 @@ const getAtlasSpecUrl = async (apiKeyword: string) => {
5059
return oasFileURL;
5160
};
5261

62+
interface GetOASpecParams {
63+
sourceType: string;
64+
source: string;
65+
output: string;
66+
pageSlug: string;
67+
repoPath: string;
68+
redocExecutor: RedocExecutor;
69+
apiVersion?: string;
70+
resourceVersion?: string;
71+
}
72+
73+
async function getOASpec({
74+
source,
75+
sourceType,
76+
repoPath,
77+
pageSlug,
78+
redocExecutor,
79+
output,
80+
apiVersion,
81+
resourceVersion,
82+
}: GetOASpecParams) {
83+
try {
84+
let spec = '';
85+
const buildOptions: RedocBuildOptions = {};
86+
if (sourceType === 'url') {
87+
spec = source;
88+
} else if (sourceType === 'local') {
89+
const localFilePath = normalizePath(`${repoPath}/source/${source}`);
90+
spec = localFilePath;
91+
} else if (sourceType === 'atlas') {
92+
spec = await getAtlasSpecUrl({ apiKeyword: source, apiVersion, resourceVersion });
93+
// Ignore "incompatible types" warnings for Atlas Admin API/cloud-docs
94+
95+
buildOptions['ignoreIncompatibleTypes'] = true;
96+
} else {
97+
throw new Error(`Unsupported source type "${sourceType}" for ${pageSlug}`);
98+
}
99+
100+
const filePathExtension = `${resourceVersion && apiVersion ? `/${resourceVersion}` : ''}`;
101+
102+
const path = `${output}/${pageSlug}${filePathExtension}/index.html`;
103+
const finalFilename = normalizePath(path);
104+
await redocExecutor.execute(spec, finalFilename, buildOptions);
105+
} catch (e) {
106+
console.error(e);
107+
}
108+
}
109+
53110
export const buildOpenAPIPages = async (
54111
entries: [string, OASPageMetadata][],
55112
{ output, redoc: redocPath, repo: repoPath, siteUrl, siteTitle }: PageBuilderOptions
56113
) => {
57114
const redocExecutor = new RedocExecutor(redocPath, siteUrl, siteTitle);
58115

59116
for (const [pageSlug, data] of entries) {
60-
const { source_type: sourceType, source } = data;
61-
62-
try {
63-
let spec = '';
64-
const buildOptions: RedocBuildOptions = {};
65-
66-
if (sourceType === 'url') {
67-
spec = source;
68-
} else if (sourceType === 'local') {
69-
const localFilePath = normalizePath(`${repoPath}/source/${source}`);
70-
spec = localFilePath;
71-
} else if (sourceType === 'atlas') {
72-
spec = await getAtlasSpecUrl(source);
73-
// Ignore "incompatible types" warnings for Atlas Admin API/cloud-docs
74-
buildOptions['ignoreIncompatibleTypes'] = true;
75-
} else {
76-
throw new Error(`Unsupported source type "${sourceType}" for ${pageSlug}`);
77-
}
117+
const { source_type: sourceType, source, api_version: apiVersion, resource_versions: resourceVersions } = data;
78118

79-
const finalFilename = normalizePath(`${output}/${pageSlug}/index.html`);
80-
await redocExecutor.execute(spec, finalFilename, buildOptions);
81-
} catch (e) {
82-
console.error(e);
83-
// Continue to try to build other pages since it's possible that mut will
84-
// still upload existing HTML files
119+
if (!apiVersion && resourceVersions && resourceVersions.length > 0) {
120+
console.error(
121+
`ERROR: API version is not specified, but resource version is present for source ${source} and sourceType: ${sourceType}`
122+
);
85123
continue;
86124
}
125+
126+
if (resourceVersions) {
127+
// if a resource versions array is provided, then we can loop through the resourceVersions array and call the getOASpec
128+
// for each minor version
129+
for (const resourceVersion of resourceVersions) {
130+
await getOASpec({ source, sourceType, output, pageSlug, redocExecutor, repoPath, apiVersion, resourceVersion });
131+
}
132+
}
133+
// apiVersion can be undefined, this case is handled within the getOASpec function
134+
await getOASpec({ source, sourceType, output, pageSlug, redocExecutor, repoPath, apiVersion });
87135
}
88136
};

modules/oas-page-builder/src/services/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { ModuleOptions } from '../types';
33
export interface OASPageMetadata {
44
source_type: string;
55
source: string;
6+
api_version?: string;
7+
resource_versions?: string[];
68
}
79

810
export type OASPagesMetadata = Record<string, OASPageMetadata>;

modules/oas-page-builder/tests/unit/services/pageBuilder.test.ts

Lines changed: 107 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@ jest.mock('../../../src/services/database', () => ({
2020
findLastSavedGitHash: jest.fn(),
2121
}));
2222

23-
// Helper function for concatendated output path
24-
const getExpectedOutputPath = (destination: string, pageSlug: string) => `${destination}/${pageSlug}/index.html`;
23+
// Helper function for concatenated output path
24+
const getExpectedOutputPath = (destination: string, pageSlug: string, apiVersion?: string, resourceVersion?: string) =>
25+
`${destination}/${pageSlug}${resourceVersion && apiVersion ? `/${resourceVersion}` : ''}/index.html`;
2526

2627
// Allows node-fetch to be mockable
2728
jest.mock('node-fetch');
@@ -67,8 +68,7 @@ describe('pageBuilder', () => {
6768
];
6869

6970
await buildOpenAPIPages(testEntries, testOptions);
70-
// const mockSoundPlayerInstance = SoundPlayer.mock.instances[0];
71-
// const mockRedocExecutorInstance = RedocExecutor.mock.instances[0];
71+
7272
expect(mockExecute).toBeCalledTimes(testEntries.length);
7373
// Local
7474
expect(mockExecute).toBeCalledWith(
@@ -90,6 +90,109 @@ describe('pageBuilder', () => {
9090
);
9191
});
9292

93+
it('builds OpenAPI pages with api version', async () => {
94+
mockFetchImplementation(true);
95+
96+
const testEntries: [string, OASPageMetadata][] = [
97+
['path/to/page/1/v1', { source_type: 'local', source: '/local-spec/v1.json', api_version: '1.0' }],
98+
[
99+
'path/to/page/2/v1',
100+
{
101+
source_type: 'url',
102+
source: 'https://raw.githubusercontent.com/mongodb/docs-landing/master/source/openapi/loremipsum/v1.json',
103+
api_version: '1.0',
104+
},
105+
],
106+
['path/to/page/3/v1', { source_type: 'atlas', source: 'cloud', api_version: '1.0' }],
107+
];
108+
109+
await buildOpenAPIPages(testEntries, testOptions);
110+
console.log(getExpectedOutputPath(testOptions.output, testEntries[0][0], '1.0'));
111+
expect(mockExecute).toBeCalledTimes(testEntries.length);
112+
// Local
113+
expect(mockExecute).toBeCalledWith(
114+
`${testOptions.repo}/source${testEntries[0][1].source}`,
115+
`${testOptions.output}/${testEntries[0][0]}/index.html`,
116+
expectedDefaultBuildOptions
117+
);
118+
// Url
119+
expect(mockExecute).toBeCalledWith(
120+
`${testEntries[1][1].source}`,
121+
getExpectedOutputPath(testOptions.output, testEntries[1][0], '1.0'),
122+
expectedDefaultBuildOptions
123+
);
124+
// Atlas
125+
expect(mockExecute).toBeCalledWith(
126+
`https://mongodb-mms-prod-build-server.s3.amazonaws.com/openapi/${MOCKED_GIT_HASH}-v1.json`,
127+
getExpectedOutputPath(testOptions.output, testEntries[2][0], '1.0'),
128+
expectedAtlasBuildOptions
129+
);
130+
});
131+
132+
it('builds OpenAPI pages with api version and resource version', async () => {
133+
mockFetchImplementation(true);
134+
135+
const testEntries: [string, OASPageMetadata][] = [
136+
[
137+
'path/to/page/1/v2',
138+
{ source_type: 'local', source: '/local-spec.json', api_version: '2.0', resource_versions: ['01-01-2020'] },
139+
],
140+
[
141+
'path/to/page/2/v2',
142+
{
143+
source_type: 'url',
144+
source: 'https://raw.githubusercontent.com/mongodb/docs-landing/master/source/openapi/loremipsum.json',
145+
api_version: '2.0',
146+
resource_versions: ['01-01-2020'],
147+
},
148+
],
149+
[
150+
'path/to/page/3/v2',
151+
{ source_type: 'atlas', source: 'cloud', api_version: '2.0', resource_versions: ['01-01-2020'] },
152+
],
153+
];
154+
155+
await buildOpenAPIPages(testEntries, testOptions);
156+
157+
expect(mockExecute).toBeCalledTimes(testEntries.length * 2);
158+
// Local
159+
expect(mockExecute).toBeCalledWith(
160+
`${testOptions.repo}/source${testEntries[0][1].source}`,
161+
`${testOptions.output}/${testEntries[0][0]}/01-01-2020/index.html`,
162+
expectedDefaultBuildOptions
163+
);
164+
165+
expect(mockExecute).toBeCalledWith(
166+
`${testOptions.repo}/source${testEntries[0][1].source}`,
167+
`${testOptions.output}/${testEntries[0][0]}/index.html`,
168+
expectedDefaultBuildOptions
169+
);
170+
// Url
171+
expect(mockExecute).toBeCalledWith(
172+
`${testEntries[1][1].source}`,
173+
getExpectedOutputPath(testOptions.output, testEntries[1][0], '2.0', '01-01-2020'),
174+
expectedDefaultBuildOptions
175+
);
176+
177+
expect(mockExecute).toBeCalledWith(
178+
`${testEntries[1][1].source}`,
179+
getExpectedOutputPath(testOptions.output, testEntries[1][0], '2.0'),
180+
expectedDefaultBuildOptions
181+
);
182+
// Atlas
183+
expect(mockExecute).toBeCalledWith(
184+
`https://mongodb-mms-prod-build-server.s3.amazonaws.com/openapi/${MOCKED_GIT_HASH}-v2-01-01-2020.json`,
185+
getExpectedOutputPath(testOptions.output, testEntries[2][0], '2.0', '01-01-2020'),
186+
expectedAtlasBuildOptions
187+
);
188+
189+
expect(mockExecute).toBeCalledWith(
190+
`https://mongodb-mms-prod-build-server.s3.amazonaws.com/openapi/${MOCKED_GIT_HASH}-v2.json`,
191+
getExpectedOutputPath(testOptions.output, testEntries[2][0], '2.0'),
192+
expectedAtlasBuildOptions
193+
);
194+
});
195+
93196
it('builds Atlas Cloud API with backup git hash', async () => {
94197
mockFetchImplementation(false);
95198
// @ts-ignore

0 commit comments

Comments
 (0)