Modifications after PR review

* Change entity filter to be an actual entity filter instead of a list of kinds or types
* Modify/simplify types a little bit
* Split common type libs to one for node and one isomorphic
* Remove unnecessary items from FactChecker interface to simplify execution loop.

Needs still matching README.md changes.

Signed-off-by: Jussi Hallila <jussi@hallila.com>
This commit is contained in:
Jussi Hallila
2021-10-28 16:20:44 +02:00
parent df000b9596
commit 2b3e959ef4
37 changed files with 393 additions and 447 deletions
+19 -20
View File
@@ -77,16 +77,18 @@ you will not have any fact retrievers present in your application. To have the i
To create factRetrieverRegistration you need to implement `FactRetriever` interface defined in `@backstage/plugin-tech-insights-common` package. After you have implemented this interface you can wrap that into a registration object like follows:
```ts
const myFactRetriever: FactRetriever = {
import { createFactRetrieverRegistration } from './createFactRetriever';
const myFactRetriever = {
/**
* snip
*/
};
const myFactRetrieverRegistration = {
cadence: '1 * 3 * * ', // On the first minute of the third day of the month
factRetriever: myFactRetriever,
};
const myFactRetrieverRegistration = createFactRetrieverRegistration(
'1 * 3 * * ', // On the first minute of the third day of the month
myFactRetriever,
);
```
Then you can modify the example `techInsights.ts` file shown above like this:
@@ -104,28 +106,25 @@ discovery,
### Creating Fact Retrievers
A Fact Retriever consist of three parts:
A Fact Retriever consist of four parts:
1. `ref` - unique identifier of a fact retriever
2. `schema` - A versioned schema defining the shape of data a fact retriever returns
3. `handler` - An asynchronous function handling the logic of retrieving and returning facts for an entity
1. `id` - unique identifier of a fact retriever
2. `version`: A semver string indicating the current version of the schema and the handler
3. `schema` - A versioned schema defining the shape of data a fact retriever returns
4. `handler` - An asynchronous function handling the logic of retrieving and returning facts for an entity
An example implementation of a FactRetriever could for example be as follows:
```ts
const myFactRetriever: FactRetriever = {
ref: 'documentation-number-factretriever', // unique ref, identifier of the fact retriever
id: 'documentation-number-factretriever', // unique identifier of the fact retriever
version: '0.1.1', // SemVer version number of this fact retriever schema. This should be incremented if the implementation changes
schema: {
version: '0.1.1', // SemVer version number of this fact retriever schema. This should be incremented if the implementation changes
// the actual schema
schema: {
// Name/identifier of an individual fact that this retriever returns
examplenumberfact: {
type: 'integer', // Type of the fact
description: 'A fact of a number', // Description of the fact
entityTypes: ['component'], // An array of entity kinds that this fact is applicable to
},
// Name/identifier of an individual fact that this retriever returns
examplenumberfact: {
type: 'integer', // Type of the fact
description: 'A fact of a number', // Description of the fact
entityTypes: ['component'], // An array of entity kinds that this fact is applicable to
},
},
handler: async ctx => {
+12 -7
View File
@@ -6,17 +6,16 @@
import { CheckResult } from '@backstage/plugin-tech-insights-common';
import { Config } from '@backstage/config';
import express from 'express';
import { FactChecker } from '@backstage/plugin-tech-insights-common';
import { FactCheckerFactory } from '@backstage/plugin-tech-insights-common';
import { FactRetrieverRegistration } from '@backstage/plugin-tech-insights-common';
import { FactChecker } from '@backstage/plugin-tech-insights-node';
import { FactCheckerFactory } from '@backstage/plugin-tech-insights-node';
import { FactRetriever } from '@backstage/plugin-tech-insights-node';
import { FactRetrieverRegistration } from '@backstage/plugin-tech-insights-node';
import { Logger as Logger_2 } from 'winston';
import { PluginDatabaseManager } from '@backstage/backend-common';
import { PluginEndpointDiscovery } from '@backstage/backend-common';
import { TechInsightCheck } from '@backstage/plugin-tech-insights-common';
import { TechInsightsStore } from '@backstage/plugin-tech-insights-common';
import { TechInsightCheck } from '@backstage/plugin-tech-insights-node';
import { TechInsightsStore } from '@backstage/plugin-tech-insights-node';
// Warning: (ae-missing-release-tag) "buildTechInsightsContext" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public
export const buildTechInsightsContext: <
CheckType extends TechInsightCheck,
@@ -25,6 +24,12 @@ export const buildTechInsightsContext: <
options: TechInsightsOptions<CheckType, CheckResultType>,
) => Promise<TechInsightsContext<CheckType, CheckResultType>>;
// @public
export function createFactRetrieverRegistration(
cadence: string,
factRetriever: FactRetriever,
): FactRetrieverRegistration;
// @public
export function createRouter<
CheckType extends TechInsightCheck,
@@ -33,10 +33,10 @@ exports.up = async function up(knex) {
.notNullable()
.comment('SemVer string defining the version of schema.');
table
.string('entityTypes')
.string('entityFilter')
.nullable()
.comment(
'A comma separated collection of entity kinds the fact retriever providing this schema affects. Defaults to null, which means all entity kinds.',
'A serialized entity filter object used to determine which entities this schema is applicable to.',
);
table
.text('schema')
@@ -37,6 +37,7 @@
"@backstage/config": "^0.1.8",
"@backstage/errors": "^0.1.1",
"@backstage/plugin-tech-insights-common": "^0.1.0",
"@backstage/plugin-tech-insights-node": "^0.1.0",
"@types/express": "^4.17.6",
"cross-fetch": "^3.0.6",
"express": "^4.17.1",
@@ -24,3 +24,4 @@ export type {
} from './service/techInsightsContextBuilder';
export type { PersistenceContext } from './service/persistence/DatabaseManager';
export { createFactRetrieverRegistration } from './service/fact/createFactRetriever';
@@ -19,7 +19,7 @@ import {
FactSchemaDefinition,
TechInsightFact,
TechInsightsStore,
} from '@backstage/plugin-tech-insights-common';
} from '@backstage/plugin-tech-insights-node';
import { FactRetrieverRegistry } from './FactRetrieverRegistry';
import { FactRetrieverEngine } from './FactRetrieverEngine';
import { getVoidLogger } from '@backstage/backend-common';
@@ -37,7 +37,7 @@ jest.mock('node-cron', () => {
const testFactRetriever: FactRetriever = {
id: 'test-factretriever',
version: '0.0.1',
entityTypes: ['component'],
entityFilter: [{ kind: 'component' }],
schema: {
testnumberfact: {
type: 'integer',
@@ -101,10 +101,10 @@ describe('FactRetrieverEngine', () => {
};
it('Should update fact retriever schemas on initialization', async () => {
factSchemaAssertionCallback = ({ id, schema, version, entityTypes }) => {
factSchemaAssertionCallback = ({ id, schema, version, entityFilter }) => {
expect(id).toEqual('test-factretriever');
expect(version).toEqual('0.0.1');
expect(entityTypes).toEqual(['component']);
expect(entityFilter).toEqual([{ kind: 'component' }]);
expect(schema).toEqual({
testnumberfact: {
type: 'integer',
@@ -17,7 +17,7 @@ import {
FactRetriever,
FactRetrieverContext,
TechInsightsStore,
} from '@backstage/plugin-tech-insights-common';
} from '@backstage/plugin-tech-insights-node';
import { FactRetrieverRegistry } from './FactRetrieverRegistry';
import { schedule, validate, ScheduledTask } from 'node-cron';
import { Logger } from 'winston';
@@ -18,7 +18,7 @@ import {
FactRetriever,
FactRetrieverRegistration,
FactSchema,
} from '@backstage/plugin-tech-insights-common';
} from '@backstage/plugin-tech-insights-node';
import { ConflictError, NotFoundError } from '@backstage/errors';
export class FactRetrieverRegistry {
@@ -0,0 +1,49 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
FactRetriever,
FactRetrieverRegistration,
} from '@backstage/plugin-tech-insights-node';
/**
* @public
*
* A helper function to construct fact retriever registrations.
*
* Cron expressions help:
* ┌────────────── second (optional)
# │ ┌──────────── minute
# │ │ ┌────────── hour
# │ │ │ ┌──────── day of month
# │ │ │ │ ┌────── month
# │ │ │ │ │ ┌──── day of week
# │ │ │ │ │ │
# │ │ │ │ │ │
# * * * * * *
*
*
* @param cadence - cron expression to indicate when the fact retriever should be triggered
* @param factRetriever - Implementation of fact retriever consisting of at least id, version, schema and handler
*/
export function createFactRetrieverRegistration(
cadence: string,
factRetriever: FactRetriever,
): FactRetrieverRegistration {
return {
cadence,
factRetriever,
};
}
@@ -18,7 +18,7 @@ import knexFactory, { Knex } from 'knex';
import { Logger } from 'winston';
import { v4 as uuidv4 } from 'uuid';
import { TechInsightsDatabase } from './TechInsightsDatabase';
import { TechInsightsStore } from '@backstage/plugin-tech-insights-common';
import { TechInsightsStore } from '@backstage/plugin-tech-insights-node';
const migrationsDir = resolvePackagePath(
'@backstage/plugin-tech-insights-backend',
@@ -15,14 +15,14 @@
*/
import { DatabaseManager } from './DatabaseManager';
import { DateTime, Duration } from 'luxon';
import { TechInsightsStore } from '@backstage/plugin-tech-insights-common';
import { TechInsightsStore } from '@backstage/plugin-tech-insights-node';
import { Knex } from 'knex';
const factSchemas = [
{
id: 'test-fact',
version: '0.0.1-test',
entityTypes: ['component'],
entityFilter: JSON.stringify([{ kind: 'component' }]),
schema: JSON.stringify({
testNumberFact: {
type: 'integer',
@@ -35,7 +35,7 @@ const additionalFactSchemas = [
{
id: 'test-fact',
version: '1.2.1-test',
entityTypes: ['component'],
entityFilter: JSON.stringify([{ kind: 'component' }]),
schema: JSON.stringify({
testNumberFact: {
type: 'integer',
@@ -50,7 +50,7 @@ const additionalFactSchemas = [
{
id: 'test-fact',
version: '1.1.1-test',
entityTypes: ['component'],
entityFilter: JSON.stringify([{ kind: 'component' }]),
schema: JSON.stringify({
testStringFact: {
type: 'string',
@@ -63,7 +63,7 @@ const additionalFactSchemas = [
const secondSchema = {
id: 'second-test-fact',
version: '0.0.1-test',
entityTypes: ['service'],
entityFilter: JSON.stringify([{ kind: 'service' }]),
schema: JSON.stringify({
testStringFact: {
type: 'string',
@@ -137,7 +137,7 @@ describe('Tech Insights database', () => {
expect(schemas[0]).toMatchObject({
id: 'test-fact',
version: '0.0.1-test',
entityTypes: ['component'],
entityFilter: [{ kind: 'component' }],
testNumberFact: {
type: 'integer',
description: 'Test fact with a number type',
@@ -152,7 +152,7 @@ describe('Tech Insights database', () => {
expect(schemas[0]).toMatchObject({
id: 'test-fact',
version: '1.2.1-test',
entityTypes: ['component'],
entityFilter: [{ kind: 'component' }],
testNumberFact: {
type: 'integer',
description: 'Test fact with a number type',
@@ -177,7 +177,7 @@ describe('Tech Insights database', () => {
expect(schemas[0]).toMatchObject({
id: 'test-fact',
version: '1.2.1-test',
entityTypes: ['component'],
entityFilter: [{ kind: 'component' }],
testNumberFact: {
type: 'integer',
description: 'Test fact with a number type',
@@ -190,7 +190,7 @@ describe('Tech Insights database', () => {
expect(schemas[1]).toMatchObject({
id: 'second',
version: '0.0.1-test',
entityTypes: ['service'],
entityFilter: [{ kind: 'service' }],
testStringFact: {
type: 'string',
description: 'Test fact with a string type',
@@ -20,7 +20,7 @@ import {
FlatTechInsightFact,
TechInsightsStore,
FactSchemaDefinition,
} from '@backstage/plugin-tech-insights-common';
} from '@backstage/plugin-tech-insights-node';
import { rsort } from 'semver';
import { groupBy, omit } from 'lodash';
import { DateTime } from 'luxon';
@@ -39,7 +39,7 @@ type RawDbFactSchemaRow = {
id: string;
version: string;
schema: string;
entityTypes?: string;
entityFilter?: string;
};
export class TechInsightsDatabase implements TechInsightsStore {
@@ -63,12 +63,12 @@ export class TechInsightsDatabase implements TechInsightsStore {
.map((it: RawDbFactSchemaRow) => ({
...omit(it, 'schema'),
...JSON.parse(it.schema),
entityTypes: it.entityTypes ? it.entityTypes.split(',') : [],
entityFilter: it.entityFilter ? JSON.parse(it.entityFilter) : null,
}));
}
async insertFactSchema(schemaDefinition: FactSchemaDefinition) {
const { id, version, schema, entityTypes } = schemaDefinition;
const { id, version, schema, entityFilter } = schemaDefinition;
const existingSchemas = await this.db<RawDbFactSchemaRow>('fact_schemas')
.where({ id })
.and.where({ version })
@@ -78,7 +78,7 @@ export class TechInsightsDatabase implements TechInsightsStore {
await this.db<RawDbFactSchemaRow>('fact_schemas').insert({
id,
version,
entityTypes: entityTypes && entityTypes.join(','),
entityFilter: entityFilter ? JSON.stringify(entityFilter) : undefined,
schema: JSON.stringify(schema),
});
}
@@ -20,7 +20,7 @@ import { ConfigReader } from '@backstage/config';
import request from 'supertest';
import express from 'express';
import { PersistenceContext } from './persistence/DatabaseManager';
import { TechInsightsStore } from '@backstage/plugin-tech-insights-common';
import { TechInsightsStore } from '@backstage/plugin-tech-insights-node';
import { DateTime } from 'luxon';
import { Knex } from 'knex';
@@ -20,8 +20,9 @@ import { Config } from '@backstage/config';
import {
FactChecker,
TechInsightCheck,
CheckResult,
} from '@backstage/plugin-tech-insights-common';
} from '@backstage/plugin-tech-insights-node';
import { CheckResult } from '@backstage/plugin-tech-insights-common';
import { Logger } from 'winston';
import { DateTime } from 'luxon';
import { PersistenceContext } from './persistence/DatabaseManager';
@@ -23,16 +23,16 @@ import {
PluginEndpointDiscovery,
} from '@backstage/backend-common';
import {
CheckResult,
FactChecker,
FactCheckerFactory,
FactRetrieverRegistration,
TechInsightCheck,
} from '@backstage/plugin-tech-insights-common';
} from '@backstage/plugin-tech-insights-node';
import {
DatabaseManager,
PersistenceContext,
} from './persistence/DatabaseManager';
import { CheckResult } from '@backstage/plugin-tech-insights-common';
/**
* @public
@@ -80,6 +80,8 @@ export type TechInsightsContext<
};
/**
* @public
*
* Constructs needed persistence context, fact retriever engine
* and optionally fact checker implementations to be used in the tech insights module.
*