From 0269b015766b65e98a6e44200135e24e88c37959 Mon Sep 17 00:00:00 2001 From: Patrick Jungermann Date: Mon, 8 Jan 2024 19:37:29 +0100 Subject: [PATCH 1/5] refactor(tech-insights): reorg exports Signed-off-by: Patrick Jungermann --- .../src/index.ts | 10 ++----- .../src/service/index.ts | 25 ++++++++++++++++++ plugins/tech-insights-backend/src/index.ts | 19 +------------- .../src/service/fact/factRetrievers/index.ts | 3 ++- .../src/service/fact/index.ts | 21 +++++++++++++++ .../src/service/index.ts | 26 +++++++++++++++++++ .../src/service/persistence/index.ts | 21 +++++++++++++++ 7 files changed, 98 insertions(+), 27 deletions(-) create mode 100644 plugins/tech-insights-backend-module-jsonfc/src/service/index.ts create mode 100644 plugins/tech-insights-backend/src/service/fact/index.ts create mode 100644 plugins/tech-insights-backend/src/service/index.ts create mode 100644 plugins/tech-insights-backend/src/service/persistence/index.ts diff --git a/plugins/tech-insights-backend-module-jsonfc/src/index.ts b/plugins/tech-insights-backend-module-jsonfc/src/index.ts index 1239211f73..6462efc403 100644 --- a/plugins/tech-insights-backend-module-jsonfc/src/index.ts +++ b/plugins/tech-insights-backend-module-jsonfc/src/index.ts @@ -13,15 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export { - JsonRulesEngineFactCheckerFactory, - JsonRulesEngineFactChecker, -} from './service/JsonRulesEngineFactChecker'; + export { JSON_RULE_ENGINE_CHECK_TYPE } from './constants'; -export type { - JsonRulesEngineFactCheckerFactoryOptions, - JsonRulesEngineFactCheckerOptions, -} from './service/JsonRulesEngineFactChecker'; +export * from './service'; export type { JsonRuleCheckResponse, JsonRuleBooleanCheckResult, diff --git a/plugins/tech-insights-backend-module-jsonfc/src/service/index.ts b/plugins/tech-insights-backend-module-jsonfc/src/service/index.ts new file mode 100644 index 0000000000..5670c138a1 --- /dev/null +++ b/plugins/tech-insights-backend-module-jsonfc/src/service/index.ts @@ -0,0 +1,25 @@ +/* + * Copyright 2024 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 { + JsonRulesEngineFactCheckerFactory, + JsonRulesEngineFactChecker, +} from './JsonRulesEngineFactChecker'; + +export type { + JsonRulesEngineFactCheckerFactoryOptions, + JsonRulesEngineFactCheckerOptions, +} from './JsonRulesEngineFactChecker'; diff --git a/plugins/tech-insights-backend/src/index.ts b/plugins/tech-insights-backend/src/index.ts index e9e037cce1..d6b845a78d 100644 --- a/plugins/tech-insights-backend/src/index.ts +++ b/plugins/tech-insights-backend/src/index.ts @@ -14,21 +14,4 @@ * limitations under the License. */ -export * from './service/router'; -export type { RouterOptions } from './service/router'; - -export { buildTechInsightsContext } from './service/techInsightsContextBuilder'; -export { initializePersistenceContext } from './service/persistence/persistenceContext'; -export type { - TechInsightsOptions, - TechInsightsContext, -} from './service/techInsightsContextBuilder'; -export type { FactRetrieverEngine } from './service/fact/FactRetrieverEngine'; -export type { - PersistenceContext, - PersistenceContextOptions, -} from './service/persistence/persistenceContext'; -export { createFactRetrieverRegistration } from './service/fact/createFactRetriever'; -export type { FactRetrieverRegistry } from './service/fact/FactRetrieverRegistry'; -export type { FactRetrieverRegistrationOptions } from './service/fact/createFactRetriever'; -export * from './service/fact/factRetrievers'; +export * from './service'; diff --git a/plugins/tech-insights-backend/src/service/fact/factRetrievers/index.ts b/plugins/tech-insights-backend/src/service/fact/factRetrievers/index.ts index 587db8c4b9..5983a82e1d 100644 --- a/plugins/tech-insights-backend/src/service/fact/factRetrievers/index.ts +++ b/plugins/tech-insights-backend/src/service/fact/factRetrievers/index.ts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export * from './entityOwnershipFactRetriever'; + export * from './entityMetadataFactRetriever'; +export * from './entityOwnershipFactRetriever'; export * from './techdocsFactRetriever'; diff --git a/plugins/tech-insights-backend/src/service/fact/index.ts b/plugins/tech-insights-backend/src/service/fact/index.ts new file mode 100644 index 0000000000..0eeb498481 --- /dev/null +++ b/plugins/tech-insights-backend/src/service/fact/index.ts @@ -0,0 +1,21 @@ +/* + * Copyright 2024 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 { createFactRetrieverRegistration } from './createFactRetriever'; +export type { FactRetrieverRegistrationOptions } from './createFactRetriever'; +export type { FactRetrieverEngine } from './FactRetrieverEngine'; +export type { FactRetrieverRegistry } from './FactRetrieverRegistry'; +export * from './factRetrievers'; diff --git a/plugins/tech-insights-backend/src/service/index.ts b/plugins/tech-insights-backend/src/service/index.ts new file mode 100644 index 0000000000..5306ad26d6 --- /dev/null +++ b/plugins/tech-insights-backend/src/service/index.ts @@ -0,0 +1,26 @@ +/* + * Copyright 2024 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 './fact'; +export * from './persistence'; +export * from './router'; +export type { RouterOptions } from './router'; + +export { buildTechInsightsContext } from './techInsightsContextBuilder'; +export type { + TechInsightsOptions, + TechInsightsContext, +} from './techInsightsContextBuilder'; diff --git a/plugins/tech-insights-backend/src/service/persistence/index.ts b/plugins/tech-insights-backend/src/service/persistence/index.ts new file mode 100644 index 0000000000..64cc876026 --- /dev/null +++ b/plugins/tech-insights-backend/src/service/persistence/index.ts @@ -0,0 +1,21 @@ +/* + * Copyright 2024 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 { initializePersistenceContext } from './persistenceContext'; +export type { + PersistenceContext, + PersistenceContextOptions, +} from './persistenceContext'; From bc72782dd66aef5bb52d434873f4182c0cb1b5c6 Mon Sep 17 00:00:00 2001 From: Patrick Jungermann Date: Mon, 8 Jan 2024 21:22:48 +0100 Subject: [PATCH 2/5] feat(tech-insights): define fact checks in app-config.yaml Allow to define JSON checks in `app-config.yaml` as `techInsights.factChecker.checks`. Signed-off-by: Patrick Jungermann --- .changeset/fuzzy-comics-collect.md | 27 +++ .../README.md | 24 ++- .../api-report.md | 6 + .../config.json | 183 ++++++++++++++++++ .../package.json | 6 +- .../src/service/JsonRulesEngineFactChecker.ts | 32 ++- .../src/service/config.test.ts | 164 ++++++++++++++++ .../src/service/config.ts | 162 ++++++++++++++++ 8 files changed, 593 insertions(+), 11 deletions(-) create mode 100644 .changeset/fuzzy-comics-collect.md create mode 100644 plugins/tech-insights-backend-module-jsonfc/config.json create mode 100644 plugins/tech-insights-backend-module-jsonfc/src/service/config.test.ts create mode 100644 plugins/tech-insights-backend-module-jsonfc/src/service/config.ts diff --git a/.changeset/fuzzy-comics-collect.md b/.changeset/fuzzy-comics-collect.md new file mode 100644 index 0000000000..5ce6b3c967 --- /dev/null +++ b/.changeset/fuzzy-comics-collect.md @@ -0,0 +1,27 @@ +--- +'@backstage/plugin-tech-insights-backend-module-jsonfc': patch +--- + +Support loading `TechInsightsJsonRuleCheck` instances from config. + +Uses the check `id` as key. + +Example: + +```yaml title="app-config.yaml" +techInsights: + factChecker: + checks: + groupOwnerCheck: + type: json-rules-engine + name: Group Owner Check + description: Verifies that a group has been set as the spec.owner for this entity + factIds: + - entityOwnershipFactRetriever + rule: + conditions: + all: + - fact: hasGroupOwner + operator: equal + value: true +``` diff --git a/plugins/tech-insights-backend-module-jsonfc/README.md b/plugins/tech-insights-backend-module-jsonfc/README.md index 5940d39b15..3f9e91408c 100644 --- a/plugins/tech-insights-backend-module-jsonfc/README.md +++ b/plugins/tech-insights-backend-module-jsonfc/README.md @@ -46,7 +46,7 @@ By default this implementation comes with an in-memory storage to store checks. ``` -## Adding checks +## Adding checks in code Checks for this FactChecker are constructed as [`json-rules-engine` compatible JSON rules](https://github.com/CacheControl/json-rules-engine/blob/master/docs/rules.md#conditions). A check could look like the following for example: @@ -86,6 +86,28 @@ export const exampleCheck: TechInsightJsonRuleCheck = { }; ``` +## Adding checks in config + +Example: + +```yaml title="app-config.yaml" +techInsights: + factChecker: + checks: + groupOwnerCheck: + type: json-rules-engine + name: Group Owner Check + description: Verifies that a group has been set as the spec.owner for this entity + factIds: + - entityOwnershipFactRetriever + rule: + conditions: + all: + - fact: hasGroupOwner + operator: equal + value: true +``` + ### More than one `factIds` for a check. When more than one is supplied, the requested fact **MUST** be present in at least one of the fact retrievers. diff --git a/plugins/tech-insights-backend-module-jsonfc/api-report.md b/plugins/tech-insights-backend-module-jsonfc/api-report.md index 5fb29d275d..d28ee28f8d 100644 --- a/plugins/tech-insights-backend-module-jsonfc/api-report.md +++ b/plugins/tech-insights-backend-module-jsonfc/api-report.md @@ -6,6 +6,7 @@ import { BooleanCheckResult } from '@backstage/plugin-tech-insights-common'; import { CheckResponse } from '@backstage/plugin-tech-insights-common'; import { CheckValidationResponse } from '@backstage/plugin-tech-insights-node'; +import { Config } from '@backstage/config'; import { FactChecker } from '@backstage/plugin-tech-insights-node'; import { Logger } from 'winston'; import { Operator } from 'json-rules-engine'; @@ -63,6 +64,11 @@ export class JsonRulesEngineFactCheckerFactory { constructor(options: JsonRulesEngineFactCheckerFactoryOptions); // (undocumented) construct(repository: TechInsightsStore): JsonRulesEngineFactChecker; + // (undocumented) + static fromConfig( + config: Config, + options: Omit, + ): JsonRulesEngineFactCheckerFactory; } // @public diff --git a/plugins/tech-insights-backend-module-jsonfc/config.json b/plugins/tech-insights-backend-module-jsonfc/config.json new file mode 100644 index 0000000000..b95b5e52bb --- /dev/null +++ b/plugins/tech-insights-backend-module-jsonfc/config.json @@ -0,0 +1,183 @@ +{ + "$schema": "https://backstage.io/schema/config-v1", + "type": "object", + "properties": { + "techInsights": { + "type": "object", + "properties": { + "factChecker": { + "type": "object", + "properties": { + "checks": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/check" + } + } + } + } + } + } + }, + "$defs": { + "check": { + "type": "object", + "required": ["type", "name", "description", "factIds", "rule"], + "properties": { + "type": { + "type": "string" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "factIds": { + "type": "array", + "items": { + "type": "string" + } + }, + "rule": { + "$ref": "#/$defs/rule" + } + } + }, + "conditionProperties": { + "type": "object", + "required": ["fact", "operator", "value"], + "properties": { + "fact": { + "type": "string" + }, + "operator": { + "type": "string" + }, + "value": { + "oneOf": [ + { + "type": "object", + "required": ["fact"], + "properties": { + "fact": { + "type": "string" + } + } + }, + {} + ] + }, + "path": { + "type": "string" + }, + "name": { + "type": "string" + }, + "priority": { + "type": "number" + }, + "params": { + "type": "object", + "additionalProperties": true + } + } + }, + "conditionReference": { + "type": "object", + "required": ["condition"], + "properties": { + "condition": { + "type": "string" + }, + "name": { + "type": "string" + }, + "priority": { + "type": "number" + } + } + }, + "nestedCondition": { + "oneOf": [ + { + "$ref": "#/$defs/conditionProperties" + }, + { + "$ref": "#/$defs/topLevelCondition" + } + ] + }, + "rule": { + "type": "string", + "required": ["conditions"], + "properties": { + "conditions": { + "$ref": "#/$defs/topLevelCondition" + }, + "name": { + "type": "string" + }, + "priority": { + "type": "number" + }, + "successMetadata": { + "type": "object", + "additionalProperties": true + }, + "failureMetadata": { + "type": "object", + "additionalProperties": true + } + } + }, + "topLevelCondition": { + "oneOf": [ + { + "type": "object", + "required": ["all"], + "properties": { + "all": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/$defs/nestedCondition" + } + ] + } + } + } + }, + { + "type": "object", + "required": ["any"], + "properties": { + "any": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/$defs/nestedCondition" + } + ] + } + } + } + }, + { + "type": "object", + "required": ["not"], + "properties": { + "not": { + "$ref": "#/$defs/nestedCondition" + } + } + }, + { + "$ref": "#/$defs/conditionReference" + } + ] + } + } +} diff --git a/plugins/tech-insights-backend-module-jsonfc/package.json b/plugins/tech-insights-backend-module-jsonfc/package.json index 1c34a2157b..431a7152d4 100644 --- a/plugins/tech-insights-backend-module-jsonfc/package.json +++ b/plugins/tech-insights-backend-module-jsonfc/package.json @@ -34,9 +34,11 @@ }, "dependencies": { "@backstage/backend-common": "workspace:^", + "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", "@backstage/plugin-tech-insights-common": "workspace:^", "@backstage/plugin-tech-insights-node": "workspace:^", + "@backstage/types": "workspace:^", "ajv": "^8.10.0", "json-rules-engine": "^6.1.2", "lodash": "^4.17.21", @@ -47,6 +49,8 @@ "@backstage/cli": "workspace:^" }, "files": [ + "config.json", "dist" - ] + ], + "configSchema": "config.json" } diff --git a/plugins/tech-insights-backend-module-jsonfc/src/service/JsonRulesEngineFactChecker.ts b/plugins/tech-insights-backend-module-jsonfc/src/service/JsonRulesEngineFactChecker.ts index 6c1c559fc4..51609bc916 100644 --- a/plugins/tech-insights-backend-module-jsonfc/src/service/JsonRulesEngineFactChecker.ts +++ b/plugins/tech-insights-backend-module-jsonfc/src/service/JsonRulesEngineFactChecker.ts @@ -14,7 +14,9 @@ * limitations under the License. */ -import { JsonRuleBooleanCheckResult, TechInsightJsonRuleCheck } from '../types'; +import { Config } from '@backstage/config'; +import { isError } from '@backstage/errors'; +import { FactResponse } from '@backstage/plugin-tech-insights-common'; import { FactChecker, TechInsightCheckRegistry, @@ -22,20 +24,20 @@ import { TechInsightsStore, CheckValidationResponse, } from '@backstage/plugin-tech-insights-node'; -import { FactResponse } from '@backstage/plugin-tech-insights-common'; +import Ajv, { SchemaObject } from 'ajv'; import { Engine, EngineResult, Operator, TopLevelCondition, } from 'json-rules-engine'; -import { DefaultCheckRegistry } from './CheckRegistry'; -import { Logger } from 'winston'; import { pick } from 'lodash'; -import Ajv, { SchemaObject } from 'ajv'; -import * as validationSchema from './validation-schema.json'; +import { Logger } from 'winston'; import { JSON_RULE_ENGINE_CHECK_TYPE } from '../constants'; -import { isError } from '@backstage/errors'; +import { JsonRuleBooleanCheckResult, TechInsightJsonRuleCheck } from '../types'; +import { DefaultCheckRegistry } from './CheckRegistry'; +import { readChecksFromConfig } from './config'; +import * as validationSchema from './validation-schema.json'; const noopEvent = { type: 'noop', @@ -340,7 +342,7 @@ export class JsonRulesEngineFactChecker * * Implementation of checkRegistry is optional. * If there is a need to use persistent storage for checks, it is recommended to inject a storage implementation here. - * Otherwise an in-memory option is instantiated and used. + * Otherwise, an in-memory option is instantiated and used. */ export type JsonRulesEngineFactCheckerFactoryOptions = { checks: TechInsightJsonRuleCheck[]; @@ -354,7 +356,7 @@ export type JsonRulesEngineFactCheckerFactoryOptions = { * * Factory to construct JsonRulesEngineFactChecker * Can be constructed with optional implementation of CheckInsightCheckRegistry if needed. - * Otherwise defaults to using in-memory CheckRegistry + * Otherwise, defaults to using in-memory CheckRegistry. */ export class JsonRulesEngineFactCheckerFactory { private readonly checks: TechInsightJsonRuleCheck[]; @@ -362,6 +364,18 @@ export class JsonRulesEngineFactCheckerFactory { private readonly checkRegistry?: TechInsightCheckRegistry; private readonly operators?: Operator[]; + static fromConfig( + config: Config, + options: Omit, + ): JsonRulesEngineFactCheckerFactory { + const checks = readChecksFromConfig(config); + + return new JsonRulesEngineFactCheckerFactory({ + ...options, + checks, + }); + } + constructor(options: JsonRulesEngineFactCheckerFactoryOptions) { this.logger = options.logger; this.checks = options.checks; diff --git a/plugins/tech-insights-backend-module-jsonfc/src/service/config.test.ts b/plugins/tech-insights-backend-module-jsonfc/src/service/config.test.ts new file mode 100644 index 0000000000..732cc7f731 --- /dev/null +++ b/plugins/tech-insights-backend-module-jsonfc/src/service/config.test.ts @@ -0,0 +1,164 @@ +/* + * Copyright 2024 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 { ConfigReader } from '@backstage/config'; +import { JSON_RULE_ENGINE_CHECK_TYPE } from '../constants'; +import { readChecksFromConfig } from './config'; + +describe('config', () => { + describe('readChecksFromConfig', () => { + it('no config return empty checks array', () => { + const config = new ConfigReader({}); + const checks = readChecksFromConfig(config); + + expect(checks).toHaveLength(0); + }); + + it('empty checks config return empty checks array', () => { + const config = new ConfigReader({ + techInsights: { + factChecker: {}, + }, + }); + const checks = readChecksFromConfig(config); + + expect(checks).toHaveLength(0); + }); + + it('with checks return parsed checks', () => { + const config = new ConfigReader({ + techInsights: { + factChecker: { + checks: { + fooCheck: { + type: JSON_RULE_ENGINE_CHECK_TYPE, + name: 'Foo Check', + description: 'Verifies foo', + factIds: ['fooFactRetriever'], + rule: { + conditions: { + all: [ + { + fact: 'numFoo', + operator: 'greaterThanInclusive', + value: 1, + }, + { + fact: 'hasFoo', + operator: 'equal', + value: true, + }, + ], + }, + }, + }, + barCheck: { + type: JSON_RULE_ENGINE_CHECK_TYPE, + name: 'Bar Check', + description: 'Verifies bar', + factIds: ['barFactRetriever'], + rule: { + conditions: { + any: [ + { + fact: 'barEnabled', + operator: 'equal', + value: false, + }, + { + fact: 'hasBar', + operator: 'equal', + value: true, + }, + ], + }, + }, + }, + bazCheck: { + type: JSON_RULE_ENGINE_CHECK_TYPE, + name: 'Baz Check', + description: 'Verifies baz', + factIds: ['bazFactRetriever'], + rule: { + conditions: { + not: { + fact: 'bazConfig', + operator: 'equal', + value: { invalid: true }, + }, + }, + }, + }, + }, + }, + }, + }); + + const checks = readChecksFromConfig(config); + + expect(checks).toHaveLength(3); + expect(checks.map(check => check.id)).toEqual([ + 'fooCheck', + 'barCheck', + 'bazCheck', + ]); + + const fooCheck = checks.find(check => check.id === 'fooCheck')!; + expect(fooCheck.name).toEqual('Foo Check'); + expect(fooCheck.rule.conditions).toEqual({ + all: [ + { + fact: 'numFoo', + operator: 'greaterThanInclusive', + value: 1, + }, + { + fact: 'hasFoo', + operator: 'equal', + value: true, + }, + ], + }); + + const barCheck = checks.find(check => check.id === 'barCheck')!; + expect(barCheck.name).toEqual('Bar Check'); + expect(barCheck.rule.conditions).toEqual({ + any: [ + { + fact: 'barEnabled', + operator: 'equal', + value: false, + }, + { + fact: 'hasBar', + operator: 'equal', + value: true, + }, + ], + }); + + const bazCheck = checks.find(check => check.id === 'bazCheck')!; + expect(bazCheck.name).toEqual('Baz Check'); + expect(bazCheck.rule.conditions).toEqual({ + not: { + fact: 'bazConfig', + operator: 'equal', + value: { invalid: true }, + }, + }); + }); + }); +}); diff --git a/plugins/tech-insights-backend-module-jsonfc/src/service/config.ts b/plugins/tech-insights-backend-module-jsonfc/src/service/config.ts new file mode 100644 index 0000000000..5c663d0d41 --- /dev/null +++ b/plugins/tech-insights-backend-module-jsonfc/src/service/config.ts @@ -0,0 +1,162 @@ +/* + * Copyright 2024 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 { Config } from '@backstage/config'; +import { TopLevelCondition } from 'json-rules-engine'; +import { Rule, TechInsightJsonRuleCheck } from '../types'; + +// copy of non-exported `ConditionProperties` from 'json-rules-engine' +interface ConditionProperties { + fact: string; + operator: string; + value: { fact: string } | any; + path?: string; + priority?: number; + params?: Record; + name?: string; +} + +// copy of non-exported `NestedCondition` from 'json-rules-engine' +type NestedCondition = ConditionProperties | TopLevelCondition; + +function readRuleConditionProperties(config: Config): ConditionProperties { + const fact = config.getString('fact'); + const name = config.getOptionalString('name'); + const operator = config.getString('operator'); + const params = config.getOptionalConfig('params')?.get>(); + const path = config.getOptionalString('path'); + const priority = config.getOptionalNumber('priority'); + const value: { fact: string } | any = config.get('value'); + + return { + fact, + name, + operator, + params, + path, + priority, + value, + }; +} + +function readRuleNestedCondition(config: Config): NestedCondition { + if (config.has('fact')) { + return readRuleConditionProperties(config); + } + + return readRuleTopLevelCondition(config); +} + +function readRuleTopLevelCondition(config: Config): TopLevelCondition { + const base = { + name: config.getOptionalString('name'), + priority: config.getOptionalNumber('priority'), + }; + + if (config.has('all')) { + const all = config + .getConfigArray('all') + .map(conditionConfig => readRuleNestedCondition(conditionConfig)); + return { + ...base, + all, + }; + } + + if (config.has('any')) { + const any = config + .getConfigArray('any') + .map(conditionConfig => readRuleNestedCondition(conditionConfig)); + return { + ...base, + any, + }; + } + + if (config.has('not')) { + const not = readRuleNestedCondition(config.getConfig('not')); + return { + ...base, + not, + }; + } + + const condition = config.getString('condition'); + + return { + ...base, + condition, + }; +} + +function readRuleFromRuleConfig(config: Config): Rule { + const conditions = readRuleTopLevelCondition(config.getConfig('conditions')); + const name = config.getOptionalString('name'); + const priority = config.getOptionalNumber('priority'); + + return { + conditions, + name, + priority, + }; +} + +function readCheckFromCheckConfig( + id: string, + config: Config, +): TechInsightJsonRuleCheck { + const type = config.getString('type'); + const name = config.getString('name'); + const description = config.getString('description'); + const factIds = config.getStringArray('factIds'); + const successMetadata = config + .getOptionalConfig('successMetadata') + ?.get>(); + const failureMetadata = config + .getOptionalConfig('failureMetadata') + ?.get>(); + const rule = readRuleFromRuleConfig(config.getConfig('rule')); + + return { + description, + factIds, + failureMetadata, + id, + name, + rule, + successMetadata, + type, + }; +} + +export function readChecksFromConfig( + config: Config, +): TechInsightJsonRuleCheck[] { + const key = 'techInsights.factChecker.checks'; + if (!config.has(key)) { + return []; + } + + const checksConfig = config.getConfig(key); + const checks: TechInsightJsonRuleCheck[] = []; + checksConfig.keys().forEach(checkId => { + const checkConfig = checksConfig.getConfig(checkId); + + checks.push(readCheckFromCheckConfig(checkId, checkConfig)); + }); + + return checks; +} From 7201af3445d3b66eb74130ea339d0fbd96c32c02 Mon Sep 17 00:00:00 2001 From: Patrick Jungermann Date: Mon, 8 Jan 2024 23:34:43 +0100 Subject: [PATCH 3/5] feat(tech-insights): add plugin for new backend system A new backend plugin for the tech-insights backend was added and exported as `default` as well as extension points to be used by modules. The plugin can be used like ```ts title="packages/backend/src/index.ts" backend.add(import('@backstage/plugin-tech-insights-backend')); ``` Fact retrievers will be added (built-in or through the extension point), but only registered if the user adds a configuration for it (cadence, etc.). Signed-off-by: Patrick Jungermann --- .changeset/green-dogs-fold.md | 15 ++ plugins/tech-insights-backend/README.md | 25 ++++ plugins/tech-insights-backend/api-report.md | 5 + plugins/tech-insights-backend/config.d.ts | 37 +++++ plugins/tech-insights-backend/package.json | 5 +- plugins/tech-insights-backend/src/index.ts | 1 + .../src/plugin/config.test.ts | 76 +++++++++++ .../src/plugin/config.ts | 94 +++++++++++++ .../tech-insights-backend/src/plugin/index.ts | 17 +++ .../src/plugin/plugin.test.ts | 50 +++++++ .../src/plugin/plugin.ts | 128 ++++++++++++++++++ plugins/tech-insights-node/api-report.md | 24 ++++ plugins/tech-insights-node/package.json | 1 + .../tech-insights-node/src/extensionPoints.ts | 59 ++++++++ plugins/tech-insights-node/src/index.ts | 1 + yarn.lock | 4 + 16 files changed, 541 insertions(+), 1 deletion(-) create mode 100644 .changeset/green-dogs-fold.md create mode 100644 plugins/tech-insights-backend/config.d.ts create mode 100644 plugins/tech-insights-backend/src/plugin/config.test.ts create mode 100644 plugins/tech-insights-backend/src/plugin/config.ts create mode 100644 plugins/tech-insights-backend/src/plugin/index.ts create mode 100644 plugins/tech-insights-backend/src/plugin/plugin.test.ts create mode 100644 plugins/tech-insights-backend/src/plugin/plugin.ts create mode 100644 plugins/tech-insights-node/src/extensionPoints.ts diff --git a/.changeset/green-dogs-fold.md b/.changeset/green-dogs-fold.md new file mode 100644 index 0000000000..c23f7c2a29 --- /dev/null +++ b/.changeset/green-dogs-fold.md @@ -0,0 +1,15 @@ +--- +'@backstage/plugin-tech-insights-backend': patch +'@backstage/plugin-tech-insights-node': patch +--- + +Add support for the new backend system. + +A new backend plugin for the tech-insights backend +was added and exported as `default`. + +You can use it with the new backend system like + +```ts title="packages/backend/src/index.ts" +backend.add(import('@backstage/plugin-tech-insights-backend')); +``` diff --git a/plugins/tech-insights-backend/README.md b/plugins/tech-insights-backend/README.md index 95fa3ccda9..29b374c18c 100644 --- a/plugins/tech-insights-backend/README.md +++ b/plugins/tech-insights-backend/README.md @@ -15,6 +15,31 @@ yarn add --cwd packages/backend @backstage/plugin-tech-insights-backend ### Adding the plugin to your `packages/backend` +```ts title="packages/backend/src/index.ts" +backend.add(import('@backstage/plugin-tech-insights-backend')); +``` + +You can use the extension points [@backstage/plugin-tech-insights-node](../tech-insights-node) +to add your `FactRetriever` or set a `FactCheckerFactory`. + +The built-in `FactRetrievers`: + +- `entityMetadataFactRetriever` +- `entityOwnershipFactRetriever` +- `techdocsFactRetriever` + +`FactRetrievers` only get registered if they get configured: + +```yaml title="app-config.yaml" +techInsights: + factRetrievers: + entityOwnershipFactRetriever: + cadence: '*/15 * * * *' + lifecycle: { timeToLive: { weeks: 2 } } +``` + +### Adding the plugin to your `packages/backend` (old) + You'll need to add the plugin to the router in your `backend` package. You can do this by creating a file called `packages/backend/src/plugins/techInsights.ts`. An example content for `techInsights.ts` could be something like this. diff --git a/plugins/tech-insights-backend/api-report.md b/plugins/tech-insights-backend/api-report.md index 4b6e5b34dc..fd12dfed24 100644 --- a/plugins/tech-insights-backend/api-report.md +++ b/plugins/tech-insights-backend/api-report.md @@ -3,6 +3,7 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { BackendFeature } from '@backstage/backend-plugin-api'; import { CheckResult } from '@backstage/plugin-tech-insights-common'; import { Config } from '@backstage/config'; import { Duration } from 'luxon'; @@ -140,5 +141,9 @@ export interface TechInsightsOptions< tokenManager: TokenManager; } +// @public +const techInsightsPlugin: () => BackendFeature; +export default techInsightsPlugin; + // (No @packageDocumentation comment for this package) ``` diff --git a/plugins/tech-insights-backend/config.d.ts b/plugins/tech-insights-backend/config.d.ts new file mode 100644 index 0000000000..cb3602dc60 --- /dev/null +++ b/plugins/tech-insights-backend/config.d.ts @@ -0,0 +1,37 @@ +/* + * Copyright 2024 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 { HumanDuration } from '@backstage/types'; + +export interface Config { + /** Configuration options for the tech-insights plugin */ + techInsights?: { + /** Configuration options for fact retrievers */ + factRetrievers?: { + /** Configuration for a fact retriever and its registration identified by its name. */ + [name: string]: { + /** A cron expression to indicate when the fact retriever should be triggered. */ + cadence: string; + /** Optional duration of the initial delay. */ + initialDelay?: HumanDuration; + /** Optional lifecycle definition indicating the cleanup logic of facts when this retriever is run. */ + lifecycle?: { timeToLive: HumanDuration } | { maxItems: number }; + /** Optional duration to determine how long the fact retriever should be allowed to run, defaults to 5 minutes. */ + timeout?: HumanDuration; + }; + }; + }; +} diff --git a/plugins/tech-insights-backend/package.json b/plugins/tech-insights-backend/package.json index 50d86931ee..5cadc140bc 100644 --- a/plugins/tech-insights-backend/package.json +++ b/plugins/tech-insights-backend/package.json @@ -34,6 +34,7 @@ }, "dependencies": { "@backstage/backend-common": "workspace:^", + "@backstage/backend-plugin-api": "workspace:^", "@backstage/backend-tasks": "workspace:^", "@backstage/catalog-client": "workspace:^", "@backstage/catalog-model": "workspace:^", @@ -63,7 +64,9 @@ "wait-for-expect": "^3.0.2" }, "files": [ + "config.d.ts", "dist", "migrations/**/*.{js,d.ts}" - ] + ], + "configSchema": "config.d.ts" } diff --git a/plugins/tech-insights-backend/src/index.ts b/plugins/tech-insights-backend/src/index.ts index d6b845a78d..2e87a53dfd 100644 --- a/plugins/tech-insights-backend/src/index.ts +++ b/plugins/tech-insights-backend/src/index.ts @@ -14,4 +14,5 @@ * limitations under the License. */ +export { techInsightsPlugin as default } from './plugin'; export * from './service'; diff --git a/plugins/tech-insights-backend/src/plugin/config.test.ts b/plugins/tech-insights-backend/src/plugin/config.test.ts new file mode 100644 index 0000000000..09b4954583 --- /dev/null +++ b/plugins/tech-insights-backend/src/plugin/config.test.ts @@ -0,0 +1,76 @@ +/* + * Copyright 2024 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 { ConfigReader } from '@backstage/config'; +import { FactRetriever, TTL } from '@backstage/plugin-tech-insights-node'; +import { createFactRetrieverRegistrationFromConfig } from './config'; + +describe('config', () => { + const mockFactRetriever = jest.fn() as unknown as FactRetriever; + + describe('createFactRetrieverRegistrationFromConfig', () => { + it('no config return undefined', () => { + const config = new ConfigReader({}); + const registration = createFactRetrieverRegistrationFromConfig( + config, + 'any', + mockFactRetriever, + ); + + expect(registration).toBeUndefined(); + }); + + it('no entry for fact retriever return undefined', () => { + const config = new ConfigReader({ + techInsights: { + factRetrievers: {}, + }, + }); + const registration = createFactRetrieverRegistrationFromConfig( + config, + 'any', + mockFactRetriever, + ); + + expect(registration).toBeUndefined(); + }); + + it('with config for fact retriever return registration', () => { + const config = new ConfigReader({ + techInsights: { + factRetrievers: { + any: { + cadence: '*/15 * * * *', + lifecycle: { timeToLive: { weeks: 2 } }, + }, + }, + }, + }); + + const registration = createFactRetrieverRegistrationFromConfig( + config, + 'any', + mockFactRetriever, + ); + + expect(registration).toBeDefined(); + expect(registration!.cadence).toEqual('*/15 * * * *'); + expect((registration!.lifecycle! as TTL).timeToLive).toEqual({ + weeks: 2, + }); + }); + }); +}); diff --git a/plugins/tech-insights-backend/src/plugin/config.ts b/plugins/tech-insights-backend/src/plugin/config.ts new file mode 100644 index 0000000000..25827fbbc5 --- /dev/null +++ b/plugins/tech-insights-backend/src/plugin/config.ts @@ -0,0 +1,94 @@ +/* + * Copyright 2024 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 { Config, readDurationFromConfig } from '@backstage/config'; +import { + FactLifecycle, + FactRetriever, + FactRetrieverRegistration, +} from '@backstage/plugin-tech-insights-node'; +import { + createFactRetrieverRegistration, + FactRetrieverRegistrationOptions, +} from '../service'; + +type FactRetrieverConfig = Omit< + FactRetrieverRegistrationOptions, + 'factRetriever' +>; + +function readLifecycleConfig( + config: Config | undefined, +): FactLifecycle | undefined { + if (!config) { + return undefined; + } + + if (config.has('maxItems')) { + return { + maxItems: config.getNumber('maxItems'), + }; + } + + return { + timeToLive: readDurationFromConfig(config.getConfig('timeToLive')), + }; +} + +function readFactRetrieverConfig( + config: Config, + name: string, +): FactRetrieverConfig | undefined { + const factRetrieverConfig = config.getOptionalConfig( + `techInsights.factRetrievers.${name}`, + ); + if (!factRetrieverConfig) { + return undefined; + } + + const cadence = factRetrieverConfig.getString('cadence'); + const initialDelay = factRetrieverConfig.has('initialDelay') + ? readDurationFromConfig(factRetrieverConfig.getConfig('initialDelay')) + : undefined; + const lifecycle = readLifecycleConfig( + factRetrieverConfig.getOptionalConfig('lifecycle'), + ); + const timeout = factRetrieverConfig.has('timeout') + ? readDurationFromConfig(factRetrieverConfig.getConfig('timeout')) + : undefined; + + return { + cadence, + initialDelay, + lifecycle, + timeout, + }; +} + +export function createFactRetrieverRegistrationFromConfig( + config: Config, + name: string, + factRetriever: FactRetriever, +): FactRetrieverRegistration | undefined { + const factRetrieverConfig = readFactRetrieverConfig(config, name); + + return factRetrieverConfig + ? createFactRetrieverRegistration({ + ...factRetrieverConfig, + factRetriever, + }) + : undefined; +} diff --git a/plugins/tech-insights-backend/src/plugin/index.ts b/plugins/tech-insights-backend/src/plugin/index.ts new file mode 100644 index 0000000000..dd13bde435 --- /dev/null +++ b/plugins/tech-insights-backend/src/plugin/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2024 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 './plugin'; diff --git a/plugins/tech-insights-backend/src/plugin/plugin.test.ts b/plugins/tech-insights-backend/src/plugin/plugin.test.ts new file mode 100644 index 0000000000..66d06b827e --- /dev/null +++ b/plugins/tech-insights-backend/src/plugin/plugin.test.ts @@ -0,0 +1,50 @@ +/* + * Copyright 2024 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 { mockServices, startTestBackend } from '@backstage/backend-test-utils'; +import { techInsightsPlugin } from './plugin'; + +describe('techInsightsPlugin', () => { + it('should register tech-insights plugin and its router', async () => { + const httpRouterMock = mockServices.httpRouter.mock(); + + await startTestBackend({ + extensionPoints: [], + features: [ + techInsightsPlugin(), + httpRouterMock.factory, + mockServices.database.factory(), + mockServices.logger.factory(), + mockServices.rootConfig.factory({ + data: { + techInsights: { + factRetrievers: { + entityOwnershipFactRetriever: { + cadence: '*/15 * * * *', + lifecycle: { timeToLive: { weeks: 2 } }, + }, + }, + }, + }, + }), + mockServices.scheduler.factory(), + mockServices.tokenManager.factory(), + ], + }); + + expect(httpRouterMock.use).toHaveBeenCalledTimes(1); + }); +}); diff --git a/plugins/tech-insights-backend/src/plugin/plugin.ts b/plugins/tech-insights-backend/src/plugin/plugin.ts new file mode 100644 index 0000000000..f443075a74 --- /dev/null +++ b/plugins/tech-insights-backend/src/plugin/plugin.ts @@ -0,0 +1,128 @@ +/* + * Copyright 2024 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 { loggerToWinstonLogger } from '@backstage/backend-common'; +import { + coreServices, + createBackendPlugin, +} from '@backstage/backend-plugin-api'; +import { CheckResult } from '@backstage/plugin-tech-insights-common'; +import { + FactCheckerFactory, + FactRetriever, + FactRetrieverRegistration, + TechInsightCheck, + techInsightsFactCheckerFactoryExtensionPoint, + techInsightsFactRetrieversExtensionPoint, +} from '@backstage/plugin-tech-insights-node'; +import { + buildTechInsightsContext, + createRouter, + entityMetadataFactRetriever, + entityOwnershipFactRetriever, + techdocsFactRetriever, +} from '../service'; +import { createFactRetrieverRegistrationFromConfig } from './config'; + +/** + * The tech-insights backend plugin. + * + * @public + */ +export const techInsightsPlugin = createBackendPlugin({ + pluginId: 'tech-insights', + register(env) { + let factCheckerFactory: + | FactCheckerFactory + | undefined = undefined; + env.registerExtensionPoint(techInsightsFactCheckerFactoryExtensionPoint, { + setFactCheckerFactory< + CheckType extends TechInsightCheck, + CheckResultType extends CheckResult, + >(factory: FactCheckerFactory): void { + factCheckerFactory = factory; + }, + }); + + // initialized with built-in fact retrievers + // only added as registration if there is config for them + const addedFactRetrievers: Record = { + entityMetadataFactRetriever, + entityOwnershipFactRetriever, + techdocsFactRetriever, + }; + env.registerExtensionPoint(techInsightsFactRetrieversExtensionPoint, { + addFactRetrievers(factRetrievers: Record): void { + Object.entries(factRetrievers).forEach(([key, value]) => { + addedFactRetrievers[key] = value; + }); + }, + }); + + env.registerInit({ + deps: { + config: coreServices.rootConfig, + database: coreServices.database, + discovery: coreServices.discovery, + httpRouter: coreServices.httpRouter, + logger: coreServices.logger, + scheduler: coreServices.scheduler, + tokenManager: coreServices.tokenManager, + }, + async init({ + config, + database, + discovery, + httpRouter, + logger, + scheduler, + tokenManager, + }) { + const winstonLogger = loggerToWinstonLogger(logger); + const factRetrievers: FactRetrieverRegistration[] = Object.entries( + addedFactRetrievers, + ) + .map(([name, factRetriever]) => + createFactRetrieverRegistrationFromConfig( + config, + name, + factRetriever, + ), + ) + .filter(registration => registration) as FactRetrieverRegistration[]; + + const context = await buildTechInsightsContext({ + config, + database, + discovery, + factCheckerFactory, + factRetrievers, + logger: winstonLogger, + scheduler, + tokenManager, + }); + + httpRouter.use( + await createRouter({ + ...context, + config, + logger: winstonLogger, + }), + ); + }, + }); + }, +}); diff --git a/plugins/tech-insights-node/api-report.md b/plugins/tech-insights-node/api-report.md index dbb8a8b674..0d0d835b95 100644 --- a/plugins/tech-insights-node/api-report.md +++ b/plugins/tech-insights-node/api-report.md @@ -8,6 +8,7 @@ import { Config } from '@backstage/config'; import { DateTime } from 'luxon'; import { Duration } from 'luxon'; import { DurationLike } from 'luxon'; +import { ExtensionPoint } from '@backstage/backend-plugin-api'; import { FactSchema } from '@backstage/plugin-tech-insights-common'; import { HumanDuration } from '@backstage/types'; import { JsonValue } from '@backstage/types'; @@ -137,6 +138,29 @@ export type TechInsightFact = { timestamp?: DateTime; }; +// @public (undocumented) +export interface TechInsightsFactCheckerFactoryExtensionPoint { + // (undocumented) + setFactCheckerFactory< + CheckType extends TechInsightCheck, + CheckResultType extends CheckResult, + >( + factory: FactCheckerFactory, + ): void; +} + +// @public +export const techInsightsFactCheckerFactoryExtensionPoint: ExtensionPoint; + +// @public (undocumented) +export interface TechInsightsFactRetrieversExtensionPoint { + // (undocumented) + addFactRetrievers(factRetrievers: Record): void; +} + +// @public +export const techInsightsFactRetrieversExtensionPoint: ExtensionPoint; + // @public export interface TechInsightsStore { getFactsBetweenTimestampsByIds( diff --git a/plugins/tech-insights-node/package.json b/plugins/tech-insights-node/package.json index 4cd03c38a7..e8f71558d7 100644 --- a/plugins/tech-insights-node/package.json +++ b/plugins/tech-insights-node/package.json @@ -33,6 +33,7 @@ }, "dependencies": { "@backstage/backend-common": "workspace:^", + "@backstage/backend-plugin-api": "workspace:^", "@backstage/config": "workspace:^", "@backstage/plugin-tech-insights-common": "workspace:^", "@backstage/types": "workspace:^", diff --git a/plugins/tech-insights-node/src/extensionPoints.ts b/plugins/tech-insights-node/src/extensionPoints.ts new file mode 100644 index 0000000000..527aac7d5d --- /dev/null +++ b/plugins/tech-insights-node/src/extensionPoints.ts @@ -0,0 +1,59 @@ +/* + * Copyright 2024 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 { createExtensionPoint } from '@backstage/backend-plugin-api'; +import { CheckResult } from '@backstage/plugin-tech-insights-common'; +import { FactRetriever } from './facts'; +import { FactCheckerFactory, TechInsightCheck } from './checks'; + +/** + * @public + */ +export interface TechInsightsFactRetrieversExtensionPoint { + addFactRetrievers(factRetrievers: Record): void; +} + +/** + * An extension point that allows other plugins or modules to add fact retrievers. + * + * @public + */ +export const techInsightsFactRetrieversExtensionPoint = + createExtensionPoint({ + id: 'tech-insights.fact-retrievers', + }); + +/** + * @public + */ +export interface TechInsightsFactCheckerFactoryExtensionPoint { + setFactCheckerFactory< + CheckType extends TechInsightCheck, + CheckResultType extends CheckResult, + >( + factory: FactCheckerFactory, + ): void; +} + +/** + * An extension point that allows other plugins or modules to set a FactCheckerFactory. + * + * @public + */ +export const techInsightsFactCheckerFactoryExtensionPoint = + createExtensionPoint({ + id: 'tech-insights.fact-checker-factory', + }); diff --git a/plugins/tech-insights-node/src/index.ts b/plugins/tech-insights-node/src/index.ts index 70ef27e159..c1c321f925 100644 --- a/plugins/tech-insights-node/src/index.ts +++ b/plugins/tech-insights-node/src/index.ts @@ -15,5 +15,6 @@ */ export * from './checks'; +export * from './extensionPoints'; export * from './facts'; export * from './persistence'; diff --git a/yarn.lock b/yarn.lock index 1d31e0fc44..171c5107b8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9089,9 +9089,11 @@ __metadata: dependencies: "@backstage/backend-common": "workspace:^" "@backstage/cli": "workspace:^" + "@backstage/config": "workspace:^" "@backstage/errors": "workspace:^" "@backstage/plugin-tech-insights-common": "workspace:^" "@backstage/plugin-tech-insights-node": "workspace:^" + "@backstage/types": "workspace:^" ajv: ^8.10.0 json-rules-engine: ^6.1.2 lodash: ^4.17.21 @@ -9105,6 +9107,7 @@ __metadata: resolution: "@backstage/plugin-tech-insights-backend@workspace:plugins/tech-insights-backend" dependencies: "@backstage/backend-common": "workspace:^" + "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-tasks": "workspace:^" "@backstage/backend-test-utils": "workspace:^" "@backstage/catalog-client": "workspace:^" @@ -9149,6 +9152,7 @@ __metadata: resolution: "@backstage/plugin-tech-insights-node@workspace:plugins/tech-insights-node" dependencies: "@backstage/backend-common": "workspace:^" + "@backstage/backend-plugin-api": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" "@backstage/plugin-tech-insights-common": "workspace:^" From 25cfb763beee12e7818c90ac31a128f9e2a89eb4 Mon Sep 17 00:00:00 2001 From: Patrick Jungermann Date: Tue, 9 Jan 2024 00:27:58 +0100 Subject: [PATCH 4/5] feat(tech-insights): add module for new backend system A new backend module for the tech-insights backend was added and exported as `default` The module will register the `JsonRulesEngineFactCheckerFactory` as `FactCheckerFactory`, loading checks from the config. The plugin can be used like ```ts title="packages/backend/src/index.ts" backend.add(import('@backstage/plugin-tech-insights-backend-module-jsonfc')); ``` Signed-off-by: Patrick Jungermann --- .changeset/dirty-plums-fix.md | 17 +++++ .../README.md | 14 +++- .../api-report.md | 5 ++ .../package.json | 2 + .../src/index.ts | 1 + .../src/module/index.ts | 17 +++++ ...eJsonRulesEngineFactCheckerFactory.test.ts | 68 +++++++++++++++++++ ...ModuleJsonRulesEngineFactCheckerFactory.ts | 51 ++++++++++++++ yarn.lock | 2 + 9 files changed, 175 insertions(+), 2 deletions(-) create mode 100644 .changeset/dirty-plums-fix.md create mode 100644 plugins/tech-insights-backend-module-jsonfc/src/module/index.ts create mode 100644 plugins/tech-insights-backend-module-jsonfc/src/module/techInsightsModuleJsonRulesEngineFactCheckerFactory.test.ts create mode 100644 plugins/tech-insights-backend-module-jsonfc/src/module/techInsightsModuleJsonRulesEngineFactCheckerFactory.ts diff --git a/.changeset/dirty-plums-fix.md b/.changeset/dirty-plums-fix.md new file mode 100644 index 0000000000..fb0655377c --- /dev/null +++ b/.changeset/dirty-plums-fix.md @@ -0,0 +1,17 @@ +--- +'@backstage/plugin-tech-insights-backend-module-jsonfc': patch +--- + +Add support for the new backend system. + +A new backend module for the tech-insights backend +was added and exported as `default`. + +The module will register the `JsonRulesEngineFactCheckerFactory` +as `FactCheckerFactory`, loading checks from the config. + +You can use it with the new backend system like + +```ts title="packages/backend/src/index.ts" +backend.add(import('@backstage/plugin-tech-insights-backend-module-jsonfc')); +``` diff --git a/plugins/tech-insights-backend-module-jsonfc/README.md b/plugins/tech-insights-backend-module-jsonfc/README.md index 3f9e91408c..c405ac34ef 100644 --- a/plugins/tech-insights-backend-module-jsonfc/README.md +++ b/plugins/tech-insights-backend-module-jsonfc/README.md @@ -13,7 +13,17 @@ To add this FactChecker into your Tech Insights you need to install the module i yarn add --cwd packages/backend @backstage/plugin-tech-insights-backend-module-jsonfc ``` -and modify the `techInsights.ts` file to contain a reference to the FactCheckers implementation. +### Add to the backend + +```ts title="packages/backend/src/index.ts" +backend.add(import('@backstage/plugin-tech-insights-backend-module-jsonfc')); +``` + +This setup requires checks to be provided using the config. + +### Add to the backend (old) + +Modify the `techInsights.ts` file to contain a reference to the FactCheckers implementation. ```diff +import { JsonRulesEngineFactCheckerFactory } from '@backstage/plugin-tech-insights-backend-module-jsonfc'; @@ -34,7 +44,7 @@ and modify the `techInsights.ts` file to contain a reference to the FactCheckers }); ``` -By default this implementation comes with an in-memory storage to store checks. You can inject an additional data store by adding an implementation of `TechInsightCheckRegistry` into the constructor options when creating a `JsonRulesEngineFactCheckerFactory`. That can be done as follows +By default, this implementation comes with an in-memory storage to store checks. You can inject an additional data store by adding an implementation of `TechInsightCheckRegistry` into the constructor options when creating a `JsonRulesEngineFactCheckerFactory`. That can be done as follows ```diff const myTechInsightCheckRegistry: TechInsightCheckRegistry = // snip diff --git a/plugins/tech-insights-backend-module-jsonfc/api-report.md b/plugins/tech-insights-backend-module-jsonfc/api-report.md index d28ee28f8d..790aa0b666 100644 --- a/plugins/tech-insights-backend-module-jsonfc/api-report.md +++ b/plugins/tech-insights-backend-module-jsonfc/api-report.md @@ -3,6 +3,7 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { BackendFeature } from '@backstage/backend-plugin-api'; import { BooleanCheckResult } from '@backstage/plugin-tech-insights-common'; import { CheckResponse } from '@backstage/plugin-tech-insights-common'; import { CheckValidationResponse } from '@backstage/plugin-tech-insights-node'; @@ -110,5 +111,9 @@ export interface TechInsightJsonRuleCheck extends TechInsightCheck { rule: Rule; } +// @public +const techInsightsModuleJsonRulesEngineFactCheckerFactory: () => BackendFeature; +export default techInsightsModuleJsonRulesEngineFactCheckerFactory; + // (No @packageDocumentation comment for this package) ``` diff --git a/plugins/tech-insights-backend-module-jsonfc/package.json b/plugins/tech-insights-backend-module-jsonfc/package.json index 431a7152d4..c2231f4c7e 100644 --- a/plugins/tech-insights-backend-module-jsonfc/package.json +++ b/plugins/tech-insights-backend-module-jsonfc/package.json @@ -34,6 +34,7 @@ }, "dependencies": { "@backstage/backend-common": "workspace:^", + "@backstage/backend-plugin-api": "workspace:^", "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", "@backstage/plugin-tech-insights-common": "workspace:^", @@ -46,6 +47,7 @@ "winston": "^3.2.1" }, "devDependencies": { + "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^" }, "files": [ diff --git a/plugins/tech-insights-backend-module-jsonfc/src/index.ts b/plugins/tech-insights-backend-module-jsonfc/src/index.ts index 6462efc403..1c1e8ab77d 100644 --- a/plugins/tech-insights-backend-module-jsonfc/src/index.ts +++ b/plugins/tech-insights-backend-module-jsonfc/src/index.ts @@ -15,6 +15,7 @@ */ export { JSON_RULE_ENGINE_CHECK_TYPE } from './constants'; +export { techInsightsModuleJsonRulesEngineFactCheckerFactory as default } from './module'; export * from './service'; export type { JsonRuleCheckResponse, diff --git a/plugins/tech-insights-backend-module-jsonfc/src/module/index.ts b/plugins/tech-insights-backend-module-jsonfc/src/module/index.ts new file mode 100644 index 0000000000..0fa167e883 --- /dev/null +++ b/plugins/tech-insights-backend-module-jsonfc/src/module/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2024 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 './techInsightsModuleJsonRulesEngineFactCheckerFactory'; diff --git a/plugins/tech-insights-backend-module-jsonfc/src/module/techInsightsModuleJsonRulesEngineFactCheckerFactory.test.ts b/plugins/tech-insights-backend-module-jsonfc/src/module/techInsightsModuleJsonRulesEngineFactCheckerFactory.test.ts new file mode 100644 index 0000000000..b6df48a8b2 --- /dev/null +++ b/plugins/tech-insights-backend-module-jsonfc/src/module/techInsightsModuleJsonRulesEngineFactCheckerFactory.test.ts @@ -0,0 +1,68 @@ +/* + * Copyright 2024 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 { mockServices, startTestBackend } from '@backstage/backend-test-utils'; +import { techInsightsFactCheckerFactoryExtensionPoint } from '@backstage/plugin-tech-insights-node'; +import { JSON_RULE_ENGINE_CHECK_TYPE } from '../constants'; +import { techInsightsModuleJsonRulesEngineFactCheckerFactory } from './techInsightsModuleJsonRulesEngineFactCheckerFactory'; + +describe('techInsightsModuleJsonRulesEngineFactCheckerFactory', () => { + it('should register the factory', async () => { + const extensionPoint = { + setFactCheckerFactory: jest.fn(), + } satisfies Partial; + + await startTestBackend({ + extensionPoints: [ + [techInsightsFactCheckerFactoryExtensionPoint, extensionPoint], + ], + features: [ + techInsightsModuleJsonRulesEngineFactCheckerFactory(), + mockServices.logger.factory(), + mockServices.rootConfig.factory({ + data: { + techInsights: { + factChecker: { + checks: { + groupOwnerCheck: { + type: JSON_RULE_ENGINE_CHECK_TYPE, + name: 'Group Owner Check', + description: + 'Verifies that a group has been set as the spec.owner for this entity', + factIds: ['entityOwnershipFactRetriever'], + rule: { + conditions: { + all: [ + { + fact: 'hasGroupOwner', + operator: 'equal', + value: true, + }, + ], + }, + }, + }, + }, + }, + }, + }, + }), + ], + }); + + expect(extensionPoint.setFactCheckerFactory).toHaveBeenCalled(); + }); +}); diff --git a/plugins/tech-insights-backend-module-jsonfc/src/module/techInsightsModuleJsonRulesEngineFactCheckerFactory.ts b/plugins/tech-insights-backend-module-jsonfc/src/module/techInsightsModuleJsonRulesEngineFactCheckerFactory.ts new file mode 100644 index 0000000000..8527b91d9d --- /dev/null +++ b/plugins/tech-insights-backend-module-jsonfc/src/module/techInsightsModuleJsonRulesEngineFactCheckerFactory.ts @@ -0,0 +1,51 @@ +/* + * Copyright 2024 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 { loggerToWinstonLogger } from '@backstage/backend-common'; +import { + coreServices, + createBackendModule, +} from '@backstage/backend-plugin-api'; +import { techInsightsFactCheckerFactoryExtensionPoint } from '@backstage/plugin-tech-insights-node'; +import { JsonRulesEngineFactCheckerFactory } from '../service'; + +/** + * Sets a JsonRulesEngineFactCheckerFactory as FactCheckerFactory + * loading checks from the config. + * + * @public + */ +export const techInsightsModuleJsonRulesEngineFactCheckerFactory = + createBackendModule({ + pluginId: 'tech-insights', + moduleId: 'json-rules-engine-fact-checker-factory', + register(env) { + env.registerInit({ + deps: { + config: coreServices.rootConfig, + logger: coreServices.logger, + techInsights: techInsightsFactCheckerFactoryExtensionPoint, + }, + async init({ config, logger, techInsights }) { + const winstonLogger = loggerToWinstonLogger(logger); + const factory = JsonRulesEngineFactCheckerFactory.fromConfig(config, { + logger: winstonLogger, + }); + techInsights.setFactCheckerFactory(factory); + }, + }); + }, + }); diff --git a/yarn.lock b/yarn.lock index 171c5107b8..db8db76869 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9088,6 +9088,8 @@ __metadata: resolution: "@backstage/plugin-tech-insights-backend-module-jsonfc@workspace:plugins/tech-insights-backend-module-jsonfc" dependencies: "@backstage/backend-common": "workspace:^" + "@backstage/backend-plugin-api": "workspace:^" + "@backstage/backend-test-utils": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" "@backstage/errors": "workspace:^" From 341c2a24a0732a2003bff5741dadc264e044225c Mon Sep 17 00:00:00 2001 From: Patrick Jungermann Date: Tue, 9 Jan 2024 12:28:31 +0100 Subject: [PATCH 5/5] feat(tech-insights): support registry and persistence at plugin Support `FactRetrieverRegistry` and `PersistenceContext` at the `techInsightsPlugin` using extension points. Additionally, move `FactRetrieverRegistry` and `PersistenceContext` to `@backstage/plugin-tech-insights-node`. Original exports are marked as deprecated and re-export the moved types. Signed-off-by: Patrick Jungermann --- .changeset/orange-gorillas-clean.md | 26 +++++++++++++ plugins/tech-insights-backend/api-report.md | 35 ++++++------------ .../tech-insights-backend/src/deprecated.ts | 32 ++++++++++++++++ plugins/tech-insights-backend/src/index.ts | 1 + .../src/plugin/plugin.ts | 23 ++++++++++++ .../service/fact/FactRetrieverEngine.test.ts | 2 +- .../src/service/fact/FactRetrieverEngine.ts | 2 +- .../src/service/fact/FactRetrieverRegistry.ts | 13 +------ .../src/service/fact/index.ts | 1 - .../src/service/persistence/index.ts | 5 +-- .../service/persistence/persistenceContext.ts | 11 +----- .../src/service/router.test.ts | 6 ++- .../src/service/router.ts | 2 +- .../src/service/techInsightsContextBuilder.ts | 12 ++---- plugins/tech-insights-node/api-report.md | 37 +++++++++++++++++++ .../tech-insights-node/src/extensionPoints.ts | 37 ++++++++++++++++++- plugins/tech-insights-node/src/facts.ts | 11 ++++++ plugins/tech-insights-node/src/persistence.ts | 9 +++++ 18 files changed, 200 insertions(+), 65 deletions(-) create mode 100644 .changeset/orange-gorillas-clean.md create mode 100644 plugins/tech-insights-backend/src/deprecated.ts diff --git a/.changeset/orange-gorillas-clean.md b/.changeset/orange-gorillas-clean.md new file mode 100644 index 0000000000..d346adce6c --- /dev/null +++ b/.changeset/orange-gorillas-clean.md @@ -0,0 +1,26 @@ +--- +'@backstage/plugin-tech-insights-backend': patch +'@backstage/plugin-tech-insights-node': patch +--- + +Move `FactRetrieverRegistry` and `PersistenceContext` to `@backstage/plugin-tech-insights-node`. + +Original exports are marked as deprecated and re-export the moved types. + +Please replace uses like + +```ts +import { + FactRetrieverRegistry, + PersistenceContext, +} from '@backstage/plugin-tech-insights-backend'; +``` + +with + +```ts +import { + FactRetrieverRegistry, + PersistenceContext, +} from '@backstage/plugin-tech-insights-node'; +``` diff --git a/plugins/tech-insights-backend/api-report.md b/plugins/tech-insights-backend/api-report.md index fd12dfed24..631e792141 100644 --- a/plugins/tech-insights-backend/api-report.md +++ b/plugins/tech-insights-backend/api-report.md @@ -13,14 +13,14 @@ import { FactCheckerFactory } from '@backstage/plugin-tech-insights-node'; import { FactLifecycle } from '@backstage/plugin-tech-insights-node'; import { FactRetriever } from '@backstage/plugin-tech-insights-node'; import { FactRetrieverRegistration } from '@backstage/plugin-tech-insights-node'; -import { FactSchema } from '@backstage/plugin-tech-insights-common'; +import { FactRetrieverRegistry as FactRetrieverRegistry_2 } from '@backstage/plugin-tech-insights-node'; import { HumanDuration } from '@backstage/types'; import { Logger } from 'winston'; +import { PersistenceContext as PersistenceContext_2 } from '@backstage/plugin-tech-insights-node'; 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'; import { TokenManager } from '@backstage/backend-common'; // @public @@ -64,30 +64,17 @@ export type FactRetrieverRegistrationOptions = { initialDelay?: Duration | HumanDuration; }; -// @public (undocumented) -export interface FactRetrieverRegistry { - // (undocumented) - get(retrieverReference: string): Promise; - // (undocumented) - getSchemas(): Promise; - // (undocumented) - listRegistrations(): Promise; - // (undocumented) - listRetrievers(): Promise; - // (undocumented) - register(registration: FactRetrieverRegistration): Promise; -} +// @public @deprecated (undocumented) +export type FactRetrieverRegistry = FactRetrieverRegistry_2; // @public export const initializePersistenceContext: ( database: PluginDatabaseManager, options?: PersistenceContextOptions, -) => Promise; +) => Promise; -// @public -export type PersistenceContext = { - techInsightsStore: TechInsightsStore; -}; +// @public @deprecated (undocumented) +export type PersistenceContext = PersistenceContext_2; // @public export type PersistenceContextOptions = { @@ -102,7 +89,7 @@ export interface RouterOptions< config: Config; factChecker?: FactChecker; logger: Logger; - persistenceContext: PersistenceContext; + persistenceContext: PersistenceContext_2; } // @public @@ -114,7 +101,7 @@ export type TechInsightsContext< CheckResultType extends CheckResult, > = { factChecker?: FactChecker; - persistenceContext: PersistenceContext; + persistenceContext: PersistenceContext_2; factRetrieverEngine: FactRetrieverEngine; }; @@ -130,11 +117,11 @@ export interface TechInsightsOptions< // (undocumented) discovery: PluginEndpointDiscovery; factCheckerFactory?: FactCheckerFactory; - factRetrieverRegistry?: FactRetrieverRegistry; + factRetrieverRegistry?: FactRetrieverRegistry_2; factRetrievers?: FactRetrieverRegistration[]; // (undocumented) logger: Logger; - persistenceContext?: PersistenceContext; + persistenceContext?: PersistenceContext_2; // (undocumented) scheduler: PluginTaskScheduler; // (undocumented) diff --git a/plugins/tech-insights-backend/src/deprecated.ts b/plugins/tech-insights-backend/src/deprecated.ts new file mode 100644 index 0000000000..1074e6f910 --- /dev/null +++ b/plugins/tech-insights-backend/src/deprecated.ts @@ -0,0 +1,32 @@ +/* + * Copyright 2024 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 { + FactRetrieverRegistry as FactRetrieverRegistry_, + PersistenceContext as PersistenceContext_, +} from '@backstage/plugin-tech-insights-node'; + +/** + * @public + * @deprecated Use FactRetrieverRegistry from `@backstage/plugin-tech-insights-node` instead. + */ +export type FactRetrieverRegistry = FactRetrieverRegistry_; + +/** + * @public + * @deprecated Use PersistenceContext from `@backstage/plugin-tech-insights-node` instead. + */ +export type PersistenceContext = PersistenceContext_; diff --git a/plugins/tech-insights-backend/src/index.ts b/plugins/tech-insights-backend/src/index.ts index 2e87a53dfd..a582a04ebf 100644 --- a/plugins/tech-insights-backend/src/index.ts +++ b/plugins/tech-insights-backend/src/index.ts @@ -15,4 +15,5 @@ */ export { techInsightsPlugin as default } from './plugin'; +export * from './deprecated'; export * from './service'; diff --git a/plugins/tech-insights-backend/src/plugin/plugin.ts b/plugins/tech-insights-backend/src/plugin/plugin.ts index f443075a74..6da1ac1637 100644 --- a/plugins/tech-insights-backend/src/plugin/plugin.ts +++ b/plugins/tech-insights-backend/src/plugin/plugin.ts @@ -24,9 +24,13 @@ import { FactCheckerFactory, FactRetriever, FactRetrieverRegistration, + FactRetrieverRegistry, + PersistenceContext, TechInsightCheck, techInsightsFactCheckerFactoryExtensionPoint, + techInsightsFactRetrieverRegistryExtensionPoint, techInsightsFactRetrieversExtensionPoint, + techInsightsPersistenceContextExtensionPoint, } from '@backstage/plugin-tech-insights-node'; import { buildTechInsightsContext, @@ -57,6 +61,16 @@ export const techInsightsPlugin = createBackendPlugin({ }, }); + let factRetrieverRegistry: FactRetrieverRegistry | undefined = undefined; + env.registerExtensionPoint( + techInsightsFactRetrieverRegistryExtensionPoint, + { + setFactRetrieverRegistry(registry: FactRetrieverRegistry): void { + factRetrieverRegistry = registry; + }, + }, + ); + // initialized with built-in fact retrievers // only added as registration if there is config for them const addedFactRetrievers: Record = { @@ -72,6 +86,13 @@ export const techInsightsPlugin = createBackendPlugin({ }, }); + let persistenceContext: PersistenceContext | undefined = undefined; + env.registerExtensionPoint(techInsightsPersistenceContextExtensionPoint, { + setPersistenceContext(context: PersistenceContext): void { + persistenceContext = context; + }, + }); + env.registerInit({ deps: { config: coreServices.rootConfig, @@ -109,8 +130,10 @@ export const techInsightsPlugin = createBackendPlugin({ database, discovery, factCheckerFactory, + factRetrieverRegistry, factRetrievers, logger: winstonLogger, + persistenceContext, scheduler, tokenManager, }); diff --git a/plugins/tech-insights-backend/src/service/fact/FactRetrieverEngine.test.ts b/plugins/tech-insights-backend/src/service/fact/FactRetrieverEngine.test.ts index 677b778b9b..3bbaf9edf5 100644 --- a/plugins/tech-insights-backend/src/service/fact/FactRetrieverEngine.test.ts +++ b/plugins/tech-insights-backend/src/service/fact/FactRetrieverEngine.test.ts @@ -17,11 +17,11 @@ import { FactRetriever, FactRetrieverRegistration, + FactRetrieverRegistry, FactSchemaDefinition, TechInsightFact, TechInsightsStore, } from '@backstage/plugin-tech-insights-node'; -import { FactRetrieverRegistry } from './FactRetrieverRegistry'; import { DefaultFactRetrieverEngine, FactRetrieverEngine, diff --git a/plugins/tech-insights-backend/src/service/fact/FactRetrieverEngine.ts b/plugins/tech-insights-backend/src/service/fact/FactRetrieverEngine.ts index b723fcd7f5..04035ca8c7 100644 --- a/plugins/tech-insights-backend/src/service/fact/FactRetrieverEngine.ts +++ b/plugins/tech-insights-backend/src/service/fact/FactRetrieverEngine.ts @@ -18,10 +18,10 @@ import { FactRetriever, FactRetrieverContext, FactRetrieverRegistration, + FactRetrieverRegistry, TechInsightFact, TechInsightsStore, } from '@backstage/plugin-tech-insights-node'; -import { FactRetrieverRegistry } from './FactRetrieverRegistry'; import { Logger } from 'winston'; import { PluginTaskScheduler } from '@backstage/backend-tasks'; import { Duration } from 'luxon'; diff --git a/plugins/tech-insights-backend/src/service/fact/FactRetrieverRegistry.ts b/plugins/tech-insights-backend/src/service/fact/FactRetrieverRegistry.ts index 8796f1511a..42caca6b20 100644 --- a/plugins/tech-insights-backend/src/service/fact/FactRetrieverRegistry.ts +++ b/plugins/tech-insights-backend/src/service/fact/FactRetrieverRegistry.ts @@ -17,22 +17,11 @@ import { FactRetriever, FactRetrieverRegistration, + FactRetrieverRegistry, } from '@backstage/plugin-tech-insights-node'; import { FactSchema } from '@backstage/plugin-tech-insights-common'; import { ConflictError, NotFoundError } from '@backstage/errors'; -/** - * @public - * - */ -export interface FactRetrieverRegistry { - register(registration: FactRetrieverRegistration): Promise; - get(retrieverReference: string): Promise; - listRetrievers(): Promise; - listRegistrations(): Promise; - getSchemas(): Promise; -} - /** * A basic in memory fact retriever registry. * diff --git a/plugins/tech-insights-backend/src/service/fact/index.ts b/plugins/tech-insights-backend/src/service/fact/index.ts index 0eeb498481..b3df648a8e 100644 --- a/plugins/tech-insights-backend/src/service/fact/index.ts +++ b/plugins/tech-insights-backend/src/service/fact/index.ts @@ -17,5 +17,4 @@ export { createFactRetrieverRegistration } from './createFactRetriever'; export type { FactRetrieverRegistrationOptions } from './createFactRetriever'; export type { FactRetrieverEngine } from './FactRetrieverEngine'; -export type { FactRetrieverRegistry } from './FactRetrieverRegistry'; export * from './factRetrievers'; diff --git a/plugins/tech-insights-backend/src/service/persistence/index.ts b/plugins/tech-insights-backend/src/service/persistence/index.ts index 64cc876026..d87f91e1b0 100644 --- a/plugins/tech-insights-backend/src/service/persistence/index.ts +++ b/plugins/tech-insights-backend/src/service/persistence/index.ts @@ -15,7 +15,4 @@ */ export { initializePersistenceContext } from './persistenceContext'; -export type { - PersistenceContext, - PersistenceContextOptions, -} from './persistenceContext'; +export type { PersistenceContextOptions } from './persistenceContext'; diff --git a/plugins/tech-insights-backend/src/service/persistence/persistenceContext.ts b/plugins/tech-insights-backend/src/service/persistence/persistenceContext.ts index 07badb038c..3ddae3197f 100644 --- a/plugins/tech-insights-backend/src/service/persistence/persistenceContext.ts +++ b/plugins/tech-insights-backend/src/service/persistence/persistenceContext.ts @@ -20,22 +20,13 @@ import { } from '@backstage/backend-common'; import { Logger } from 'winston'; import { TechInsightsDatabase } from './TechInsightsDatabase'; -import { TechInsightsStore } from '@backstage/plugin-tech-insights-node'; +import { PersistenceContext } from '@backstage/plugin-tech-insights-node'; const migrationsDir = resolvePackagePath( '@backstage/plugin-tech-insights-backend', 'migrations', ); -/** - * A Container for persistence related components in TechInsights - * - * @public - */ -export type PersistenceContext = { - techInsightsStore: TechInsightsStore; -}; - /** * A Container for persistence context initialization options * diff --git a/plugins/tech-insights-backend/src/service/router.test.ts b/plugins/tech-insights-backend/src/service/router.test.ts index 6935338a56..bf4313748f 100644 --- a/plugins/tech-insights-backend/src/service/router.test.ts +++ b/plugins/tech-insights-backend/src/service/router.test.ts @@ -24,8 +24,10 @@ import { import { ConfigReader } from '@backstage/config'; import request from 'supertest'; import express from 'express'; -import { PersistenceContext } from './persistence/persistenceContext'; -import { TechInsightsStore } from '@backstage/plugin-tech-insights-node'; +import { + PersistenceContext, + TechInsightsStore, +} from '@backstage/plugin-tech-insights-node'; import { DateTime } from 'luxon'; import { Knex } from 'knex'; import { TaskScheduler } from '@backstage/backend-tasks'; diff --git a/plugins/tech-insights-backend/src/service/router.ts b/plugins/tech-insights-backend/src/service/router.ts index 5e58df1b20..53c1351ffa 100644 --- a/plugins/tech-insights-backend/src/service/router.ts +++ b/plugins/tech-insights-backend/src/service/router.ts @@ -19,13 +19,13 @@ import Router from 'express-promise-router'; import { Config } from '@backstage/config'; import { FactChecker, + PersistenceContext, TechInsightCheck, } 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/persistenceContext'; import { CompoundEntityRef, parseEntityRef, diff --git a/plugins/tech-insights-backend/src/service/techInsightsContextBuilder.ts b/plugins/tech-insights-backend/src/service/techInsightsContextBuilder.ts index 20693d0a7d..4f49e0031b 100644 --- a/plugins/tech-insights-backend/src/service/techInsightsContextBuilder.ts +++ b/plugins/tech-insights-backend/src/service/techInsightsContextBuilder.ts @@ -19,10 +19,7 @@ import { FactRetrieverEngine, } from './fact/FactRetrieverEngine'; import { Logger } from 'winston'; -import { - DefaultFactRetrieverRegistry, - FactRetrieverRegistry, -} from './fact/FactRetrieverRegistry'; +import { DefaultFactRetrieverRegistry } from './fact/FactRetrieverRegistry'; import { Config } from '@backstage/config'; import { PluginDatabaseManager, @@ -33,12 +30,11 @@ import { FactChecker, FactCheckerFactory, FactRetrieverRegistration, + FactRetrieverRegistry, + PersistenceContext, TechInsightCheck, } from '@backstage/plugin-tech-insights-node'; -import { - initializePersistenceContext, - PersistenceContext, -} from './persistence/persistenceContext'; +import { initializePersistenceContext } from './persistence'; import { CheckResult } from '@backstage/plugin-tech-insights-common'; import { PluginTaskScheduler } from '@backstage/backend-tasks'; diff --git a/plugins/tech-insights-node/api-report.md b/plugins/tech-insights-node/api-report.md index 0d0d835b95..13056b5eaa 100644 --- a/plugins/tech-insights-node/api-report.md +++ b/plugins/tech-insights-node/api-report.md @@ -80,6 +80,20 @@ export type FactRetrieverRegistration = { initialDelay?: Duration | HumanDuration; }; +// @public (undocumented) +export interface FactRetrieverRegistry { + // (undocumented) + get(retrieverReference: string): Promise; + // (undocumented) + getSchemas(): Promise; + // (undocumented) + listRegistrations(): Promise; + // (undocumented) + listRetrievers(): Promise; + // (undocumented) + register(registration: FactRetrieverRegistration): Promise; +} + // @public export type FactSchemaDefinition = Omit; @@ -93,6 +107,11 @@ export type MaxItems = { maxItems: number; }; +// @public +export type PersistenceContext = { + techInsightsStore: TechInsightsStore; +}; + // @public export interface TechInsightCheck { description: string; @@ -152,6 +171,15 @@ export interface TechInsightsFactCheckerFactoryExtensionPoint { // @public export const techInsightsFactCheckerFactoryExtensionPoint: ExtensionPoint; +// @public (undocumented) +export interface TechInsightsFactRetrieverRegistryExtensionPoint { + // (undocumented) + setFactRetrieverRegistry(registry: FactRetrieverRegistry): void; +} + +// @public +export const techInsightsFactRetrieverRegistryExtensionPoint: ExtensionPoint; + // @public (undocumented) export interface TechInsightsFactRetrieversExtensionPoint { // (undocumented) @@ -161,6 +189,15 @@ export interface TechInsightsFactRetrieversExtensionPoint { // @public export const techInsightsFactRetrieversExtensionPoint: ExtensionPoint; +// @public (undocumented) +export interface TechInsightsPersistenceContextExtensionPoint { + // (undocumented) + setPersistenceContext(context: PersistenceContext): void; +} + +// @public +export const techInsightsPersistenceContextExtensionPoint: ExtensionPoint; + // @public export interface TechInsightsStore { getFactsBetweenTimestampsByIds( diff --git a/plugins/tech-insights-node/src/extensionPoints.ts b/plugins/tech-insights-node/src/extensionPoints.ts index 527aac7d5d..e1f581a840 100644 --- a/plugins/tech-insights-node/src/extensionPoints.ts +++ b/plugins/tech-insights-node/src/extensionPoints.ts @@ -16,8 +16,9 @@ import { createExtensionPoint } from '@backstage/backend-plugin-api'; import { CheckResult } from '@backstage/plugin-tech-insights-common'; -import { FactRetriever } from './facts'; import { FactCheckerFactory, TechInsightCheck } from './checks'; +import { FactRetriever, FactRetrieverRegistry } from './facts'; +import { PersistenceContext } from './persistence'; /** * @public @@ -57,3 +58,37 @@ export const techInsightsFactCheckerFactoryExtensionPoint = createExtensionPoint({ id: 'tech-insights.fact-checker-factory', }); + +/** + * @public + */ +export interface TechInsightsFactRetrieverRegistryExtensionPoint { + setFactRetrieverRegistry(registry: FactRetrieverRegistry): void; +} + +/** + * An extension point that allows other plugins or modules to set a custom FactRetrieverRegistry. + * + * @public + */ +export const techInsightsFactRetrieverRegistryExtensionPoint = + createExtensionPoint({ + id: 'tech-insights.fact-retriever-registry', + }); + +/** + * @public + */ +export interface TechInsightsPersistenceContextExtensionPoint { + setPersistenceContext(context: PersistenceContext): void; +} + +/** + * An extension point that allows other plugins or modules to set a custom PersistenceContext. + * + * @public + */ +export const techInsightsPersistenceContextExtensionPoint = + createExtensionPoint({ + id: 'tech-insights.persistence-context', + }); diff --git a/plugins/tech-insights-node/src/facts.ts b/plugins/tech-insights-node/src/facts.ts index b35ad756ed..9257ea789e 100644 --- a/plugins/tech-insights-node/src/facts.ts +++ b/plugins/tech-insights-node/src/facts.ts @@ -230,3 +230,14 @@ export type FactRetrieverRegistration = { */ initialDelay?: Duration | HumanDuration; }; + +/** + * @public + */ +export interface FactRetrieverRegistry { + register(registration: FactRetrieverRegistration): Promise; + get(retrieverReference: string): Promise; + listRetrievers(): Promise; + listRegistrations(): Promise; + getSchemas(): Promise; +} diff --git a/plugins/tech-insights-node/src/persistence.ts b/plugins/tech-insights-node/src/persistence.ts index 73abb6c5af..391d23bae5 100644 --- a/plugins/tech-insights-node/src/persistence.ts +++ b/plugins/tech-insights-node/src/persistence.ts @@ -22,6 +22,15 @@ import { import { DateTime } from 'luxon'; import { FactSchema } from '@backstage/plugin-tech-insights-common'; +/** + * A Container for persistence related components in TechInsights + * + * @public + */ +export type PersistenceContext = { + techInsightsStore: TechInsightsStore; +}; + /** * TechInsights Database *