Skip to content

Commit

Permalink
Closes #2: Adding support for all events alongside specific events tr…
Browse files Browse the repository at this point in the history
…igger

Signed-off-by: Peter Zhu <[email protected]>
  • Loading branch information
peterzhuamazon committed Sep 25, 2024
1 parent c34612b commit f85774e
Show file tree
Hide file tree
Showing 6 changed files with 60 additions and 50 deletions.
2 changes: 2 additions & 0 deletions configs/operations/hello-world.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
---
name: Hello World Operation

# You can listen to either `all` events or specific events
events:
# - all
- issues.labeled
- issues.assigned

Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
"automation-app"
],
"scripts": {
"build": "npm run clean && npm run format && npm run lint && tsc",
"watch": "tsc -w",
"build": "npm run clean && npm run format && npm run lint && npm run compile",
"compile": "tsc",
"start": "npm run build && probot run ./bin/app.js",
"clean": "rm -rf ./bin/*",
"format": "prettier --write \"src/**/*.{js,ts}\" \"test/**/*.ts\" \"configs/**/*.{yaml,yml}\"",
Expand Down
3 changes: 1 addition & 2 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ export default async (app: Probot) => {
app.log.info('OpenSearch Automation App is starting now......');

const srvObj = new Service('Hello World Service');
await srvObj.initService('configs/resources/peterzhu-organization.yml', 'configs/operations/hello-world.yml', app);
await srvObj.registerEvents();
await srvObj.initService(app, 'configs/resources/peterzhu-organization.yml', 'configs/operations/hello-world.yml');

app.log.info('All objects initialized, start listening events......');
};
10 changes: 1 addition & 9 deletions src/config/operation-config.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
import { Probot } from 'probot';
import { Operation } from '../service/operation/operation';
import { Task } from '../service/operation/task';
import { OperationData, TaskData } from './types';
import { Config } from './config';

export class OperationConfig extends Config {
private app: Probot;

private static configSchema = {
type: 'object',
properties: {
Expand Down Expand Up @@ -44,16 +41,11 @@ export class OperationConfig extends Config {
required: ['name', 'events', 'tasks'],
};

constructor(configPath: string, app: Probot) {
constructor(configPath: string) {
super('OperationConfig');
this.configData = OperationConfig.readConfig(configPath);
this.configSchema = OperationConfig.configSchema;
OperationConfig.validateConfig(this.configData, this.configSchema);
this.app = app;
}

public getApp(): Probot {
return this.app;
}

private static async _initTasks(taskDataArray: TaskData[]): Promise<Task[]> {
Expand Down
2 changes: 1 addition & 1 deletion src/config/resource-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export class ResourceConfig extends Config {
required: ['organizations'],
};

constructor(configPath: string, octokit: ProbotOctokit) {
constructor(octokit: ProbotOctokit, configPath: string) {
super('ResourceConfig');
this.configData = ResourceConfig.readConfig(configPath);
this.configSchema = ResourceConfig.configSchema;
Expand Down
89 changes: 53 additions & 36 deletions src/service/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { WebhookEventMap } from '@octokit/webhooks-types';
import { access, realpath } from 'fs/promises';
import { Resource } from './resource/resource';
import { Operation } from './operation/operation';
import { Task } from './operation/task';
import { ResourceConfig } from '../config/resource-config';
import { OperationConfig } from '../config/operation-config';
import { octokitAuth } from '../utility/octokit';
Expand Down Expand Up @@ -32,56 +33,72 @@ export class Service {
return this.operation;
}

public async initService(resourceConfigPath: string, operationConfigPath: string, app: Probot): Promise<void> {
public async initService(app: Probot, resourceConfigPath: string, operationConfigPath: string): Promise<void> {
app.log.info(`Initializing Service: ${this.name} `);
this.app = app;
// Get octokit client so Resource object will get context
const octokit = await octokitAuth(app, Number(process.env.INSTALLATION_ID));
const resConfigObj = new ResourceConfig(resourceConfigPath, octokit);
const octokit = await octokitAuth(this.app, Number(process.env.INSTALLATION_ID));
const resConfigObj = new ResourceConfig(octokit, resourceConfigPath);
this.resource = await resConfigObj.initResource();
const opConfigObj = new OperationConfig(operationConfigPath, app);
const opConfigObj = new OperationConfig(operationConfigPath);
this.operation = await opConfigObj.initOperation();
this.app = app;
this._registerEvents();
}

private async _registerTasks(context: any, event: string, tasks: Task[]): Promise<void> {
await tasks.reduce(async (promise, task) => {
await promise; // Make sure tasks are completed in sequential orders

const callPath = await realpath(`./bin/call/${task.getCallName()}.js`);
const callFunc = task.getCallFunc();
const callArgs = task.getCallArgs();

console.log(`[${event}]: Verify call lib: ${callPath}`);
try {
await access(callPath);
} catch (e) {
console.error(`ERROR: ${e}`);
}

const callStack = await import(callPath);
if (callFunc === 'default') {
console.log(`[${event}]: Call default function: [${callStack.default.name}]`);
await callStack.default(this.app, context, { ...callArgs });
} else {
console.log(callStack);
const callFuncCustom = callStack[callFunc];
console.log(`[${event}]: Call custom function: [${callFuncCustom.name}]`);
if (!(typeof callFuncCustom === 'function')) {
throw new Error(`[${event}]: ${callFuncCustom} is not a function, please verify in ${callPath}`);
}
await callFuncCustom(this.app, context, { ...callArgs });
}
}, Promise.resolve());
}

public async registerEvents(): Promise<void> {
private async _registerEvents(): Promise<void> {
const events = this.operation.getEvents();
const tasks = this.operation.getTasks();
console.log(`Evaluate events: [${events}]`);
if (!events) {
throw new Error('No events defined in the operation!');
}
if (events.includes('all') && events.length > 1) {
throw new Error("Either listen to 'all' events or specific events! Not both!");
}

events.forEach((event) => {
console.log(`Register event: "${event}"`);
this.app.on(event as keyof WebhookEventMap, async (context) => {
await tasks.reduce(async (promise, task) => {
await promise; // Make sure tasks are completed in sequential orders

const callPath = await realpath(`./bin/call/${task.getCallName()}.js`);
const callFunc = task.getCallFunc();
const callArgs = task.getCallArgs();

console.log(`[${event}]: Verify call lib: ${callPath}`);
try {
await access(callPath);
} catch (e) {
console.error(`ERROR: ${e}`);
}

const callStack = await import(callPath);
if (callFunc === 'default') {
console.log(`[${event}]: Call default function: [${callStack.default.name}]`);
await callStack.default(this.app, context, { ...callArgs });
} else {
console.log(callStack);
const callFuncCustom = callStack[callFunc];
console.log(`[${event}]: Call custom function: [${callFuncCustom.name}]`);
if (!(typeof callFuncCustom === 'function')) {
throw new Error(`[${event}]: ${callFuncCustom} is not a function, please verify in ${callPath}`);
}
await callFuncCustom(this.app, context, { ...callArgs });
}
}, Promise.resolve());
});
if (event === 'all') {
console.warn('WARNING! All events will be listened based on the config!');
this.app.onAny(async (context) => {
this._registerTasks(context, event, tasks);
});
} else {
this.app.on(event as keyof WebhookEventMap, async (context) => {
this._registerTasks(context, event, tasks);
});
}
});
}
}

0 comments on commit f85774e

Please sign in to comment.