Introduce Lighthouse Backend with Scheduling of Audits
Signed-off-by: Dominik Pfaffenbauer <dominik@pfaffenbauer.at>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-lighthouse-backend': minor
|
||||
---
|
||||
|
||||
Introduce Lighthouse Backend Plugin to run scheduled Lighthouse Audits
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-lighthouse-common': minor
|
||||
---
|
||||
|
||||
Introduce @backstage/plugin-lighthouse-common with the API implementation
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'example-backend': minor
|
||||
---
|
||||
|
||||
Add Lighthouse Backend to Example Backend
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-lighthouse': minor
|
||||
---
|
||||
|
||||
Require @backstage/plugin-lighthouse-common package where API implementation moved to
|
||||
@@ -49,6 +49,7 @@
|
||||
"@backstage/plugin-jenkins-backend": "workspace:^",
|
||||
"@backstage/plugin-kafka-backend": "workspace:^",
|
||||
"@backstage/plugin-kubernetes-backend": "workspace:^",
|
||||
"@backstage/plugin-lighthouse-backend": "workspace:^",
|
||||
"@backstage/plugin-permission-backend": "workspace:^",
|
||||
"@backstage/plugin-permission-common": "workspace:^",
|
||||
"@backstage/plugin-permission-node": "workspace:^",
|
||||
|
||||
@@ -62,6 +62,7 @@ import jenkins from './plugins/jenkins';
|
||||
import permission from './plugins/permission';
|
||||
import playlist from './plugins/playlist';
|
||||
import adr from './plugins/adr';
|
||||
import lighthouse from './plugins/lighthouse';
|
||||
import { PluginEnvironment } from './types';
|
||||
import { ServerPermissionClient } from '@backstage/plugin-permission-node';
|
||||
import { DefaultIdentityClient } from '@backstage/plugin-auth-node';
|
||||
@@ -148,6 +149,7 @@ async function main() {
|
||||
const playlistEnv = useHotMemoize(module, () => createEnv('playlist'));
|
||||
const eventsEnv = useHotMemoize(module, () => createEnv('events'));
|
||||
const exploreEnv = useHotMemoize(module, () => createEnv('explore'));
|
||||
const lighthouseEnv = useHotMemoize(module, () => createEnv('lighthouse'));
|
||||
|
||||
const eventBasedEntityProviders = await catalogEventBasedProviders(
|
||||
catalogEnv,
|
||||
@@ -180,6 +182,8 @@ async function main() {
|
||||
apiRouter.use('/adr', await adr(adrEnv));
|
||||
apiRouter.use(notFoundHandler());
|
||||
|
||||
await lighthouse(lighthouseEnv);
|
||||
|
||||
const service = createServiceBuilder(module)
|
||||
.loadConfig(config)
|
||||
.addRouter('', await healthcheck(healthcheckEnv))
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2020 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 { create } from '@backstage/plugin-lighthouse-backend';
|
||||
import { PluginEnvironment } from '../types';
|
||||
import { CatalogClient } from '@backstage/catalog-client';
|
||||
|
||||
export default async function createPlugin(env: PluginEnvironment) {
|
||||
const { logger, scheduler, config } = env;
|
||||
|
||||
const catalogClient = new CatalogClient({
|
||||
discoveryApi: env.discovery,
|
||||
});
|
||||
|
||||
await create({ logger, scheduler, config, catalogClient });
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
|
||||
@@ -0,0 +1,70 @@
|
||||
# Lighthouse Backend
|
||||
|
||||
Lighthouse Backend allows you to run scheduled lighthouse Tests for each Website with the annotation `lighthouse.com/website-url`.
|
||||
|
||||
## Setup
|
||||
|
||||
1. Install the plugin using:
|
||||
|
||||
```bash
|
||||
# From your Backstage root directory
|
||||
yarn add --cwd packages/backend @backstage/lighthouse-backend
|
||||
```
|
||||
|
||||
2. Create a `lighthouse.ts` file inside `packages/backend/src/plugins/`:
|
||||
|
||||
```typescript
|
||||
import { Router } from 'express';
|
||||
import { create } from '@backstage/plugin-lighthouse-backend';
|
||||
import { PluginEnvironment } from '../types';
|
||||
import { CatalogClient } from '@backstage/catalog-client';
|
||||
|
||||
export default async function createPlugin(env: PluginEnvironment) {
|
||||
const { logger, scheduler, config } = env;
|
||||
|
||||
const catalogClient = new CatalogClient({
|
||||
discoveryApi: env.discovery,
|
||||
});
|
||||
|
||||
await create({ logger, scheduler, config, catalogClient });
|
||||
}
|
||||
```
|
||||
|
||||
3. Modify your `packages/backend/src/index.ts` to include:
|
||||
|
||||
```diff
|
||||
...
|
||||
|
||||
import { Config } from '@backstage/config';
|
||||
import app from './plugins/app';
|
||||
+import lighthouse from './plugins/lighthouse';
|
||||
import scaffolder from './plugins/scaffolder';
|
||||
|
||||
...
|
||||
|
||||
async function main() {
|
||||
|
||||
...
|
||||
|
||||
const authEnv = useHotMemoize(module, () => createEnv('auth'));
|
||||
+ const lighthouseEnv = useHotMemoize(module, () => createEnv('lighthouse'));
|
||||
const proxyEnv = useHotMemoize(module, () => createEnv('proxy'));
|
||||
|
||||
...
|
||||
|
||||
const apiRouter = Router();
|
||||
apiRouter.use('/catalog', await catalog(catalogEnv));
|
||||
apiRouter.use('/scaffolder', await scaffolder(scaffolderEnv));
|
||||
|
||||
+ await lighthouse(lighthouseEnv)
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
You can define how often and when the scheduler should run the audits:
|
||||
|
||||
```yaml
|
||||
lighthouse:
|
||||
schedule:
|
||||
days: 1
|
||||
```
|
||||
@@ -0,0 +1,286 @@
|
||||
## API Report File for "@backstage/plugin-kubernetes-common"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import type { JsonObject } from '@backstage/types';
|
||||
import { PodStatus } from '@kubernetes/client-node';
|
||||
import { V1ConfigMap } from '@kubernetes/client-node';
|
||||
import { V1CronJob } from '@kubernetes/client-node';
|
||||
import { V1DaemonSet } from '@kubernetes/client-node';
|
||||
import { V1Deployment } from '@kubernetes/client-node';
|
||||
import { V1HorizontalPodAutoscaler } from '@kubernetes/client-node';
|
||||
import { V1Ingress } from '@kubernetes/client-node';
|
||||
import { V1Job } from '@kubernetes/client-node';
|
||||
import { V1LimitRange } from '@kubernetes/client-node';
|
||||
import { V1Pod } from '@kubernetes/client-node';
|
||||
import { V1ReplicaSet } from '@kubernetes/client-node';
|
||||
import { V1Service } from '@kubernetes/client-node';
|
||||
import { V1StatefulSet } from '@kubernetes/client-node';
|
||||
|
||||
// @public (undocumented)
|
||||
export type AuthProviderType = 'google' | 'serviceAccount' | 'aws' | 'azure';
|
||||
|
||||
// @public (undocumented)
|
||||
export interface ClientContainerStatus {
|
||||
// (undocumented)
|
||||
container: string;
|
||||
// (undocumented)
|
||||
cpuUsage: ClientCurrentResourceUsage;
|
||||
// (undocumented)
|
||||
memoryUsage: ClientCurrentResourceUsage;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface ClientCurrentResourceUsage {
|
||||
// (undocumented)
|
||||
currentUsage: number | string;
|
||||
// (undocumented)
|
||||
limitTotal: number | string;
|
||||
// (undocumented)
|
||||
requestTotal: number | string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface ClientPodStatus {
|
||||
// (undocumented)
|
||||
containers: ClientContainerStatus[];
|
||||
// (undocumented)
|
||||
cpu: ClientCurrentResourceUsage;
|
||||
// (undocumented)
|
||||
memory: ClientCurrentResourceUsage;
|
||||
// (undocumented)
|
||||
pod: V1Pod;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface ClusterAttributes {
|
||||
dashboardApp?: string;
|
||||
dashboardParameters?: JsonObject;
|
||||
dashboardUrl?: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface ClusterObjects {
|
||||
// (undocumented)
|
||||
cluster: ClusterAttributes;
|
||||
// (undocumented)
|
||||
errors: KubernetesFetchError[];
|
||||
// (undocumented)
|
||||
podMetrics: ClientPodStatus[];
|
||||
// (undocumented)
|
||||
resources: FetchResponse[];
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface ConfigMapFetchResponse {
|
||||
// (undocumented)
|
||||
resources: Array<V1ConfigMap>;
|
||||
// (undocumented)
|
||||
type: 'configmaps';
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface CronJobsFetchResponse {
|
||||
// (undocumented)
|
||||
resources: Array<V1CronJob>;
|
||||
// (undocumented)
|
||||
type: 'cronjobs';
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface CustomObjectsByEntityRequest {
|
||||
// (undocumented)
|
||||
auth: KubernetesRequestAuth;
|
||||
// (undocumented)
|
||||
customResources: CustomResourceMatcher[];
|
||||
// (undocumented)
|
||||
entity: Entity;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface CustomResourceFetchResponse {
|
||||
// (undocumented)
|
||||
resources: Array<any>;
|
||||
// (undocumented)
|
||||
type: 'customresources';
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface CustomResourceMatcher {
|
||||
// (undocumented)
|
||||
apiVersion: string;
|
||||
// (undocumented)
|
||||
group: string;
|
||||
// (undocumented)
|
||||
plural: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface DaemonSetsFetchResponse {
|
||||
// (undocumented)
|
||||
resources: Array<V1DaemonSet>;
|
||||
// (undocumented)
|
||||
type: 'daemonsets';
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface DeploymentFetchResponse {
|
||||
// (undocumented)
|
||||
resources: Array<V1Deployment>;
|
||||
// (undocumented)
|
||||
type: 'deployments';
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type FetchResponse =
|
||||
| PodFetchResponse
|
||||
| ServiceFetchResponse
|
||||
| ConfigMapFetchResponse
|
||||
| DeploymentFetchResponse
|
||||
| LimitRangeFetchReponse
|
||||
| ReplicaSetsFetchResponse
|
||||
| HorizontalPodAutoscalersFetchResponse
|
||||
| JobsFetchResponse
|
||||
| CronJobsFetchResponse
|
||||
| IngressesFetchResponse
|
||||
| CustomResourceFetchResponse
|
||||
| StatefulSetsFetchResponse
|
||||
| DaemonSetsFetchResponse
|
||||
| PodStatusFetchResponse;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface HorizontalPodAutoscalersFetchResponse {
|
||||
// (undocumented)
|
||||
resources: Array<V1HorizontalPodAutoscaler>;
|
||||
// (undocumented)
|
||||
type: 'horizontalpodautoscalers';
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface IngressesFetchResponse {
|
||||
// (undocumented)
|
||||
resources: Array<V1Ingress>;
|
||||
// (undocumented)
|
||||
type: 'ingresses';
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface JobsFetchResponse {
|
||||
// (undocumented)
|
||||
resources: Array<V1Job>;
|
||||
// (undocumented)
|
||||
type: 'jobs';
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type KubernetesErrorTypes =
|
||||
| 'BAD_REQUEST'
|
||||
| 'UNAUTHORIZED_ERROR'
|
||||
| 'NOT_FOUND'
|
||||
| 'SYSTEM_ERROR'
|
||||
| 'UNKNOWN_ERROR';
|
||||
|
||||
// @public (undocumented)
|
||||
export type KubernetesFetchError = StatusError | RawFetchError;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface KubernetesRequestAuth {
|
||||
// (undocumented)
|
||||
google?: string;
|
||||
// (undocumented)
|
||||
oidc?: {
|
||||
[key: string]: string;
|
||||
};
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface KubernetesRequestBody {
|
||||
// (undocumented)
|
||||
auth?: KubernetesRequestAuth;
|
||||
// (undocumented)
|
||||
entity: Entity;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface LimitRangeFetchReponse {
|
||||
// (undocumented)
|
||||
resources: Array<V1LimitRange>;
|
||||
// (undocumented)
|
||||
type: 'limitranges';
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface ObjectsByEntityResponse {
|
||||
// (undocumented)
|
||||
items: ClusterObjects[];
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface PodFetchResponse {
|
||||
// (undocumented)
|
||||
resources: Array<V1Pod>;
|
||||
// (undocumented)
|
||||
type: 'pods';
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface PodStatusFetchResponse {
|
||||
// (undocumented)
|
||||
resources: Array<PodStatus>;
|
||||
// (undocumented)
|
||||
type: 'podstatus';
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface RawFetchError {
|
||||
// (undocumented)
|
||||
errorType: 'FETCH_ERROR';
|
||||
// (undocumented)
|
||||
message: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface ReplicaSetsFetchResponse {
|
||||
// (undocumented)
|
||||
resources: Array<V1ReplicaSet>;
|
||||
// (undocumented)
|
||||
type: 'replicasets';
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface ServiceFetchResponse {
|
||||
// (undocumented)
|
||||
resources: Array<V1Service>;
|
||||
// (undocumented)
|
||||
type: 'services';
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface StatefulSetsFetchResponse {
|
||||
// (undocumented)
|
||||
resources: Array<V1StatefulSet>;
|
||||
// (undocumented)
|
||||
type: 'statefulsets';
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface StatusError {
|
||||
// (undocumented)
|
||||
errorType: KubernetesErrorTypes;
|
||||
// (undocumented)
|
||||
resourcePath?: string;
|
||||
// (undocumented)
|
||||
statusCode?: number;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface WorkloadsByEntityRequest {
|
||||
// (undocumented)
|
||||
auth: KubernetesRequestAuth;
|
||||
// (undocumented)
|
||||
entity: Entity;
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"name": "@backstage/plugin-lighthouse-backend",
|
||||
"description": "Backend functionalities for lighthouse",
|
||||
"version": "0.0.0",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"main": "dist/index.cjs.js",
|
||||
"module": "dist/index.esm.js",
|
||||
"types": "dist/index.d.ts"
|
||||
},
|
||||
"backstage": {
|
||||
"role": "common-library"
|
||||
},
|
||||
"homepage": "https://backstage.io",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/backstage/backstage",
|
||||
"directory": "plugins/lighthouse-backend"
|
||||
},
|
||||
"keywords": [
|
||||
"lighthouse"
|
||||
],
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "backstage-cli package build",
|
||||
"lint": "backstage-cli package lint",
|
||||
"test": "backstage-cli package test",
|
||||
"prepack": "backstage-cli package prepack",
|
||||
"postpack": "backstage-cli package postpack",
|
||||
"clean": "backstage-cli package clean"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/backstage/backstage/issues"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-common": "workspace:^",
|
||||
"@backstage/backend-tasks": "workspace:^",
|
||||
"@backstage/catalog-client": "workspace:^",
|
||||
"@backstage/catalog-model": "workspace:^",
|
||||
"@backstage/config": "workspace:^",
|
||||
"@backstage/plugin-lighthouse-common": "workspace:^",
|
||||
"@backstage/types": "workspace:^",
|
||||
"winston": "^3.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "workspace:^"
|
||||
},
|
||||
"jest": {
|
||||
"roots": [
|
||||
".."
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright 2020 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 { HumanDuration as HumanDuration } from '@backstage/types';
|
||||
|
||||
export interface LighthouseAuditScheduleConfig {
|
||||
schedule: HumanDuration;
|
||||
timeout: HumanDuration;
|
||||
auditDetail: HumanDuration;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export type LighthouseAuditSchedule = {
|
||||
getSchedule: () => HumanDuration;
|
||||
getTimeout: () => HumanDuration;
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export class LighthouseAuditScheduleImpl implements LighthouseAuditSchedule {
|
||||
static fromConfig(config: Config) {
|
||||
const lighthouse = config.getOptionalConfig('lighthouse');
|
||||
|
||||
let schedule: HumanDuration = { days: 1 };
|
||||
let timeout: HumanDuration = {};
|
||||
|
||||
if (lighthouse) {
|
||||
const scheduleConfig = lighthouse.getOptionalConfig('schedule');
|
||||
const timeoutConfig = lighthouse.getOptionalConfig('timeout');
|
||||
|
||||
if (scheduleConfig) {
|
||||
schedule = {
|
||||
milliseconds: scheduleConfig.getOptionalNumber('milliseconds'),
|
||||
seconds: scheduleConfig.getOptionalNumber('seconds'),
|
||||
minutes: scheduleConfig.getOptionalNumber('minutes'),
|
||||
hours: scheduleConfig.getOptionalNumber('hours'),
|
||||
days: scheduleConfig.getOptionalNumber('days'),
|
||||
weeks: scheduleConfig.getOptionalNumber('weeks'),
|
||||
months: scheduleConfig.getOptionalNumber('months'),
|
||||
years: scheduleConfig.getOptionalNumber('years'),
|
||||
};
|
||||
}
|
||||
|
||||
if (timeoutConfig) {
|
||||
timeout = {
|
||||
milliseconds: timeoutConfig.getOptionalNumber('milliseconds'),
|
||||
seconds: timeoutConfig.getOptionalNumber('seconds'),
|
||||
minutes: timeoutConfig.getOptionalNumber('minutes'),
|
||||
hours: timeoutConfig.getOptionalNumber('hours'),
|
||||
days: timeoutConfig.getOptionalNumber('days'),
|
||||
weeks: timeoutConfig.getOptionalNumber('weeks'),
|
||||
months: timeoutConfig.getOptionalNumber('months'),
|
||||
years: timeoutConfig.getOptionalNumber('years'),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return new LighthouseAuditScheduleImpl(schedule, timeout);
|
||||
}
|
||||
|
||||
constructor(
|
||||
private schedule: HumanDuration,
|
||||
private timeout: HumanDuration,
|
||||
) {}
|
||||
|
||||
getSchedule(): HumanDuration {
|
||||
return this.schedule;
|
||||
}
|
||||
|
||||
getTimeout(): HumanDuration {
|
||||
return this.timeout;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2020 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 './service/plugin';
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2020 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 {
|
||||
CatalogClient,
|
||||
CATALOG_FILTER_EXISTS,
|
||||
} from '@backstage/catalog-client';
|
||||
|
||||
export async function loadLighthouseEntities(catalogClient: CatalogClient) {
|
||||
const filter: Record<string, symbol | string> = {
|
||||
kind: 'Component',
|
||||
'spec.type': 'website',
|
||||
['lighthouse.com/website-url']: CATALOG_FILTER_EXISTS,
|
||||
};
|
||||
|
||||
return await catalogClient.getEntities({
|
||||
filter: [filter],
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* Copyright 2020 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 { Logger } from 'winston';
|
||||
import { PluginTaskScheduler } from '@backstage/backend-tasks';
|
||||
import {
|
||||
CATALOG_FILTER_EXISTS,
|
||||
CatalogClient,
|
||||
} from '@backstage/catalog-client';
|
||||
import { Config } from '@backstage/config';
|
||||
import { LighthouseRestApi } from '@backstage/plugin-lighthouse-common';
|
||||
import { stringifyEntityRef } from '@backstage/catalog-model';
|
||||
import { LighthouseAuditScheduleImpl } from '../config';
|
||||
|
||||
export interface RouterOptions {
|
||||
logger: Logger;
|
||||
config: Config;
|
||||
scheduler?: PluginTaskScheduler;
|
||||
catalogClient: CatalogClient;
|
||||
}
|
||||
|
||||
export async function create(options: RouterOptions) {
|
||||
const { logger, scheduler, catalogClient, config } = options;
|
||||
const lighthouseApi = LighthouseRestApi.fromConfig(config);
|
||||
|
||||
const lighthouseAuditConfig = LighthouseAuditScheduleImpl.fromConfig(config);
|
||||
const formFactorToScreenEmulationMap = {
|
||||
// the default is mobile, so no need to override
|
||||
mobile: undefined,
|
||||
// Values from lighthouse's cli "desktop" preset
|
||||
// https://github.com/GoogleChrome/lighthouse/blob/a6738e0033e7e5ca308b97c1c36f298b7d399402/lighthouse-core/config/constants.js#L71-L77
|
||||
desktop: {
|
||||
mobile: false,
|
||||
width: 1350,
|
||||
height: 940,
|
||||
deviceScaleFactor: 1,
|
||||
disabled: false,
|
||||
},
|
||||
};
|
||||
|
||||
logger.info(
|
||||
`Running with Scheduler Config ${JSON.stringify(
|
||||
lighthouseAuditConfig.getSchedule(),
|
||||
)} and timeout ${JSON.stringify(lighthouseAuditConfig.getTimeout())}`,
|
||||
);
|
||||
|
||||
if (scheduler) {
|
||||
await scheduler.scheduleTask({
|
||||
id: 'lighthouse_audit',
|
||||
frequency: lighthouseAuditConfig.getSchedule(),
|
||||
timeout: lighthouseAuditConfig.getTimeout(),
|
||||
initialDelay: { minutes: 15 },
|
||||
fn: async () => {
|
||||
const filter: Record<string, symbol | string> = {
|
||||
kind: 'Component',
|
||||
'spec.type': 'website',
|
||||
['metadata.annotations.lighthouse.com/website-url']:
|
||||
CATALOG_FILTER_EXISTS,
|
||||
};
|
||||
|
||||
logger.info('Running Lighthouse Audit Task');
|
||||
|
||||
const websitesWithUrl = await catalogClient.getEntities({
|
||||
filter: [filter],
|
||||
});
|
||||
|
||||
let index = 0;
|
||||
for (const entity of websitesWithUrl.items) {
|
||||
const websiteUrl =
|
||||
entity.metadata.annotations?.['lighthouse.com/website-url'] ?? '';
|
||||
|
||||
if (!websiteUrl) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const controller = new AbortController();
|
||||
|
||||
await scheduler.scheduleTask({
|
||||
id: `lighthouse_audit_${stringifyEntityRef(entity)}`,
|
||||
frequency: {},
|
||||
timeout: {},
|
||||
initialDelay: { minutes: index * 2 },
|
||||
signal: controller.signal,
|
||||
fn: async () => {
|
||||
logger.info(
|
||||
`Processing Website Url ${websiteUrl} for Entity ${entity.metadata.name}`,
|
||||
);
|
||||
|
||||
await lighthouseApi.triggerAudit({
|
||||
url: websiteUrl,
|
||||
options: {
|
||||
lighthouseConfig: {
|
||||
settings: {
|
||||
formFactor: 'mobile',
|
||||
emulatedFormFactor: 'mobile',
|
||||
screenEmulation: formFactorToScreenEmulationMap.mobile,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
logger.info('Stop Scheduled Task');
|
||||
controller.abort();
|
||||
},
|
||||
});
|
||||
index++;
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2020 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 {};
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
|
||||
@@ -0,0 +1,3 @@
|
||||
# @backstage/plugin-lighthouse-common
|
||||
|
||||
Common types and functionalities for lighthouse, to be shared between lighthouse and lighthouse-backend.
|
||||
@@ -0,0 +1,286 @@
|
||||
## API Report File for "@backstage/plugin-kubernetes-common"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import type { JsonObject } from '@backstage/types';
|
||||
import { PodStatus } from '@kubernetes/client-node';
|
||||
import { V1ConfigMap } from '@kubernetes/client-node';
|
||||
import { V1CronJob } from '@kubernetes/client-node';
|
||||
import { V1DaemonSet } from '@kubernetes/client-node';
|
||||
import { V1Deployment } from '@kubernetes/client-node';
|
||||
import { V1HorizontalPodAutoscaler } from '@kubernetes/client-node';
|
||||
import { V1Ingress } from '@kubernetes/client-node';
|
||||
import { V1Job } from '@kubernetes/client-node';
|
||||
import { V1LimitRange } from '@kubernetes/client-node';
|
||||
import { V1Pod } from '@kubernetes/client-node';
|
||||
import { V1ReplicaSet } from '@kubernetes/client-node';
|
||||
import { V1Service } from '@kubernetes/client-node';
|
||||
import { V1StatefulSet } from '@kubernetes/client-node';
|
||||
|
||||
// @public (undocumented)
|
||||
export type AuthProviderType = 'google' | 'serviceAccount' | 'aws' | 'azure';
|
||||
|
||||
// @public (undocumented)
|
||||
export interface ClientContainerStatus {
|
||||
// (undocumented)
|
||||
container: string;
|
||||
// (undocumented)
|
||||
cpuUsage: ClientCurrentResourceUsage;
|
||||
// (undocumented)
|
||||
memoryUsage: ClientCurrentResourceUsage;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface ClientCurrentResourceUsage {
|
||||
// (undocumented)
|
||||
currentUsage: number | string;
|
||||
// (undocumented)
|
||||
limitTotal: number | string;
|
||||
// (undocumented)
|
||||
requestTotal: number | string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface ClientPodStatus {
|
||||
// (undocumented)
|
||||
containers: ClientContainerStatus[];
|
||||
// (undocumented)
|
||||
cpu: ClientCurrentResourceUsage;
|
||||
// (undocumented)
|
||||
memory: ClientCurrentResourceUsage;
|
||||
// (undocumented)
|
||||
pod: V1Pod;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface ClusterAttributes {
|
||||
dashboardApp?: string;
|
||||
dashboardParameters?: JsonObject;
|
||||
dashboardUrl?: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface ClusterObjects {
|
||||
// (undocumented)
|
||||
cluster: ClusterAttributes;
|
||||
// (undocumented)
|
||||
errors: KubernetesFetchError[];
|
||||
// (undocumented)
|
||||
podMetrics: ClientPodStatus[];
|
||||
// (undocumented)
|
||||
resources: FetchResponse[];
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface ConfigMapFetchResponse {
|
||||
// (undocumented)
|
||||
resources: Array<V1ConfigMap>;
|
||||
// (undocumented)
|
||||
type: 'configmaps';
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface CronJobsFetchResponse {
|
||||
// (undocumented)
|
||||
resources: Array<V1CronJob>;
|
||||
// (undocumented)
|
||||
type: 'cronjobs';
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface CustomObjectsByEntityRequest {
|
||||
// (undocumented)
|
||||
auth: KubernetesRequestAuth;
|
||||
// (undocumented)
|
||||
customResources: CustomResourceMatcher[];
|
||||
// (undocumented)
|
||||
entity: Entity;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface CustomResourceFetchResponse {
|
||||
// (undocumented)
|
||||
resources: Array<any>;
|
||||
// (undocumented)
|
||||
type: 'customresources';
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface CustomResourceMatcher {
|
||||
// (undocumented)
|
||||
apiVersion: string;
|
||||
// (undocumented)
|
||||
group: string;
|
||||
// (undocumented)
|
||||
plural: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface DaemonSetsFetchResponse {
|
||||
// (undocumented)
|
||||
resources: Array<V1DaemonSet>;
|
||||
// (undocumented)
|
||||
type: 'daemonsets';
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface DeploymentFetchResponse {
|
||||
// (undocumented)
|
||||
resources: Array<V1Deployment>;
|
||||
// (undocumented)
|
||||
type: 'deployments';
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type FetchResponse =
|
||||
| PodFetchResponse
|
||||
| ServiceFetchResponse
|
||||
| ConfigMapFetchResponse
|
||||
| DeploymentFetchResponse
|
||||
| LimitRangeFetchReponse
|
||||
| ReplicaSetsFetchResponse
|
||||
| HorizontalPodAutoscalersFetchResponse
|
||||
| JobsFetchResponse
|
||||
| CronJobsFetchResponse
|
||||
| IngressesFetchResponse
|
||||
| CustomResourceFetchResponse
|
||||
| StatefulSetsFetchResponse
|
||||
| DaemonSetsFetchResponse
|
||||
| PodStatusFetchResponse;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface HorizontalPodAutoscalersFetchResponse {
|
||||
// (undocumented)
|
||||
resources: Array<V1HorizontalPodAutoscaler>;
|
||||
// (undocumented)
|
||||
type: 'horizontalpodautoscalers';
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface IngressesFetchResponse {
|
||||
// (undocumented)
|
||||
resources: Array<V1Ingress>;
|
||||
// (undocumented)
|
||||
type: 'ingresses';
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface JobsFetchResponse {
|
||||
// (undocumented)
|
||||
resources: Array<V1Job>;
|
||||
// (undocumented)
|
||||
type: 'jobs';
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type KubernetesErrorTypes =
|
||||
| 'BAD_REQUEST'
|
||||
| 'UNAUTHORIZED_ERROR'
|
||||
| 'NOT_FOUND'
|
||||
| 'SYSTEM_ERROR'
|
||||
| 'UNKNOWN_ERROR';
|
||||
|
||||
// @public (undocumented)
|
||||
export type KubernetesFetchError = StatusError | RawFetchError;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface KubernetesRequestAuth {
|
||||
// (undocumented)
|
||||
google?: string;
|
||||
// (undocumented)
|
||||
oidc?: {
|
||||
[key: string]: string;
|
||||
};
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface KubernetesRequestBody {
|
||||
// (undocumented)
|
||||
auth?: KubernetesRequestAuth;
|
||||
// (undocumented)
|
||||
entity: Entity;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface LimitRangeFetchReponse {
|
||||
// (undocumented)
|
||||
resources: Array<V1LimitRange>;
|
||||
// (undocumented)
|
||||
type: 'limitranges';
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface ObjectsByEntityResponse {
|
||||
// (undocumented)
|
||||
items: ClusterObjects[];
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface PodFetchResponse {
|
||||
// (undocumented)
|
||||
resources: Array<V1Pod>;
|
||||
// (undocumented)
|
||||
type: 'pods';
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface PodStatusFetchResponse {
|
||||
// (undocumented)
|
||||
resources: Array<PodStatus>;
|
||||
// (undocumented)
|
||||
type: 'podstatus';
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface RawFetchError {
|
||||
// (undocumented)
|
||||
errorType: 'FETCH_ERROR';
|
||||
// (undocumented)
|
||||
message: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface ReplicaSetsFetchResponse {
|
||||
// (undocumented)
|
||||
resources: Array<V1ReplicaSet>;
|
||||
// (undocumented)
|
||||
type: 'replicasets';
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface ServiceFetchResponse {
|
||||
// (undocumented)
|
||||
resources: Array<V1Service>;
|
||||
// (undocumented)
|
||||
type: 'services';
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface StatefulSetsFetchResponse {
|
||||
// (undocumented)
|
||||
resources: Array<V1StatefulSet>;
|
||||
// (undocumented)
|
||||
type: 'statefulsets';
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface StatusError {
|
||||
// (undocumented)
|
||||
errorType: KubernetesErrorTypes;
|
||||
// (undocumented)
|
||||
resourcePath?: string;
|
||||
// (undocumented)
|
||||
statusCode?: number;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface WorkloadsByEntityRequest {
|
||||
// (undocumented)
|
||||
auth: KubernetesRequestAuth;
|
||||
// (undocumented)
|
||||
entity: Entity;
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"name": "@backstage/plugin-lighthouse-common",
|
||||
"description": "Common functionalities for lighthouse, to be shared between lighthouse and lighthouse-backend plugin",
|
||||
"version": "0.0.0",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"main": "dist/index.cjs.js",
|
||||
"module": "dist/index.esm.js",
|
||||
"types": "dist/index.d.ts"
|
||||
},
|
||||
"backstage": {
|
||||
"role": "common-library"
|
||||
},
|
||||
"homepage": "https://backstage.io",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/backstage/backstage",
|
||||
"directory": "plugins/lighthouse-common"
|
||||
},
|
||||
"keywords": [
|
||||
"lighthouse"
|
||||
],
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "backstage-cli package build",
|
||||
"lint": "backstage-cli package lint",
|
||||
"test": "backstage-cli package test",
|
||||
"prepack": "backstage-cli package prepack",
|
||||
"postpack": "backstage-cli package postpack",
|
||||
"clean": "backstage-cli package clean"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/backstage/backstage/issues"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/config": "workspace:^"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "workspace:^"
|
||||
},
|
||||
"jest": {
|
||||
"roots": [
|
||||
".."
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
/*
|
||||
* Copyright 2020 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';
|
||||
|
||||
/** @public */
|
||||
export type LighthouseCategoryId =
|
||||
| 'pwa'
|
||||
| 'seo'
|
||||
| 'performance'
|
||||
| 'accessibility'
|
||||
| 'best-practices';
|
||||
|
||||
/** @public */
|
||||
export interface LighthouseCategoryAbbr {
|
||||
id: LighthouseCategoryId;
|
||||
score: number;
|
||||
title: string;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export interface LASListRequest {
|
||||
offset?: number;
|
||||
limit?: number;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export interface LASListResponse<Item> {
|
||||
items: Item[];
|
||||
total: number;
|
||||
offset: number;
|
||||
limit: number;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export interface AuditBase {
|
||||
id: string;
|
||||
url: string;
|
||||
timeCreated: string;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export interface AuditRunning extends AuditBase {
|
||||
status: 'RUNNING';
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export interface AuditFailed extends AuditBase {
|
||||
status: 'FAILED';
|
||||
timeCompleted: string;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export interface AuditCompleted extends AuditBase {
|
||||
status: 'COMPLETED';
|
||||
timeCompleted: string;
|
||||
report: Object;
|
||||
categories: Record<LighthouseCategoryId, LighthouseCategoryAbbr>;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export type Audit = AuditRunning | AuditFailed | AuditCompleted;
|
||||
|
||||
/** @public */
|
||||
export interface Website {
|
||||
url: string;
|
||||
audits: Audit[];
|
||||
lastAudit: Audit;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export type WebsiteListResponse = LASListResponse<Website>;
|
||||
|
||||
/** @public */
|
||||
export type FormFactor = 'mobile' | 'desktop';
|
||||
|
||||
/** @public */
|
||||
export type LighthouseConfigSettings = {
|
||||
// For lighthouse 7+
|
||||
formFactor: FormFactor;
|
||||
screenEmulation:
|
||||
| {
|
||||
mobile: boolean;
|
||||
width: number;
|
||||
height: number;
|
||||
deviceScaleFactor: number;
|
||||
disabled: boolean;
|
||||
}
|
||||
| undefined;
|
||||
// For lighthouse before 7
|
||||
emulatedFormFactor: FormFactor;
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export interface TriggerAuditPayload {
|
||||
url: string;
|
||||
options: {
|
||||
lighthouseConfig: {
|
||||
settings: LighthouseConfigSettings;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export class FetchError extends Error {
|
||||
get name(): string {
|
||||
return this.constructor.name;
|
||||
}
|
||||
|
||||
static async forResponse(resp: Response): Promise<FetchError> {
|
||||
return new FetchError(
|
||||
`Request failed with status code ${
|
||||
resp.status
|
||||
}.\nReason: ${await resp.text()}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export type LighthouseApi = {
|
||||
url: string;
|
||||
getWebsiteList: (listOptions: LASListRequest) => Promise<WebsiteListResponse>;
|
||||
getWebsiteForAuditId: (auditId: string) => Promise<Website>;
|
||||
triggerAudit: (payload: TriggerAuditPayload) => Promise<Audit>;
|
||||
getWebsiteByUrl: (websiteUrl: string) => Promise<Website>;
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export class LighthouseRestApi implements LighthouseApi {
|
||||
static fromConfig(config: Config) {
|
||||
return new LighthouseRestApi(config.getString('lighthouse.baseUrl'));
|
||||
}
|
||||
|
||||
constructor(public url: string) {}
|
||||
|
||||
private async fetch<T = any>(input: string, init?: RequestInit): Promise<T> {
|
||||
const resp = await fetch(`${this.url}${input}`, init);
|
||||
if (!resp.ok) throw await FetchError.forResponse(resp);
|
||||
return await resp.json();
|
||||
}
|
||||
|
||||
async getWebsiteList(
|
||||
options: LASListRequest = {},
|
||||
): Promise<WebsiteListResponse> {
|
||||
const { limit, offset } = options;
|
||||
const params = new URLSearchParams();
|
||||
if (typeof limit === 'number') params.append('limit', limit.toString());
|
||||
if (typeof offset === 'number') params.append('offset', offset.toString());
|
||||
return await this.fetch<WebsiteListResponse>(
|
||||
`/v1/websites?${params.toString()}`,
|
||||
);
|
||||
}
|
||||
|
||||
async getWebsiteForAuditId(auditId: string): Promise<Website> {
|
||||
return await this.fetch<Website>(
|
||||
`/v1/audits/${encodeURIComponent(auditId)}/website`,
|
||||
);
|
||||
}
|
||||
|
||||
async triggerAudit(payload: TriggerAuditPayload): Promise<Audit> {
|
||||
return await this.fetch<Audit>('/v1/audits', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(payload),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async getWebsiteByUrl(websiteUrl: string): Promise<Website> {
|
||||
return this.fetch<Website>(
|
||||
`/v1/websites/${encodeURIComponent(websiteUrl)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright 2020 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Common functionalities for Lighthouse, to be shared between the `lighthouse` and `lighthouse-backend` plugins
|
||||
*
|
||||
* @packageDocumentation
|
||||
*/
|
||||
|
||||
export * from './api';
|
||||
@@ -105,3 +105,7 @@ const overviewContent = (
|
||||
</EntitySwitch.Case>
|
||||
</EntitySwitch>
|
||||
```
|
||||
|
||||
## Schedule
|
||||
|
||||
If you want to run automated scheduled runs, you can install the [@backstage/lighthouse-backend](../lighthouse-backend/README.md) plugin
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
"@backstage/core-components": "workspace:^",
|
||||
"@backstage/core-plugin-api": "workspace:^",
|
||||
"@backstage/plugin-catalog-react": "workspace:^",
|
||||
"@backstage/plugin-lighthouse-common": "workspace:^",
|
||||
"@backstage/theme": "workspace:^",
|
||||
"@material-ui/core": "^4.12.2",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
|
||||
@@ -14,181 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Config } from '@backstage/config';
|
||||
import { createApiRef } from '@backstage/core-plugin-api';
|
||||
|
||||
/** @public */
|
||||
export type LighthouseCategoryId =
|
||||
| 'pwa'
|
||||
| 'seo'
|
||||
| 'performance'
|
||||
| 'accessibility'
|
||||
| 'best-practices';
|
||||
|
||||
/** @public */
|
||||
export interface LighthouseCategoryAbbr {
|
||||
id: LighthouseCategoryId;
|
||||
score: number;
|
||||
title: string;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export interface LASListRequest {
|
||||
offset?: number;
|
||||
limit?: number;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export interface LASListResponse<Item> {
|
||||
items: Item[];
|
||||
total: number;
|
||||
offset: number;
|
||||
limit: number;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export interface AuditBase {
|
||||
id: string;
|
||||
url: string;
|
||||
timeCreated: string;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export interface AuditRunning extends AuditBase {
|
||||
status: 'RUNNING';
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export interface AuditFailed extends AuditBase {
|
||||
status: 'FAILED';
|
||||
timeCompleted: string;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export interface AuditCompleted extends AuditBase {
|
||||
status: 'COMPLETED';
|
||||
timeCompleted: string;
|
||||
report: Object;
|
||||
categories: Record<LighthouseCategoryId, LighthouseCategoryAbbr>;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export type Audit = AuditRunning | AuditFailed | AuditCompleted;
|
||||
|
||||
/** @public */
|
||||
export interface Website {
|
||||
url: string;
|
||||
audits: Audit[];
|
||||
lastAudit: Audit;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export type WebsiteListResponse = LASListResponse<Website>;
|
||||
|
||||
/** @public */
|
||||
export type FormFactor = 'mobile' | 'desktop';
|
||||
|
||||
/** @public */
|
||||
export type LighthouseConfigSettings = {
|
||||
// For lighthouse 7+
|
||||
formFactor: FormFactor;
|
||||
screenEmulation:
|
||||
| {
|
||||
mobile: boolean;
|
||||
width: number;
|
||||
height: number;
|
||||
deviceScaleFactor: number;
|
||||
disabled: boolean;
|
||||
}
|
||||
| undefined;
|
||||
// For lighthouse before 7
|
||||
emulatedFormFactor: FormFactor;
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export interface TriggerAuditPayload {
|
||||
url: string;
|
||||
options: {
|
||||
lighthouseConfig: {
|
||||
settings: LighthouseConfigSettings;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export class FetchError extends Error {
|
||||
get name(): string {
|
||||
return this.constructor.name;
|
||||
}
|
||||
|
||||
static async forResponse(resp: Response): Promise<FetchError> {
|
||||
return new FetchError(
|
||||
`Request failed with status code ${
|
||||
resp.status
|
||||
}.\nReason: ${await resp.text()}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export type LighthouseApi = {
|
||||
url: string;
|
||||
getWebsiteList: (listOptions: LASListRequest) => Promise<WebsiteListResponse>;
|
||||
getWebsiteForAuditId: (auditId: string) => Promise<Website>;
|
||||
triggerAudit: (payload: TriggerAuditPayload) => Promise<Audit>;
|
||||
getWebsiteByUrl: (websiteUrl: string) => Promise<Website>;
|
||||
};
|
||||
import { LighthouseApi } from '@backstage/plugin-lighthouse-common';
|
||||
|
||||
/** @public */
|
||||
export const lighthouseApiRef = createApiRef<LighthouseApi>({
|
||||
id: 'plugin.lighthouse.service',
|
||||
});
|
||||
|
||||
/** @public */
|
||||
export class LighthouseRestApi implements LighthouseApi {
|
||||
static fromConfig(config: Config) {
|
||||
return new LighthouseRestApi(config.getString('lighthouse.baseUrl'));
|
||||
}
|
||||
|
||||
constructor(public url: string) {}
|
||||
|
||||
private async fetch<T = any>(input: string, init?: RequestInit): Promise<T> {
|
||||
const resp = await fetch(`${this.url}${input}`, init);
|
||||
if (!resp.ok) throw await FetchError.forResponse(resp);
|
||||
return await resp.json();
|
||||
}
|
||||
|
||||
async getWebsiteList(
|
||||
options: LASListRequest = {},
|
||||
): Promise<WebsiteListResponse> {
|
||||
const { limit, offset } = options;
|
||||
const params = new URLSearchParams();
|
||||
if (typeof limit === 'number') params.append('limit', limit.toString());
|
||||
if (typeof offset === 'number') params.append('offset', offset.toString());
|
||||
return await this.fetch<WebsiteListResponse>(
|
||||
`/v1/websites?${params.toString()}`,
|
||||
);
|
||||
}
|
||||
|
||||
async getWebsiteForAuditId(auditId: string): Promise<Website> {
|
||||
return await this.fetch<Website>(
|
||||
`/v1/audits/${encodeURIComponent(auditId)}/website`,
|
||||
);
|
||||
}
|
||||
|
||||
async triggerAudit(payload: TriggerAuditPayload): Promise<Audit> {
|
||||
return await this.fetch<Audit>('/v1/audits', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(payload),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async getWebsiteByUrl(websiteUrl: string): Promise<Website> {
|
||||
return this.fetch<Website>(
|
||||
`/v1/websites/${encodeURIComponent(websiteUrl)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,10 +19,10 @@ import { EntityProvider } from '@backstage/plugin-catalog-react';
|
||||
import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
|
||||
import React from 'react';
|
||||
import {
|
||||
lighthouseApiRef,
|
||||
LighthouseRestApi,
|
||||
WebsiteListResponse,
|
||||
} from '../../api';
|
||||
} from '@backstage/plugin-lighthouse-common';
|
||||
import { lighthouseApiRef } from '../../api';
|
||||
import { useWebsiteForEntity } from '../../hooks/useWebsiteForEntity';
|
||||
import * as data from '../../__fixtures__/website-list-response.json';
|
||||
import { AuditListForEntity } from './AuditListForEntity';
|
||||
|
||||
@@ -25,9 +25,9 @@ import AuditListTable from './AuditListTable';
|
||||
|
||||
import {
|
||||
WebsiteListResponse,
|
||||
lighthouseApiRef,
|
||||
LighthouseRestApi,
|
||||
} from '../../api';
|
||||
} from '@backstage/plugin-lighthouse-common';
|
||||
import { lighthouseApiRef } from '../../api';
|
||||
import { formatTime } from '../../utils';
|
||||
import { setupServer } from 'msw/node';
|
||||
import * as data from '../../__fixtures__/website-list-response.json';
|
||||
|
||||
@@ -15,7 +15,8 @@
|
||||
*/
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import { Website, lighthouseApiRef } from '../../api';
|
||||
import { Website } from '@backstage/plugin-lighthouse-common';
|
||||
import { lighthouseApiRef } from '../../api';
|
||||
import useInterval from 'react-use/lib/useInterval';
|
||||
import {
|
||||
formatTime,
|
||||
|
||||
@@ -33,10 +33,10 @@ import { rest } from 'msw';
|
||||
import { setupServer } from 'msw/node';
|
||||
import React from 'react';
|
||||
import {
|
||||
lighthouseApiRef,
|
||||
LighthouseRestApi,
|
||||
WebsiteListResponse,
|
||||
} from '../../api';
|
||||
} from '@backstage/plugin-lighthouse-common';
|
||||
import { lighthouseApiRef } from '../../api';
|
||||
import * as data from '../../__fixtures__/website-list-response.json';
|
||||
import AuditList from './index';
|
||||
import { ApiProvider } from '@backstage/core-app-api';
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Audit } from '../../api';
|
||||
import { Audit } from '@backstage/plugin-lighthouse-common';
|
||||
import {
|
||||
StatusError,
|
||||
StatusOK,
|
||||
|
||||
@@ -38,7 +38,12 @@ import { render } from '@testing-library/react';
|
||||
import { rest } from 'msw';
|
||||
import { setupServer } from 'msw/node';
|
||||
import React from 'react';
|
||||
import { Audit, lighthouseApiRef, LighthouseRestApi, Website } from '../../api';
|
||||
import {
|
||||
Audit,
|
||||
LighthouseRestApi,
|
||||
Website,
|
||||
} from '@backstage/plugin-lighthouse-common';
|
||||
import { lighthouseApiRef } from '../../api';
|
||||
import { formatTime } from '../../utils';
|
||||
import * as data from '../../__fixtures__/website-response.json';
|
||||
import AuditView from './index';
|
||||
|
||||
@@ -33,7 +33,8 @@ import {
|
||||
useParams,
|
||||
} from 'react-router-dom';
|
||||
import useAsync from 'react-use/lib/useAsync';
|
||||
import { Audit, lighthouseApiRef, Website } from '../../api';
|
||||
import { Audit, Website } from '@backstage/plugin-lighthouse-common';
|
||||
import { lighthouseApiRef } from '../../api';
|
||||
import { formatTime } from '../../utils';
|
||||
import AuditStatusIcon from '../AuditStatusIcon';
|
||||
import LighthouseSupportButton from '../SupportButton';
|
||||
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
AuditCompleted,
|
||||
LighthouseCategoryId,
|
||||
WebsiteListResponse,
|
||||
} from '../../api';
|
||||
} from '@backstage/plugin-lighthouse-common';
|
||||
import { useWebsiteForEntity } from '../../hooks/useWebsiteForEntity';
|
||||
import * as data from '../../__fixtures__/website-list-response.json';
|
||||
import { LastLighthouseAuditCard } from './LastLighthouseAuditCard';
|
||||
|
||||
@@ -15,7 +15,11 @@
|
||||
*/
|
||||
import React from 'react';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import { Audit, AuditCompleted, LighthouseCategoryId } from '../../api';
|
||||
import {
|
||||
Audit,
|
||||
AuditCompleted,
|
||||
LighthouseCategoryId,
|
||||
} from '@backstage/plugin-lighthouse-common';
|
||||
import { useWebsiteForEntity } from '../../hooks/useWebsiteForEntity';
|
||||
import AuditStatusIcon from '../AuditStatusIcon';
|
||||
import {
|
||||
|
||||
@@ -32,7 +32,8 @@ import { fireEvent, render, waitFor } from '@testing-library/react';
|
||||
import { rest } from 'msw';
|
||||
import { setupServer } from 'msw/node';
|
||||
import React from 'react';
|
||||
import { Audit, lighthouseApiRef, LighthouseRestApi } from '../../api';
|
||||
import { Audit, LighthouseRestApi } from '@backstage/plugin-lighthouse-common';
|
||||
import { lighthouseApiRef } from '../../api';
|
||||
import * as data from '../../__fixtures__/create-audit-response.json';
|
||||
import CreateAudit from './index';
|
||||
|
||||
|
||||
@@ -27,9 +27,9 @@ import React, { useCallback, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import {
|
||||
FormFactor,
|
||||
lighthouseApiRef,
|
||||
LighthouseConfigSettings,
|
||||
} from '../../api';
|
||||
} from '@backstage/plugin-lighthouse-common';
|
||||
import { lighthouseApiRef } from '../../api';
|
||||
import { useQuery } from '../../utils';
|
||||
import LighthouseSupportButton from '../SupportButton';
|
||||
|
||||
|
||||
@@ -17,7 +17,8 @@ import { Entity } from '@backstage/catalog-model';
|
||||
import { EntityProvider } from '@backstage/plugin-catalog-react';
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import React, { PropsWithChildren } from 'react';
|
||||
import { lighthouseApiRef, WebsiteListResponse } from '../api';
|
||||
import { WebsiteListResponse } from '@backstage/plugin-lighthouse-common';
|
||||
import { lighthouseApiRef } from '../api';
|
||||
import * as data from '../__fixtures__/website-list-response.json';
|
||||
import { useWebsiteForEntity } from './useWebsiteForEntity';
|
||||
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { lighthouseApiRef, LighthouseRestApi } from './api';
|
||||
import { LighthouseRestApi } from '@backstage/plugin-lighthouse-common';
|
||||
import { lighthouseApiRef } from './api';
|
||||
import {
|
||||
createPlugin,
|
||||
createRouteRef,
|
||||
|
||||
@@ -14,7 +14,12 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { Website, Audit, LighthouseCategoryId, AuditCompleted } from './api';
|
||||
import {
|
||||
Website,
|
||||
Audit,
|
||||
LighthouseCategoryId,
|
||||
AuditCompleted,
|
||||
} from '@backstage/plugin-lighthouse-common';
|
||||
|
||||
export function useQuery(): URLSearchParams {
|
||||
return new URLSearchParams(useLocation().search);
|
||||
|
||||
@@ -6589,6 +6589,31 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@backstage/plugin-lighthouse-backend@workspace:^, @backstage/plugin-lighthouse-backend@workspace:plugins/lighthouse-backend":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@backstage/plugin-lighthouse-backend@workspace:plugins/lighthouse-backend"
|
||||
dependencies:
|
||||
"@backstage/backend-common": "workspace:^"
|
||||
"@backstage/backend-tasks": "workspace:^"
|
||||
"@backstage/catalog-client": "workspace:^"
|
||||
"@backstage/catalog-model": "workspace:^"
|
||||
"@backstage/cli": "workspace:^"
|
||||
"@backstage/config": "workspace:^"
|
||||
"@backstage/plugin-lighthouse-common": "workspace:^"
|
||||
"@backstage/types": "workspace:^"
|
||||
winston: ^3.2.1
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@backstage/plugin-lighthouse-common@workspace:^, @backstage/plugin-lighthouse-common@workspace:plugins/lighthouse-common":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@backstage/plugin-lighthouse-common@workspace:plugins/lighthouse-common"
|
||||
dependencies:
|
||||
"@backstage/cli": "workspace:^"
|
||||
"@backstage/config": "workspace:^"
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@backstage/plugin-lighthouse@workspace:^, @backstage/plugin-lighthouse@workspace:plugins/lighthouse":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@backstage/plugin-lighthouse@workspace:plugins/lighthouse"
|
||||
@@ -6601,6 +6626,7 @@ __metadata:
|
||||
"@backstage/core-plugin-api": "workspace:^"
|
||||
"@backstage/dev-utils": "workspace:^"
|
||||
"@backstage/plugin-catalog-react": "workspace:^"
|
||||
"@backstage/plugin-lighthouse-common": "workspace:^"
|
||||
"@backstage/test-utils": "workspace:^"
|
||||
"@backstage/theme": "workspace:^"
|
||||
"@material-ui/core": ^4.12.2
|
||||
@@ -22295,6 +22321,7 @@ __metadata:
|
||||
"@backstage/plugin-jenkins-backend": "workspace:^"
|
||||
"@backstage/plugin-kafka-backend": "workspace:^"
|
||||
"@backstage/plugin-kubernetes-backend": "workspace:^"
|
||||
"@backstage/plugin-lighthouse-backend": "workspace:^"
|
||||
"@backstage/plugin-permission-backend": "workspace:^"
|
||||
"@backstage/plugin-permission-common": "workspace:^"
|
||||
"@backstage/plugin-permission-node": "workspace:^"
|
||||
|
||||
Reference in New Issue
Block a user