@backstage-community/plugin-tech-insights-backend 0.5.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,386 @@
1
+ # Tech Insights Backend
2
+
3
+ This is the backend for the default Backstage Tech Insights feature.
4
+ This provides the API for the frontend tech insights, scorecards and fact visualization functionality,
5
+ as well as a framework to run fact retrievers and store fact values in to a data store.
6
+
7
+ ## Installation
8
+
9
+ ### Install the package
10
+
11
+ ```bash
12
+ # From your Backstage root directory
13
+ yarn --cwd packages/backend add @backstage-community/plugin-tech-insights-backend
14
+ ```
15
+
16
+ ### Adding the plugin to your `packages/backend`
17
+
18
+ ```ts title="packages/backend/src/index.ts"
19
+ backend.add(import('@backstage-community/plugin-tech-insights-backend'));
20
+ ```
21
+
22
+ You can use the extension points [@backstage-community/plugin-tech-insights-node](../tech-insights-node)
23
+ to add your `FactRetriever` or set a `FactCheckerFactory`.
24
+
25
+ The built-in `FactRetrievers`:
26
+
27
+ - `entityMetadataFactRetriever`
28
+ - `entityOwnershipFactRetriever`
29
+ - `techdocsFactRetriever`
30
+
31
+ `FactRetrievers` only get registered if they get configured:
32
+
33
+ ```yaml title="app-config.yaml"
34
+ techInsights:
35
+ factRetrievers:
36
+ entityOwnershipFactRetriever:
37
+ cadence: '*/15 * * * *'
38
+ lifecycle: { timeToLive: { weeks: 2 } }
39
+ ```
40
+
41
+ ### Adding the plugin to your `packages/backend` (old)
42
+
43
+ You'll need to add the plugin to the router in your `backend` package. You can
44
+ do this by creating a file called `packages/backend/src/plugins/techInsights.ts`. An example content for `techInsights.ts` could be something like this.
45
+
46
+ ```ts
47
+ import {
48
+ createRouter,
49
+ buildTechInsightsContext,
50
+ } from '@backstage-community/plugin-tech-insights-backend';
51
+ import { Router } from 'express';
52
+ import { PluginEnvironment } from '../types';
53
+
54
+ export default async function createPlugin(
55
+ env: PluginEnvironment,
56
+ ): Promise<Router> {
57
+ const builder = buildTechInsightsContext({
58
+ logger: env.logger,
59
+ config: env.config,
60
+ database: env.database,
61
+ discovery: env.discovery,
62
+ scheduler: env.scheduler,
63
+ tokenManager: env.tokenManager,
64
+ factRetrievers: [], // Fact retrievers registrations you want tech insights to use
65
+ });
66
+
67
+ return await createRouter({
68
+ ...(await builder),
69
+ logger: env.logger,
70
+ config: env.config,
71
+ });
72
+ }
73
+ ```
74
+
75
+ With the `techInsights.ts` router setup in place, add the router to
76
+ `packages/backend/src/index.ts`:
77
+
78
+ ```diff
79
+ +import techInsights from './plugins/techInsights';
80
+
81
+ async function main() {
82
+ ...
83
+ const createEnv = makeCreateEnv(config);
84
+
85
+ const catalogEnv = useHotMemoize(module, () => createEnv('catalog'));
86
+ + const techInsightsEnv = useHotMemoize(module, () => createEnv('tech_insights'));
87
+
88
+ const apiRouter = Router();
89
+ + apiRouter.use('/tech-insights', await techInsights(techInsightsEnv));
90
+ ...
91
+ apiRouter.use(notFoundHandler());
92
+ }
93
+ ```
94
+
95
+ ### Adding fact retrievers
96
+
97
+ At this point the Tech Insights backend is installed in your backend package, but
98
+ you will not have any fact retrievers present in your application. To have the implemented FactRetrieverEngine within this package to be able to retrieve and store fact data into the database, you need to add these.
99
+
100
+ To create factRetrieverRegistration you need to implement `FactRetriever` interface defined in `@backstage-community/plugin-tech-insights-node` package (see [Creating fact retrievers](#creating-fact-retrievers) for details). After you have implemented this interface you can wrap that into a registration object like follows:
101
+
102
+ ```ts
103
+ import { createFactRetrieverRegistration } from '@backstage-community/plugin-tech-insights-backend';
104
+
105
+ const myFactRetriever = {
106
+ /**
107
+ * snip
108
+ */
109
+ };
110
+
111
+ const myFactRetrieverRegistration = createFactRetrieverRegistration({
112
+ cadence: '1 * 2 * * ', // On the first minute of the second day of the month
113
+ factRetriever: myFactRetriever,
114
+ });
115
+ ```
116
+
117
+ FactRetrieverRegistration also accepts an optional `lifecycle` configuration value. This can be either MaxItems or TTL (time to live). Valid options for this value are either a number for MaxItems or a Luxon duration like object for TTL. For example:
118
+
119
+ ```ts
120
+ const maxItems = { maxItems: 7 }; // Deletes all but 7 latest facts for each id/entity pair
121
+ const ttl = { timeToLive: 1209600000 }; // (2 weeks) Deletes items older than 2 weeks
122
+ const ttlWithAHumanReadableValue = { timeToLive: { weeks: 2 } }; // Deletes items older than 2 weeks
123
+ ```
124
+
125
+ To register these fact retrievers to your application you can modify the example `techInsights.ts` file shown above like this:
126
+
127
+ ```diff
128
+ const builder = buildTechInsightsContext({
129
+ logger: env.logger,
130
+ config: env.config,
131
+ database: env.database,
132
+ discovery: env.discovery,
133
+ tokenManager: env.tokenManager,
134
+ scheduler: env.scheduler,
135
+ - factRetrievers: [],
136
+ + factRetrievers: [myFactRetrieverRegistration],
137
+ });
138
+ ```
139
+
140
+ #### Running fact retrievers in a multi-instance installation
141
+
142
+ The Tech Insights plugin utilizes the `PluginTaskScheduler` for scheduling tasks and coordinating the task invocation across instances. See [the PluginTaskScheduler documentation](https://backstage.io/docs/reference/backend-tasks.plugintaskscheduler) for more information.
143
+
144
+ ### Creating Fact Retrievers
145
+
146
+ A Fact Retriever consist of four required and one optional parts:
147
+
148
+ 1. `id` - unique identifier of a fact retriever
149
+ 2. `version`: A semver string indicating the current version of the schema and the handler
150
+ 3. `schema` - A versioned schema defining the shape of data a fact retriever returns
151
+ 4. `handler` - An asynchronous function handling the logic of retrieving and returning facts for an entity
152
+ 5. `entityFilter` - (Optional) EntityFilter object defining the entity kinds, types and/or names this fact retriever handles
153
+
154
+ An example implementation of a FactRetriever could for example be as follows:
155
+
156
+ ```ts
157
+ import { FactRetriever } from '@backstage-community/plugin-tech-insights-node';
158
+
159
+ const myFactRetriever: FactRetriever = {
160
+ id: 'documentation-number-factretriever', // unique identifier of the fact retriever
161
+ version: '0.1.1', // SemVer version number of this fact retriever schema. This should be incremented if the implementation changes
162
+ entityFilter: [{ kind: 'component' }], // EntityFilter to be used in the future (creating checks, graphs etc.) to figure out which entities this fact retrieves data for.
163
+ schema: {
164
+ // Name/identifier of an individual fact that this retriever returns
165
+ examplenumberfact: {
166
+ type: 'integer', // Type of the fact
167
+ description: 'A fact of a number', // Description of the fact
168
+ },
169
+ },
170
+ handler: async ctx => {
171
+ // Handler function that retrieves the fact
172
+ const { discovery, config, logger } = ctx;
173
+ const catalogClient = new CatalogClient({
174
+ discoveryApi: discovery,
175
+ });
176
+ const entities = await catalogClient.getEntities(
177
+ {
178
+ filter: [{ kind: 'component' }],
179
+ },
180
+ { token },
181
+ );
182
+ /**
183
+ * snip: Do complex logic to retrieve facts from external system or calculate fact values
184
+ */
185
+
186
+ // Respond with an array of entity/fact values
187
+ return entities.items.map(it => {
188
+ return {
189
+ // Entity information that this fact relates to
190
+ entity: {
191
+ namespace: it.metadata.namespace,
192
+ kind: it.kind,
193
+ name: it.metadata.name,
194
+ },
195
+
196
+ // All facts that this retriever returns
197
+ facts: {
198
+ examplenumberfact: 2, //
199
+ },
200
+ // (optional) timestamp to use as a Luxon DateTime object
201
+ };
202
+ });
203
+ },
204
+ };
205
+ ```
206
+
207
+ ### Adding a fact checker
208
+
209
+ This module comes with a possibility to additionally add a fact checker and expose fact checking endpoints from the API. To be able to enable this feature you need to add a FactCheckerFactory implementation to be part of the `DefaultTechInsightsBuilder` constructor call.
210
+
211
+ There is a default FactChecker implementation provided in module `@backstage-community/plugin-tech-insights-backend-module-jsonfc`. This implementation uses `json-rules-engine` as the underlying functionality to run checks. If you want to implement your own FactChecker, for example to be able to handle other than `boolean` result types, you can do so by implementing `FactCheckerFactory` and `FactChecker` interfaces from `@backstage-community/plugin-tech-insights-common` package.
212
+
213
+ To add the default FactChecker into your Tech Insights you need to install the module into your backend application:
214
+
215
+ ```bash
216
+ # From your Backstage root directory
217
+ yarn --cwd packages/backend add @backstage-community/plugin-tech-insights-backend-module-jsonfc
218
+ ```
219
+
220
+ and modify the `techInsights.ts` file to contain a reference to the FactChecker implementation.
221
+
222
+ ```diff
223
+ +import { JsonRulesEngineFactCheckerFactory } from '@backstage-community/plugin-tech-insights-backend-module-jsonfc';
224
+
225
+ +const myFactCheckerFactory = new JsonRulesEngineFactCheckerFactory({
226
+ + checks: [],
227
+ + logger: env.logger,
228
+ +}),
229
+
230
+ const builder = new DefaultTechInsightsBuilder({
231
+ logger: env.logger,
232
+ config: env.config,
233
+ database: env.database,
234
+ discovery: env.discovery,
235
+ tokenManager: env.tokenManager,
236
+ factRetrievers: [myFactRetrieverRegistration],
237
+ + factCheckerFactory: myFactCheckerFactory
238
+ });
239
+ ```
240
+
241
+ NOTE: You need a Fact Checker Factory to get access to the backend routes that will allow the facts to be checked. If you don't have a Fact Checker Factory you will see 404s and potentially other errors.
242
+
243
+ To be able to run checks, you need to additionally add individual checks into your FactChecker implementation. For examples how to add these, you can check the documentation of the individual implementation of the FactChecker
244
+
245
+ #### Modifying check persistence
246
+
247
+ The default FactChecker 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:
248
+
249
+ ```diff
250
+ const myTechInsightCheckRegistry: TechInsightCheckRegistry<MyCheckType> = // snip
251
+ const myFactCheckerFactory = new JsonRulesEngineFactCheckerFactory({
252
+ checks: [],
253
+ logger: env.logger,
254
+ + checkRegistry: myTechInsightCheckRegistry
255
+ }),
256
+
257
+ ```
258
+
259
+ ## Included FactRetrievers
260
+
261
+ There are three FactRetrievers that come out of the box with Tech Insights:
262
+
263
+ - `entityMetadataFactRetriever`: Generates facts which indicate the completeness of entity metadata
264
+ - `entityOwnershipFactRetriever`: Generates facts which indicate the quality of data in the spec.owner field
265
+ - `techdocsFactRetriever`: Generates facts related to the completeness of techdocs configuration for entities
266
+
267
+ ## Backend Example
268
+
269
+ Here's an example backend setup that will use the three included fact retrievers so you can get an idea of how this all works. This will be the entire contents of your `techInsights.ts` file found at `\packages\backend\src\plugins` as per [Adding the plugin to your `packages/backend`](#adding-the-plugin-to-your-packagesbackend)
270
+
271
+ ```ts
272
+ import {
273
+ createRouter,
274
+ buildTechInsightsContext,
275
+ createFactRetrieverRegistration,
276
+ entityOwnershipFactRetriever,
277
+ entityMetadataFactRetriever,
278
+ techdocsFactRetriever,
279
+ } from '@backstage-community/plugin-tech-insights-backend';
280
+ import { Router } from 'express';
281
+ import { PluginEnvironment } from '../types';
282
+ import {
283
+ JsonRulesEngineFactCheckerFactory,
284
+ JSON_RULE_ENGINE_CHECK_TYPE,
285
+ } from '@backstage-community/plugin-tech-insights-backend-module-jsonfc';
286
+
287
+ const ttlTwoWeeks = { timeToLive: { weeks: 2 } };
288
+
289
+ export default async function createPlugin(
290
+ env: PluginEnvironment,
291
+ ): Promise<Router> {
292
+ const techInsightsContext = await buildTechInsightsContext({
293
+ logger: env.logger,
294
+ config: env.config,
295
+ database: env.database,
296
+ discovery: env.discovery,
297
+ tokenManager: env.tokenManager,
298
+ scheduler: env.scheduler,
299
+ factRetrievers: [
300
+ createFactRetrieverRegistration({
301
+ cadence: '0 */6 * * *', // Run every 6 hours - https://crontab.guru/#0_*/6_*_*_*
302
+ factRetriever: entityOwnershipFactRetriever,
303
+ lifecycle: ttlTwoWeeks,
304
+ }),
305
+ createFactRetrieverRegistration({
306
+ cadence: '0 */6 * * *',
307
+ factRetriever: entityMetadataFactRetriever,
308
+ lifecycle: ttlTwoWeeks,
309
+ }),
310
+ createFactRetrieverRegistration({
311
+ cadence: '0 */6 * * *',
312
+ factRetriever: techdocsFactRetriever,
313
+ lifecycle: ttlTwoWeeks,
314
+ }),
315
+ ],
316
+ factCheckerFactory: new JsonRulesEngineFactCheckerFactory({
317
+ logger: env.logger,
318
+ checks: [
319
+ {
320
+ id: 'groupOwnerCheck',
321
+ type: JSON_RULE_ENGINE_CHECK_TYPE,
322
+ name: 'Group Owner Check',
323
+ description:
324
+ 'Verifies that a Group has been set as the owner for this entity',
325
+ factIds: ['entityOwnershipFactRetriever'],
326
+ rule: {
327
+ conditions: {
328
+ all: [
329
+ {
330
+ fact: 'hasGroupOwner',
331
+ operator: 'equal',
332
+ value: true,
333
+ },
334
+ ],
335
+ },
336
+ },
337
+ },
338
+ {
339
+ id: 'titleCheck',
340
+ type: JSON_RULE_ENGINE_CHECK_TYPE,
341
+ name: 'Title Check',
342
+ description:
343
+ 'Verifies that a Title, used to improve readability, has been set for this entity',
344
+ factIds: ['entityMetadataFactRetriever'],
345
+ rule: {
346
+ conditions: {
347
+ all: [
348
+ {
349
+ fact: 'hasTitle',
350
+ operator: 'equal',
351
+ value: true,
352
+ },
353
+ ],
354
+ },
355
+ },
356
+ },
357
+ {
358
+ id: 'techDocsCheck',
359
+ type: JSON_RULE_ENGINE_CHECK_TYPE,
360
+ name: 'TechDocs Check',
361
+ description:
362
+ 'Verifies that TechDocs has been enabled for this entity',
363
+ factIds: ['techdocsFactRetriever'],
364
+ rule: {
365
+ conditions: {
366
+ all: [
367
+ {
368
+ fact: 'hasAnnotationBackstageIoTechdocsRef',
369
+ operator: 'equal',
370
+ value: true,
371
+ },
372
+ ],
373
+ },
374
+ },
375
+ },
376
+ ],
377
+ }),
378
+ });
379
+
380
+ return await createRouter({
381
+ ...techInsightsContext,
382
+ logger: env.logger,
383
+ config: env.config,
384
+ });
385
+ }
386
+ ```
package/config.d.ts ADDED
@@ -0,0 +1,37 @@
1
+ /*
2
+ * Copyright 2024 The Backstage Authors
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ import { HumanDuration } from '@backstage/types';
18
+
19
+ export interface Config {
20
+ /** Configuration options for the tech-insights plugin */
21
+ techInsights?: {
22
+ /** Configuration options for fact retrievers */
23
+ factRetrievers?: {
24
+ /** Configuration for a fact retriever and its registration identified by its name. */
25
+ [name: string]: {
26
+ /** A cron expression to indicate when the fact retriever should be triggered. */
27
+ cadence: string;
28
+ /** Optional duration of the initial delay. */
29
+ initialDelay?: HumanDuration;
30
+ /** Optional lifecycle definition indicating the cleanup logic of facts when this retriever is run. */
31
+ lifecycle?: { timeToLive: HumanDuration } | { maxItems: number };
32
+ /** Optional duration to determine how long the fact retriever should be allowed to run, defaults to 5 minutes. */
33
+ timeout?: HumanDuration;
34
+ };
35
+ };
36
+ };
37
+ }