diff --git a/.changeset/seven-swans-glow.md b/.changeset/seven-swans-glow.md new file mode 100644 index 0000000000..8a7a6eab76 --- /dev/null +++ b/.changeset/seven-swans-glow.md @@ -0,0 +1,8 @@ +--- +'@backstage/plugin-tech-insights': minor +'@backstage/plugin-tech-insights-backend': minor +'@backstage/plugin-tech-insights-common': minor +'@backstage/plugin-tech-insights-backend-module-jsonfc': patch +--- + +adding new operation to run checks for multiple entities in one request 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 aef09d5434..333744a203 100644 --- a/plugins/tech-insights-backend-module-jsonfc/src/service/JsonRulesEngineFactChecker.ts +++ b/plugins/tech-insights-backend-module-jsonfc/src/service/JsonRulesEngineFactChecker.ts @@ -117,7 +117,7 @@ export class JsonRulesEngineFactChecker if (hasAllFacts) { engine.addRule({ ...techInsightCheck.rule, event: noopEvent }); } else { - this.logger.warn( + this.logger.debug( `Skipping ${ rule.name } due to missing facts: ${techInsightCheck.factIds @@ -138,7 +138,7 @@ export class JsonRulesEngineFactChecker techInsightChecks, Object.values(facts), ); - } catch (e) { + } catch (e: any) { throw new Error(`Failed to run rules engine, ${e.message}`); } } diff --git a/plugins/tech-insights-backend/src/service/router.ts b/plugins/tech-insights-backend/src/service/router.ts index 80902c307c..dd89fefed8 100644 --- a/plugins/tech-insights-backend/src/service/router.ts +++ b/plugins/tech-insights-backend/src/service/router.ts @@ -27,6 +27,7 @@ import { Logger } from 'winston'; import { DateTime } from 'luxon'; import { PersistenceContext } from './persistence/persistenceContext'; import { + EntityName, EntityRef, parseEntityName, stringifyEntityRef, @@ -96,7 +97,29 @@ export async function createRouter< const entityTriplet = stringifyEntityRef({ namespace, kind, name }); const checkResult = await factChecker.runChecks(entityTriplet, checks); return res.send(checkResult); - } catch (e) { + } catch (e: any) { + return res.status(500).json({ message: e.message }).send(); + } + }); + + router.post('/checks/run', async (req, res) => { + try { + const { + checks, + entities, + }: { checks: string[]; entities: EntityName[] } = req.body; + const tasks = entities.map(async entity => { + const entityTriplet = + typeof entity === 'string' ? entity : stringifyEntityRef(entity); + const results = await factChecker.runChecks(entityTriplet, checks); + return { + entity: entityTriplet, + results, + }; + }); + const results = await Promise.all(tasks); + return res.send(results); + } catch (e: any) { return res.status(500).json({ message: e.message }).send(); } }); diff --git a/plugins/tech-insights-common/api-report.md b/plugins/tech-insights-common/api-report.md index 8618f6266b..0236c0619a 100644 --- a/plugins/tech-insights-common/api-report.md +++ b/plugins/tech-insights-common/api-report.md @@ -12,6 +12,12 @@ export interface BooleanCheckResult extends CheckResult { result: boolean; } +// @public +export type BulkCheckResponse = Array<{ + entity: string; + results: CheckResult[]; +}>; + // @public export interface CheckResponse { description: string; diff --git a/plugins/tech-insights-common/src/index.ts b/plugins/tech-insights-common/src/index.ts index ecdaa298c1..66af5c2f93 100644 --- a/plugins/tech-insights-common/src/index.ts +++ b/plugins/tech-insights-common/src/index.ts @@ -124,3 +124,13 @@ export type CheckResult = { export interface BooleanCheckResult extends CheckResult { result: boolean; } + +/** + * Response type for bulk check opretation. Contains a list of entities and their respective check results. + * + * @public + */ +export type BulkCheckResponse = Array<{ + entity: string; + results: CheckResult[]; +}>; diff --git a/plugins/tech-insights/api-report.md b/plugins/tech-insights/api-report.md index 52712b14a4..79854e505f 100644 --- a/plugins/tech-insights/api-report.md +++ b/plugins/tech-insights/api-report.md @@ -7,6 +7,7 @@ import { ApiRef } from '@backstage/core-plugin-api'; import { BackstagePlugin } from '@backstage/core-plugin-api'; +import { BulkCheckResponse } from '@backstage/plugin-tech-insights-common'; import { CheckResult } from '@backstage/plugin-tech-insights-common'; import { EntityName } from '@backstage/catalog-model'; import { default as React_2 } from 'react'; @@ -50,6 +51,11 @@ export interface TechInsightsApi { description?: string, ) => CheckResultRenderer | undefined; // (undocumented) + runBulkChecks( + entities: EntityName[], + checks?: Check[], + ): Promise; + // (undocumented) runChecks(entityParams: EntityName, checks?: Check[]): Promise; } diff --git a/plugins/tech-insights/src/api/TechInsightsApi.ts b/plugins/tech-insights/src/api/TechInsightsApi.ts index 9f2d4f14c4..b1287a62dc 100644 --- a/plugins/tech-insights/src/api/TechInsightsApi.ts +++ b/plugins/tech-insights/src/api/TechInsightsApi.ts @@ -15,7 +15,10 @@ */ import { createApiRef } from '@backstage/core-plugin-api'; -import { CheckResult } from '@backstage/plugin-tech-insights-common'; +import { + CheckResult, + BulkCheckResponse, +} from '@backstage/plugin-tech-insights-common'; import { Check } from './types'; import { CheckResultRenderer } from '../components/CheckResultRenderer'; import { EntityName } from '@backstage/catalog-model'; @@ -43,4 +46,8 @@ export interface TechInsightsApi { ) => CheckResultRenderer | undefined; getAllChecks(): Promise; runChecks(entityParams: EntityName, checks?: Check[]): Promise; + runBulkChecks( + entities: EntityName[], + checks?: Check[], + ): Promise; } diff --git a/plugins/tech-insights/src/api/TechInsightsClient.ts b/plugins/tech-insights/src/api/TechInsightsClient.ts index 8678ec13c8..692169e80e 100644 --- a/plugins/tech-insights/src/api/TechInsightsClient.ts +++ b/plugins/tech-insights/src/api/TechInsightsClient.ts @@ -15,7 +15,10 @@ */ import { TechInsightsApi } from './TechInsightsApi'; -import { CheckResult } from '@backstage/plugin-tech-insights-common'; +import { + BulkCheckResponse, + CheckResult, +} from '@backstage/plugin-tech-insights-common'; import { Check } from './types'; import { DiscoveryApi, IdentityApi } from '@backstage/core-plugin-api'; import { ResponseError } from '@backstage/errors'; @@ -97,4 +100,29 @@ export class TechInsightsClient implements TechInsightsApi { } return await response.json(); } + + async runBulkChecks( + entities: EntityName[], + checks?: Check[], + ): Promise { + const url = await this.discoveryApi.getBaseUrl('tech-insights'); + const token = await this.identityApi.getIdToken(); + const checkIds = checks ? checks.map(check => check.id) : []; + const requestBody = { + entities, + checks: checkIds.length > 0 ? checkIds : undefined, + }; + const response = await fetch(`${url}/checks/run`, { + method: 'POST', + body: JSON.stringify(requestBody), + headers: { + 'Content-Type': 'application/json', + ...(token && { Authorization: `Bearer ${token}` }), + }, + }); + if (!response.ok) { + throw await ResponseError.fromResponse(response); + } + return await response.json(); + } }