diff --git a/plugins/tech-insights/api-report.md b/plugins/tech-insights/api-report.md index 03155a5958..b40bf9fe92 100644 --- a/plugins/tech-insights/api-report.md +++ b/plugins/tech-insights/api-report.md @@ -10,8 +10,10 @@ import { BackstagePlugin } from '@backstage/core-plugin-api'; import { BulkCheckResponse } from '@backstage/plugin-tech-insights-common'; import { CheckResult } from '@backstage/plugin-tech-insights-common'; import { CompoundEntityRef } from '@backstage/catalog-model'; +import { DateTime } from 'luxon'; import { DiscoveryApi } from '@backstage/core-plugin-api'; import { IdentityApi } from '@backstage/core-plugin-api'; +import { JsonValue } from '@backstage/types'; import { default as React_2 } from 'react'; import { RouteRef } from '@backstage/core-plugin-api'; @@ -51,6 +53,27 @@ export const EntityTechInsightsScorecardContent: (props: { checksId?: string[] | undefined; }) => JSX.Element; +// @public +export interface InsightFact { + // (undocumented) + [factId: string]: { + timestamp: string; + version: string; + facts: Record< + string, + | number + | string + | boolean + | DateTime + | number[] + | string[] + | boolean[] + | DateTime[] + | JsonValue + >; + }; +} + // @public export const jsonRulesEngineCheckResultRenderer: CheckResultRenderer; @@ -61,6 +84,11 @@ export interface TechInsightsApi { // (undocumented) getCheckResultRenderers: (types: string[]) => CheckResultRenderer[]; // (undocumented) + getLatestFacts( + entity: CompoundEntityRef, + facts: string[], + ): Promise; + // (undocumented) runBulkChecks( entities: CompoundEntityRef[], checks?: Check[], @@ -87,6 +115,11 @@ export class TechInsightsClient implements TechInsightsApi { // (undocumented) getCheckResultRenderers(types: string[]): CheckResultRenderer[]; // (undocumented) + getLatestFacts( + entity: CompoundEntityRef, + facts: string[], + ): Promise; + // (undocumented) runBulkChecks( entities: CompoundEntityRef[], checks?: Check[], diff --git a/plugins/tech-insights/package.json b/plugins/tech-insights/package.json index dad3263f10..06be8fd415 100644 --- a/plugins/tech-insights/package.json +++ b/plugins/tech-insights/package.json @@ -38,6 +38,7 @@ "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", + "luxon": "^3.0.3", "react-use": "^17.2.4" }, "peerDependencies": { diff --git a/plugins/tech-insights/src/api/TechInsightsApi.ts b/plugins/tech-insights/src/api/TechInsightsApi.ts index 8ab1090304..79754d53b1 100644 --- a/plugins/tech-insights/src/api/TechInsightsApi.ts +++ b/plugins/tech-insights/src/api/TechInsightsApi.ts @@ -19,7 +19,7 @@ import { CheckResult, BulkCheckResponse, } from '@backstage/plugin-tech-insights-common'; -import { Check } from './types'; +import { Check, InsightFact } from './types'; import { CheckResultRenderer } from '../components/CheckResultRenderer'; import { CompoundEntityRef } from '@backstage/catalog-model'; @@ -48,4 +48,8 @@ export interface TechInsightsApi { entities: CompoundEntityRef[], checks?: Check[], ): Promise; + getLatestFacts( + entity: CompoundEntityRef, + facts: string[], + ): Promise; } diff --git a/plugins/tech-insights/src/api/TechInsightsClient.ts b/plugins/tech-insights/src/api/TechInsightsClient.ts index 8db5de3945..bcfb045d00 100644 --- a/plugins/tech-insights/src/api/TechInsightsClient.ts +++ b/plugins/tech-insights/src/api/TechInsightsClient.ts @@ -19,7 +19,7 @@ import { BulkCheckResponse, CheckResult, } from '@backstage/plugin-tech-insights-common'; -import { Check } from './types'; +import { Check, InsightFact } from './types'; import { DiscoveryApi, IdentityApi } from '@backstage/core-plugin-api'; import { ResponseError } from '@backstage/errors'; import { CompoundEntityRef } from '@backstage/catalog-model'; @@ -45,76 +45,76 @@ export class TechInsightsClient implements TechInsightsApi { this.renderers = options.renderers; } + async getLatestFacts( + entity: CompoundEntityRef, + facts: string[], + ): Promise { + const { namespace, kind, name } = entity; + const entityQuery = `entity=${encodeURIComponent( + kind.toLocaleLowerCase('en-US'), + )}:${encodeURIComponent(namespace)}/${encodeURIComponent(name)}`; + const idsQuery = facts.map(id => `ids[]=${id}`).join('&'); + return await this.api( + `/facts/latest?${entityQuery}&${idsQuery}`, + ); + } + getCheckResultRenderers(types: string[]): CheckResultRenderer[] { const renderers = this.renderers ?? [jsonRulesEngineCheckResultRenderer]; return renderers.filter(d => types.includes(d.type)); } async getAllChecks(): Promise { - const url = await this.discoveryApi.getBaseUrl('tech-insights'); - const { token } = await this.identityApi.getCredentials(); - const response = await fetch(`${url}/checks`, { - headers: token - ? { - Authorization: `Bearer ${token}`, - } - : undefined, - }); - if (!response.ok) { - throw await ResponseError.fromResponse(response); - } - return await response.json(); + return this.api('/checks'); } async runChecks( entityParams: CompoundEntityRef, checks?: string[], ): Promise { - const url = await this.discoveryApi.getBaseUrl('tech-insights'); - const { token } = await this.identityApi.getCredentials(); const { namespace, kind, name } = entityParams; const requestBody = { checks }; - const response = await fetch( - `${url}/checks/run/${encodeURIComponent(namespace)}/${encodeURIComponent( + return this.api( + `/checks/run/${encodeURIComponent(namespace)}/${encodeURIComponent( kind, )}/${encodeURIComponent(name)}`, { 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(); } async runBulkChecks( entities: CompoundEntityRef[], checks?: Check[], ): Promise { - const url = await this.discoveryApi.getBaseUrl('tech-insights'); - const { token } = await this.identityApi.getCredentials(); const checkIds = checks ? checks.map(check => check.id) : []; const requestBody = { entities, checks: checkIds.length > 0 ? checkIds : undefined, }; - const response = await fetch(`${url}/checks/run`, { + return this.api('/checks/run', { method: 'POST', body: JSON.stringify(requestBody), + }); + } + + private async api(path: string, init?: RequestInit): Promise { + const url = await this.discoveryApi.getBaseUrl('tech-insights'); + const { token } = await this.identityApi.getCredentials(); + + return fetch(`${url}${path}`, { + ...init, headers: { 'Content-Type': 'application/json', ...(token && { Authorization: `Bearer ${token}` }), }, + }).then(async response => { + if (!response.ok) { + throw await ResponseError.fromResponse(response); + } + return response.json() as Promise; }); - if (!response.ok) { - throw await ResponseError.fromResponse(response); - } - return await response.json(); } } diff --git a/plugins/tech-insights/src/api/types.ts b/plugins/tech-insights/src/api/types.ts index 10dfa06420..b69d4d6264 100644 --- a/plugins/tech-insights/src/api/types.ts +++ b/plugins/tech-insights/src/api/types.ts @@ -14,6 +14,9 @@ * limitations under the License. */ +import { DateTime } from 'luxon'; +import { JsonValue } from '@backstage/types'; + /** * Represents a single check defined on the TechInsights backend. * @@ -26,3 +29,27 @@ export type Check = { description: string; factIds: string[]; }; + +/** + * Represents a Fact defined on the TechInsights backend. + * + * @public + */ +export interface InsightFact { + [factId: string]: { + timestamp: string; + version: string; + facts: Record< + string, + | number + | string + | boolean + | DateTime + | number[] + | string[] + | boolean[] + | DateTime[] + | JsonValue + >; + }; +} diff --git a/plugins/tech-insights/src/index.ts b/plugins/tech-insights/src/index.ts index f3dd05c3aa..ae2f9d53a7 100644 --- a/plugins/tech-insights/src/index.ts +++ b/plugins/tech-insights/src/index.ts @@ -20,7 +20,7 @@ export { } from './plugin'; export { techInsightsApiRef, TechInsightsClient } from './api'; -export type { TechInsightsApi, Check } from './api'; +export type { TechInsightsApi, Check, InsightFact } from './api'; export { BooleanCheck } from './components/BooleanCheck'; export { jsonRulesEngineCheckResultRenderer } from './components/CheckResultRenderer'; export type { CheckResultRenderer } from './components/CheckResultRenderer'; diff --git a/yarn.lock b/yarn.lock index 919949a804..f0186e432e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6936,6 +6936,7 @@ __metadata: "@testing-library/user-event": ^14.0.0 "@types/node": ^16.11.26 cross-fetch: ^3.1.5 + luxon: ^3.0.3 msw: ^0.47.0 react-use: ^17.2.4 peerDependencies: @@ -29340,6 +29341,13 @@ __metadata: languageName: node linkType: hard +"luxon@npm:^3.0.3": + version: 3.0.3 + resolution: "luxon@npm:3.0.3" + checksum: 67d143f102f520761c4202086579a5cf30b230ea299da27cacb31e9f9d372cadef90f14cbe7e2621b70825b267fe00128b8176ed8b7172b48f77a403a662d5aa + languageName: node + linkType: hard + "lz-string@npm:^1.4.4": version: 1.4.4 resolution: "lz-string@npm:1.4.4"