Skip to content

Commit

Permalink
updating tasks and tech-insights to work together
Browse files Browse the repository at this point in the history
Signed-off-by: Phil Gore <pgore@ea.com>
  • Loading branch information
Erog38 authored and freben committed Mar 22, 2022
1 parent 6bc4253 commit 231fee7
Show file tree
Hide file tree
Showing 18 changed files with 355 additions and 131 deletions.
5 changes: 5 additions & 0 deletions .changeset/fresh-dodos-rush.md
@@ -0,0 +1,5 @@
---
'@backstage/plugin-tech-insights-node': patch
---

Adds an optional timeout to fact retriever registrations to stop a task if it runs too long.
9 changes: 9 additions & 0 deletions .changeset/shiny-seas-yell.md
@@ -0,0 +1,9 @@
---
'@backstage/plugin-tech-insights-backend': minor
---

Updates tech-insights to use backend-tasks as the Fact Retriever scheduler.

```
```
1 change: 1 addition & 0 deletions packages/backend-tasks/api-report.md
Expand Up @@ -15,6 +15,7 @@ export interface PluginTaskScheduler {
scheduleTask(
task: TaskScheduleDefinition & TaskInvocationDefinition,
): Promise<void>;
triggerTask(id: string): Promise<void>;
}

// @public
Expand Down
69 changes: 69 additions & 0 deletions packages/backend-tasks/src/tasks/PluginTaskSchedulerImpl.test.ts
Expand Up @@ -20,6 +20,9 @@ import { Duration } from 'luxon';
import waitForExpect from 'wait-for-expect';
import { migrateBackendTasks } from '../database/migrateBackendTasks';
import { PluginTaskSchedulerImpl } from './PluginTaskSchedulerImpl';
import { ConflictError, NotFoundError } from '@backstage/errors';

jest.useFakeTimers();

