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

Commit cda66c7

Browse files
authored
DOP-2549-2: Absolute minimal Search infrastructure POC (#592)
* DOP-2549-2: Absolute minimal POC * DOP-2549-2: Add python script, change Job classes to types * DOP-2549-2: Move Job fields, integrate tests * DOP-2549-2: Remove unrelated import error * DOP-2549-2: Fix Dockerfile COPY location * DOP-2549-2: Add missing await * DOP-2549-2: Add tests * DOP-2549-2: Rearchitecture * DOP-2549-2: Add forgotten type * DOP-2549-2: Add missing JobStatus import
1 parent 8cca3a2 commit cda66c7

21 files changed

+383
-180
lines changed

src/entities/job.ts

Lines changed: 20 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,21 @@
11
// TODO: Cut down on null and undefined type definition allowances
2+
23
export enum JobStatus {
34
inQueue = 'inQueue',
45
inProgress = 'inProgress',
56
completed = 'completed',
67
failed = 'failed',
78
}
89

9-
export interface IJob {
10-
_id: string;
11-
payload: IPayload;
12-
createdTime: Date;
13-
email: string;
14-
endTime: Date | null | undefined;
15-
error: any | null | undefined;
16-
logs: string[] | null | undefined;
17-
priority: number | null | undefined;
18-
result: any | null | undefined;
19-
startTime: Date;
20-
status: string;
21-
title: string;
22-
user: string;
23-
comMessage: string[] | null | undefined;
24-
purgedUrls: string[] | null | undefined;
25-
buildCommands: Array<string>;
26-
deployCommands: Array<string>;
27-
}
10+
// TODO: Formalize JobTypes
11+
// export enum JobType {
12+
// githubPush = 'githubPush',
13+
// manifestGeneration = 'manifestGeneration',
14+
// productionDeploy = 'productionDeploy',
15+
// regression = 'regression',
16+
// }
2817

29-
export interface IPayload {
18+
export type Payload = {
3019
jobType: string;
3120
source: string;
3221
action: string;
@@ -53,28 +42,28 @@ export interface IPayload {
5342
prefix: string;
5443
project: string;
5544
includeInGlobalSearch: boolean;
56-
}
45+
};
5746

58-
export class Job implements IJob {
47+
export type Job = {
5948
_id: string;
60-
payload: IPayload;
49+
payload: Payload;
6150
createdTime: Date;
62-
email: string;
6351
endTime: Date | null | undefined;
64-
error: any;
52+
error: any | null | undefined;
6553
logs: string[] | null | undefined;
6654
priority: number | null | undefined;
67-
result: any;
55+
result: any | null | undefined;
6856
startTime: Date;
69-
status: string;
57+
status: JobStatus | null;
7058
title: string;
7159
user: string;
72-
comMessage: string[] | null | undefined;
73-
purgedUrls: string[] | null | undefined;
7460
manifestPrefix: string | undefined;
7561
pathPrefix: string | null | undefined;
7662
mutPrefix: string | null | undefined;
7763
buildCommands: string[];
7864
deployCommands: string[];
79-
invalidationStatusURL: string | null | undefined;
80-
}
65+
email: string; // probably can be removed
66+
comMessage: string[] | null | undefined;
67+
purgedUrls: string[] | null | undefined;
68+
shouldGenerateSearchManifest: boolean;
69+
};

src/job/jobHandler.ts

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { IJob } from '../entities/job';
1+
import type { Payload, Job } from '../entities/job';
22
import { JobRepository } from '../repositories/jobRepository';
33
import { RepoBranchesRepository } from '../repositories/repoBranchesRepository';
44
import { ICDNConnector } from '../services/cdn';
@@ -12,8 +12,8 @@ import { IJobValidator } from './jobValidator';
1212
require('fs');
1313

1414
export abstract class JobHandler {
15-
private _currJob: IJob;
16-
public get currJob(): IJob {
15+
private _currJob: Job;
16+
public get currJob(): Job {
1717
return this._currJob;
1818
}
1919

@@ -55,7 +55,7 @@ export abstract class JobHandler {
5555
protected _repoBranchesRepo: RepoBranchesRepository;
5656

5757
constructor(
58-
job: IJob,
58+
job: Job,
5959
config: IConfig,
6060
jobRepository: JobRepository,
6161
fileSystemServices: IFileSystemServices,
@@ -414,6 +414,54 @@ export abstract class JobHandler {
414414
}
415415
}
416416

417+
// Creates and pushes the related manifestJob
418+
@throwIfJobInterupted()
419+
public async queueManifestJob(): Promise<void> {
420+
// Rudimentary error prevention
421+
if (this._currJob.payload.jobType.includes('manifestGeneration')) {
422+
this._logger.error(
423+
this._currJob._id,
424+
`Incorrectly attempted to queue another search manifest job
425+
based on known ManifestJob '${this._currJob._id}'. Please contact documentation platform team.`
426+
);
427+
return;
428+
}
429+
const manifestPayload: Payload = this._currJob.payload;
430+
manifestPayload.jobType = 'manifestGeneration';
431+
const manifestJob: Job = {
432+
_id: '',
433+
payload: manifestPayload,
434+
createdTime: new Date(),
435+
endTime: undefined,
436+
error: undefined,
437+
logs: undefined,
438+
priority: 2,
439+
result: undefined,
440+
startTime: new Date(),
441+
status: null,
442+
title: this._currJob.title + ' - search manifest generation',
443+
user: this._currJob.user,
444+
manifestPrefix: this._currJob.manifestPrefix,
445+
pathPrefix: this._currJob.pathPrefix,
446+
mutPrefix: this._currJob.mutPrefix,
447+
buildCommands: [],
448+
deployCommands: [],
449+
email: '',
450+
comMessage: null,
451+
purgedUrls: null,
452+
shouldGenerateSearchManifest: false,
453+
};
454+
try {
455+
await this._jobRepository.insertJob(manifestJob, this._config.get('jobsQueueUrl'));
456+
} catch (error) {
457+
this._logger.error(
458+
manifestJob._id,
459+
`Failed to queue search manifest job for build job '${this._currJob._id}'.
460+
Error: ${error.message}. Search results for this property will not be updated.`
461+
);
462+
}
463+
}
464+
417465
public stop(): void {
418466
this._shouldStop = true;
419467
}

src/job/jobManager.ts

Lines changed: 17 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,29 @@ import { IJobValidator } from './jobValidator';
22
import { ICDNConnector } from '../services/cdn';
33
import { ProductionJobHandler } from './productionJobHandler';
44
import { RegressionJobHandler } from './regressionJobHandler';
5+
import { ManifestJobHandler } from './manifestJobHandler';
56
import { StagingJobHandler } from './stagingJobHandler';
67
import { IRepoConnector } from '../services/repo';
78
import { IJobRepoLogger } from '../services/logger';
89
import { JobHandler } from './jobHandler';
910
import { IJobCommandExecutor } from '../services/commandExecutor';
1011
import { InvalidJobError } from '../errors/errors';
11-
import { IJob } from '../entities/job';
12+
import { Job } from '../entities/job';
1213
import { JobRepository } from '../repositories/jobRepository';
1314
import { IFileSystemServices } from '../services/fileServices';
1415
import { IConfig } from 'config';
1516
import { RepoBranchesRepository } from '../repositories/repoBranchesRepository';
1617

18+
export const jobHandlerMap = {
19+
githubPush: StagingJobHandler,
20+
manifestGeneration: ManifestJobHandler,
21+
productionDeploy: ProductionJobHandler,
22+
regression: RegressionJobHandler,
23+
};
24+
1725
export class JobHandlerFactory {
1826
public createJobHandler(
19-
job: IJob,
27+
job: Job,
2028
config: IConfig,
2129
jobRepository: JobRepository,
2230
fileSystemServices: IFileSystemServices,
@@ -27,34 +35,9 @@ export class JobHandlerFactory {
2735
validator: IJobValidator,
2836
repoBranchesRepo: RepoBranchesRepository
2937
): JobHandler {
30-
if (job.payload.jobType === 'regression') {
31-
return new RegressionJobHandler(
32-
job,
33-
config,
34-
jobRepository,
35-
fileSystemServices,
36-
commandExecutor,
37-
cdnConnector,
38-
repoConnector,
39-
logger,
40-
validator,
41-
repoBranchesRepo
42-
);
43-
} else if (job.payload.jobType === 'githubPush') {
44-
return new StagingJobHandler(
45-
job,
46-
config,
47-
jobRepository,
48-
fileSystemServices,
49-
commandExecutor,
50-
cdnConnector,
51-
repoConnector,
52-
logger,
53-
validator,
54-
repoBranchesRepo
55-
);
56-
} else if (job.payload.jobType === 'productionDeploy') {
57-
return new ProductionJobHandler(
38+
const jt = job.payload?.jobType;
39+
if (jt in jobHandlerMap) {
40+
return new jobHandlerMap[jt](
5841
job,
5942
config,
6043
jobRepository,
@@ -129,7 +112,7 @@ export class JobManager {
129112
return this._shouldStop;
130113
}
131114

132-
async workEx(job: IJob): Promise<void> {
115+
async workEx(job: Job): Promise<void> {
133116
try {
134117
this._jobHandler = null;
135118
if (job?.payload) {
@@ -145,21 +128,21 @@ export class JobManager {
145128
}
146129
}
147130

148-
async getQueuedJob(): Promise<IJob | null> {
131+
async getQueuedJob(): Promise<Job | null> {
149132
return await this._jobRepository.getOneQueuedJobAndUpdate().catch((error) => {
150133
this._logger.error('JobManager', `Error: ${error}`);
151134
return null;
152135
});
153136
}
154137

155-
async getJob(jobId: string): Promise<IJob | null> {
138+
async getJob(jobId: string): Promise<Job | null> {
156139
return await this._jobRepository.getJobByIdAndUpdate(jobId).catch((error) => {
157140
this._logger.error('JobManager', `Error: ${error}`);
158141
return null;
159142
});
160143
}
161144

162-
async createHandlerAndExecute(job: IJob): Promise<void> {
145+
async createHandlerAndExecute(job: Job): Promise<void> {
163146
this._jobHandler = this._jobHandlerFactory.createJobHandler(
164147
job,
165148
this._config,

src/job/jobValidator.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
import { AuthorizationError, InvalidJobError } from '../errors/errors';
22
import validator from 'validator';
3-
import { IJob } from '../entities/job';
3+
import type { Job } from '../entities/job';
44
import { IFileSystemServices } from '../services/fileServices';
55
import { RepoEntitlementsRepository } from '../repositories/repoEntitlementsRepository';
66
import { RepoBranchesRepository } from '../repositories/repoBranchesRepository';
77

88
export interface IJobValidator {
9-
throwIfJobInvalid(job: IJob): Promise<void>;
10-
throwIfBranchNotConfigured(job: IJob): Promise<void>;
11-
throwIfUserNotEntitled(job: IJob): Promise<void>;
12-
throwIfNotPublishable(job: IJob): void;
9+
throwIfJobInvalid(job: Job): Promise<void>;
10+
throwIfBranchNotConfigured(job: Job): Promise<void>;
11+
throwIfUserNotEntitled(job: Job): Promise<void>;
12+
throwIfNotPublishable(job: Job): void;
1313
}
1414

1515
export class JobValidator implements IJobValidator {
@@ -26,21 +26,21 @@ export class JobValidator implements IJobValidator {
2626
this._repoBranchesRepository = repoBranchesRepository;
2727
}
2828

29-
async throwIfUserNotEntitled(job: IJob): Promise<void> {
29+
async throwIfUserNotEntitled(job: Job): Promise<void> {
3030
const entitlementsObject = await this._repoEntitlementRepository.getRepoEntitlementsByGithubUsername(job.user);
3131
if (!entitlementsObject?.repos?.includes(`${job.payload.repoOwner}/${job.payload.repoName}`)) {
3232
throw new AuthorizationError(`${job.user} is not entitled for repo ${job.payload.repoName}`);
3333
}
3434
}
3535

36-
async throwIfBranchNotConfigured(job: IJob): Promise<void> {
36+
async throwIfBranchNotConfigured(job: Job): Promise<void> {
3737
job.payload.repoBranches = await this._repoBranchesRepository.getRepoBranchesByRepoName(job.payload.repoName);
3838
if (!job.payload?.repoBranches) {
3939
throw new AuthorizationError(`repoBranches not found for ${job.payload.repoName}`);
4040
}
4141
}
4242

43-
throwIfNotPublishable(job: IJob): void {
43+
throwIfNotPublishable(job: Job): void {
4444
let found = false;
4545
if (job?.payload?.repoBranches) {
4646
job.payload.repoBranches['branches'].forEach((branch) => {
@@ -55,7 +55,7 @@ export class JobValidator implements IJobValidator {
5555
}
5656
}
5757

58-
public async throwIfJobInvalid(job: IJob): Promise<void> {
58+
public async throwIfJobInvalid(job: Job): Promise<void> {
5959
this._validateInput(job);
6060
if (this.isProd(job.payload.jobType)) {
6161
await this.throwIfUserNotEntitled(job);
@@ -66,7 +66,7 @@ export class JobValidator implements IJobValidator {
6666
return jobType === 'productionDeploy';
6767
}
6868

69-
private _validateInput(job: IJob): void {
69+
private _validateInput(job: Job): void {
7070
if (!job.payload.project) {
7171
throw new InvalidJobError('Invalid project');
7272
}

src/job/manifestJobHandler.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { JobHandler } from './jobHandler';
2+
import { IConfig } from 'config';
3+
import type { Job } from '../entities/job';
4+
import { JobRepository } from '../repositories/jobRepository';
5+
import { ICDNConnector } from '../services/cdn';
6+
import { CommandExecutorResponse, IJobCommandExecutor } from '../services/commandExecutor';
7+
import { IFileSystemServices } from '../services/fileServices';
8+
import { IJobRepoLogger } from '../services/logger';
9+
import { IRepoConnector } from '../services/repo';
10+
import { IJobValidator } from './jobValidator';
11+
import { RepoBranchesRepository } from '../repositories/repoBranchesRepository';
12+
13+
export class ManifestJobHandler extends JobHandler {
14+
constructor(
15+
job: Job,
16+
config: IConfig,
17+
jobRepository: JobRepository,
18+
fileSystemServices: IFileSystemServices,
19+
commandExecutor: IJobCommandExecutor,
20+
cdnConnector: ICDNConnector,
21+
repoConnector: IRepoConnector,
22+
logger: IJobRepoLogger,
23+
validator: IJobValidator,
24+
repoBranchesRepo: RepoBranchesRepository
25+
) {
26+
super(
27+
job,
28+
config,
29+
jobRepository,
30+
fileSystemServices,
31+
commandExecutor,
32+
cdnConnector,
33+
repoConnector,
34+
logger,
35+
validator,
36+
repoBranchesRepo
37+
);
38+
this.name = 'Manifest';
39+
}
40+
41+
// TODO: Make this a non-state-mutating function, e.g. return the deployCommands?
42+
prepDeployCommands(): void {
43+
this.currJob.deployCommands = [
44+
'. /venv/bin/activate',
45+
`cd repos/${this.currJob.payload.repoName}`,
46+
'echo IGNORE: testing manifest generation deploy commands',
47+
'python3 test-mut-script.py',
48+
];
49+
}
50+
51+
prepStageSpecificNextGenCommands(): void {
52+
return;
53+
}
54+
55+
async deploy(): Promise<CommandExecutorResponse> {
56+
try {
57+
const resp = await this.deployGeneric(); // runs prepDeployCommands
58+
await this.logger.save(this.currJob._id, `(generate manifest) Manifest generation details:\n\n${resp?.output}`);
59+
return resp;
60+
} catch (errResult) {
61+
await this.logger.save(this.currJob._id, `(generate manifest) stdErr: ${errResult.stderr}`);
62+
throw errResult;
63+
}
64+
}
65+
}

0 commit comments

Comments
 (0)