Skip to content

Commit

Permalink
feat(tech-insights): allow usage of API client from backend plugins (#…
Browse files Browse the repository at this point in the history
…630)

* feat(tech-insights): move client into separate package and allow using backend auth

Signed-off-by: secustor <[email protected]>

* feat(tech-insights): move client into common package under /client export

Signed-off-by: secustor <[email protected]>

* feat(tech-insights): run yarn fix

Signed-off-by: secustor <[email protected]>

* remove backend from changeset

Signed-off-by: secustor <[email protected]>

* update api report

Signed-off-by: secustor <[email protected]>

* add client api report

Signed-off-by: secustor <[email protected]>

* add missing documentation and try to solve api report problem

Signed-off-by: secustor <[email protected]>

* pin version

Signed-off-by: secustor <[email protected]>

* dedupe lock file

Signed-off-by: secustor <[email protected]>

* Apply suggestions from code review

Co-authored-by: Jussi Hallila <[email protected]>
Signed-off-by: Sebastian Poxhofer <[email protected]>

---------

Signed-off-by: secustor <[email protected]>
Signed-off-by: Sebastian Poxhofer <[email protected]>
Co-authored-by: Jussi Hallila <[email protected]>
  • Loading branch information
secustor and Xantier authored Aug 30, 2024
1 parent 2e21f59 commit a84eb44
Show file tree
Hide file tree
Showing 12 changed files with 539 additions and 181 deletions.
6 changes: 6 additions & 0 deletions workspaces/tech-insights/.changeset/selfish-bats-pretend.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@backstage-community/plugin-tech-insights-common': patch
'@backstage-community/plugin-tech-insights': patch
---

Move client to common package and allow to use backend auth system
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
## API Report File for "@backstage-community/plugin-tech-insights-common"

> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
```ts
import { AuthService } from '@backstage/backend-plugin-api';
import { BulkCheckResponse } from '@backstage-community/plugin-tech-insights-common';
import { CheckResult } from '@backstage-community/plugin-tech-insights-common';
import { CompoundEntityRef } from '@backstage/catalog-model';
import { DiscoveryApi } from '@backstage/core-plugin-api';
import { FactSchema } from '@backstage-community/plugin-tech-insights-common';
import { IdentityApi } from '@backstage/core-plugin-api';
import { JsonValue } from '@backstage/types';

// @public
export type Check = {
id: string;
type: string;
name: string;
description: string;
factIds: string[];
successMetadata?: Record<string, unknown>;
failureMetadata?: Record<string, unknown>;
};

// @public
export interface InsightFacts {
[factId: string]: {
timestamp: string;
version: string;
facts: Record<string, JsonValue>;
};
}

// @public
export class TechInsightsClient {
constructor(options: {
discoveryApi: DiscoveryApi;
identityApi: IdentityApi | AuthService;
});
getAllChecks(): Promise<Check[]>;
getFacts(entity: CompoundEntityRef, facts: string[]): Promise<InsightFacts>;
getFactSchemas(): Promise<FactSchema[]>;
runBulkChecks(
entities: CompoundEntityRef[],
checks?: Check[],
): Promise<BulkCheckResponse>;
runChecks(
entityParams: CompoundEntityRef,
checks?: string[],
): Promise<CheckResult[]>;
}
```
26 changes: 22 additions & 4 deletions workspaces/tech-insights/plugins/tech-insights-common/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,22 @@
]
},
"publishConfig": {
"access": "public",
"main": "dist/index.cjs.js",
"types": "dist/index.d.ts"
"access": "public"
},
"exports": {
".": "./src/index.ts",
"./client": "./src/client/index.ts",
"./package.json": "./package.json"
},
"typesVersions": {
"*": {
"client": [
"src/client/index.ts"
],
"package.json": [
"package.json"
]
}
},
"keywords": [
"backstage",
Expand Down Expand Up @@ -43,9 +56,14 @@
"test": "backstage-cli package test"
},
"dependencies": {
"@backstage/backend-plugin-api": "^0.6.18",
"@backstage/catalog-model": "^1.5.0",
"@backstage/core-plugin-api": "^1.9.2",
"@backstage/errors": "^1.2.4",
"@backstage/types": "^1.1.1",
"@types/luxon": "^3.0.0",
"luxon": "^3.0.0"
"luxon": "^3.0.0",
"qs": "^6.12.3"
},
"devDependencies": {
"@backstage/cli": "^0.27.0"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
/*
* 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 {
BulkCheckResponse,
CheckResult,
FactSchema,
} from '@backstage-community/plugin-tech-insights-common';
import { Check, InsightFacts } from './types';
import { DiscoveryApi, IdentityApi } from '@backstage/core-plugin-api';
import { ResponseError } from '@backstage/errors';
import {
CompoundEntityRef,
stringifyEntityRef,
} from '@backstage/catalog-model';
import qs from 'qs';
import { AuthService } from '@backstage/backend-plugin-api';

/**
* Client to fetch data from tech-insights backend
*
* @public */
export class TechInsightsClient {
private readonly discoveryApi: DiscoveryApi;
private readonly identityApi: IdentityApi | AuthService;

constructor(options: {
discoveryApi: DiscoveryApi;
identityApi: IdentityApi | AuthService;
}) {
this.discoveryApi = options.discoveryApi;
this.identityApi = options.identityApi;
}

/**
* Get facts for a specific entity
* @param entity - a component reference
* @param facts - a list fact ids to fetch
* @public
*/
async getFacts(
entity: CompoundEntityRef,
facts: string[],
): Promise<InsightFacts> {
const query = qs.stringify({
entity: stringifyEntityRef(entity),
ids: facts,
});
return await this.api<InsightFacts>(`/facts/latest?${query}`);
}

/**
* Get all checks.
* This will not return the actual status, but only metadata. To get the status use the /run endpoint
* @public
*/
async getAllChecks(): Promise<Check[]> {
return this.api('/checks');
}

/**
* Get schemas of facts.
* Use this for example to understand what the return values will be
* @public
*/
async getFactSchemas(): Promise<FactSchema[]> {
return this.api('/fact-schemas');
}

/**
* Run checks for a specific entity
* @param entityParams - reference to an entity
* @param checks - IDs of checks to run
* @public
*/
async runChecks(
entityParams: CompoundEntityRef,
checks?: string[],
): Promise<CheckResult[]> {
const { namespace, kind, name } = entityParams;
const requestBody = { checks };
return this.api(
`/checks/run/${encodeURIComponent(namespace)}/${encodeURIComponent(
kind,
)}/${encodeURIComponent(name)}`,
{
method: 'POST',
body: JSON.stringify(requestBody),
},
);
}

/**
* Return checks for multiple entities
* @param entities - list of entity references
* @param checks - list of check IDs
* @public
*/
async runBulkChecks(
entities: CompoundEntityRef[],
checks?: Check[],
): Promise<BulkCheckResponse> {
const checkIds = checks ? checks.map(check => check.id) : [];
const requestBody = {
entities,
checks: checkIds.length > 0 ? checkIds : undefined,
};
return this.api('/checks/run', {
method: 'POST',
body: JSON.stringify(requestBody),
});
}

private async api<T>(path: string, init?: RequestInit): Promise<T> {
const url = await this.discoveryApi.getBaseUrl('tech-insights');
const token = await this.getToken();

const headers: HeadersInit = new Headers(init?.headers);
if (!headers.has('content-type'))
headers.set('content-type', 'application/json');
if (token && !headers.has('authorization')) {
headers.set('authorization', `Bearer ${token}`);
}

const request = new Request(`${url}${path}`, {
...init,
headers,
});

return fetch(request).then(async response => {
if (!response.ok) {
throw await ResponseError.fromResponse(response);
}
return response.json() as Promise<T>;
});
}

private async getToken(): Promise<string | null> {
let result: { token?: string | undefined };

if ('getCredentials' in this.identityApi) {
result = await this.identityApi.getCredentials();
} else {
result = await this.identityApi.getPluginRequestToken({
onBehalfOf: await this.identityApi.getOwnServiceCredentials(),
targetPluginId: 'tech-insights',
});
}

return result.token ?? null;
}
}
Original file line number Diff line number Diff line change
@@ -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.
*/
/**
* API client for tech-insights
* @packageDocumentation
*/
export * from './TechInsightsClient';
export type * from './types';
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export {};
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* 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 { JsonValue } from '@backstage/types';

/**
* Represents a single check defined on the TechInsights backend.
*
* @public
*/
export type Check = {
/**
* Unique identifier of the check
*
* Used to identify which checks to use when running checks.
*/
id: string;

/**
* Type identifier for the check.
* Can be used to determine storage options, logical routing to correct FactChecker implementation
* or to help frontend render correct component types based on this
*/
type: string;

/**
* Human readable name of the check, may be displayed in the UI
*/
name: string;

/**
* Human readable description of the check, may be displayed in the UI
*/
description: string;

/**
* A collection of strings referencing fact rows that a check will be run against.
*
* References the fact container, aka fact retriever itself which may or may not contain multiple individual facts and values
*/
factIds: string[];

/**
* Metadata to be returned in case a check has been successfully evaluated
* Can contain links, description texts or other actionable items
*/
successMetadata?: Record<string, unknown>;

/**
* Metadata to be returned in case a check evaluation has ended in failure
* Can contain links, description texts or other actionable items
*/
failureMetadata?: Record<string, unknown>;
};

/**
* Represents a Fact defined on the TechInsights backend.
*
* @public
*/
export interface InsightFacts {
/**
* a single fact in the backend
*/
[factId: string]: {
timestamp: string;
version: string;
facts: Record<string, JsonValue>;
};
}
Loading

0 comments on commit a84eb44

Please sign in to comment.