describe('PluginTaskManagerImpl', () => {
const databases = TestDatabases.create({
Expand Down Expand Up @@ -80,6 +83,72 @@ describe('PluginTaskManagerImpl', () => {
);
});

describe('triggerTask', () => {
it.each(databases.eachSupportedId())(
'can manually trigger a task, %p',
async databaseId => {
const { manager } = await init(databaseId);

const fn = jest.fn();
await manager.scheduleTask({
id: 'task1',
timeout: Duration.fromMillis(5000),
frequency: Duration.fromObject({ years: 1 }),
initialDelay: Duration.fromObject({ years: 1 }),
fn,
});

await manager.triggerTask('task1');
jest.advanceTimersByTime(5000);

await waitForExpect(() => {
expect(fn).toBeCalled();
});
},
60_000,
);

it.each(databases.eachSupportedId())(
'cant trigger a non-existent task, %p',
async databaseId => {
const { manager } = await init(databaseId);

const fn = jest.fn();
await manager.scheduleTask({
id: 'task1',
timeout: Duration.fromMillis(5000),
frequency: Duration.fromObject({ years: 1 }),
fn,
});

await expect(() => manager.triggerTask('task2')).rejects.toThrow(
NotFoundError,
);
},
60_000,
);

it.each(databases.eachSupportedId())(
'cant trigger a running task, %p',
async databaseId => {
const { manager } = await init(databaseId);

const fn = jest.fn();
await manager.scheduleTask({
id: 'task1',
timeout: Duration.fromMillis(5000),
frequency: Duration.fromObject({ years: 1 }),
fn,
});

await expect(() => manager.triggerTask('task1')).rejects.toThrow(
ConflictError,
);
},
60_000,
);
});

// This is just to test the wrapper code; most of the actual tests are in
// TaskWorker.test.ts
describe('createScheduledTaskRunner', () => {
Expand Down
33 changes: 33 additions & 0 deletions packages/backend-tasks/src/tasks/PluginTaskSchedulerImpl.ts
Expand Up @@ -24,6 +24,8 @@ import {
TaskScheduleDefinition,
} from './types';
import { validateId } from './util';
import { DB_TASKS_TABLE, DbTasksRow } from '../database/tables';
import { ConflictError, NotFoundError } from '@backstage/errors';

/**
* Implements the actual task management.
Expand All @@ -34,6 +36,37 @@ export class PluginTaskSchedulerImpl implements PluginTaskScheduler {
private readonly logger: Logger,
) {}

async triggerTask(id: string): Promise<void> {
const knex = await this.databaseFactory();

// get the task definition
const rows = await knex<DbTasksRow>(DB_TASKS_TABLE)
.select({
currentRun: 'current_run_ticket',
id: 'id',
})
.where('id', '=', id);

// validate the task exists
if (rows.length <= 0 || rows[0].id !== id) {
throw new NotFoundError(`Task ${id} does not exist`);
}

if (rows[0].currentRun) {
throw new ConflictError(`task ${id} is currently running`);
}

const updatedRows = await knex<DbTasksRow>(DB_TASKS_TABLE)
.where('id', '=', id)
.whereNull('current_run_ticket')
.update({
next_run_start_at: knex.fn.now(),
});
if (updatedRows < 1) {
throw new ConflictError(`task ${id} is currently running`);
}
}

async scheduleTask(
task: TaskScheduleDefinition & TaskInvocationDefinition,
): Promise<void> {
Expand Down
11 changes: 11 additions & 0 deletions packages/backend-tasks/src/tasks/types.ts
Expand Up @@ -134,6 +134,17 @@ export interface TaskRunner {
* @public
*/
export interface PluginTaskScheduler {
/**
* Manually triggers a task by ID.
*
* If the task doesn't exist, a NotFoundError is thrown.
* If the task is currently running, a ConflictError is thrown.
*
* @param id - The task ID
*
*/
triggerTask(id: string): Promise<void>;

/**
* Schedules a task function for coordinated exclusive invocation across
* workers. This convenience method performs both the scheduling and
Expand Down
1 change: 1 addition & 0 deletions packages/backend/src/plugins/techInsights.ts
Expand Up @@ -36,6 +36,7 @@ export default async function createPlugin(
logger: env.logger,
config: env.config,
database: env.database,
scheduler: env.scheduler,
discovery: env.discovery,
factRetrievers: [
createFactRetrieverRegistration({
Expand Down
3 changes: 1 addition & 2 deletions plugins/tech-insights-backend-module-jsonfc/package.json
Expand Up @@ -46,8 +46,7 @@
"winston": "^3.2.1"
},
"devDependencies": {
"@backstage/cli": "^0.16.1-next.0",
"@types/node-cron": "^3.0.1"
"@backstage/cli": "^0.16.1-next.0"
},
"files": [
"dist"
Expand Down
3 changes: 3 additions & 0 deletions plugins/tech-insights-backend/api-report.md
Expand Up @@ -14,6 +14,7 @@ import { FactRetrieverRegistration } from '@backstage/plugin-tech-insights-node'
import { Logger } from 'winston';
import { PluginDatabaseManager } from '@backstage/backend-common';
import { PluginEndpointDiscovery } from '@backstage/backend-common';
import { PluginTaskScheduler } from '@backstage/backend-tasks';
import { TechInsightCheck } from '@backstage/plugin-tech-insights-node';
import { TechInsightsStore } from '@backstage/plugin-tech-insights-node';

Expand Down Expand Up @@ -94,6 +95,8 @@ export interface TechInsightsOptions<
factRetrievers: FactRetrieverRegistration[];
// (undocumented)
logger: Logger;
// (undocumented)
scheduler: PluginTaskScheduler;
}

// (No @packageDocumentation comment for this package)
Expand Down
6 changes: 3 additions & 3 deletions plugins/tech-insights-backend/package.json
Expand Up @@ -35,6 +35,7 @@
},
"dependencies": {
"@backstage/backend-common": "^0.13.2-next.0",
"@backstage/backend-tasks": "^0.2.2-next.0",
"@backstage/catalog-client": "^1.0.1-next.0",
"@backstage/catalog-model": "^1.0.1-next.0",
"@backstage/config": "^1.0.0",
Expand All @@ -47,7 +48,6 @@
"knex": "^1.0.2",
"lodash": "^4.17.21",
"luxon": "^2.0.2",
"node-cron": "^3.0.0",
"semver": "^7.3.5",
"uuid": "^8.3.2",
"winston": "^3.2.1",
Expand All @@ -57,9 +57,9 @@
"@backstage/backend-test-utils": "^0.1.23-next.0",
"@backstage/cli": "^0.16.1-next.0",
"@types/supertest": "^2.0.8",
"@types/node-cron": "^3.0.0",
"@types/semver": "^7.3.8",
"supertest": "^6.1.3"
"supertest": "^6.1.3",
"wait-for-expect": "^3.0.2"
},
"files": [
"dist",
Expand Down

0 comments on commit 231fee7

Please sign in to comment.