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
@@ -5,12 +5,12 @@
```ts
import { BooleanCheckResult } from '@backstage/plugin-tech-insights-common';
import { CheckResponse } from '@backstage/plugin-tech-insights-common';
import { CheckValidationResponse } from '@backstage/plugin-tech-insights-common';
import { FactChecker } from '@backstage/plugin-tech-insights-common';
import { CheckValidationResponse } from '@backstage/plugin-tech-insights-node';
import { FactChecker } from '@backstage/plugin-tech-insights-node';
import { Logger as Logger_2 } from 'winston';
import { TechInsightCheck } from '@backstage/plugin-tech-insights-common';
import { TechInsightCheckRegistry } from '@backstage/plugin-tech-insights-common';
import { TechInsightsStore } from '@backstage/plugin-tech-insights-common';
import { TechInsightCheck } from '@backstage/plugin-tech-insights-node';
import { TechInsightCheckRegistry } from '@backstage/plugin-tech-insights-node';
import { TechInsightsStore } from '@backstage/plugin-tech-insights-node';
import { TopLevelCondition } from 'json-rules-engine';
// @public (undocumented)
@@ -52,13 +52,11 @@ export class JsonRulesEngineFactChecker
checkRegistry,
}: JsonRulesEngineFactCheckerOptions);
// (undocumented)
addCheck(check: TechInsightJsonRuleCheck): Promise<TechInsightJsonRuleCheck>;
// (undocumented)
getChecks(): Promise<TechInsightJsonRuleCheck[]>;
// (undocumented)
runChecks(
entity: string,
checks: string[],
checks?: string[],
): Promise<JsonRuleBooleanCheckResult[]>;
// (undocumented)
validate(check: TechInsightJsonRuleCheck): Promise<CheckValidationResponse>;
@@ -79,7 +77,7 @@ export class JsonRulesEngineFactCheckerFactory {
export type JsonRulesEngineFactCheckerFactoryOptions = {
checks: TechInsightJsonRuleCheck[];
logger: Logger_2;
checkRegistry?: TechInsightCheckRegistry<any>;
checkRegistry?: TechInsightCheckRegistry<TechInsightJsonRuleCheck>;
};
// @public
@@ -1,8 +1,8 @@
{
"name": "@backstage/plugin-tech-insights-backend-module-jsonfc",
"version": "0.1.0",
"main": "dist/index.cjs.js",
"types": "dist/index.d.ts",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
"private": false,
"publishConfig": {
@@ -35,11 +35,11 @@
"@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",
"ajv": "^7.0.3",
"json-rules-engine": "^6.1.2",
"lodash": "^4.17.21",
"luxon": "^2.0.2",
"node-cron": "^3.0.0",
"winston": "^3.2.1"
},
"devDependencies": {
@@ -18,7 +18,7 @@ import { ConflictError, NotFoundError } from '@backstage/errors';
import {
TechInsightCheck,
TechInsightCheckRegistry,
} from '@backstage/plugin-tech-insights-common';
} from '@backstage/plugin-tech-insights-node';
export class DefaultCheckRegistry<CheckType extends TechInsightCheck>
implements TechInsightCheckRegistry<CheckType>
@@ -17,7 +17,7 @@
import {
TechInsightCheckRegistry,
TechInsightsStore,
} from '@backstage/plugin-tech-insights-common';
} from '@backstage/plugin-tech-insights-node';
import {
JSON_RULE_ENGINE_CHECK_TYPE,
JsonRulesEngineFactCheckerFactory,
@@ -17,13 +17,12 @@
import { JsonRuleBooleanCheckResult, TechInsightJsonRuleCheck } from '../types';
import {
FactChecker,
FactResponse,
TechInsightCheckRegistry,
FlatTechInsightFact,
TechInsightsStore,
CheckValidationResponse,
CheckValidationError,
} from '@backstage/plugin-tech-insights-common';
} from '@backstage/plugin-tech-insights-node';
import { FactResponse } from '@backstage/plugin-tech-insights-common';
import { Engine, EngineResult, TopLevelCondition } from 'json-rules-engine';
import { DefaultCheckRegistry } from './CheckRegistry';
import { Logger } from 'winston';
@@ -71,15 +70,19 @@ export class JsonRulesEngineFactChecker
this.repository = repository;
this.logger = logger;
checks.forEach(check => this.validate(check));
this.checkRegistry = checkRegistry ?? new DefaultCheckRegistry(checks);
this.checkRegistry =
checkRegistry ??
new DefaultCheckRegistry<TechInsightJsonRuleCheck>(checks);
}
async runChecks(
entity: string,
checks: string[],
checks?: string[],
): Promise<JsonRuleBooleanCheckResult[]> {
const engine = new Engine();
const techInsightChecks = await this.checkRegistry.getAll(checks);
const techInsightChecks = checks
? await this.checkRegistry.getAll(checks)
: await this.checkRegistry.list();
const factIds = techInsightChecks.flatMap(it => it.factIds);
const facts = await this.repository.getLatestFactsByIds(factIds, entity);
techInsightChecks.forEach(techInsightCheck => {
@@ -127,7 +130,7 @@ export class JsonRulesEngineFactChecker
return {
valid: false,
message: msg,
errors: validator.errors,
errors: validator.errors ? validator.errors : undefined,
};
}
@@ -166,21 +169,6 @@ export class JsonRulesEngineFactChecker
return this.checkRegistry.list();
}
async addCheck(
check: TechInsightJsonRuleCheck,
): Promise<TechInsightJsonRuleCheck> {
const checkValidationResponse = await this.validate(check);
if (!checkValidationResponse.valid) {
const msg = `Check validation failed when adding check ${check.name} to check registry.`;
this.logger.warn(msg);
throw new CheckValidationError({
message: checkValidationResponse.message || msg,
errors: checkValidationResponse.errors,
});
}
return await this.checkRegistry.register(check);
}
private retrieveIndividualFactReferences(
condition: TopLevelCondition | { fact: string },
): string[] {
@@ -255,8 +243,10 @@ export class JsonRulesEngineFactChecker
};
if ('toJSON' in result) {
// Results serialize "wrong" since the objects are creating their own serialization implementations
// 'toJSON' should always be present in the result object but it is missing from the types
// Results from json-rules-engine serialize "wrong" since the objects are creating their own serialization implementations.
// 'toJSON' should always be present in the result object but it is missing from the types.
// Parsing the stringified representation into a plain object here to be able to serialize it later
// along with other items present in the returned response.
const rule = JSON.parse(result.toJSON());
return { ...returnable, rule: pick(rule, ['conditions']) };
}
@@ -312,7 +302,7 @@ export class JsonRulesEngineFactChecker
export type JsonRulesEngineFactCheckerFactoryOptions = {
checks: TechInsightJsonRuleCheck[];
logger: Logger;
checkRegistry?: TechInsightCheckRegistry<any>;
checkRegistry?: TechInsightCheckRegistry<TechInsightJsonRuleCheck>;
};
/**
@@ -325,7 +315,7 @@ export type JsonRulesEngineFactCheckerFactoryOptions = {
export class JsonRulesEngineFactCheckerFactory {
private readonly checks: TechInsightJsonRuleCheck[];
private readonly logger: Logger;
private readonly checkRegistry?: TechInsightCheckRegistry<any>;
private readonly checkRegistry?: TechInsightCheckRegistry<TechInsightJsonRuleCheck>;
constructor({
checks,
@@ -14,10 +14,10 @@
* limitations under the License.
*/
import { TopLevelCondition } from 'json-rules-engine';
import { TechInsightCheck } from '@backstage/plugin-tech-insights-node';
import {
BooleanCheckResult,
CheckResponse,
TechInsightCheck,
} from '@backstage/plugin-tech-insights-common';
/**
+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.
*
+1 -1
View File
@@ -1,3 +1,3 @@
# Tech Insights Common
Common types and functionalities for tech insights, to be shared between tech-insights-backend and individual implementations of FactRetrievers, FactCheckers and their persistence options.
Common types and functionalities for tech insights shared in an isomorphic manner between BE and FE implementations.
-147
View File
@@ -3,10 +3,7 @@
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
```ts
import { Config } from '@backstage/config';
import { DateTime } from 'luxon';
import { Logger as Logger_2 } from 'winston';
import { PluginEndpointDiscovery } from '@backstage/backend-common';
// @public
export interface BooleanCheckResult extends CheckResult {
@@ -30,42 +27,6 @@ export type CheckResult = {
check: CheckResponse;
};
// @public
export class CheckValidationError extends Error {
constructor({ message, errors }: { message: string; errors?: any });
// (undocumented)
errors?: any;
}
// @public
export type CheckValidationResponse = {
valid: boolean;
message?: string;
errors?: any;
};
// @public
export interface FactChecker<
CheckType extends TechInsightCheck,
CheckResultType extends CheckResult,
> {
addCheck(check: CheckType): Promise<CheckType>;
getChecks(): Promise<CheckType[]>;
runChecks(entity: string, checks: string[]): Promise<CheckResultType[]>;
validate(check: CheckType): Promise<CheckValidationResponse>;
}
// @public
export interface FactCheckerFactory<
CheckType extends TechInsightCheck,
CheckResultType extends CheckResult,
> {
// (undocumented)
construct(
repository: TechInsightsStore,
): FactChecker<CheckType, CheckResultType>;
}
// @public
export type FactResponse = {
[id: string]: {
@@ -78,113 +39,5 @@ export type FactResponse = {
};
};
// @public
export interface FactRetriever {
entityTypes?: string[];
handler: (ctx: FactRetrieverContext) => Promise<TechInsightFact[]>;
id: string;
schema: FactSchema;
version: string;
}
// @public
export type FactRetrieverContext = {
config: Config;
discovery: PluginEndpointDiscovery;
logger: Logger_2;
};
// @public
export type FactRetrieverRegistration = {
factRetriever: FactRetriever;
cadence?: string;
};
// @public
export type FactSchema = {
[name: string]: {
type: 'integer' | 'float' | 'string' | 'boolean' | 'datetime' | 'set';
description: string;
since?: string;
metadata?: Record<string, any>;
};
};
// Warning: (ae-missing-release-tag) "FactSchemaDefinition" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
export type FactSchemaDefinition = Omit<FactRetriever, 'handler'>;
// @public
export type FlatTechInsightFact = TechInsightFact & {
id: string;
};
// @public
export interface TechInsightCheck {
description: string;
factIds: string[];
failureMetadata?: Record<string, any>;
id: string;
name: string;
successMetadata?: Record<string, any>;
type: string;
}
// @public
export interface TechInsightCheckRegistry<CheckType extends TechInsightCheck> {
// (undocumented)
get(checkId: string): Promise<CheckType>;
// (undocumented)
getAll(checks: string[]): Promise<CheckType[]>;
// (undocumented)
list(): Promise<CheckType[]>;
// (undocumented)
register(check: CheckType): Promise<CheckType>;
}
// @public
export type TechInsightFact = {
entity: {
namespace: string;
kind: string;
name: string;
};
facts: Record<
string,
| number
| string
| boolean
| DateTime
| number[]
| string[]
| boolean[]
| DateTime[]
>;
timestamp?: DateTime;
};
// @public
export interface TechInsightsStore {
getFactsBetweenTimestampsByIds(
ids: string[],
entity: string,
startDateTime: DateTime,
endDateTime: DateTime,
): Promise<{
[factRef: string]: FlatTechInsightFact[];
}>;
// (undocumented)
getLatestFactsByIds(
ids: string[],
entity: string,
): Promise<{
[factRef: string]: FlatTechInsightFact;
}>;
getLatestSchemas(ids?: string[]): Promise<FactSchema[]>;
insertFacts(id: string, facts: TechInsightFact[]): Promise<void>;
insertFactSchema(schemaDefinition: FactSchemaDefinition): Promise<void>;
}
// (No @packageDocumentation comment for this package)
```
+1 -4
View File
@@ -30,11 +30,8 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/backend-common": "^0.9.0",
"@backstage/config": "^0.1.8",
"@types/luxon": "^2.0.5",
"luxon": "^2.0.2",
"winston": "^3.2.1"
"luxon": "^2.0.2"
},
"devDependencies": {
"@backstage/cli": "^0.7.1"
+108 -4
View File
@@ -14,7 +14,111 @@
* limitations under the License.
*/
export * from './checks';
export * from './facts';
export * from './persistence';
export * from './responses';
import { DateTime } from 'luxon';
/**
* @public
*
* Response type for checks.
*/
export interface CheckResponse {
/**
* Identifier of the Check
*/
id: string;
/**
* Type identifier for the check.
* Can be used to determine storage options, logical routing to correct FactChecker implementation
* or to help frontend render correct component types based on this
*/
type: string;
/**
* Human readable name of the Check
*/
name: string;
/**
* Description of the Check
*/
description: string;
/**
* A collection of references to fact rows used to run this checks against
*/
factIds: string[];
/**
* Metadata related to a check.
* Can contain links, additional description texts and other actionable data.
*
* Currently loosely typed, but in the future when patterns emerge, key shapes can be defined
*/
metadata?: Record<string, any>;
}
/**
* @public
*
* Individual fact response type.
* Keyed by the name of the fact
*/
export type FactResponse = {
[id: string]: {
/**
* Reference and unique identifier of the fact row
*/
id: string;
/**
* Type of the individual fact value
*
* Numbers are split into integers and floating point values.
* `set` indicates a collection of values
*/
type: 'integer' | 'float' | 'string' | 'boolean' | 'datetime' | 'set';
/**
* Description of the individual fact
*/
description: string;
/**
* Actual value of the fact
*/
value: number | string | boolean | DateTime | [];
/**
* An optional SemVer version identifying when this fact was added to the FactSchema
*/
since?: string;
/**
* Metadata related to an individual fact.
* Can contain links, additional description texts and other actionable data.
*
* Currently loosely typed, but in the future when patterns emerge, key shapes can be defined
*/
metadata?: Record<string, any>;
};
};
/**
* Generic CheckResult
*
* Contains information about the facts used to calculate the check result
* and information about the check itself. Both may include metadata to be able to display additional information.
* A collection of these should be parseable by the frontend to display scorecards
*
* @public
*/
export type CheckResult = {
facts: FactResponse;
check: CheckResponse;
};
/**
* CheckResult of type Boolean.
*
* @public
*/
export interface BooleanCheckResult extends CheckResult {
result: boolean;
}
@@ -1,101 +0,0 @@
/*
* 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 { DateTime } from 'luxon';
/**
* @public
*
* Response type for checks.
*/
export interface CheckResponse {
/**
* Identifier of the Check
*/
id: string;
/**
* Type identifier for the check.
* Can be used to determine storage options, logical routing to correct FactChecker implementation
* or to help frontend render correct component types based on this
*/
type: string;
/**
* Human readable name of the Check
*/
name: string;
/**
* Description of the Check
*/
description: string;
/**
* A collection of references to fact rows used to run this checks against
*/
factIds: string[];
/**
* Metadata related to a check.
* Can contain links, additional description texts and other actionable data.
*
* Currently loosely typed, but in the future when patterns emerge, key shapes can be defined
*/
metadata?: Record<string, any>;
}
/**
* @public
*
* Individual fact response type.
* Keyed by the name of the fact
*/
export type FactResponse = {
[id: string]: {
/**
* Reference and unique identifier of the fact row
*/
id: string;
/**
* Type of the individual fact value
*
* Numbers are split into integers and floating point values.
* `set` indicates a collection of values
*/
type: 'integer' | 'float' | 'string' | 'boolean' | 'datetime' | 'set';
/**
* Description of the individual fact
*/
description: string;
/**
* Actual value of the fact
*/
value: number | string | boolean | DateTime | [];
/**
* An optional SemVer version identifying when this fact was added to the FactSchema
*/
since?: string;
/**
* Metadata related to an individual fact.
* Can contain links, additional description texts and other actionable data.
*
* Currently loosely typed, but in the future when patterns emerge, key shapes can be defined
*/
metadata?: Record<string, any>;
};
};
+3
View File
@@ -0,0 +1,3 @@
module.exports = {
extends: [require.resolve('@backstage/cli/config/eslint.backend')],
};
+3
View File
@@ -0,0 +1,3 @@
# Tech Insights Node
Common types and functionalities for tech insights backend implementations to be shared between tech-insights-backend and individual implementations of FactRetrievers, FactCheckers and their persistence options.
+46
View File
@@ -0,0 +1,46 @@
{
"name": "@backstage/plugin-tech-insights-node",
"version": "0.1.0",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
"private": false,
"publishConfig": {
"access": "public",
"main": "dist/index.cjs.js",
"types": "dist/index.d.ts"
},
"homepage": "https://backstage.io",
"repository": {
"type": "git",
"url": "https://github.com/backstage/backstage",
"directory": "plugins/tech-insights-node"
},
"keywords": [
"backstage",
"tech-insights"
],
"scripts": {
"start": "backstage-cli backend:dev",
"build": "backstage-cli backend:build",
"lint": "backstage-cli lint",
"test": "backstage-cli test",
"prepack": "backstage-cli prepack",
"postpack": "backstage-cli postpack",
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/backend-common": "^0.9.0",
"@backstage/config": "^0.1.8",
"@backstage/plugin-tech-insights-common": "^0.1.0 ",
"@types/luxon": "^2.0.5",
"luxon": "^2.0.2",
"winston": "^3.2.1"
},
"devDependencies": {
"@backstage/cli": "^0.7.1"
},
"files": [
"dist"
]
}
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { TechInsightsStore } from './persistence';
import { CheckResponse, FactResponse } from './responses';
import { CheckResult } from '@backstage/plugin-tech-insights-common';
/**
* A factory wrapper to construct FactChecker implementations.
@@ -57,16 +57,7 @@ export interface FactChecker<
* @param checks - A collection of checks to run against provided entity
* @returns - A collection containing check/fact information and the actual results of the check
*/
runChecks(entity: string, checks: string[]): Promise<CheckResultType[]>;
/**
* Adds and stores new checks so they can be run checks against.
* Implementation should ideally run validation against the check.
*
* @param check - The actual check to be added.
* @returns - An indicator if fact was successfully added
*/
addCheck(check: CheckType): Promise<CheckType>;
runChecks(entity: string, checks?: string[]): Promise<CheckResultType[]>;
/**
* Retrieves all available checks that can be used to run checks against.
@@ -99,29 +90,6 @@ export interface TechInsightCheckRegistry<CheckType extends TechInsightCheck> {
list(): Promise<CheckType[]>;
}
/**
* Generic CheckResult
*
* Contains information about the facts used to calculate the check result
* and information about the check itself. Both may include metadata to be able to display additional information.
* A collection of these should be parseable by the frontend to display scorecards
*
* @public
*/
export type CheckResult = {
facts: FactResponse;
check: CheckResponse;
};
/**
* CheckResult of type Boolean.
*
* @public
*/
export interface BooleanCheckResult extends CheckResult {
result: boolean;
}
/**
* Generic definition of a check for Tech Insights
*
@@ -181,21 +149,5 @@ export interface TechInsightCheck {
export type CheckValidationResponse = {
valid: boolean;
message?: string;
errors?: any;
errors?: unknown[];
};
/**
* Error object for Validation Errors
*
* Can be used to short circuit larger execution paths instead of passing validation response around
*
* @public
*/
export class CheckValidationError extends Error {
errors?: any;
constructor({ message, errors }: { message: string; errors?: any }) {
super(message);
this.errors = errors;
}
}
@@ -175,14 +175,16 @@ export interface FactRetriever {
schema: FactSchema;
/**
* An optional list of entity type descriptors to indicate if this fact retriever is valid for an entity type.
* An optional object/array of objects of entity filters to indicate if this fact retriever is valid for an entity type.
* If omitted, the retriever should apply to all entities.
*
* Should be defined as:
* ['component', 'group', 'user'] for top level items
* ['component:service', 'component:website'] for component types.
* Should be defined for example:
* { field: 'kind', values: ['component'] }
* { field: 'metadata.name', values: ['component-1', 'component-2'] }
*/
entityTypes?: string[];
entityFilter?:
| Record<string, string | symbol | (string | symbol)[]>[]
| Record<string, string | symbol | (string | symbol)[]>;
}
export type FactSchemaDefinition = Omit<FactRetriever, 'handler'>;
@@ -0,0 +1,24 @@
/*
* 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 * as anything from './';
describe('tech-insights-node', () => {
// Types only
it('should exist', () => {
expect(anything).toBeTruthy();
});
});
+19
View File
@@ -0,0 +1,19 @@
/*
* 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.
*/
export * from './checks';
export * from './facts';
export * from './persistence';