Implement Tech Insights backend

* Add common types and interfaces for Tech Insights. Exposing components needed to use and modify tech-insights-backend module and to implement individual Fact Retrievers, Fact Checkers and their persistence options.

* Implement a framework to run fact retrievers and store fact data into the database. Add migration scripts to create a new database for `tech_insights`.

* Create a default implementation of a FactChecker enabling users to construct checks and run them and generate scorecards based on checks.

* To be able to use tech insights in your application you need to implement `FactRetriever`s to retrieve and return data for facts and register them to the tech-insights-backend. If you want to use fact checking functionality, you need to create `check`s and register them to an implementation of a `FactChecker`.

For more information see documentation on the README.md files of the respective packages.

Signed-off-by: Jussi Hallila <jussi@hallila.com>
This commit is contained in:
Jussi Hallila
2021-09-14 12:54:52 +02:00
parent bb1539a89f
commit 75fc6ac85b
47 changed files with 4065 additions and 3 deletions
@@ -0,0 +1,3 @@
module.exports = {
extends: [require.resolve('@backstage/cli/config/eslint.backend')],
};
@@ -0,0 +1,7 @@
# @backstage/plugin-tech-insights-backend
## 0.0.1
### Patch Changes
- Initial implementation
+3
View File
@@ -0,0 +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.
+167
View File
@@ -0,0 +1,167 @@
## API Report File for "@backstage/plugin-tech-insights-common"
> 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 {
// (undocumented)
result: boolean;
}
// @public
export interface CheckResponse {
description: string;
factRefs: string[];
id: string;
metadata?: Record<string, any>;
name: string;
}
// @public
export type CheckResult = {
facts: FactResponse;
check: CheckResponse;
};
// @public
export interface FactChecker<
CheckType extends TechInsightCheck,
CheckResultType extends CheckResult,
> {
addCheck(check: CheckType): Promise<boolean>;
getChecks(): Promise<CheckType[]>;
runChecks(entity: string, checks: string[]): Promise<CheckResultType[]>;
validate(check: CheckType): Promise<boolean>;
}
// @public
export interface FactCheckerFactory<
CheckType extends TechInsightCheck,
CheckResultType extends CheckResult,
> {
// (undocumented)
construct(
repository: TechInsightsStore,
): FactChecker<CheckType, CheckResultType>;
}
// @public
export type FactResponse = {
[key: string]: {
ref: string;
type: 'integer' | 'float' | 'string' | 'boolean' | 'datetime' | 'set';
description: string;
value: number | string | boolean | DateTime | [];
since?: string;
metadata?: Record<string, any>;
entityKinds: string[];
};
};
// @public
export interface FactRetriever {
handler: (ctx: FactRetrieverContext) => Promise<TechInsightFact[]>;
ref: string;
schema: FactSchema;
}
// @public
export type FactRetrieverContext = {
config: Config;
discovery: PluginEndpointDiscovery;
logger: Logger_2;
};
// @public
export type FactRetrieverRegistration = {
factRetriever: FactRetriever;
cadence?: string;
};
// @public
export type FactSchema = {
version: string;
schema: FactValueDefinitions;
};
// @public
export type FactValueDefinitions = {
[key: string]: {
type: 'integer' | 'float' | 'string' | 'boolean' | 'datetime' | 'set';
description: string;
since?: string;
metadata?: Record<string, any>;
entityKinds: string[];
};
};
// @public
export type FlatTechInsightFact = TechInsightFact & {
ref: string;
};
// @public
export interface TechInsightCheck {
// (undocumented)
description: string;
factRefs: string[];
failureMetadata?: Record<string, any>;
id: string;
// (undocumented)
name: string;
successMetadata?: Record<string, any>;
}
// @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<void>;
}
// @public
export type TechInsightFact = {
entity: {
namespace: string;
kind: string;
name: string;
};
facts: Record<string, number | string | boolean | DateTime | []>;
timestamp?: DateTime;
};
// @public
export interface TechInsightsStore {
getFactsBetweenTimestampsForRefs(
refs: string[],
entity: string,
startDateTime: DateTime,
endDateTime: DateTime,
): Promise<{
[factRef: string]: FlatTechInsightFact[];
}>;
// (undocumented)
getLatestFactsForRefs(
refs: string[],
entity: string,
): Promise<{
[factRef: string]: FlatTechInsightFact;
}>;
getLatestSchemas(refs?: string[]): Promise<FactSchema[]>;
insertFacts(ref: string, facts: TechInsightFact[]): Promise<void>;
insertFactSchema(ref: string, schema: FactSchema): Promise<void>;
}
// (No @packageDocumentation comment for this package)
```
+45
View File
@@ -0,0 +1,45 @@
{
"name": "@backstage/plugin-tech-insights-common",
"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-common"
},
"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",
"@types/luxon": "^2.0.5",
"luxon": "^2.0.2",
"winston": "^3.2.1"
},
"devDependencies": {
"@backstage/cli": "^0.7.1"
},
"files": [
"dist"
]
}
+159
View File
@@ -0,0 +1,159 @@
/*
* 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 { TechInsightsStore } from './persistence';
import { CheckResponse, FactResponse } from './responses';
/**
* A factory wrapper to construct FactChecker implementations.
*
* @public
* @typeParam CheckType - Implementation specific Check. Can extend TechInsightCheck with additional information
* @typeParam CheckResultType - Implementation specific result of a check. Can extend CheckResult with additional information
*/
export interface FactCheckerFactory<
CheckType extends TechInsightCheck,
CheckResultType extends CheckResult,
> {
/**
* @param repository - TechInsightsStore
* @returns an implementation of a FactChecker for generic types defined in the factory
*/
construct(
repository: TechInsightsStore,
): FactChecker<CheckType, CheckResultType>;
}
/**
* FactChecker interface
*
* A generic interface that can be implemented to create checkers for specific check and check return types.
* This is used especially when creating Scorecards and displaying results of rules when run against facts.
*
* @public
* @typeParam CheckType - Implementation specific Check. Can extend TechInsightCheck with additional information
* @typeParam CheckResultType - Implementation specific result of a check. Can extend CheckResult with additional information
*/
export interface FactChecker<
CheckType extends TechInsightCheck,
CheckResultType extends CheckResult,
> {
/**
* Runs checks against an entity.
*
* @param entity - A reference to an entity to run checks against. In a format namespace/kind/name
* @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<boolean>;
/**
* Retrieves all available checks that can be used to run checks against.
* The implementation can be just a piping through to CheckRegistry implementation if such is in use.
*
* @returns - A collection of checks
*/
getChecks(): Promise<CheckType[]>;
/**
* Validates if check is valid and can be run with the current implementation
*
* @param check - The check to be validated
* @returns - Validation result
*/
validate(check: CheckType): Promise<boolean>;
}
/**
* Registry containing checks for tech insights.
*
* @public
* @typeParam CheckType - Implementation specific Check. Can extend TechInsightCheck with additional information
*
*/
export interface TechInsightCheckRegistry<CheckType extends TechInsightCheck> {
register(check: CheckType): Promise<void>;
get(checkId: string): Promise<CheckType>;
getAll(checks: string[]): Promise<CheckType[]>;
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
*
* @public
*/
export interface TechInsightCheck {
/**
* Unique identifier of the check
*
* Used to identify which checks to use when running checks.
*/
id: string;
name: string;
description: string;
/**
* A collection of string referencing fact rows that a check will be run against.
*
* References the fact container, aka fact retriever itself which may or may not contain multiple individual facts and values
*/
factRefs: string[];
/**
* Metadata to be returned in case a check has been successfully evaluated
* Can contain links, description texts or other actionable items
*/
successMetadata?: Record<string, any>;
/**
* Metadata to be returned in case a check evaluation has ended in failure
* Can contain links, description texts or other actionable items
*/
failureMetadata?: Record<string, any>;
}
+196
View File
@@ -0,0 +1,196 @@
/*
* 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';
import { Config } from '@backstage/config';
import { PluginEndpointDiscovery } from '@backstage/backend-common';
import { Logger } from 'winston';
/**
* A container for facts. The shape of the fact records needs to correspond to the FactSchema with same `ref` value.
* Each container contains a reference to an entity which can be a Backstage entity or a generic construct
* outside of backstage with same shape.
*
* Container may contain multiple individual facts and their values
*
* @public
*/
export type TechInsightFact = {
/**
* Entity reference that this fact relates to
*/
entity: {
namespace: string;
kind: string;
name: string;
};
/**
* A collection of fact values as key value pairs.
*
* Key indicates fact name as it is defined in FactSchema
*/
facts: Record<string, number | string | boolean | DateTime | []>;
/**
* Optional timestamp value which can be used to override retrieval time of the fact row.
* Otherwise when stored into data storage, defaults to current time
*/
timestamp?: DateTime;
};
/**
* Response type used when returning from database and API.
* Adds a field for ref for easier usage
*
* @public
*/
export type FlatTechInsightFact = TechInsightFact & {
/**
* Reference and unique identifier of the fact row
*/
ref: string;
};
/**
* @public
*
* A record type to specify individual fact shapes
*
* Used as part of a schema to validate, identify and generically construct usage implementations
* of individual fact values in the system.
*/
export type FactValueDefinitions = {
[key: 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';
/**
* A description of this individual fact value
*/
description: string;
/**
* Optional semver string to indicate when this specific fact definition was added to the schema
*/
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
*
* examples:
* ```
* \{
* link: 'https://sonarqube.mycompany.com/fix-these-issues',
* suggestion: 'To affect this value, you can do x, y, z',
* minValue: 0
* \}
* ```
*/
metadata?: Record<string, any>;
/**
* A list of entity kind descriptors to indicate if this fact is valid for an entity kind
*/
entityKinds: string[];
};
};
/**
* @public
*
* Container for FactSchema
*/
export type FactSchema = {
/**
* Semver string indicating the version of this schema
*/
version: string;
/**
* Actual schema definitions for this schema
*/
schema: FactValueDefinitions;
};
/**
* @public
*
* FactRetrieverContext injected into individual handler methods of FactRetriever implementations.
* The context can be used to construct logic to retrieve entities, contact integration points
* and fetch and calculate fact values from external sources.
*/
export type FactRetrieverContext = {
config: Config;
discovery: PluginEndpointDiscovery;
logger: Logger;
};
/**
* @public
*
* FactRetriever interface
*
* A component specifying
*/
export interface FactRetriever {
/**
* A unique identifier of the retriever.
* Used to identify and store individual facts returned from this retriever
* and schemas defined by this retriever.
*/
ref: string;
/**
* Handler function that needs to be implemented to retrieve fact values for entities.
*
* @param ctx - FactRetrieverContext which can be used to retrieve config and contact integrations
* @returns - A collection of TechInsightFacts grouped by entities.
*/
handler: (ctx: FactRetrieverContext) => Promise<TechInsightFact[]>;
/**
* A fact schema defining the shape of data returned from the handler method for each entity
*/
schema: FactSchema;
}
/**
* @public
*
* Registration of a fact retriever
* Used to add and schedule individual fact retrievers to the fact retriever engine.
*/
export type FactRetrieverRegistration = {
/**
* Actual FactRetriever implementation
*/
factRetriever: FactRetriever;
/**
* Cron expression to indicate when the retriever should be triggered.
* Defaults to a random point in time every 24h
*
*/
cadence?: string;
};
@@ -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-common', () => {
// Types only
it('should exist', () => {
expect(anything).toBeTruthy();
});
});
+20
View File
@@ -0,0 +1,20 @@
/*
* 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';
export * from './responses';
@@ -0,0 +1,79 @@
/*
* 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 { FactSchema, TechInsightFact, FlatTechInsightFact } from './facts';
import { DateTime } from 'luxon';
/**
* TechInsights Database
*
* @public
*/
export interface TechInsightsStore {
/**
* Stores fact containers as rows into data store.
* Individual items in array correspond to a fact schema based on reference and entity based on entity identifier.
*
* Each row may contain multiple individual facts and values
*
* @param ref - Unique identifier of the fact retriever these facts relate to
* @param facts - A collection of TechInsightFacts
*/
insertFacts(ref: string, facts: TechInsightFact[]): Promise<void>;
/**
* @param refs - A collection of reference string to a fact row
* @param entity - A string identifying an entity. In a format namespace/kind/name
*
* @returns - An object keyed by a fact reference and containing an individual TechInsightFact
*/
getLatestFactsForRefs(
refs: string[],
entity: string,
): Promise<{ [factRef: string]: FlatTechInsightFact }>;
/**
* Retrieves fact values identified by fact row references for an individual entity.
*
* @param refs - A collection of reference string to a fact row
* @param entity - A string identifying an entity. In a format namespace/kind/name
* @param startDateTime - DateTime object indicating start of the time frame
* @param endDateTime - DateTime object indicating start of the time frame
*
* @returns - An object keyed by a fact reference and containing a collection of TechInsightFacts matching the time frame
*/
getFactsBetweenTimestampsForRefs(
refs: string[],
entity: string,
startDateTime: DateTime,
endDateTime: DateTime,
): Promise<{ [factRef: string]: FlatTechInsightFact[] }>;
/**
* Stores versioned fact schemas into data store
*
* @param ref - Identifier of the fact schema. Reference to a fact retriever.
* @param schema - The actual schema to store
*/
insertFactSchema(ref: string, schema: FactSchema): Promise<void>;
/**
* Retrieves latest versions (as defined by semver) of fact schemas from the data store.
*
* @param refs - Collection of refs to return. If omitted, all Schemas should be returned.
* @returns - A collection of schemas
*/
getLatestSchemas(refs?: string[]): Promise<FactSchema[]>;
}
@@ -0,0 +1,100 @@
/*
* 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;
/**
* 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
*/
factRefs: 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 = {
[key: string]: {
/**
* Reference and unique identifier of the fact row
*/
ref: 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>;
/**
* A list of entity kind descriptors to indicate if this fact is valid for an entity kind
*/
entityKinds: string[];
};
};
@@ -0,0 +1,17 @@
/*
* 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 {};