chore: removed things

Signed-off-by: blam <ben@blam.sh>
This commit is contained in:
blam
2024-04-19 17:06:23 +02:00
parent 7a33f95483
commit 8b50969764
181 changed files with 2338 additions and 19263 deletions
+9 -48
View File
@@ -15,90 +15,51 @@
"@backstage/app-defaults": "workspace:^",
"@backstage/catalog-model": "workspace:^",
"@backstage/cli": "workspace:^",
"@backstage/config": "workspace:^",
"@backstage/core-app-api": "workspace:^",
"@backstage/core-compat-api": "workspace:^",
"@backstage/core-components": "workspace:^",
"@backstage/core-plugin-api": "workspace:^",
"@backstage/frontend-app-api": "workspace:^",
"@backstage/frontend-plugin-api": "workspace:^",
"@backstage/integration-react": "workspace:^",
"@backstage/plugin-adr": "workspace:^",
"@backstage/plugin-airbrake": "workspace:^",
"@backstage/plugin-apache-airflow": "workspace:^",
"@backstage/plugin-api-docs": "workspace:^",
"@backstage/plugin-app-visualizer": "workspace:^",
"@backstage/plugin-azure-devops": "workspace:^",
"@backstage/plugin-azure-sites": "workspace:^",
"@backstage/plugin-badges": "workspace:^",
"@backstage/plugin-auth-react": "workspace:^",
"@backstage/plugin-catalog": "workspace:^",
"@backstage/plugin-catalog-common": "workspace:^",
"@backstage/plugin-catalog-graph": "workspace:^",
"@backstage/plugin-catalog-import": "workspace:^",
"@backstage/plugin-catalog-react": "workspace:^",
"@backstage/plugin-catalog-unprocessed-entities": "workspace:^",
"@backstage/plugin-cloudbuild": "workspace:^",
"@backstage/plugin-code-coverage": "workspace:^",
"@backstage/plugin-cost-insights": "workspace:^",
"@backstage/plugin-devtools": "workspace:^",
"@backstage/plugin-dynatrace": "workspace:^",
"@backstage/plugin-entity-feedback": "workspace:^",
"@backstage/plugin-explore": "workspace:^",
"@backstage/plugin-gcalendar": "workspace:^",
"@backstage/plugin-gcp-projects": "workspace:^",
"@backstage/plugin-github-actions": "workspace:^",
"@backstage/plugin-gocd": "workspace:^",
"@backstage/plugin-graphiql": "workspace:^",
"@backstage/plugin-home": "workspace:^",
"@backstage/plugin-jenkins": "workspace:^",
"@backstage/plugin-kafka": "workspace:^",
"@backstage/plugin-kubernetes": "workspace:^",
"@backstage/plugin-lighthouse": "workspace:^",
"@backstage/plugin-linguist": "workspace:^",
"@backstage/plugin-linguist-common": "workspace:^",
"@backstage/plugin-microsoft-calendar": "workspace:^",
"@backstage/plugin-newrelic": "workspace:^",
"@backstage/plugin-newrelic-dashboard": "workspace:^",
"@backstage/plugin-octopus-deploy": "workspace:^",
"@backstage/plugin-kubernetes-cluster": "workspace:^",
"@backstage/plugin-notifications": "workspace:^",
"@backstage/plugin-org": "workspace:^",
"@backstage/plugin-pagerduty": "workspace:^",
"@backstage/plugin-permission-react": "workspace:^",
"@backstage/plugin-playlist": "workspace:^",
"@backstage/plugin-puppetdb": "workspace:^",
"@backstage/plugin-rollbar": "workspace:^",
"@backstage/plugin-scaffolder": "workspace:^",
"@backstage/plugin-scaffolder-react": "workspace:^",
"@backstage/plugin-search": "workspace:^",
"@backstage/plugin-search-common": "workspace:^",
"@backstage/plugin-search-react": "workspace:^",
"@backstage/plugin-sentry": "workspace:^",
"@backstage/plugin-shortcuts": "workspace:^",
"@backstage/plugin-stackstorm": "workspace:^",
"@backstage/plugin-tech-insights": "workspace:^",
"@backstage/plugin-tech-radar": "workspace:^",
"@backstage/plugin-signals": "workspace:^",
"@backstage/plugin-techdocs": "workspace:^",
"@backstage/plugin-techdocs-module-addons-contrib": "workspace:^",
"@backstage/plugin-techdocs-react": "workspace:^",
"@backstage/plugin-todo": "workspace:^",
"@backstage/plugin-user-settings": "workspace:^",
"@backstage/theme": "workspace:^",
"@circleci/backstage-plugin": "^0.1.1",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.61",
"@octokit/rest": "^19.0.3",
"@oriflame/backstage-plugin-score-card": "^0.8.0",
"@roadiehq/backstage-plugin-buildkite": "^2.0.8",
"@roadiehq/backstage-plugin-github-insights": "^2.0.5",
"@roadiehq/backstage-plugin-github-pull-requests": "^2.2.7",
"@roadiehq/backstage-plugin-travis-ci": "^2.0.5",
"app-next-example-plugin": "workspace:^",
"@vitejs/plugin-react": "^4.0.4",
"history": "^5.0.0",
"lodash": "^4.17.21",
"react": "^18.0.2",
"react-dom": "^18.0.2",
"react-router": "^6.3.0",
"react-router-dom": "^6.3.0",
"react-use": "^17.2.4",
"vite": "^4.4.9",
"vite-plugin-html": "^3.2.0",
"vite-plugin-node-polyfills": "^0.21.0",
"zen-observable": "^0.10.0"
},
"devDependencies": {
-46
View File
@@ -44,81 +44,35 @@
"@backstage/core-plugin-api": "workspace:^",
"@backstage/frontend-app-api": "workspace:^",
"@backstage/integration-react": "workspace:^",
"@backstage/plugin-adr": "workspace:^",
"@backstage/plugin-airbrake": "workspace:^",
"@backstage/plugin-apache-airflow": "workspace:^",
"@backstage/plugin-api-docs": "workspace:^",
"@backstage/plugin-auth-react": "workspace:^",
"@backstage/plugin-azure-devops": "workspace:^",
"@backstage/plugin-azure-sites": "workspace:^",
"@backstage/plugin-badges": "workspace:^",
"@backstage/plugin-catalog": "workspace:^",
"@backstage/plugin-catalog-common": "workspace:^",
"@backstage/plugin-catalog-graph": "workspace:^",
"@backstage/plugin-catalog-import": "workspace:^",
"@backstage/plugin-catalog-react": "workspace:^",
"@backstage/plugin-catalog-unprocessed-entities": "workspace:^",
"@backstage/plugin-cloudbuild": "workspace:^",
"@backstage/plugin-code-coverage": "workspace:^",
"@backstage/plugin-cost-insights": "workspace:^",
"@backstage/plugin-devtools": "workspace:^",
"@backstage/plugin-dynatrace": "workspace:^",
"@backstage/plugin-entity-feedback": "workspace:^",
"@backstage/plugin-explore": "workspace:^",
"@backstage/plugin-gcalendar": "workspace:^",
"@backstage/plugin-gcp-projects": "workspace:^",
"@backstage/plugin-github-actions": "workspace:^",
"@backstage/plugin-github-pull-requests-board": "workspace:^",
"@backstage/plugin-gocd": "workspace:^",
"@backstage/plugin-graphiql": "workspace:^",
"@backstage/plugin-home": "workspace:^",
"@backstage/plugin-jenkins": "workspace:^",
"@backstage/plugin-kafka": "workspace:^",
"@backstage/plugin-kubernetes": "workspace:^",
"@backstage/plugin-kubernetes-cluster": "workspace:^",
"@backstage/plugin-lighthouse": "workspace:^",
"@backstage/plugin-linguist": "workspace:^",
"@backstage/plugin-linguist-common": "workspace:^",
"@backstage/plugin-microsoft-calendar": "workspace:^",
"@backstage/plugin-newrelic": "workspace:^",
"@backstage/plugin-newrelic-dashboard": "workspace:^",
"@backstage/plugin-nomad": "workspace:^",
"@backstage/plugin-notifications": "workspace:^",
"@backstage/plugin-octopus-deploy": "workspace:^",
"@backstage/plugin-org": "workspace:^",
"@backstage/plugin-pagerduty": "workspace:^",
"@backstage/plugin-permission-react": "workspace:^",
"@backstage/plugin-playlist": "workspace:^",
"@backstage/plugin-puppetdb": "workspace:^",
"@backstage/plugin-rollbar": "workspace:^",
"@backstage/plugin-scaffolder": "workspace:^",
"@backstage/plugin-scaffolder-react": "workspace:^",
"@backstage/plugin-search": "workspace:^",
"@backstage/plugin-search-common": "workspace:^",
"@backstage/plugin-search-react": "workspace:^",
"@backstage/plugin-sentry": "workspace:^",
"@backstage/plugin-shortcuts": "workspace:^",
"@backstage/plugin-signals": "workspace:^",
"@backstage/plugin-stack-overflow": "workspace:^",
"@backstage/plugin-stackstorm": "workspace:^",
"@backstage/plugin-tech-insights": "workspace:^",
"@backstage/plugin-tech-radar": "workspace:^",
"@backstage/plugin-techdocs": "workspace:^",
"@backstage/plugin-techdocs-module-addons-contrib": "workspace:^",
"@backstage/plugin-techdocs-react": "workspace:^",
"@backstage/plugin-todo": "workspace:^",
"@backstage/plugin-user-settings": "workspace:^",
"@backstage/theme": "workspace:^",
"@circleci/backstage-plugin": "^0.1.1",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.61",
"@octokit/rest": "^19.0.3",
"@oriflame/backstage-plugin-score-card": "^0.8.0",
"@roadiehq/backstage-plugin-buildkite": "^2.0.8",
"@roadiehq/backstage-plugin-github-insights": "^2.0.5",
"@roadiehq/backstage-plugin-github-pull-requests": "^2.2.7",
"@roadiehq/backstage-plugin-travis-ci": "^2.0.5",
"@vitejs/plugin-react": "^4.0.4",
"history": "^5.0.0",
"react": "^18.0.2",
+1 -12
View File
@@ -29,32 +29,23 @@
"@backstage/backend-plugin-api": "workspace:^",
"@backstage/backend-tasks": "workspace:^",
"@backstage/catalog-model": "workspace:^",
"@backstage/plugin-adr-backend": "workspace:^",
"@backstage/plugin-app-backend": "workspace:^",
"@backstage/plugin-auth-backend": "workspace:^",
"@backstage/plugin-auth-backend-module-github-provider": "workspace:^",
"@backstage/plugin-auth-backend-module-guest-provider": "workspace:^",
"@backstage/plugin-auth-node": "workspace:^",
"@backstage/plugin-azure-devops-backend": "workspace:^",
"@backstage/plugin-badges-backend": "workspace:^",
"@backstage/plugin-catalog-backend": "workspace:^",
"@backstage/plugin-catalog-backend-module-backstage-openapi": "workspace:^",
"@backstage/plugin-catalog-backend-module-openapi": "workspace:^",
"@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "workspace:^",
"@backstage/plugin-catalog-backend-module-unprocessed": "workspace:^",
"@backstage/plugin-devtools-backend": "workspace:^",
"@backstage/plugin-entity-feedback-backend": "workspace:^",
"@backstage/plugin-jenkins-backend": "workspace:^",
"@backstage/plugin-kubernetes-backend": "workspace:^",
"@backstage/plugin-lighthouse-backend": "workspace:^",
"@backstage/plugin-linguist-backend": "workspace:^",
"@backstage/plugin-nomad-backend": "workspace:^",
"@backstage/plugin-notifications-backend": "workspace:^",
"@backstage/plugin-permission-backend": "workspace:^",
"@backstage/plugin-permission-backend-module-allow-all-policy": "workspace:^",
"@backstage/plugin-permission-common": "workspace:^",
"@backstage/plugin-permission-node": "workspace:^",
"@backstage/plugin-playlist-backend": "workspace:^",
"@backstage/plugin-proxy-backend": "workspace:^",
"@backstage/plugin-scaffolder-backend": "workspace:^",
"@backstage/plugin-scaffolder-backend-module-github": "workspace:^",
@@ -64,9 +55,7 @@
"@backstage/plugin-search-backend-module-techdocs": "workspace:^",
"@backstage/plugin-search-backend-node": "workspace:^",
"@backstage/plugin-signals-backend": "workspace:^",
"@backstage/plugin-sonarqube-backend": "workspace:^",
"@backstage/plugin-techdocs-backend": "workspace:^",
"@backstage/plugin-todo-backend": "workspace:^"
"@backstage/plugin-techdocs-backend": "workspace:^"
},
"devDependencies": {
"@backstage/cli": "workspace:^"
-18
View File
@@ -32,35 +32,21 @@
"@backstage/catalog-model": "workspace:^",
"@backstage/config": "workspace:^",
"@backstage/integration": "workspace:^",
"@backstage/plugin-adr-backend": "workspace:^",
"@backstage/plugin-app-backend": "workspace:^",
"@backstage/plugin-auth-backend": "workspace:^",
"@backstage/plugin-auth-node": "workspace:^",
"@backstage/plugin-azure-devops-backend": "workspace:^",
"@backstage/plugin-azure-sites-common": "workspace:^",
"@backstage/plugin-badges-backend": "workspace:^",
"@backstage/plugin-catalog-backend": "workspace:^",
"@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "workspace:^",
"@backstage/plugin-catalog-backend-module-unprocessed": "workspace:^",
"@backstage/plugin-catalog-node": "workspace:^",
"@backstage/plugin-code-coverage-backend": "workspace:^",
"@backstage/plugin-devtools-backend": "workspace:^",
"@backstage/plugin-entity-feedback-backend": "workspace:^",
"@backstage/plugin-events-backend": "workspace:^",
"@backstage/plugin-events-node": "workspace:^",
"@backstage/plugin-explore-backend": "workspace:^",
"@backstage/plugin-jenkins-backend": "workspace:^",
"@backstage/plugin-kafka-backend": "workspace:^",
"@backstage/plugin-kubernetes-backend": "workspace:^",
"@backstage/plugin-lighthouse-backend": "workspace:^",
"@backstage/plugin-linguist-backend": "workspace:^",
"@backstage/plugin-nomad-backend": "workspace:^",
"@backstage/plugin-permission-backend": "workspace:^",
"@backstage/plugin-permission-common": "workspace:^",
"@backstage/plugin-permission-node": "workspace:^",
"@backstage/plugin-playlist-backend": "workspace:^",
"@backstage/plugin-proxy-backend": "workspace:^",
"@backstage/plugin-rollbar-backend": "workspace:^",
"@backstage/plugin-scaffolder-backend": "workspace:^",
"@backstage/plugin-scaffolder-backend-module-confluence-to-markdown": "workspace:^",
"@backstage/plugin-scaffolder-backend-module-gitlab": "workspace:^",
@@ -74,11 +60,7 @@
"@backstage/plugin-search-backend-node": "workspace:^",
"@backstage/plugin-signals-backend": "workspace:^",
"@backstage/plugin-signals-node": "workspace:^",
"@backstage/plugin-tech-insights-backend": "workspace:^",
"@backstage/plugin-tech-insights-backend-module-jsonfc": "workspace:^",
"@backstage/plugin-tech-insights-node": "workspace:^",
"@backstage/plugin-techdocs-backend": "workspace:^",
"@backstage/plugin-todo-backend": "workspace:^",
"@gitbeaker/node": "^35.1.0",
"@octokit/rest": "^19.0.3",
"@opentelemetry/api": "^1.4.1",
+2 -2
View File
@@ -37,13 +37,13 @@
"@types/react": "^16.13.1 || ^17.0.0"
},
"devDependencies": {
"@backstage-community/plugin-puppetdb": "^0.1.18",
"@backstage-community/plugin-stackstorm": "^0.1.16",
"@backstage/cli": "workspace:^",
"@backstage/core-app-api": "workspace:^",
"@backstage/frontend-app-api": "workspace:^",
"@backstage/frontend-test-utils": "workspace:^",
"@backstage/plugin-catalog": "workspace:^",
"@backstage/plugin-puppetdb": "workspace:^",
"@backstage/plugin-stackstorm": "workspace:^",
"@oriflame/backstage-plugin-score-card": "^0.8.0",
"@testing-library/jest-dom": "^6.0.0",
"@testing-library/react": "^15.0.0"
File diff suppressed because it is too large Load Diff
-3
View File
@@ -1,3 +0,0 @@
# Deprecated
This package has been moved to the [backstage-community/plugins](https://github.com/backstage/community-plugins) repository. Migrate to using `@backstage-community/plugin-linguist-backend` instead.
-123
View File
@@ -1,123 +0,0 @@
## API Report File for "@backstage/plugin-linguist-backend"
> 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 { BackendFeature } from '@backstage/backend-plugin-api';
import { CatalogProcessor } from '@backstage/plugin-catalog-node';
import { CatalogProcessorCache } from '@backstage/plugin-catalog-node';
import { Config } from '@backstage/config';
import { DiscoveryService } from '@backstage/backend-plugin-api';
import { Entity } from '@backstage/catalog-model';
import express from 'express';
import { HttpAuthService } from '@backstage/backend-plugin-api';
import { HumanDuration } from '@backstage/types';
import { Languages } from '@backstage/plugin-linguist-common';
import { LanguageType } from '@backstage/plugin-linguist-common';
import { LoggerService } from '@backstage/backend-plugin-api';
import { PluginDatabaseManager } from '@backstage/backend-common';
import { PluginEndpointDiscovery } from '@backstage/backend-common';
import { PluginTaskScheduler } from '@backstage/backend-tasks';
import { TaskScheduleDefinition } from '@backstage/backend-tasks';
import { TokenManager } from '@backstage/backend-common';
import { UrlReader } from '@backstage/backend-common';
// @public (undocumented)
export function createRouter(
pluginOptions: PluginOptions,
routerOptions: RouterOptions,
): Promise<express.Router>;
// @public (undocumented)
export function createRouterFromConfig(
routerOptions: RouterOptions,
): Promise<express.Router>;
// @public (undocumented)
export interface LinguistBackendApi {
// (undocumented)
getEntityLanguages(entityRef: string): Promise<Languages>;
// (undocumented)
processEntities(): Promise<void>;
}
// @public
const linguistPlugin: () => BackendFeature;
export default linguistPlugin;
// @public
export class LinguistTagsProcessor implements CatalogProcessor {
constructor(options: LinguistTagsProcessorOptions);
// (undocumented)
static fromConfig(
config: Config,
options: LinguistTagsProcessorOptions,
): LinguistTagsProcessor;
// (undocumented)
getProcessorName(): string;
preProcessEntity(
entity: Entity,
_: any,
__: any,
___: any,
cache: CatalogProcessorCache,
): Promise<Entity>;
}
// @public
export interface LinguistTagsProcessorOptions {
bytesThreshold?: number;
cacheTTL?: HumanDuration;
// (undocumented)
discovery: DiscoveryService;
languageMap?: Record<string, string | undefined>;
languageTypes?: LanguageType[];
// (undocumented)
logger: LoggerService;
shouldProcessEntity?: ShouldProcessEntity;
}
// @public (undocumented)
export interface PluginOptions {
// (undocumented)
age?: HumanDuration;
// (undocumented)
batchSize?: number;
// (undocumented)
kind?: string[];
// (undocumented)
linguistJsOptions?: Record<string, unknown>;
// (undocumented)
schedule?: TaskScheduleDefinition;
// (undocumented)
useSourceLocation?: boolean;
}
// @public (undocumented)
export interface RouterOptions {
// (undocumented)
auth?: AuthService;
// (undocumented)
config?: Config;
// (undocumented)
database: PluginDatabaseManager;
// (undocumented)
discovery: PluginEndpointDiscovery;
// (undocumented)
httpAuth?: HttpAuthService;
// (undocumented)
linguistBackendApi?: LinguistBackendApi;
// (undocumented)
logger: LoggerService;
// (undocumented)
reader: UrlReader;
// (undocumented)
scheduler?: PluginTaskScheduler;
// (undocumented)
tokenManager: TokenManager;
}
// @public
export type ShouldProcessEntity = (entity: Entity) => boolean;
```
@@ -1,9 +0,0 @@
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: backstage-plugin-linguist-backend
title: '@backstage/plugin-linguist-backend'
spec:
lifecycle: experimental
type: backstage-backend-plugin
owner: awanlin
-72
View File
@@ -1,72 +0,0 @@
/*
* Copyright 2023 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 { TaskScheduleDefinition } from '@backstage/backend-tasks';
import { HumanDuration } from '@backstage/types';
import { Options as LinguistJsOptions } from 'linguist-js/dist/types';
export interface Config {
/** Configuration options for the linguist plugin */
linguist?: {
schedule?: TaskScheduleDefinition;
/**
* @default 20
*/
batchSize?: number;
/**
* @default false
*/
useSourceLocation?: boolean;
/**
* Refresh generated language breakdown
*/
age?: HumanDuration;
/**
* @default ['API', 'Component', 'Template']
*/
kind?: string[];
/**
* [linguist-js](https://www.npmjs.com/package/linguist-js) options
*/
linguistJsOptions?: LinguistJsOptions;
/** Options for the tags processor */
tagsProcessor?: {
/**
* Determines how many bytes of a language should be in a repo
* for it to be added as an entity tag. Defaults to 0.
*/
bytesThreshold?: number;
/**
* The types of linguist languages that should be processed. Can be
* any of "programming", "data", "markup", "prose". Defaults to ["programming"].
*/
languageTypes?: string[];
/**
* A custom mapping of linguist languages to how they should be rendered as entity tags.
* If a language is mapped to '' it will not be included as a tag.
*/
languageMap?: {
[language: string]: string | undefined;
};
/**
* How long to cache entity languages for in memory. Used to avoid constant db hits during
* processing. Defaults to 30 minutes.
*/
cacheTTL?: HumanDuration;
};
};
}
-2
View File
@@ -1,2 +0,0 @@
# Knip report
@@ -1,63 +0,0 @@
/*
* Copyright 2022 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.
*/
/**
* @param {import('knex').Knex} knex
*/
exports.up = async function up(knex) {
// Note for the reader: the knex increments types automatically make it a
// primary column, whether you like it or not. That's why the id column is
// not marked as primary as one might have expected; it's only used for
// lookups by ID. Because, SQLite and MySQL don't return RETURNING on
// inserts ... so we want a manually generated key for lookups (an uuid),
// and also an index for ordering guarantees :)
await knex.schema.createTable('entity_result', table => {
table.comment('Table containing results from running Linguist');
table
.bigIncrements('index')
.notNullable()
.comment('An insert counter to ensure ordering');
table.uuid('id').notNullable().comment('The ID of the Linguist result');
table
.string('entity_ref')
.unique()
.notNullable()
.comment('The entity ref that this Linguist result applies to');
table.text('languages').comment('The results json as a string');
table
.dateTime('created_at')
.defaultTo(knex.fn.now())
.notNullable()
.comment('The timestamp when this entry was created');
table
.dateTime('processed_date')
.defaultTo(knex.fn.now())
.comment('The timestamp when this entity was processed');
table.index('index', 'entity_result_index_idx');
table.index('entity_ref', 'entity_result_entity_ref_idx');
});
};
/**
* @param {import('knex').Knex} knex
*/
exports.down = async function down(knex) {
await knex.schema.alterTable('entity_result', table => {
table.dropIndex([], 'entity_result_index_idx');
table.dropIndex([], 'entity_result_entity_ref_idx');
});
await knex.schema.dropTable('entity_result');
};
@@ -1,39 +0,0 @@
/*
* Copyright 2023 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.
*/
/**
* @param {import('knex').Knex} knex
*/
exports.up = async function up(knex) {
// Sqlite does not support this raw SQL
if (!knex.client.config.client.includes('sqlite3')) {
await knex.raw(
'ALTER TABLE entity_result ALTER COLUMN processed_date DROP DEFAULT;',
);
}
};
/**
* @param {import('knex').Knex} knex
*/
exports.down = async function down(knex) {
// Sqlite does not support this raw SQL
if (!knex.client.config.client.includes('sqlite3')) {
await knex.raw(
'ALTER TABLE entity_result ALTER COLUMN processed_date SET DEFAULT now();',
);
}
};
-67
View File
@@ -1,67 +0,0 @@
{
"name": "@backstage/plugin-linguist-backend",
"version": "0.5.15",
"backstage": {
"role": "backend-plugin",
"moved": "@backstage-community/plugin-linguist-backend"
},
"publishConfig": {
"access": "public",
"main": "dist/index.cjs.js",
"types": "dist/index.d.ts"
},
"homepage": "https://backstage.io",
"repository": {
"type": "git",
"url": "https://github.com/backstage/backstage",
"directory": "plugins/linguist-backend"
},
"license": "Apache-2.0",
"main": "src/index.ts",
"types": "src/index.ts",
"files": [
"dist",
"config.d.ts",
"migrations/**/*.{js,d.ts}"
],
"scripts": {
"build": "backstage-cli package build",
"clean": "backstage-cli package clean",
"lint": "backstage-cli package lint",
"prepack": "backstage-cli package prepack",
"postpack": "backstage-cli package postpack",
"start": "backstage-cli package start",
"test": "backstage-cli package test"
},
"dependencies": {
"@backstage/backend-common": "workspace:^",
"@backstage/backend-plugin-api": "workspace:^",
"@backstage/backend-tasks": "workspace:^",
"@backstage/catalog-client": "workspace:^",
"@backstage/catalog-model": "workspace:^",
"@backstage/config": "workspace:^",
"@backstage/errors": "workspace:^",
"@backstage/plugin-catalog-node": "workspace:^",
"@backstage/plugin-linguist-common": "workspace:^",
"@backstage/types": "workspace:^",
"@types/express": "*",
"express": "^4.18.1",
"express-promise-router": "^4.1.0",
"fs-extra": "^11.0.0",
"knex": "^3.0.0",
"linguist-js": "^2.5.3",
"luxon": "^3.0.0",
"node-fetch": "^2.6.7",
"uuid": "^9.0.0",
"yn": "^4.0.0"
},
"devDependencies": {
"@backstage/backend-test-utils": "workspace:^",
"@backstage/cli": "workspace:^",
"@types/supertest": "^2.0.8",
"js-yaml": "^4.1.0",
"supertest": "^6.2.4"
},
"configSchema": "config.d.ts",
"deprecated": "This package has been moved to the backstage/community-plugins repository. You should migrate to using @backstage-community/plugin-linguist-backend instead."
}
@@ -1,417 +0,0 @@
/*
* Copyright 2023 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 {
getVoidLogger,
ReadTreeResponse,
UrlReader,
} from '@backstage/backend-common';
import { CatalogApi, GetEntitiesResponse } from '@backstage/catalog-client';
import { Results } from 'linguist-js/dist/types';
import { DateTime } from 'luxon';
import { LinguistBackendStore } from '../db';
import { kindOrDefault, LinguistBackendClient } from './LinguistBackendClient';
import fs from 'fs-extra';
import { LINGUIST_ANNOTATION } from '@backstage/plugin-linguist-common';
import { mockServices } from '@backstage/backend-test-utils';
const linguistResultMock = Promise.resolve({
files: {
count: 4,
bytes: 6010,
results: {
'/src/index.ts': 'TypeScript',
'/src/cli.js': 'JavaScript',
'/readme.md': 'Markdown',
'/no-lang': null,
},
alternatives: {
'~/alternatives.asc': ['AsciiDoc', 'Public Key'],
},
},
languages: {
count: 3,
bytes: 6000,
results: {
JavaScript: { type: 'programming', bytes: 1000, color: '#f1e05a' },
TypeScript: { type: 'programming', bytes: 2000, color: '#2b7489' },
Markdown: { type: 'prose', bytes: 3000, color: '#083fa1' },
},
},
unknown: {
count: 1,
bytes: 10,
filenames: {
'no-lang': 10,
},
extensions: {},
},
} as Results);
describe('kindOrDefault', () => {
it('should return default kind when undefined', () => {
expect(kindOrDefault()).toEqual(['API', 'Component', 'Template']);
});
it('should return the default kind when empty', () => {
expect(kindOrDefault([])).toEqual(['API', 'Component', 'Template']);
});
it('should return provided kind when not empty', () => {
expect(kindOrDefault(['API'])).toEqual(['API']);
});
});
describe('Linguist backend API', () => {
const logger = getVoidLogger();
const store: jest.Mocked<LinguistBackendStore> = {
insertEntityResults: jest.fn(),
insertNewEntity: jest.fn(),
getEntityResults: jest.fn(),
getProcessedEntities: jest.fn(),
getUnprocessedEntities: jest.fn(),
getAllEntities: jest.fn(),
deleteEntity: jest.fn(),
};
const urlReader: jest.Mocked<UrlReader> = {
readTree: jest.fn(),
search: jest.fn(),
readUrl: jest.fn(),
};
const catalogApi: jest.Mocked<CatalogApi> = {
getEntities: jest.fn(),
getEntityByRef: jest.fn(),
} as any;
const api = new LinguistBackendClient(
logger,
store,
urlReader,
mockServices.auth(),
catalogApi,
);
beforeEach(() => {
jest.resetAllMocks();
});
it('should get languages for an entity', async () => {
store.getEntityResults.mockResolvedValue({
languageCount: 1,
totalBytes: 2205,
processedDate: '2023-02-15T20:10:21.378Z',
breakdown: [
{
name: 'YAML',
percentage: 100,
bytes: 2205,
type: 'data',
color: '#cb171e',
},
],
});
const entityRef = 'template:default/create-react-app-template';
const languages = await api.getEntityLanguages(entityRef);
expect(languages).toEqual({
languageCount: 1,
totalBytes: 2205,
processedDate: '2023-02-15T20:10:21.378Z',
breakdown: [
{
name: 'YAML',
percentage: 100,
bytes: 2205,
type: 'data',
color: '#cb171e',
},
],
});
});
it('should insert new entities', async () => {
const testEntityListResponse: GetEntitiesResponse = {
items: [
{
apiVersion: 'backstage.io/v1beta1',
metadata: {
name: 'service-one',
},
kind: 'Component',
},
{
apiVersion: 'backstage.io/v1beta1',
metadata: {
name: 'service-two',
},
kind: 'Component',
},
{
apiVersion: 'backstage.io/v1beta1',
metadata: {
name: 'service-three',
},
kind: 'Component',
},
],
};
catalogApi.getEntities.mockResolvedValue(testEntityListResponse);
await api.addNewEntities();
expect(store.insertNewEntity).toHaveBeenCalledTimes(3);
});
it('should delete entities not in Catalog', async () => {
store.getAllEntities.mockResolvedValue([
'component:default/service-one',
'component:default/stale-service-two',
]);
catalogApi.getEntityByRef.mockResolvedValueOnce({
apiVersion: 'backstage.io/v1beta1',
metadata: {
name: 'service-one',
},
kind: 'Component',
});
await api.cleanEntities();
expect(store.deleteEntity).toHaveBeenCalledTimes(1);
});
it('should get default entity overview', async () => {
store.getProcessedEntities.mockResolvedValue([
{
entityRef: 'component:default/service-one',
processedDate: DateTime.now().toJSDate(),
},
{
entityRef: 'component:default/stale-service-two',
processedDate: DateTime.now().minus({ days: 45 }).toJSDate(),
},
]);
store.getUnprocessedEntities.mockResolvedValue([
'component:default/service-three',
'component:default/service-four',
'component:default/service-five',
]);
const overview = await api.getEntitiesOverview();
expect(overview.entityCount).toEqual(5);
expect(overview.processedCount).toEqual(2);
expect(overview.staleCount).toEqual(0);
expect(overview.pendingCount).toEqual(3);
expect(overview.filteredEntities).toEqual([
'component:default/service-three',
'component:default/service-four',
'component:default/service-five',
]);
});
it('should get entity overview with stale items', async () => {
const apiWithAge = new LinguistBackendClient(
logger,
store,
urlReader,
mockServices.auth(),
catalogApi,
{ days: 5 },
);
store.getProcessedEntities.mockResolvedValue([
{
entityRef: 'component:default/service-one',
processedDate: DateTime.now().toJSDate(),
},
{
entityRef: 'component:default/stale-service-two',
processedDate: DateTime.now().minus({ days: 45 }).toJSDate(),
},
]);
store.getUnprocessedEntities.mockResolvedValue([
'component:default/service-three',
'component:default/service-four',
'component:default/service-five',
]);
const overview = await apiWithAge.getEntitiesOverview();
expect(overview.entityCount).toEqual(5);
expect(overview.processedCount).toEqual(2);
expect(overview.staleCount).toEqual(1);
expect(overview.pendingCount).toEqual(4);
expect(overview.filteredEntities).toEqual([
'component:default/service-three',
'component:default/service-four',
'component:default/service-five',
'component:default/stale-service-two',
]);
});
it('should generate and save languages for an entity', async () => {
const spy = jest
.spyOn(api, 'getLinguistResults')
.mockImplementation(() => linguistResultMock);
urlReader.readTree.mockResolvedValueOnce({
files: async () => [
{
content: async () => Buffer.from('-- XXX: code-data', 'utf8'),
path: 'my-file.js',
},
],
dir: async () => '/temp/my-code',
} as ReadTreeResponse);
const fsSpy = jest.spyOn(fs, 'remove');
await api.generateEntityLanguages(
'component:default/fake-service',
'https://some.fake/service/',
);
expect(api.getLinguistResults).toHaveBeenCalled();
expect(store.insertEntityResults).toHaveBeenCalled();
expect(fs.remove).toHaveBeenCalled();
spy.mockClear();
fsSpy.mockClear();
});
it('should generate languages for entities using default', async () => {
store.getProcessedEntities.mockResolvedValue([
{
entityRef: 'component:default/service-one',
processedDate: DateTime.now().toJSDate(),
},
{
entityRef: 'component:default/stale-service-two',
processedDate: DateTime.now().minus({ days: 45 }).toJSDate(),
},
]);
store.getUnprocessedEntities.mockResolvedValue([
'component:default/service-three',
'component:default/service-four',
'component:default/service-five',
]);
const entity = {
apiVersion: 'backstage.io/v1beta1',
metadata: {
name: 'service-one',
annotations: {
[LINGUIST_ANNOTATION]: 'https://some.fake/service/',
},
},
kind: 'Component',
};
catalogApi.getEntityByRef.mockResolvedValue(entity);
const resultsSpy = jest
.spyOn(api, 'getLinguistResults')
.mockImplementation(() => linguistResultMock);
urlReader.readTree.mockResolvedValue({
files: async () => [
{
content: async () => Buffer.from('-- XXX: code-data', 'utf8'),
path: 'my-file.js',
},
],
dir: async () => '/temp/my-code',
} as ReadTreeResponse);
const fsSpy = jest.spyOn(fs, 'remove');
const generateEntityLanguages = jest.spyOn(api, 'generateEntityLanguages');
await api.generateEntitiesLanguages();
expect(generateEntityLanguages).toHaveBeenCalledTimes(3);
generateEntityLanguages.mockClear();
resultsSpy.mockClear();
fsSpy.mockClear();
});
it('should generate languages for entities using defined batch size', async () => {
const apiWithBatchSize = new LinguistBackendClient(
logger,
store,
urlReader,
mockServices.auth(),
catalogApi,
undefined,
2,
);
store.getProcessedEntities.mockResolvedValue([
{
entityRef: 'component:default/service-one',
processedDate: DateTime.now().toJSDate(),
},
{
entityRef: 'component:default/stale-service-two',
processedDate: DateTime.now().minus({ days: 45 }).toJSDate(),
},
]);
store.getUnprocessedEntities.mockResolvedValue([
'component:default/service-three',
'component:default/service-four',
'component:default/service-five',
]);
const entity = {
apiVersion: 'backstage.io/v1beta1',
metadata: {
name: 'service-one',
annotations: {
[LINGUIST_ANNOTATION]: 'https://some.fake/service/',
},
},
kind: 'Component',
};
catalogApi.getEntityByRef.mockResolvedValue(entity);
const resultsSpy = jest
.spyOn(apiWithBatchSize, 'getLinguistResults')
.mockImplementation(() => linguistResultMock);
urlReader.readTree.mockResolvedValue({
files: async () => [
{
content: async () => Buffer.from('-- XXX: code-data', 'utf8'),
path: 'my-file.js',
},
],
dir: async () => '/temp/my-code',
} as ReadTreeResponse);
const fsSpy = jest.spyOn(fs, 'remove');
const generateEntityLanguages = jest.spyOn(
apiWithBatchSize,
'generateEntityLanguages',
);
await apiWithBatchSize.generateEntitiesLanguages();
expect(generateEntityLanguages).toHaveBeenCalledTimes(2);
generateEntityLanguages.mockClear();
resultsSpy.mockClear();
fsSpy.mockClear();
});
});
@@ -1,283 +0,0 @@
/*
* Copyright 2022 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 {
EntitiesOverview,
EntityResults,
Language,
Languages,
LINGUIST_ANNOTATION,
} from '@backstage/plugin-linguist-common';
import {
CATALOG_FILTER_EXISTS,
CatalogApi,
GetEntitiesRequest,
} from '@backstage/catalog-client';
import { UrlReader } from '@backstage/backend-common';
import { DateTime } from 'luxon';
import { LinguistBackendStore } from '../db';
import fs from 'fs-extra';
import linguist from 'linguist-js';
import {
ANNOTATION_SOURCE_LOCATION,
stringifyEntityRef,
} from '@backstage/catalog-model';
import { assertError } from '@backstage/errors';
import { HumanDuration } from '@backstage/types';
import { Results } from 'linguist-js/dist/types';
import { type AuthService, LoggerService } from '@backstage/backend-plugin-api';
/** @public */
export interface LinguistBackendApi {
getEntityLanguages(entityRef: string): Promise<Languages>;
processEntities(): Promise<void>;
}
/** @public */
export class LinguistBackendClient implements LinguistBackendApi {
private readonly logger: LoggerService;
private readonly store: LinguistBackendStore;
private readonly urlReader: UrlReader;
private readonly auth: AuthService;
private readonly catalogApi: CatalogApi;
private readonly age?: HumanDuration;
private readonly batchSize?: number;
private readonly useSourceLocation?: boolean;
private readonly kind: string[];
private readonly linguistJsOptions?: Record<string, unknown>;
public constructor(
logger: LoggerService,
store: LinguistBackendStore,
urlReader: UrlReader,
auth: AuthService,
catalogApi: CatalogApi,
age?: HumanDuration,
batchSize?: number,
useSourceLocation?: boolean,
kind?: string[],
linguistJsOptions?: Record<string, unknown>,
) {
this.logger = logger;
this.store = store;
this.urlReader = urlReader;
this.auth = auth;
this.catalogApi = catalogApi;
this.batchSize = batchSize;
this.age = age;
this.useSourceLocation = useSourceLocation;
this.kind = kindOrDefault(kind);
this.linguistJsOptions = linguistJsOptions;
}
async getEntityLanguages(entityRef: string): Promise<Languages> {
this.logger?.debug(`Getting languages for entity "${entityRef}"`);
return this.store.getEntityResults(entityRef);
}
async processEntities(): Promise<void> {
this.logger?.info('Updating list of entities');
await this.addNewEntities();
this.logger?.info('Cleaning list of entities');
await this.cleanEntities();
this.logger?.info('Processing applicable entities through Linguist');
await this.generateEntitiesLanguages();
}
/** @internal */
async addNewEntities(): Promise<void> {
const annotationKey = this.useSourceLocation
? ANNOTATION_SOURCE_LOCATION
: LINGUIST_ANNOTATION;
const request: GetEntitiesRequest = {
filter: {
kind: this.kind,
[`metadata.annotations.${annotationKey}`]: CATALOG_FILTER_EXISTS,
},
fields: ['kind', 'metadata'],
};
const { token } = await this.auth.getPluginRequestToken({
onBehalfOf: await this.auth.getOwnServiceCredentials(),
targetPluginId: 'catalog',
});
const response = await this.catalogApi.getEntities(request, { token });
const entities = response.items;
entities.forEach(entity => {
const entityRef = stringifyEntityRef(entity);
this.store.insertNewEntity(entityRef);
});
}
/** @internal */
async cleanEntities(): Promise<void> {
this.logger?.info('Cleaning entities in Linguist queue');
const allEntities = await this.store.getAllEntities();
for (const entityRef of allEntities) {
const { token } = await this.auth.getPluginRequestToken({
onBehalfOf: await this.auth.getOwnServiceCredentials(),
targetPluginId: 'catalog',
});
const result = await this.catalogApi.getEntityByRef(entityRef, { token });
if (!result) {
this.logger?.info(
`Entity ${entityRef} was not found in the Catalog, it will be deleted`,
);
await this.store.deleteEntity(entityRef);
}
}
}
/** @internal */
async generateEntitiesLanguages(): Promise<void> {
const entitiesOverview = await this.getEntitiesOverview();
this.logger?.info(
`Entities overview: Entity: ${entitiesOverview.entityCount}, Processed: ${entitiesOverview.processedCount}, Pending: ${entitiesOverview.pendingCount}, Stale ${entitiesOverview.staleCount}`,
);
const entities = entitiesOverview.filteredEntities.slice(
0,
this.batchSize ?? 20,
);
for (const entityRef of entities) {
const { token } = await this.auth.getPluginRequestToken({
onBehalfOf: await this.auth.getOwnServiceCredentials(),
targetPluginId: 'catalog',
});
const entity = await this.catalogApi.getEntityByRef(entityRef, {
token,
});
const annotationKey = this.useSourceLocation
? ANNOTATION_SOURCE_LOCATION
: LINGUIST_ANNOTATION;
let url = entity?.metadata.annotations?.[annotationKey] ?? '';
if (url.startsWith('url:')) {
url = url.slice(4);
}
try {
await this.generateEntityLanguages(entityRef, url);
} catch (error) {
assertError(error);
this.logger.error(
`Unable to process "${entityRef}" using "${url}", message: ${error.message}, stack: ${error.stack}`,
);
}
}
}
/** @internal */
async getEntitiesOverview(): Promise<EntitiesOverview> {
this.logger?.debug('Getting pending entities');
const processedEntities = await this.store.getProcessedEntities();
const staleEntities = processedEntities
.filter(pe => {
if (this.age === undefined) return false;
const staleDate = DateTime.now().minus(this.age as HumanDuration);
return DateTime.fromJSDate(pe.processedDate) <= staleDate;
})
.map(pe => pe.entityRef);
const unprocessedEntities = await this.store.getUnprocessedEntities();
const filteredEntities = unprocessedEntities.concat(staleEntities);
const entitiesOverview: EntitiesOverview = {
entityCount: unprocessedEntities.length + processedEntities.length,
processedCount: processedEntities.length,
staleCount: staleEntities.length,
pendingCount: filteredEntities.length,
filteredEntities: filteredEntities,
};
return entitiesOverview;
}
/** @internal */
async generateEntityLanguages(
entityRef: string,
url: string,
): Promise<string> {
this.logger?.info(
`Processing languages for entity ${entityRef} from ${url}`,
);
const readTreeResponse = await this.urlReader.readTree(url);
const dir = await readTreeResponse.dir();
const results = await this.getLinguistResults(dir);
try {
const totalBytes = results.languages.bytes;
const langResults = results.languages.results;
const breakdown: Language[] = [];
for (const key in langResults) {
if (Object.prototype.hasOwnProperty.call(langResults, key)) {
const lang: Language = {
name: key,
percentage: +((langResults[key].bytes / totalBytes) * 100).toFixed(
2,
),
bytes: langResults[key].bytes,
type: langResults[key].type,
color: langResults[key].color,
};
breakdown.push(lang);
}
}
const languages: Languages = {
languageCount: results.languages.count,
totalBytes: totalBytes,
processedDate: new Date().toISOString(),
breakdown: breakdown,
};
const entityResults: EntityResults = {
entityRef: entityRef,
results: languages,
};
return await this.store.insertEntityResults(entityResults);
} finally {
this.logger?.info(`Cleaning up files from ${dir}`);
await fs.remove(dir);
}
}
/** @internal */
async getLinguistResults(dir: string): Promise<Results> {
const results = await linguist(dir, { ...this.linguistJsOptions });
return results;
}
}
export function kindOrDefault(kind?: string[]): string[] {
if (!kind || kind.length === 0) {
return ['API', 'Component', 'Template'];
}
return kind;
}
-17
View File
@@ -1,17 +0,0 @@
/*
* Copyright 2022 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 type { LinguistBackendApi } from './LinguistBackendClient';
@@ -1,200 +0,0 @@
/*
* Copyright 2021 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 { Knex as KnexType, Knex } from 'knex';
import { TestDatabases } from '@backstage/backend-test-utils';
import {
LinguistBackendDatabase,
LinguistBackendStore,
} from './LinguistBackendDatabase';
import { Languages, ProcessedEntity } from '@backstage/plugin-linguist-common';
function createDatabaseManager(
client: KnexType,
skipMigrations: boolean = false,
) {
return {
getClient: async () => client,
migrations: {
skip: skipMigrations,
},
};
}
const rawDbEntityResultRows = [
{
id: '14b32439-848a-49b2-92a4-98753f932606',
entity_ref: 'template:default/create-react-app-template',
languages: undefined,
processed_date: undefined,
},
{
id: '8c85d3ec-ccea-4b29-9de9-ccdd7e5ba452',
entity_ref: 'template:default/docs-template',
languages: undefined,
processed_date: undefined,
},
{
id: 'b922c87b-37bc-4505-b4af-70a1438decda',
entity_ref: 'template:default/pull-request',
languages:
'{"languageCount":1,"totalBytes":2205,"processedDate":"2023-02-15T20:10:21.378Z","breakdown":[{"name":"YAML","percentage":100,"bytes":2205,"type":"data","color":"#cb171e"}]}',
processed_date: new Date('2023-02-15 20:10:21.378Z'),
},
{
id: 'bd555e6d-a3d0-4b48-a930-194db8f80db7',
entity_ref: 'template:default/react-ssr-template',
languages:
'{"languageCount":8,"totalBytes":8988,"processedDate":"2023-02-15T20:10:21.388Z","breakdown":[{"name":"INI","percentage":2.26,"bytes":203,"type":"data","color":"#d1dbe0"},{"name":"JavaScript","percentage":5.94,"bytes":534,"type":"programming","color":"#f1e05a"},{"name":"YAML","percentage":31.09,"bytes":2794,"type":"data","color":"#cb171e"},{"name":"Markdown","percentage":11.79,"bytes":1060,"type":"prose","color":"#083fa1"},{"name":"JSON","percentage":21.09,"bytes":1896,"type":"data","color":"#292929"},{"name":"CSS","percentage":0,"bytes":0,"type":"markup","color":"#563d7c"},{"name":"TSX","percentage":26.01,"bytes":2338,"type":"programming","color":"#3178c6"},{"name":"TypeScript","percentage":1.81,"bytes":163,"type":"programming","color":"#3178c6"}]}',
processed_date: new Date('2023-02-15 20:10:21.388Z'),
},
{
id: '4145c0cf-44e9-4e95-9d57-781af4685b28',
entity_ref: 'template:default/springboot-template',
languages:
'{"languageCount":9,"totalBytes":80986,"processedDate":"2023-02-15T20:10:21.419Z","breakdown":[{"name":"Shell","percentage":0.16,"bytes":130,"type":"programming","color":"#89e051"},{"name":"INI","percentage":0.35,"bytes":286,"type":"data","color":"#d1dbe0"},{"name":"Dockerfile","percentage":0.3,"bytes":246,"type":"programming","color":"#384d54"},{"name":"YAML","percentage":3.92,"bytes":3171,"type":"data","color":"#cb171e"},{"name":"Markdown","percentage":1.31,"bytes":1059,"type":"prose","color":"#083fa1"},{"name":"XML","percentage":10.48,"bytes":8491,"type":"data","color":"#0060ac"},{"name":"Java","percentage":1.8,"bytes":1455,"type":"programming","color":"#b07219"},{"name":"Text","percentage":81.22,"bytes":65780,"type":"prose"},{"name":"Protocol Buffer","percentage":0.45,"bytes":368,"type":"data"}]}',
processed_date: new Date('2023-02-15 20:10:21.419Z'),
},
];
describe('Linguist database', () => {
const databases = TestDatabases.create();
let store: LinguistBackendStore;
let testDbClient: Knex<any, unknown[]>;
beforeAll(async () => {
testDbClient = await databases.init('SQLITE_3');
const database = createDatabaseManager(testDbClient);
store = await LinguistBackendDatabase.create(await database.getClient());
});
beforeEach(async () => {
await testDbClient.batchInsert('entity_result', rawDbEntityResultRows);
});
afterEach(async () => {
await testDbClient('entity_result').delete();
});
it('should be able to return entity results', async () => {
const validLanguagesResult: Languages = {
languageCount: 1,
totalBytes: 2205,
processedDate: '2023-02-15T20:10:21.378Z',
breakdown: [
{
name: 'YAML',
percentage: 100,
bytes: 2205,
type: 'data',
color: '#cb171e',
},
],
};
const entityResult = await store.getEntityResults(
'template:default/pull-request',
);
expect(entityResult).toMatchObject(validLanguagesResult);
});
it('should return empty entity results when not found', async () => {
const validEmptyLanguagesResult: Languages = {
languageCount: 0,
totalBytes: 0,
processedDate: 'undefined',
breakdown: [],
};
const entityResult = await store.getEntityResults(
'template:default/create-react-app-template',
);
expect(entityResult).toMatchObject(validEmptyLanguagesResult);
});
it('should be able to return unprocessed entities', async () => {
const validUnprocessedEntities: string[] = [
'template:default/create-react-app-template',
'template:default/docs-template',
];
const unprocessedEntities = await store.getUnprocessedEntities();
expect(unprocessedEntities).toMatchObject(validUnprocessedEntities);
});
it('should return string[] when there is no unprocessed entities', async () => {
await testDbClient('entity_result').delete();
const unprocessedEntities = await store.getUnprocessedEntities();
expect(unprocessedEntities).toMatchObject([]);
});
it('should be able to return processed entities', async () => {
const validProcessedEntities: ProcessedEntity[] = [
{
entityRef: 'template:default/pull-request',
processedDate: new Date('2023-02-15 20:10:21.378Z'),
},
{
entityRef: 'template:default/react-ssr-template',
processedDate: new Date('2023-02-15 20:10:21.388Z'),
},
{
entityRef: 'template:default/springboot-template',
processedDate: new Date('2023-02-15 20:10:21.419Z'),
},
];
const processedEntities = await store.getProcessedEntities();
expect(processedEntities).toMatchObject(validProcessedEntities);
});
it('should return string[] when there is no processed entities', async () => {
await testDbClient('entity_result').delete();
const unprocessedEntities = await store.getProcessedEntities();
expect(unprocessedEntities).toMatchObject([]);
});
it('should insert new entities and ignore duplicates', async () => {
const before = testDbClient.from('entity_result').count();
await store.insertNewEntity('component:/default/new-entity-one');
await store.insertNewEntity('component:/default/new-entity-two');
await store.insertNewEntity('template:default/pull-request');
const after = testDbClient.from('entity_result').count();
expect(before).toEqual(after);
});
it('should get all entities', async () => {
const allEntities = await store.getAllEntities();
expect(allEntities.length).toEqual(5);
});
it('should delete entity by its entityRef', async () => {
const before = await testDbClient.from('entity_result').count();
await store.deleteEntity('template:default/create-react-app-template');
const after = await testDbClient.from('entity_result').count();
expect(before).toEqual([{ 'count(*)': 5 }]);
expect(after).toEqual([{ 'count(*)': 4 }]);
});
});
@@ -1,183 +0,0 @@
/*
* Copyright 2022 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 { resolvePackagePath } from '@backstage/backend-common';
import { Knex } from 'knex';
import { v4 as uuid } from 'uuid';
import {
EntityResults,
Languages,
ProcessedEntity,
} from '@backstage/plugin-linguist-common';
export type RawDbEntityResultRow = {
id: string;
entity_ref: string;
languages?: string;
processed_date?: Date;
};
/** @public */
export interface LinguistBackendStore {
insertEntityResults(entityLanguages: EntityResults): Promise<string>;
insertNewEntity(entityRef: string): Promise<void>;
getEntityResults(entityRef: string): Promise<Languages>;
getProcessedEntities(): Promise<ProcessedEntity[]>;
getUnprocessedEntities(): Promise<string[]>;
getAllEntities(): Promise<string[]>;
deleteEntity(entityRef: string): Promise<void>;
}
const migrationsDir = resolvePackagePath(
'@backstage/plugin-linguist-backend',
'migrations',
);
/** @public */
export class LinguistBackendDatabase implements LinguistBackendStore {
static async create(knex: Knex): Promise<LinguistBackendStore> {
await knex.migrate.latest({
directory: migrationsDir,
});
return new LinguistBackendDatabase(knex);
}
constructor(private readonly db: Knex) {}
async insertEntityResults(entityLanguages: EntityResults): Promise<string> {
const entityLanguageId = uuid();
const entityRef = entityLanguages.entityRef;
const [result] = await this.db<RawDbEntityResultRow>('entity_result')
.insert({
id: entityLanguageId,
entity_ref: entityRef,
languages: JSON.stringify(entityLanguages.results),
processed_date: new Date(entityLanguages.results.processedDate),
})
.onConflict('entity_ref')
.merge(['languages', 'processed_date'])
.returning('id');
return result.id;
}
async insertNewEntity(entityRef: string): Promise<void> {
const entityLanguageId = uuid();
await this.db<RawDbEntityResultRow>('entity_result')
.insert({
id: entityLanguageId,
entity_ref: entityRef,
})
.onConflict('entity_ref')
.ignore(); // If the entity_ref is in the table already then we don't want to add it again
}
async getEntityResults(entityRef: string): Promise<Languages> {
const entityResults = await this.db<RawDbEntityResultRow>('entity_result')
.whereNotNull('languages')
.where({ entity_ref: entityRef })
.first();
if (!entityResults || !entityResults.languages) {
const emptyResults: Languages = {
languageCount: 0,
totalBytes: 0,
processedDate: 'undefined',
breakdown: [],
};
return emptyResults;
}
try {
return JSON.parse(entityResults.languages);
} catch (error) {
throw new Error(`Failed to parse languages for '${entityRef}', ${error}`);
}
}
async getProcessedEntities(): Promise<ProcessedEntity[]> {
const rawEntities = await this.db<RawDbEntityResultRow>('entity_result')
.whereNotNull('processed_date')
.whereNotNull('languages');
if (!rawEntities) {
return [];
}
const processedEntities = rawEntities.map(rawEntity => {
// Note: processed_date should never be null, this is handled by the DB query above
let processedDate = new Date();
if (rawEntity.processed_date) {
// SQLite will return a Timestamp whereas Postgres will return a proper Date
// This tests to see if we are getting a timestamp and convert if needed
processedDate = new Date(+rawEntity.processed_date.toString());
if (isNaN(+rawEntity.processed_date.toString())) {
processedDate = rawEntity.processed_date;
}
}
const processEntity = {
entityRef: rawEntity.entity_ref,
processedDate,
};
return processEntity;
});
return processedEntities;
}
async getUnprocessedEntities(): Promise<string[]> {
const rawEntities = await this.db<RawDbEntityResultRow>('entity_result')
// TODO(ahhhndre) processed_date should always be null as well but it had a default to the current date
// once the default has been removed and released, we can then come back an enable this check
// .whereNull('processed_date')
.whereNull('languages')
.orderBy('created_at', 'asc');
if (!rawEntities) {
return [];
}
const unprocessedEntities = rawEntities.map(rawEntity => {
return rawEntity.entity_ref;
});
return unprocessedEntities;
}
async getAllEntities(): Promise<string[]> {
const rawEntities = await this.db<RawDbEntityResultRow>('entity_result');
if (!rawEntities) {
return [];
}
const allEntities = rawEntities.map(rawEntity => {
return rawEntity.entity_ref;
});
return allEntities;
}
async deleteEntity(entityRef: string): Promise<void> {
await this.db<RawDbEntityResultRow>('entity_result')
.where('entity_ref', entityRef)
.delete();
}
}
-18
View File
@@ -1,18 +0,0 @@
/*
* Copyright 2022 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 { LinguistBackendDatabase } from './LinguistBackendDatabase';
export type { LinguistBackendStore } from './LinguistBackendDatabase';
-26
View File
@@ -1,26 +0,0 @@
/*
* Copyright 2022 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.
*/
/**
* Linguist backend plugin that contains the API for generating and retrieving language breakdown which is used by the Linguist frontend plugin.
*
* @packageDocumentation
*/
export * from './processor';
export * from './service/router';
export type { LinguistBackendApi } from './api';
export { linguistPlugin as default } from './plugin';
-77
View File
@@ -1,77 +0,0 @@
/*
* Copyright 2023 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 {
coreServices,
createBackendPlugin,
} from '@backstage/backend-plugin-api';
import { createRouterFromConfig } from './service/router';
/**
* Linguist backend plugin
*
* @public
*/
export const linguistPlugin = createBackendPlugin({
pluginId: 'linguist',
register(env) {
env.registerInit({
deps: {
auth: coreServices.auth,
httpAuth: coreServices.httpAuth,
logger: coreServices.logger,
config: coreServices.rootConfig,
reader: coreServices.urlReader,
database: coreServices.database,
discovery: coreServices.discovery,
scheduler: coreServices.scheduler,
tokenManager: coreServices.tokenManager,
httpRouter: coreServices.httpRouter,
},
async init({
auth,
httpAuth,
logger,
config,
reader,
database,
discovery,
scheduler,
tokenManager,
httpRouter,
}) {
httpRouter.use(
await createRouterFromConfig({
auth,
httpAuth,
logger,
config,
reader,
database,
discovery,
scheduler,
tokenManager,
}),
);
httpRouter.addAuthPolicy({
path: '/health',
allow: 'unauthenticated',
});
},
});
},
});
@@ -1,343 +0,0 @@
/*
* Copyright 2023 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 {
LinguistTagsProcessor,
LinguistTagsProcessorOptions,
sanitizeTag,
} from './LinguistTagsProcessor';
import { ConfigReader } from '@backstage/config';
import { getVoidLogger } from '@backstage/backend-common';
import { CatalogProcessorCache } from '@backstage/plugin-catalog-node';
import { Entity, makeValidator } from '@backstage/catalog-model';
import { DiscoveryService } from '@backstage/backend-plugin-api';
import fetch, { Response } from 'node-fetch';
import * as path from 'path';
import yaml from 'js-yaml';
import * as fs from 'fs';
const { isValidTag } = makeValidator();
jest.mock('node-fetch', () => jest.fn());
const mockedFetch: jest.MockedFunction<typeof fetch> =
fetch as jest.MockedFunction<typeof fetch>;
const discovery: DiscoveryService = {
getBaseUrl: jest.fn().mockResolvedValue('http://example.com/api/linguist'),
getExternalBaseUrl: jest.fn(),
};
let state: Record<string, any> = {};
const mockCacheGet = jest
.fn()
.mockImplementation(async (key: string) => state[key]);
const mockCacheSet = jest.fn().mockImplementation((key: string, value: any) => {
state[key] = value;
});
const cache: CatalogProcessorCache = {
get: mockCacheGet,
set: mockCacheSet,
};
describe('sanitizeTag', () => {
const linguistDataSet = yaml.load(
fs.readFileSync(
path.resolve(require.resolve('linguist-js'), '../../ext/languages.yml'),
'utf-8',
),
) as Object;
const languages = Object.keys(linguistDataSet);
test('Should clean up all linguist languages', () => {
const invalid = languages
.map(sanitizeTag)
.filter(lang => !isValidTag(lang));
expect(invalid).toStrictEqual([]);
// Keep a snapshot here so that as new languages are added to linguist,
// we can spot check them to make sure the transformer for them makes sense.
expect(languages.map(sanitizeTag)).toMatchSnapshot();
});
});
describe('LinguistTagsProcessor', () => {
afterEach(() => {
mockedFetch.mockReset();
mockCacheGet.mockClear();
mockCacheSet.mockClear();
state = {};
});
test('Should construct fromConfig', () => {
const config = new ConfigReader({
linguist: {},
});
expect(() => {
return LinguistTagsProcessor.fromConfig(config, {
logger: getVoidLogger(),
discovery,
});
}).not.toThrow();
});
test('Should assign valid language tags', async () => {
const processor = buildProcessor({});
mockFetchImplementation();
const entity = baseEntity();
await processor.preProcessEntity(entity, null, null, null, cache);
expect(mockedFetch).toHaveBeenCalledTimes(1);
expect(entity.metadata.tags).toStrictEqual([
'c++',
'asp-dot-net',
'java',
'common-lisp',
]);
entity.metadata.tags?.forEach(tag => {
expect(isValidTag(tag)).toBeTruthy();
});
});
test('Should not duplicate existing tags', async () => {
const processor = buildProcessor({});
mockFetchImplementation();
const entity = baseEntity();
entity.metadata.tags = ['existing', 'tags', 'java'];
await processor.preProcessEntity(entity, null, null, null, cache);
expect(mockedFetch).toHaveBeenCalledTimes(1);
expect(entity.metadata.tags).toStrictEqual([
'existing',
'tags',
'java',
'c++',
'asp-dot-net',
'common-lisp',
]);
});
test('Should not process Resource entities by default', async () => {
const processor = buildProcessor({});
mockFetchImplementation();
const entity = baseEntity();
entity.kind = 'Resource';
await processor.preProcessEntity(entity, null, null, null, cache);
expect(mockedFetch).toHaveBeenCalledTimes(0);
expect(entity.metadata.tags).toStrictEqual(undefined);
});
test('Can process Resource entities by overriding shouldProcessEntity', async () => {
const processor = buildProcessor({
shouldProcessEntity: (entity: Entity) => {
return entity.kind === 'Resource';
},
});
mockFetchImplementation();
const entity = baseEntity();
entity.kind = 'Resource';
await processor.preProcessEntity(entity, null, null, null, cache);
expect(mockedFetch).toHaveBeenCalledTimes(1);
expect(entity.metadata.tags).toStrictEqual([
'c++',
'asp-dot-net',
'java',
'common-lisp',
]);
});
test('Can omit languages using languageMap', async () => {
const processor = buildProcessor({
languageMap: {
Java: '',
'ASP.net': '',
},
});
mockFetchImplementation();
const entity = baseEntity();
await processor.preProcessEntity(entity, null, null, null, cache);
expect(mockedFetch).toHaveBeenCalledTimes(1);
expect(entity.metadata.tags).toStrictEqual(['c++', 'common-lisp']);
});
test('Can rewrite langs using languageMap', async () => {
const processor = buildProcessor({
languageMap: {
Java: 'notjava',
},
});
mockFetchImplementation();
const entity = baseEntity();
await processor.preProcessEntity(entity, null, null, null, cache);
expect(mockedFetch).toHaveBeenCalledTimes(1);
expect(entity.metadata.tags).toStrictEqual([
'c++',
'asp-dot-net',
'notjava',
'common-lisp',
]);
});
test('Can omit languages less than bytesThreshold', async () => {
const processor = buildProcessor({
bytesThreshold: 5000,
});
mockFetchImplementation();
const entity = baseEntity();
await processor.preProcessEntity(entity, null, null, null, cache);
expect(mockedFetch).toHaveBeenCalledTimes(1);
expect(entity.metadata.tags).toStrictEqual(['java', 'common-lisp']);
});
test('Can include languages that arent programming', async () => {
const processor = buildProcessor({
languageTypes: ['data'],
});
mockFetchImplementation();
const entity = baseEntity();
await processor.preProcessEntity(entity, null, null, null, cache);
expect(mockedFetch).toHaveBeenCalledTimes(1);
expect(entity.metadata.tags).toStrictEqual(['yaml', 'json']);
});
test('Refetches from API when cache disabled', async () => {
const processor = buildProcessor({
cacheTTL: { minutes: 0 },
});
mockFetchImplementation();
const entity = baseEntity();
await processor.preProcessEntity(entity, null, null, null, cache);
expect(mockedFetch).toHaveBeenCalledTimes(1);
expect(mockCacheGet).toHaveBeenCalledTimes(0);
expect(mockCacheSet).toHaveBeenCalledTimes(0);
mockedFetch.mockClear();
await processor.preProcessEntity(entity, null, null, null, cache);
expect(mockedFetch).toHaveBeenCalledTimes(1);
expect(mockCacheGet).toHaveBeenCalledTimes(0);
expect(mockCacheSet).toHaveBeenCalledTimes(0);
});
test('Caches across runs with cache enabled', async () => {
const processor = buildProcessor({
cacheTTL: { minutes: 5 },
});
mockFetchImplementation();
const entity = baseEntity();
await processor.preProcessEntity(entity, null, null, null, cache);
expect(mockedFetch).toHaveBeenCalledTimes(1);
expect(mockCacheGet).toHaveBeenCalledTimes(1);
expect(mockCacheSet).toHaveBeenCalledTimes(1);
mockedFetch.mockClear();
mockCacheGet.mockClear();
mockCacheSet.mockClear();
await processor.preProcessEntity(entity, null, null, null, cache);
expect(mockedFetch).toHaveBeenCalledTimes(0);
expect(mockCacheGet).toHaveBeenCalledTimes(1);
expect(mockCacheSet).toHaveBeenCalledTimes(0);
});
});
function mockFetchImplementation(): void {
mockedFetch.mockResolvedValue({
json: jest.fn().mockResolvedValue({
languageCount: 6,
totalBytes: 43823,
processedDate: '2023-06-20T21:37:48.337Z',
breakdown: [
{
name: 'YAML',
percentage: 2.23,
bytes: 979,
type: 'data',
color: '#cb171e',
},
{
name: 'JSON',
percentage: 1.31,
bytes: 574,
type: 'data',
color: '#292929',
},
{
name: 'C++',
percentage: 5.25,
bytes: 2300,
type: 'programming',
color: '#f34b7d',
},
{
name: 'ASP.net',
percentage: 6.97,
bytes: 3053,
type: 'programming',
color: '#178600',
},
{
name: 'Java',
percentage: 12.79,
bytes: 5603,
type: 'programming',
color: '#b07219',
},
{
name: 'Common Lisp',
percentage: 71.46,
bytes: 31314,
type: 'programming',
color: '#3fb68b',
},
],
}),
} as unknown as Response);
}
function baseEntity(): Entity {
return {
apiVersion: 'backstage.io/v1alpha1',
kind: 'Component',
metadata: {
name: 'foo',
},
};
}
function buildProcessor(options: Partial<LinguistTagsProcessorOptions>) {
const config = new ConfigReader({
linguist: {
tagsProcessor: {
bytesThreshold: options.bytesThreshold,
languageTypes: options.languageTypes,
languageMap: options.languageMap,
cacheTTL: options.cacheTTL,
},
},
});
return LinguistTagsProcessor.fromConfig(config, {
logger: getVoidLogger(),
discovery,
shouldProcessEntity: options.shouldProcessEntity,
});
}
@@ -1,268 +0,0 @@
/*
* Copyright 2023 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 { Entity, stringifyEntityRef } from '@backstage/catalog-model';
import {
CatalogProcessor,
CatalogProcessorCache,
} from '@backstage/plugin-catalog-node';
import { DiscoveryService, LoggerService } from '@backstage/backend-plugin-api';
import { Languages, LanguageType } from '@backstage/plugin-linguist-common';
import fetch from 'node-fetch';
import { durationToMilliseconds, HumanDuration } from '@backstage/types';
import { Config } from '@backstage/config';
/**
* A function which given an entity, determines if it should be processed for linguist tags.
* @public
*/
export type ShouldProcessEntity = (entity: Entity) => boolean;
interface CachedData {
[key: string]: number | string[];
languages: string[];
cachedTime: number;
}
/**
* The constructor options for building the LinguistTagsProcessor
* @public
*/
export interface LinguistTagsProcessorOptions {
logger: LoggerService;
discovery: DiscoveryService;
/**
* Optional map that gives full control over which linguist languages should be included as tags and
* how they should be represented. The keys should be exact matches to languages in the linguist
* and the values should be how they render as backstage tags. Keep in mind that backstage has character
* requirements for tags. If you map a key to a falsey value, it will not be emitted as a tag.
*/
languageMap?: Record<string, string | undefined>;
/**
* A function which determines which entities should be processed by the LinguistTagProcessor.
*
* The default is to process all entities of kind=Component
*/
shouldProcessEntity?: ShouldProcessEntity;
/**
* Determines how long to cache language breakdowns for entities in the processor. Considering
* how often this processor runs, caching can help move some read traffic off of the linguist DB.
*
* If this caching is using up too much memory, you can disable it by setting cacheTTL to 0.
*/
cacheTTL?: HumanDuration;
/**
* How many bytes must exist of a language in a repo before we consider it for adding a tag to
* the entity. This can be used if some repos have short utility scripts that may not be the primary
* language for the repo.
*/
bytesThreshold?: number;
/**
* Which linguist file types to process tags for.
*/
languageTypes?: LanguageType[];
}
/**
* This processor will fetch the language breakdown from the linguist API and
* add the languages to the entity as searchable tags.
*
* @public
* */
export class LinguistTagsProcessor implements CatalogProcessor {
private logger: LoggerService;
private discovery: DiscoveryService;
private loggerMeta = { plugin: 'LinguistTagsProcessor' };
private languageMap: Record<string, string | undefined> = {};
private shouldProcessEntity: ShouldProcessEntity = (entity: Entity) => {
return entity.kind === 'Component';
};
private cacheTTLMilliseconds: number;
private bytesThreshold = 0;
private languageTypes: LanguageType[] = ['programming'];
getProcessorName(): string {
return 'LinguistTagsProcessor';
}
constructor(options: LinguistTagsProcessorOptions) {
this.logger = options.logger;
this.discovery = options.discovery;
if (options.shouldProcessEntity) {
this.shouldProcessEntity = options.shouldProcessEntity;
}
this.cacheTTLMilliseconds = durationToMilliseconds(
options.cacheTTL || { minutes: 30 },
);
if (options.bytesThreshold) {
this.bytesThreshold = options.bytesThreshold;
}
if (options.languageTypes) {
this.languageTypes = options.languageTypes;
}
if (options.languageMap) {
this.languageMap = options.languageMap;
}
}
static fromConfig(
config: Config,
options: LinguistTagsProcessorOptions,
): LinguistTagsProcessor {
const c = config.getOptionalConfig('linguist.tagsProcessor');
if (c) {
options.bytesThreshold ??= c.getOptionalNumber('bytesThreshold');
options.languageTypes ??= c.getOptionalStringArray(
'languageTypes',
) as LanguageType[];
options.languageMap ??= c.getOptional('languageMap');
options.cacheTTL ??= c.getOptional('cacheTTL');
}
return new LinguistTagsProcessor(options);
}
/**
* Given an entity ref, fetches the language breakdown from the Linguist backend HTTP API.
* @param entityRef - stringified entity ref
* @returns The language breakdown
*/
private async getLanguagesFromLinguistAPI(
entityRef: string,
): Promise<string[]> {
this.logger.debug(`Fetching languages from linguist API`, {
...this.loggerMeta,
entityRef,
});
const baseUrl = await this.discovery.getBaseUrl('linguist');
const linguistApi = new URL(`${baseUrl}/entity-languages`);
linguistApi.searchParams.append('entityRef', entityRef);
const linguistData = await fetch(linguistApi).then(
res => res.json() as Promise<Languages>,
);
if (!linguistData || !linguistData.processedDate) {
return [];
}
return linguistData.breakdown
.filter(
b =>
this.languageTypes.includes(b.type) && b.bytes > this.bytesThreshold,
)
.map(b => b.name);
}
/**
* Cached wrapper around getLanguagesFromLinguistAPI
* @param cache - The CatalogProcessorCache
* @param entityRef - Stringified entity references
*
* @returns List of languages
*/
private async getCachedLanguages(
cache: CatalogProcessorCache,
entityRef: string,
): Promise<string[]> {
let cachedData = (await cache.get(entityRef)) as CachedData | undefined;
if (!cachedData || this.isExpired(cachedData)) {
const languages = await this.getLanguagesFromLinguistAPI(entityRef);
cachedData = { languages, cachedTime: Date.now() };
await cache.set(entityRef, cachedData);
}
this.logger.debug(`Fetched cached languages ${cachedData.languages}`, {
...this.loggerMeta,
entityRef,
});
return cachedData.languages;
}
/**
* Determines if cached data is expired based on TTL
*
* @param cachedData - The cached data for this entity
* @returns True if data is expired
*/
private isExpired(cachedData: CachedData): boolean {
const elapsed = Date.now() - (cachedData.cachedTime || 0);
return elapsed > this.cacheTTLMilliseconds;
}
/**
* This pre-processor will fetch linguist data for a Component and convert the language breakdown
* into entity tags which will be appended to the entity.
*
* @public
*/
async preProcessEntity(
entity: Entity,
_: any,
__: any,
___: any,
cache: CatalogProcessorCache,
): Promise<Entity> {
if (!this.shouldProcessEntity(entity)) {
return entity;
}
const entityRef = stringifyEntityRef(entity);
this.logger.debug(`Processing ${entityRef}`, {
...this.loggerMeta,
entityRef,
});
const languages =
this.cacheTTLMilliseconds > 0
? await this.getCachedLanguages(cache, entityRef)
: await this.getLanguagesFromLinguistAPI(entityRef);
const tags = (entity.metadata.tags ||= []);
const originalTagCount = tags.length;
languages.forEach(lang => {
const cleanedUpLangTag =
lang in this.languageMap ? this.languageMap[lang] : sanitizeTag(lang);
if (cleanedUpLangTag && !tags.includes(cleanedUpLangTag)) {
tags.push(cleanedUpLangTag);
}
});
const addedCount = tags.length - originalTagCount;
this.logger.debug(`Added ${addedCount} language tags from linguist`, {
...this.loggerMeta,
entityRef,
});
return entity;
}
}
/**
* Converts language tags from linguist to something acceptable by
* the tag requirements for backstage
*
* @param tag - A language tag from linguist
* @returns Cleaned up language tag
* @internal
*/
export function sanitizeTag(tag: string): string {
return tag
.toLowerCase()
.replace(/\.net/g, '-dot-net')
.replace(/[^a-z0-9:+#-]+/g, '-')
.replace(/-{2,}/g, '-')
.replace(/^-+|-+$/g, '');
}
@@ -1,707 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`sanitizeTag Should clean up all linguist languages 1`] = `
[
"1c-enterprise",
"2-dimensional-array",
"4d",
"abap",
"abap-cds",
"abnf",
"ags-script",
"aidl",
"al",
"ampl",
"antlr",
"api-blueprint",
"apl",
"asl",
"asn-1",
"asp-dot-net",
"ats",
"actionscript",
"ada",
"adblock-filter-list",
"adobe-font-metrics",
"agda",
"alloy",
"alpine-abuild",
"altium-designer",
"angelscript",
"ant-build-system",
"antlers",
"apacheconf",
"apex",
"apollo-guidance-computer",
"applescript",
"arc",
"asciidoc",
"aspectj",
"assembly",
"astro",
"asymptote",
"augeas",
"autohotkey",
"autoit",
"avro-idl",
"awk",
"basic",
"ballerina",
"batchfile",
"beef",
"befunge",
"berry",
"bibtex",
"bicep",
"bikeshed",
"bison",
"bitbake",
"blade",
"blitzbasic",
"blitzmax",
"bluespec",
"boo",
"boogie",
"brainfuck",
"brighterscript",
"brightscript",
"browserslist",
"c",
"c#",
"c++",
"c-objdump",
"c2hs-haskell",
"cap-cds",
"cil",
"clips",
"cmake",
"cobol",
"codeowners",
"collada",
"cson",
"css",
"csv",
"cue",
"cweb",
"cabal-config",
"cadence",
"cairo",
"cameligo",
"cap-n-proto",
"cartocss",
"ceylon",
"chapel",
"charity",
"checksums",
"chuck",
"circom",
"cirru",
"clarion",
"clarity",
"classic-asp",
"clean",
"click",
"clojure",
"closure-templates",
"cloud-firestore-security-rules",
"conll-u",
"codeql",
"coffeescript",
"coldfusion",
"coldfusion-cfc",
"common-lisp",
"common-workflow-language",
"component-pascal",
"cool",
"coq",
"cpp-objdump",
"creole",
"crystal",
"csound",
"csound-document",
"csound-score",
"cuda",
"cue-sheet",
"curry",
"cycript",
"cypher",
"cython",
"d",
"d-objdump",
"d2",
"digital-command-language",
"dm",
"dns-zone",
"dtrace",
"dafny",
"darcs-patch",
"dart",
"dataweave",
"debian-package-control-file",
"denizenscript",
"dhall",
"diff",
"directx-3d-file",
"dockerfile",
"dogescript",
"dotenv",
"dylan",
"e",
"e-mail",
"ebnf",
"ecl",
"eclipse",
"ejs",
"eq",
"eagle",
"earthly",
"easybuild",
"ecere-projects",
"ecmarkup",
"editorconfig",
"edje-data-collection",
"eiffel",
"elixir",
"elm",
"elvish",
"elvish-transcript",
"emacs-lisp",
"emberscript",
"erlang",
"euphoria",
"f#",
"f",
"figlet-font",
"flux",
"factor",
"fancy",
"fantom",
"faust",
"fennel",
"filebench-wml",
"filterscript",
"fluent",
"formatted",
"forth",
"fortran",
"fortran-free-form",
"freebasic",
"freemarker",
"frege",
"futhark",
"g-code",
"gaml",
"gams",
"gap",
"gcc-machine-description",
"gdb",
"gdscript",
"gedcom",
"glsl",
"gn",
"gsc",
"game-maker-language",
"gemfile-lock",
"gemini",
"genero",
"genero-forms",
"genie",
"genshi",
"gentoo-ebuild",
"gentoo-eclass",
"gerber-image",
"gettext-catalog",
"gherkin",
"git-attributes",
"git-config",
"git-revision-list",
"gleam",
"glyph",
"glyph-bitmap-distribution-format",
"gnuplot",
"go",
"go-checksums",
"go-module",
"go-workspace",
"godot-resource",
"golo",
"gosu",
"grace",
"gradle",
"grammatical-framework",
"graph-modeling-language",
"graphql",
"graphviz-dot",
"groovy",
"groovy-server-pages",
"haproxy",
"hcl",
"hlsl",
"hocon",
"html",
"html+ecr",
"html+eex",
"html+erb",
"html+php",
"html+razor",
"http",
"hxml",
"hack",
"haml",
"handlebars",
"harbour",
"haskell",
"haxe",
"hiveql",
"holyc",
"hosts-file",
"hy",
"hyphy",
"idl",
"igor-pro",
"ini",
"irc-log",
"idris",
"ignore-list",
"imagej-macro",
"imba",
"inform-7",
"ink",
"inno-setup",
"io",
"ioke",
"isabelle",
"isabelle-root",
"j",
"jar-manifest",
"jcl",
"jflex",
"json",
"json-with-comments",
"json5",
"jsonld",
"jsoniq",
"janet",
"jasmin",
"java",
"java-properties",
"java-server-pages",
"javascript",
"javascript+erb",
"jest-snapshot",
"jetbrains-mps",
"jinja",
"jison",
"jison-lex",
"jolie",
"jsonnet",
"julia",
"jupyter-notebook",
"just",
"krl",
"kaitai-struct",
"kakounescript",
"kerboscript",
"kicad-layout",
"kicad-legacy-layout",
"kicad-schematic",
"kickstart",
"kit",
"kotlin",
"kusto",
"lfe",
"llvm",
"lolcode",
"lsl",
"ltspice-symbol",
"labview",
"lark",
"lasso",
"latte",
"lean",
"less",
"lex",
"ligolang",
"lilypond",
"limbo",
"linker-script",
"linux-kernel-module",
"liquid",
"literate-agda",
"literate-coffeescript",
"literate-haskell",
"livescript",
"logos",
"logtalk",
"lookml",
"loomscript",
"lua",
"m",
"m4",
"m4sugar",
"matlab",
"maxscript",
"mdx",
"mlir",
"mql4",
"mql5",
"mtml",
"muf",
"macaulay2",
"makefile",
"mako",
"markdown",
"marko",
"mask",
"mathematica",
"maven-pom",
"max",
"mercury",
"mermaid",
"meson",
"metal",
"microsoft-developer-studio-project",
"microsoft-visual-studio-solution",
"minid",
"miniyaml",
"mint",
"mirah",
"modelica",
"modula-2",
"modula-3",
"module-management-system",
"monkey",
"monkey-c",
"moocode",
"moonscript",
"motoko",
"motorola-68k-assembly",
"move",
"muse",
"mustache",
"myghty",
"nasl",
"ncl",
"neon",
"nl",
"npm-config",
"nsis",
"nwscript",
"nasal",
"nearley",
"nemerle",
"netlinx",
"netlinx+erb",
"netlogo",
"newlisp",
"nextflow",
"nginx",
"nim",
"ninja",
"nit",
"nix",
"nu",
"numpy",
"nunjucks",
"nushell",
"oasv2-json",
"oasv2-yaml",
"oasv3-json",
"oasv3-yaml",
"ocaml",
"objdump",
"object-data-instance-notation",
"objectscript",
"objective-c",
"objective-c++",
"objective-j",
"odin",
"omgrofl",
"opa",
"opal",
"open-policy-agent",
"openapi-specification-v2",
"openapi-specification-v3",
"opencl",
"openedge-abl",
"openqasm",
"openrc-runscript",
"openscad",
"openstep-property-list",
"opentype-feature-file",
"option-list",
"org",
"ox",
"oxygene",
"oz",
"p4",
"pddl",
"peg-js",
"php",
"plsql",
"plpgsql",
"pov-ray-sdl",
"pact",
"pan",
"papyrus",
"parrot",
"parrot-assembly",
"parrot-internal-representation",
"pascal",
"pawn",
"pep8",
"perl",
"pic",
"pickle",
"picolisp",
"piglatin",
"pike",
"plantuml",
"pod",
"pod-6",
"pogoscript",
"polar",
"pony",
"portugol",
"postcss",
"postscript",
"powerbuilder",
"powershell",
"prisma",
"processing",
"procfile",
"proguard",
"prolog",
"promela",
"propeller-spin",
"protocol-buffer",
"protocol-buffer-text-format",
"public-key",
"pug",
"puppet",
"pure-data",
"purebasic",
"purescript",
"pyret",
"python",
"python-console",
"python-traceback",
"q#",
"qml",
"qmake",
"qt-script",
"quake",
"r",
"raml",
"rbs",
"rdoc",
"realbasic",
"rexx",
"rmarkdown",
"rpc",
"rpgle",
"rpm-spec",
"runoff",
"racket",
"ragel",
"raku",
"rascal",
"raw-token-data",
"rescript",
"readline-config",
"reason",
"reasonligo",
"rebol",
"record-jar",
"red",
"redcode",
"redirect-rules",
"regular-expression",
"ren-py",
"renderscript",
"rich-text-format",
"ring",
"riot",
"robotframework",
"roff",
"roff-manpage",
"rouge",
"routeros-script",
"ruby",
"rust",
"sas",
"scss",
"selinux-policy",
"smt",
"sparql",
"sqf",
"sql",
"sqlpl",
"srecode-template",
"ssh-config",
"star",
"stl",
"ston",
"svg",
"swig",
"sage",
"saltstack",
"sass",
"scala",
"scaml",
"scenic",
"scheme",
"scilab",
"self",
"shaderlab",
"shell",
"shellcheck-config",
"shellsession",
"shen",
"sieve",
"simple-file-verification",
"singularity",
"slash",
"slice",
"slim",
"smpl",
"smali",
"smalltalk",
"smarty",
"smithy",
"snakemake",
"solidity",
"soong",
"sourcepawn",
"spline-font-database",
"squirrel",
"stan",
"standard-ml",
"starlark",
"stata",
"stringtemplate",
"stylus",
"subrip-text",
"sugarss",
"supercollider",
"svelte",
"sway",
"swift",
"systemverilog",
"ti-program",
"tl-verilog",
"tla",
"toml",
"tsql",
"tsv",
"tsx",
"txl",
"talon",
"tcl",
"tcsh",
"tex",
"tea",
"terra",
"texinfo",
"text",
"textmate-properties",
"textile",
"thrift",
"turing",
"turtle",
"twig",
"type-language",
"typescript",
"unified-parallel-c",
"unity3d-asset",
"unix-assembly",
"uno",
"unrealscript",
"urweb",
"v",
"vba",
"vbscript",
"vcl",
"vhdl",
"vala",
"valve-data-format",
"velocity-template-language",
"verilog",
"vim-help-file",
"vim-script",
"vim-snippet",
"visual-basic-dot-net",
"visual-basic-6-0",
"volt",
"vue",
"vyper",
"wdl",
"wgsl",
"wavefront-material",
"wavefront-object",
"web-ontology-language",
"webassembly",
"webassembly-interface-type",
"webidl",
"webvtt",
"wget-config",
"whiley",
"wikitext",
"win32-message-file",
"windows-registry-entries",
"witcher-script",
"wollok",
"world-of-warcraft-addon-data",
"wren",
"x-bitmap",
"x-font-directory-index",
"x-pixmap",
"x10",
"xc",
"xcompose",
"xml",
"xml-property-list",
"xpages",
"xproc",
"xquery",
"xs",
"xslt",
"xojo",
"xonsh",
"xtend",
"yaml",
"yang",
"yara",
"yasnippet",
"yacc",
"yul",
"zap",
"zil",
"zeek",
"zenscript",
"zephir",
"zig",
"zimpl",
"curl-config",
"desktop",
"dircolors",
"ec",
"edn",
"fish",
"hoon",
"jq",
"kvlang",
"mirc-script",
"mcfunction",
"mupad",
"nanorc",
"nesc",
"ooc",
"q",
"restructuredtext",
"robots-txt",
"sed",
"wisp",
"xbase",
]
`;
-33
View File
@@ -1,33 +0,0 @@
/*
* Copyright 2022 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 { getRootLogger } from '@backstage/backend-common';
import yn from 'yn';
import { startStandaloneServer } from './service/standaloneServer';
const port = process.env.PLUGIN_PORT ? Number(process.env.PLUGIN_PORT) : 7007;
const enableCors = yn(process.env.PLUGIN_CORS, { default: false });
const logger = getRootLogger();
startStandaloneServer({ port, enableCors, logger }).catch(err => {
logger.error(err);
process.exit(1);
});
process.on('SIGINT', () => {
logger.info('CTRL+C pressed; exiting.');
process.exit(0);
});
@@ -1,130 +0,0 @@
/*
* Copyright 2022 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 {
DatabaseManager,
getVoidLogger,
PluginDatabaseManager,
PluginEndpointDiscovery,
TokenManager,
UrlReaders,
} from '@backstage/backend-common';
import express from 'express';
import request from 'supertest';
import { ConfigReader } from '@backstage/config';
import { createRouter, createRouterFromConfig } from './router';
import { LinguistBackendApi } from '../api';
import { TaskScheduleDefinition } from '@backstage/backend-tasks';
function createDatabase(): PluginDatabaseManager {
return DatabaseManager.fromConfig(
new ConfigReader({
backend: {
database: {
client: 'better-sqlite3',
connection: ':memory:',
},
},
}),
).forPlugin('code-coverage');
}
const testDiscovery: jest.Mocked<PluginEndpointDiscovery> = {
getBaseUrl: jest
.fn()
.mockResolvedValue('http://localhost:7007/api/code-coverage'),
getExternalBaseUrl: jest.fn(),
};
const mockedTokenManager: jest.Mocked<TokenManager> = {
getToken: jest.fn(),
authenticate: jest.fn(),
};
const mockUrlReader = UrlReaders.default({
logger: getVoidLogger(),
config: new ConfigReader({}),
});
const schedule: TaskScheduleDefinition = {
frequency: { minutes: 2 },
timeout: { minutes: 15 },
initialDelay: { seconds: 15 },
};
describe('createRouter', () => {
let linguistBackendApi: jest.Mocked<LinguistBackendApi>;
let app: express.Express;
beforeEach(() => {
jest.resetAllMocks();
});
describe('GET /health', () => {
beforeAll(async () => {
const router = await createRouter(
{ schedule: schedule, age: { days: 30 }, useSourceLocation: false },
{
linguistBackendApi: linguistBackendApi,
discovery: testDiscovery,
database: createDatabase(),
reader: mockUrlReader,
logger: getVoidLogger(),
tokenManager: mockedTokenManager,
},
);
app = express().use(router);
});
it('returns ok', async () => {
const response = await request(app).get('/health');
expect(response.status).toEqual(200);
expect(response.body).toEqual({ status: 'ok' });
});
});
describe('GET /health from config', () => {
beforeAll(async () => {
const config = new ConfigReader({
linguist: {
schedule: {
frequency: { minutes: 2 },
timeout: { minutes: 15 },
initialDelay: { seconds: 15 },
},
age: { days: 30 },
useSourceLocation: false,
},
});
const router = await createRouterFromConfig({
linguistBackendApi: linguistBackendApi,
discovery: testDiscovery,
database: createDatabase(),
reader: mockUrlReader,
logger: getVoidLogger(),
config,
tokenManager: mockedTokenManager,
});
app = express().use(router);
});
it('returns ok', async () => {
const response = await request(app).get('/health');
expect(response.status).toEqual(200);
expect(response.body).toEqual({ status: 'ok' });
});
});
});
@@ -1,177 +0,0 @@
/*
* Copyright 2022 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 {
createLegacyAuthAdapters,
errorHandler,
PluginDatabaseManager,
PluginEndpointDiscovery,
TokenManager,
UrlReader,
} from '@backstage/backend-common';
import express from 'express';
import Router from 'express-promise-router';
import { LinguistBackendApi } from '../api';
import { LinguistBackendDatabase } from '../db';
import {
PluginTaskScheduler,
readTaskScheduleDefinitionFromConfig,
TaskScheduleDefinition,
} from '@backstage/backend-tasks';
import { HumanDuration } from '@backstage/types';
import { CatalogClient } from '@backstage/catalog-client';
import { LinguistBackendClient } from '../api/LinguistBackendClient';
import { Config } from '@backstage/config';
import {
AuthService,
HttpAuthService,
LoggerService,
} from '@backstage/backend-plugin-api';
/** @public */
export interface PluginOptions {
schedule?: TaskScheduleDefinition;
age?: HumanDuration;
batchSize?: number;
useSourceLocation?: boolean;
linguistJsOptions?: Record<string, unknown>;
kind?: string[];
}
/** @public */
export interface RouterOptions {
linguistBackendApi?: LinguistBackendApi;
logger: LoggerService;
reader: UrlReader;
tokenManager: TokenManager;
database: PluginDatabaseManager;
discovery: PluginEndpointDiscovery;
scheduler?: PluginTaskScheduler;
config?: Config;
auth?: AuthService;
httpAuth?: HttpAuthService;
}
/** @public */
export async function createRouter(
pluginOptions: PluginOptions,
routerOptions: RouterOptions,
): Promise<express.Router> {
const { logger, reader, database, discovery, scheduler, tokenManager, auth } =
routerOptions;
const {
schedule,
age,
batchSize,
useSourceLocation,
kind,
linguistJsOptions,
} = pluginOptions;
const linguistBackendStore = await LinguistBackendDatabase.create(
await database.getClient(),
);
const catalogClient = new CatalogClient({ discoveryApi: discovery });
const { auth: adaptedAuth } = createLegacyAuthAdapters({
auth,
tokenManager: tokenManager,
discovery: discovery,
});
const linguistBackendClient =
routerOptions.linguistBackendApi ||
new LinguistBackendClient(
logger,
linguistBackendStore,
reader,
adaptedAuth,
catalogClient,
age,
batchSize,
useSourceLocation,
kind,
linguistJsOptions,
);
if (scheduler && schedule) {
logger.info(
`Scheduling processing of entities with: ${JSON.stringify(schedule)}`,
);
await scheduler.scheduleTask({
id: 'linguist_process_entities',
frequency: schedule.frequency,
timeout: schedule.timeout,
initialDelay: schedule.initialDelay,
scope: schedule.scope,
fn: async () => {
await linguistBackendClient.processEntities();
},
});
}
const router = Router();
router.use(express.json());
router.get('/health', (_, response) => {
response.send({ status: 'ok' });
});
/**
* /entity-languages?entity=component:default/my-component
*/
router.get('/entity-languages', async (req, res) => {
const { entityRef: entityRef } = req.query;
if (!entityRef) {
throw new Error('No entityRef was provided');
}
const entityLanguages = await linguistBackendClient.getEntityLanguages(
entityRef as string,
);
res.status(200).json(entityLanguages);
});
router.use(errorHandler());
return router;
}
/** @public */
export async function createRouterFromConfig(routerOptions: RouterOptions) {
const { config } = routerOptions;
const pluginOptions: PluginOptions = {};
if (config) {
if (config.has('linguist.schedule')) {
pluginOptions.schedule = readTaskScheduleDefinitionFromConfig(
config.getConfig('linguist.schedule'),
);
}
pluginOptions.batchSize = config.getOptionalNumber('linguist.batchSize');
pluginOptions.useSourceLocation =
config.getOptionalBoolean('linguist.useSourceLocation') ?? false;
pluginOptions.age = config.getOptionalConfig('linguist.age') as
| HumanDuration
| undefined;
pluginOptions.kind = config.getOptionalStringArray('linguist.kind');
pluginOptions.linguistJsOptions = config.getOptionalConfig(
'linguist.linguistJsOptions',
);
}
return createRouter(pluginOptions, routerOptions);
}
@@ -1,82 +0,0 @@
/*
* Copyright 2022 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 {
createServiceBuilder,
DatabaseManager,
HostDiscovery,
loadBackendConfig,
ServerTokenManager,
UrlReaders,
} from '@backstage/backend-common';
import { Server } from 'http';
import { createRouter } from './router';
import { TaskScheduleDefinition } from '@backstage/backend-tasks';
import { ConfigReader } from '@backstage/config';
import { LoggerService } from '@backstage/backend-plugin-api';
export interface ServerOptions {
port: number;
enableCors: boolean;
logger: LoggerService;
}
export async function startStandaloneServer(
options: ServerOptions,
): Promise<Server> {
const logger = options.logger.child({ service: 'linguist-backend' });
const config = await loadBackendConfig({ logger, argv: process.argv });
const manager = DatabaseManager.fromConfig(
new ConfigReader({
backend: {
database: { client: 'better-sqlite3', connection: ':memory:' },
},
}),
);
const database = manager.forPlugin('linguist');
const schedule: TaskScheduleDefinition = {
frequency: { minutes: 2 },
timeout: { minutes: 15 },
initialDelay: { seconds: 15 },
};
logger.debug('Starting application server...');
const router = await createRouter(
{ schedule: schedule, age: { days: 30 }, useSourceLocation: false },
{
database,
discovery: HostDiscovery.fromConfig(config),
reader: UrlReaders.default({ logger, config }),
logger,
tokenManager: ServerTokenManager.noop(),
},
);
let service = createServiceBuilder(module)
.setPort(options.port)
.addRouter('/linguist', router);
if (options.enableCors) {
service = service.enableCors({ origin: 'http://localhost:3000' });
}
return await service.start().catch(err => {
logger.error(err);
process.exit(1);
});
}
module.hot?.accept();
@@ -1,17 +0,0 @@
/*
* Copyright 2022 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 {};
-1
View File
@@ -1 +0,0 @@
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
-38
View File
@@ -1,38 +0,0 @@
# @backstage/plugin-linguist-common
## 0.1.2
### Patch Changes
- 406b786a2a2c: Mark package as being free of side effects, allowing more optimized Webpack builds.
## 0.1.2-next.0
### Patch Changes
- 406b786a2a2c: Mark package as being free of side effects, allowing more optimized Webpack builds.
## 0.1.1
### Patch Changes
- 12a8c94eda8d: Add package repository and homepage metadata
- d440f1dd0e72: Exported new LanguageType type alias
## 0.1.1-next.1
### Patch Changes
- d440f1dd0e72: Exported new LanguageType type alias
## 0.1.1-next.0
### Patch Changes
- 12a8c94eda8d: Add package repository and homepage metadata
## 0.1.0
### Minor Changes
- 75cfee5688: Introduced the Linguist plugin, checkout the plugin's `README.md` for more details!
-3
View File
@@ -1,3 +0,0 @@
# Deprecated
This package has been moved to the [backstage-community/plugins](https://github.com/backstage/community-plugins) repository. Migrate to using `@backstage-community/plugin-linguist-common` instead.
-51
View File
@@ -1,51 +0,0 @@
## API Report File for "@backstage/plugin-linguist-common"
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
```ts
// @public (undocumented)
export type EntitiesOverview = {
entityCount: number;
processedCount: number;
pendingCount: number;
staleCount: number;
filteredEntities: string[];
};
// @public (undocumented)
export type EntityResults = {
entityRef: string;
results: Languages;
};
// @public (undocumented)
export type Language = {
name: string;
percentage: number;
bytes: number;
type: LanguageType;
color?: `#${string}`;
};
// @public (undocumented)
export type Languages = {
languageCount: number;
totalBytes: number;
processedDate: string;
breakdown: Language[];
};
// @public (undocumented)
export type LanguageType = 'programming' | 'data' | 'markup' | 'prose';
// @public (undocumented)
export const LINGUIST_ANNOTATION = 'backstage.io/linguist';
// @public (undocumented)
export type ProcessedEntity = {
entityRef: string;
processedDate: Date;
};
// (No @packageDocumentation comment for this package)
```
-10
View File
@@ -1,10 +0,0 @@
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: backstage-plugin-linguist-common
title: '@backstage/plugin-linguist-common'
description: Common functionalities for the linguist plugin
spec:
lifecycle: experimental
type: backstage-common-library
owner: awanlin
-2
View File
@@ -1,2 +0,0 @@
# Knip report
-40
View File
@@ -1,40 +0,0 @@
{
"name": "@backstage/plugin-linguist-common",
"version": "0.1.2",
"description": "Common functionalities for the linguist plugin",
"backstage": {
"role": "common-library",
"moved": "@backstage-community/plugin-linguist-common"
},
"publishConfig": {
"access": "public",
"main": "dist/index.cjs.js",
"module": "dist/index.esm.js",
"types": "dist/index.d.ts"
},
"homepage": "https://backstage.io",
"repository": {
"type": "git",
"url": "https://github.com/backstage/backstage",
"directory": "plugins/linguist-common"
},
"license": "Apache-2.0",
"sideEffects": false,
"main": "src/index.ts",
"types": "src/index.ts",
"files": [
"dist"
],
"scripts": {
"build": "backstage-cli package build",
"clean": "backstage-cli package clean",
"lint": "backstage-cli package lint",
"prepack": "backstage-cli package prepack",
"postpack": "backstage-cli package postpack",
"test": "backstage-cli package test"
},
"devDependencies": {
"@backstage/cli": "workspace:^"
},
"deprecated": "This package has been moved to the backstage/community-plugins repository. You should migrate to using @backstage-community/plugin-linguist-common instead."
}
-18
View File
@@ -1,18 +0,0 @@
/*
* Copyright 2022 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.
*/
/** @public */
export const LINGUIST_ANNOTATION = 'backstage.io/linguist';
-18
View File
@@ -1,18 +0,0 @@
/*
* Copyright 2022 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 './types';
export * from './constants';
-16
View File
@@ -1,16 +0,0 @@
/*
* Copyright 2022 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 {};
-56
View File
@@ -1,56 +0,0 @@
/*
* Copyright 2022 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.
*/
/** @public */
export type EntityResults = {
entityRef: string;
results: Languages;
};
/** @public */
export type Languages = {
languageCount: number;
totalBytes: number;
processedDate: string;
breakdown: Language[];
};
/** @public */
export type LanguageType = 'programming' | 'data' | 'markup' | 'prose';
/** @public */
export type Language = {
name: string;
percentage: number;
bytes: number;
type: LanguageType;
color?: `#${string}`;
};
/** @public */
export type ProcessedEntity = {
entityRef: string;
processedDate: Date;
};
/** @public */
export type EntitiesOverview = {
entityCount: number;
processedCount: number;
pendingCount: number;
staleCount: number;
filteredEntities: string[];
};
-5
View File
@@ -1,5 +0,0 @@
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname, {
rules: {
'@backstage/no-top-level-material-ui-4-imports': 'error',
},
});
-844
View File
@@ -1,844 +0,0 @@
# @backstage/plugin-linguist
## 0.1.19
### Patch Changes
- Updated dependencies
- @backstage/plugin-catalog-react@1.11.3
- @backstage/core-compat-api@0.2.4
- @backstage/core-components@0.14.4
- @backstage/core-plugin-api@1.9.2
- @backstage/frontend-plugin-api@0.6.4
- @backstage/catalog-model@1.4.5
- @backstage/errors@1.2.4
- @backstage/plugin-linguist-common@0.1.2
## 0.1.19-next.1
### Patch Changes
- Updated dependencies
- @backstage/frontend-plugin-api@0.6.4-next.1
- @backstage/core-compat-api@0.2.4-next.1
- @backstage/catalog-model@1.4.5
- @backstage/core-components@0.14.4-next.0
- @backstage/core-plugin-api@1.9.1
- @backstage/errors@1.2.4
- @backstage/plugin-catalog-react@1.11.3-next.1
- @backstage/plugin-linguist-common@0.1.2
## 0.1.19-next.0
### Patch Changes
- Updated dependencies
- @backstage/core-components@0.14.4-next.0
- @backstage/catalog-model@1.4.5
- @backstage/core-compat-api@0.2.4-next.0
- @backstage/core-plugin-api@1.9.1
- @backstage/errors@1.2.4
- @backstage/frontend-plugin-api@0.6.4-next.0
- @backstage/plugin-catalog-react@1.11.3-next.0
- @backstage/plugin-linguist-common@0.1.2
## 0.1.18
### Patch Changes
- e8f026a: Use ESM exports of react-use library
- Updated dependencies
- @backstage/core-components@0.14.3
- @backstage/plugin-catalog-react@1.11.2
- @backstage/frontend-plugin-api@0.6.3
- @backstage/core-compat-api@0.2.3
- @backstage/core-plugin-api@1.9.1
- @backstage/catalog-model@1.4.5
- @backstage/errors@1.2.4
- @backstage/plugin-linguist-common@0.1.2
## 0.1.17
### Patch Changes
- e8f026a: Use ESM exports of react-use library
- Updated dependencies
- @backstage/core-components@0.14.2
- @backstage/plugin-catalog-react@1.11.1
- @backstage/frontend-plugin-api@0.6.2
- @backstage/core-compat-api@0.2.2
- @backstage/core-plugin-api@1.9.1
- @backstage/catalog-model@1.4.5
- @backstage/errors@1.2.4
- @backstage/plugin-linguist-common@0.1.2
## 0.1.16
### Patch Changes
- 4fb9600: Get component's title from translation file. See: https://backstage.io/docs/plugins/internationalization#for-an-application-developer-overwrite-plugin-messages
- a0e3393: Updated to use `fetchApi` as per [ADR013](https://backstage.io/docs/architecture-decisions/adrs-adr013)
- 786c9c4: Updated dependency `luxon` to `^3.0.0`.
- 051fda1: Removed unused `dependencies` and `devDependencies` identified in Knip Report
- Updated dependencies
- @backstage/core-components@0.14.1
- @backstage/errors@1.2.4
- @backstage/plugin-catalog-react@1.11.0
- @backstage/catalog-model@1.4.5
- @backstage/core-compat-api@0.2.1
- @backstage/core-plugin-api@1.9.1
- @backstage/frontend-plugin-api@0.6.1
- @backstage/plugin-linguist-common@0.1.2
## 0.1.16-next.2
### Patch Changes
- 051fda1: Removed unused `dependencies` and `devDependencies` identified in Knip Report
- Updated dependencies
- @backstage/core-components@0.14.1-next.2
- @backstage/plugin-catalog-react@1.11.0-next.2
- @backstage/frontend-plugin-api@0.6.1-next.2
- @backstage/core-compat-api@0.2.1-next.2
- @backstage/catalog-model@1.4.5-next.0
- @backstage/core-plugin-api@1.9.1-next.1
- @backstage/errors@1.2.4-next.0
- @backstage/plugin-linguist-common@0.1.2
## 0.1.16-next.1
### Patch Changes
- Updated dependencies
- @backstage/core-components@0.14.1-next.1
- @backstage/plugin-catalog-react@1.10.1-next.1
- @backstage/core-plugin-api@1.9.1-next.1
- @backstage/frontend-plugin-api@0.6.1-next.1
- @backstage/catalog-model@1.4.5-next.0
- @backstage/core-compat-api@0.2.1-next.1
- @backstage/errors@1.2.4-next.0
- @backstage/plugin-linguist-common@0.1.2
## 0.1.16-next.0
### Patch Changes
- 4fb9600: Get component's title from translation file. See: https://backstage.io/docs/plugins/internationalization#for-an-application-developer-overwrite-plugin-messages
- a0e3393: Updated to use `fetchApi` as per [ADR013](https://backstage.io/docs/architecture-decisions/adrs-adr013)
- 786c9c4: Updated dependency `luxon` to `^3.0.0`.
- Updated dependencies
- @backstage/errors@1.2.4-next.0
- @backstage/core-components@0.14.1-next.0
- @backstage/plugin-catalog-react@1.10.1-next.0
- @backstage/catalog-model@1.4.5-next.0
- @backstage/core-plugin-api@1.9.1-next.0
- @backstage/frontend-plugin-api@0.6.1-next.0
- @backstage/core-compat-api@0.2.1-next.0
- @backstage/plugin-linguist-common@0.1.2
## 0.1.15
### Patch Changes
- 9aac2b0: Use `--cwd` as the first `yarn` argument
- 8fe56a8: Widen `@types/react` dependency range to include version 18.
- 995d280: Updated imports from named to default imports to help with the Material UI v4 to v5 migration
- Updated dependencies
- @backstage/frontend-plugin-api@0.6.0
- @backstage/core-compat-api@0.2.0
- @backstage/plugin-catalog-react@1.10.0
- @backstage/core-components@0.14.0
- @backstage/catalog-model@1.4.4
- @backstage/core-plugin-api@1.9.0
- @backstage/errors@1.2.3
- @backstage/plugin-linguist-common@0.1.2
## 0.1.15-next.3
### Patch Changes
- Updated dependencies
- @backstage/core-components@0.14.0-next.2
- @backstage/plugin-catalog-react@1.10.0-next.3
- @backstage/catalog-model@1.4.4-next.0
- @backstage/core-compat-api@0.2.0-next.3
- @backstage/core-plugin-api@1.9.0-next.1
- @backstage/errors@1.2.3
- @backstage/frontend-plugin-api@0.6.0-next.3
- @backstage/plugin-linguist-common@0.1.2
## 0.1.15-next.2
### Patch Changes
- 9aac2b0: Use `--cwd` as the first `yarn` argument
- 8fe56a8: Widen `@types/react` dependency range to include version 18.
- Updated dependencies
- @backstage/core-components@0.14.0-next.1
- @backstage/core-plugin-api@1.9.0-next.1
- @backstage/frontend-plugin-api@0.6.0-next.2
- @backstage/plugin-catalog-react@1.10.0-next.2
- @backstage/core-compat-api@0.2.0-next.2
- @backstage/catalog-model@1.4.4-next.0
- @backstage/errors@1.2.3
- @backstage/plugin-linguist-common@0.1.2
## 0.1.15-next.1
### Patch Changes
- Updated dependencies
- @backstage/frontend-plugin-api@0.6.0-next.1
- @backstage/core-compat-api@0.2.0-next.1
- @backstage/core-components@0.14.0-next.0
- @backstage/catalog-model@1.4.4-next.0
- @backstage/core-plugin-api@1.8.3-next.0
- @backstage/plugin-catalog-react@1.9.4-next.1
- @backstage/errors@1.2.3
- @backstage/plugin-linguist-common@0.1.2
## 0.1.15-next.0
### Patch Changes
- 995d280: Updated imports from named to default imports to help with the Material UI v4 to v5 migration
- Updated dependencies
- @backstage/core-compat-api@0.1.2-next.0
- @backstage/plugin-catalog-react@1.9.4-next.0
- @backstage/frontend-plugin-api@0.5.1-next.0
- @backstage/core-components@0.13.10
- @backstage/catalog-model@1.4.3
- @backstage/core-plugin-api@1.8.2
- @backstage/errors@1.2.3
- @backstage/plugin-linguist-common@0.1.2
## 0.1.14
### Patch Changes
- 4016f21: Remove some unused dependencies
- 4f42918: Added alpha support for the New Frontend System (Declarative Integration)
- Updated dependencies
- @backstage/core-compat-api@0.1.1
- @backstage/frontend-plugin-api@0.5.0
- @backstage/core-components@0.13.10
- @backstage/core-plugin-api@1.8.2
- @backstage/plugin-catalog-react@1.9.3
- @backstage/catalog-model@1.4.3
- @backstage/errors@1.2.3
- @backstage/plugin-linguist-common@0.1.2
## 0.1.14-next.2
### Patch Changes
- 4f42918: Added alpha support for the New Frontend System (Declarative Integration)
- Updated dependencies
- @backstage/core-compat-api@0.1.1-next.2
- @backstage/frontend-plugin-api@0.4.1-next.2
- @backstage/plugin-catalog-react@1.9.3-next.2
## 0.1.14-next.1
### Patch Changes
- Updated dependencies
- @backstage/core-plugin-api@1.8.2-next.0
- @backstage/core-components@0.13.10-next.1
- @backstage/plugin-catalog-react@1.9.3-next.1
- @backstage/catalog-model@1.4.3
- @backstage/errors@1.2.3
- @backstage/plugin-linguist-common@0.1.2
## 0.1.14-next.0
### Patch Changes
- 4016f21: Remove some unused dependencies
- Updated dependencies
- @backstage/core-components@0.13.10-next.0
- @backstage/plugin-catalog-react@1.9.3-next.0
- @backstage/catalog-model@1.4.3
- @backstage/core-plugin-api@1.8.1
- @backstage/errors@1.2.3
- @backstage/plugin-linguist-common@0.1.2
## 0.1.13
### Patch Changes
- Updated dependencies
- @backstage/core-plugin-api@1.8.1
- @backstage/plugin-catalog-react@1.9.2
- @backstage/core-components@0.13.9
- @backstage/theme@0.5.0
- @backstage/catalog-model@1.4.3
- @backstage/errors@1.2.3
- @backstage/plugin-linguist-common@0.1.2
## 0.1.13-next.3
### Patch Changes
- Updated dependencies
- @backstage/core-components@0.13.9-next.3
- @backstage/catalog-model@1.4.3
- @backstage/core-plugin-api@1.8.1-next.1
- @backstage/errors@1.2.3
- @backstage/theme@0.5.0-next.1
- @backstage/plugin-catalog-react@1.9.2-next.3
- @backstage/plugin-linguist-common@0.1.2
## 0.1.13-next.2
### Patch Changes
- Updated dependencies
- @backstage/theme@0.5.0-next.1
- @backstage/plugin-catalog-react@1.9.2-next.2
- @backstage/catalog-model@1.4.3
- @backstage/core-components@0.13.9-next.2
- @backstage/core-plugin-api@1.8.1-next.1
- @backstage/errors@1.2.3
- @backstage/plugin-linguist-common@0.1.2
## 0.1.13-next.1
### Patch Changes
- Updated dependencies
- @backstage/core-components@0.13.9-next.1
- @backstage/core-plugin-api@1.8.1-next.1
- @backstage/plugin-catalog-react@1.9.2-next.1
- @backstage/catalog-model@1.4.3
- @backstage/errors@1.2.3
- @backstage/theme@0.5.0-next.0
- @backstage/plugin-linguist-common@0.1.2
## 0.1.13-next.0
### Patch Changes
- Updated dependencies
- @backstage/core-plugin-api@1.8.1-next.0
- @backstage/plugin-catalog-react@1.9.2-next.0
- @backstage/core-components@0.13.9-next.0
- @backstage/theme@0.5.0-next.0
- @backstage/catalog-model@1.4.3
- @backstage/errors@1.2.3
- @backstage/plugin-linguist-common@0.1.2
## 0.1.11
### Patch Changes
- 6c2b872153: Add official support for React 18.
- Updated dependencies
- @backstage/plugin-catalog-react@1.9.0
- @backstage/core-components@0.13.8
- @backstage/core-plugin-api@1.8.0
- @backstage/theme@0.4.4
- @backstage/catalog-model@1.4.3
- @backstage/errors@1.2.3
- @backstage/plugin-linguist-common@0.1.2
## 0.1.11-next.2
### Patch Changes
- Updated dependencies
- @backstage/core-components@0.13.8-next.2
- @backstage/plugin-catalog-react@1.9.0-next.2
## 0.1.11-next.1
### Patch Changes
- Updated dependencies
- @backstage/plugin-catalog-react@1.9.0-next.1
- @backstage/core-components@0.13.8-next.1
- @backstage/catalog-model@1.4.3
- @backstage/core-plugin-api@1.8.0-next.0
- @backstage/errors@1.2.3
- @backstage/theme@0.4.4-next.0
- @backstage/plugin-linguist-common@0.1.2
## 0.1.11-next.0
### Patch Changes
- 6c2b872153: Add official support for React 18.
- Updated dependencies
- @backstage/core-components@0.13.7-next.0
- @backstage/plugin-catalog-react@1.9.0-next.0
- @backstage/core-plugin-api@1.8.0-next.0
- @backstage/theme@0.4.4-next.0
- @backstage/catalog-model@1.4.3
- @backstage/errors@1.2.3
- @backstage/plugin-linguist-common@0.1.2
## 0.1.10
### Patch Changes
- 9a1fce352e: Updated dependency `@testing-library/jest-dom` to `^6.0.0`.
- f95af4e540: Updated dependency `@testing-library/dom` to `^9.0.0`.
- Updated dependencies
- @backstage/plugin-catalog-react@1.8.5
- @backstage/core-plugin-api@1.7.0
- @backstage/core-components@0.13.6
- @backstage/catalog-model@1.4.3
- @backstage/errors@1.2.3
- @backstage/theme@0.4.3
- @backstage/plugin-linguist-common@0.1.2
## 0.1.10-next.2
### Patch Changes
- Updated dependencies
- @backstage/core-components@0.13.6-next.2
- @backstage/core-plugin-api@1.7.0-next.1
- @backstage/catalog-model@1.4.3-next.0
- @backstage/plugin-catalog-react@1.8.5-next.2
- @backstage/errors@1.2.3-next.0
- @backstage/theme@0.4.3-next.0
- @backstage/plugin-linguist-common@0.1.2
## 0.1.10-next.1
### Patch Changes
- Updated dependencies
- @backstage/core-components@0.13.6-next.1
- @backstage/plugin-catalog-react@1.8.5-next.1
- @backstage/core-plugin-api@1.7.0-next.0
- @backstage/catalog-model@1.4.2
- @backstage/errors@1.2.2
- @backstage/theme@0.4.2
- @backstage/plugin-linguist-common@0.1.2
## 0.1.10-next.0
### Patch Changes
- Updated dependencies
- @backstage/plugin-catalog-react@1.8.5-next.0
- @backstage/core-plugin-api@1.7.0-next.0
- @backstage/core-components@0.13.6-next.0
- @backstage/catalog-model@1.4.2
- @backstage/errors@1.2.2
- @backstage/theme@0.4.2
- @backstage/plugin-linguist-common@0.1.2
## 0.1.9
### Patch Changes
- 482bb5c0bbf8: Moved `@types/react` to be a regular dependency
- 406b786a2a2c: Mark package as being free of side effects, allowing more optimized Webpack builds.
- 8cec7664e146: Removed `@types/node` dependency
- Updated dependencies
- @backstage/plugin-catalog-react@1.8.4
- @backstage/core-components@0.13.5
- @backstage/catalog-model@1.4.2
- @backstage/core-plugin-api@1.6.0
- @backstage/errors@1.2.2
- @backstage/plugin-linguist-common@0.1.2
- @backstage/theme@0.4.2
## 0.1.9-next.3
### Patch Changes
- 406b786a2a2c: Mark package as being free of side effects, allowing more optimized Webpack builds.
- Updated dependencies
- @backstage/catalog-model@1.4.2-next.2
- @backstage/core-components@0.13.5-next.3
- @backstage/core-plugin-api@1.6.0-next.3
- @backstage/errors@1.2.2-next.0
- @backstage/plugin-catalog-react@1.8.4-next.3
- @backstage/plugin-linguist-common@0.1.2-next.0
- @backstage/theme@0.4.2-next.0
## 0.1.9-next.2
### Patch Changes
- 8cec7664e146: Removed `@types/node` dependency
- Updated dependencies
- @backstage/core-components@0.13.5-next.2
- @backstage/core-plugin-api@1.6.0-next.2
- @backstage/plugin-catalog-react@1.8.4-next.2
- @backstage/catalog-model@1.4.2-next.1
- @backstage/errors@1.2.1
- @backstage/theme@0.4.1
- @backstage/plugin-linguist-common@0.1.1
## 0.1.9-next.1
### Patch Changes
- Updated dependencies
- @backstage/plugin-catalog-react@1.8.4-next.1
- @backstage/core-components@0.13.5-next.1
- @backstage/catalog-model@1.4.2-next.0
- @backstage/core-plugin-api@1.6.0-next.1
- @backstage/errors@1.2.1
- @backstage/theme@0.4.1
- @backstage/plugin-linguist-common@0.1.1
## 0.1.8-next.0
### Patch Changes
- 482bb5c0bbf8: Moved `@types/react` to be a regular dependency
- Updated dependencies
- @backstage/core-plugin-api@1.6.0-next.0
- @backstage/core-components@0.13.5-next.0
- @backstage/catalog-model@1.4.1
- @backstage/errors@1.2.1
- @backstage/theme@0.4.1
- @backstage/plugin-catalog-react@1.8.3-next.0
- @backstage/plugin-linguist-common@0.1.1
## 0.1.6
### Patch Changes
- Updated dependencies
- @backstage/plugin-linguist-common@0.1.1
- @backstage/core-components@0.13.4
- @backstage/plugin-catalog-react@1.8.1
- @backstage/core-plugin-api@1.5.3
- @backstage/catalog-model@1.4.1
- @backstage/errors@1.2.1
- @backstage/theme@0.4.1
## 0.1.6-next.2
### Patch Changes
- Updated dependencies
- @backstage/plugin-linguist-common@0.1.1-next.1
- @backstage/plugin-catalog-react@1.8.1-next.1
## 0.1.6-next.1
### Patch Changes
- Updated dependencies
- @backstage/plugin-linguist-common@0.1.1-next.0
- @backstage/catalog-model@1.4.1
- @backstage/core-components@0.13.4-next.0
- @backstage/core-plugin-api@1.5.3
- @backstage/errors@1.2.1
- @backstage/theme@0.4.1
- @backstage/plugin-catalog-react@1.8.1-next.0
## 0.1.6-next.0
### Patch Changes
- Updated dependencies
- @backstage/core-components@0.13.4-next.0
- @backstage/core-plugin-api@1.5.3
- @backstage/plugin-catalog-react@1.8.1-next.0
- @backstage/catalog-model@1.4.1
- @backstage/errors@1.2.1
- @backstage/theme@0.4.1
- @backstage/plugin-linguist-common@0.1.0
## 0.1.5
### Patch Changes
- Updated dependencies
- @backstage/theme@0.4.1
- @backstage/errors@1.2.1
- @backstage/plugin-catalog-react@1.8.0
- @backstage/core-components@0.13.3
- @backstage/core-plugin-api@1.5.3
- @backstage/catalog-model@1.4.1
- @backstage/plugin-linguist-common@0.1.0
## 0.1.5-next.2
### Patch Changes
- Updated dependencies
- @backstage/plugin-catalog-react@1.8.0-next.2
- @backstage/theme@0.4.1-next.1
- @backstage/core-plugin-api@1.5.3-next.1
- @backstage/core-components@0.13.3-next.2
- @backstage/catalog-model@1.4.1-next.0
- @backstage/errors@1.2.1-next.0
- @backstage/plugin-linguist-common@0.1.0
## 0.1.5-next.1
### Patch Changes
- Updated dependencies
- @backstage/theme@0.4.1-next.0
- @backstage/core-components@0.13.3-next.1
- @backstage/core-plugin-api@1.5.3-next.0
- @backstage/plugin-catalog-react@1.7.1-next.1
## 0.1.5-next.0
### Patch Changes
- Updated dependencies
- @backstage/errors@1.2.1-next.0
- @backstage/core-components@0.13.3-next.0
- @backstage/catalog-model@1.4.1-next.0
- @backstage/core-plugin-api@1.5.2
- @backstage/theme@0.4.0
- @backstage/plugin-catalog-react@1.7.1-next.0
- @backstage/plugin-linguist-common@0.1.0
## 0.1.4
### Patch Changes
- Updated dependencies
- @backstage/core-plugin-api@1.5.2
- @backstage/core-components@0.13.2
- @backstage/theme@0.4.0
- @backstage/plugin-catalog-react@1.7.0
- @backstage/catalog-model@1.4.0
- @backstage/errors@1.2.0
- @backstage/plugin-linguist-common@0.1.0
## 0.1.4-next.3
### Patch Changes
- Updated dependencies
- @backstage/core-components@0.13.2-next.3
- @backstage/catalog-model@1.4.0-next.1
- @backstage/core-plugin-api@1.5.2-next.0
- @backstage/errors@1.2.0-next.0
- @backstage/theme@0.4.0-next.1
- @backstage/plugin-catalog-react@1.7.0-next.3
- @backstage/plugin-linguist-common@0.1.0
## 0.1.4-next.2
### Patch Changes
- Updated dependencies
- @backstage/theme@0.4.0-next.1
- @backstage/plugin-catalog-react@1.7.0-next.2
- @backstage/core-components@0.13.2-next.2
- @backstage/core-plugin-api@1.5.2-next.0
## 0.1.4-next.1
### Patch Changes
- Updated dependencies
- @backstage/errors@1.2.0-next.0
- @backstage/core-components@0.13.2-next.1
- @backstage/plugin-catalog-react@1.7.0-next.1
- @backstage/catalog-model@1.4.0-next.0
- @backstage/core-plugin-api@1.5.2-next.0
- @backstage/theme@0.4.0-next.0
- @backstage/plugin-linguist-common@0.1.0
## 0.1.4-next.0
### Patch Changes
- Updated dependencies
- @backstage/plugin-catalog-react@1.7.0-next.0
- @backstage/theme@0.4.0-next.0
- @backstage/core-components@0.13.2-next.0
- @backstage/core-plugin-api@1.5.1
- @backstage/catalog-model@1.3.0
- @backstage/errors@1.1.5
- @backstage/plugin-linguist-common@0.1.0
## 0.1.3
### Patch Changes
- Updated dependencies
- @backstage/theme@0.3.0
- @backstage/plugin-catalog-react@1.6.0
- @backstage/core-components@0.13.1
- @backstage/catalog-model@1.3.0
- @backstage/core-plugin-api@1.5.1
- @backstage/errors@1.1.5
- @backstage/plugin-linguist-common@0.1.0
## 0.1.3-next.2
### Patch Changes
- Updated dependencies
- @backstage/theme@0.3.0-next.0
- @backstage/core-components@0.13.1-next.1
- @backstage/plugin-catalog-react@1.6.0-next.2
- @backstage/core-plugin-api@1.5.1
## 0.1.3-next.1
### Patch Changes
- Updated dependencies
- @backstage/core-components@0.13.1-next.0
- @backstage/core-plugin-api@1.5.1
- @backstage/plugin-catalog-react@1.6.0-next.1
## 0.1.3-next.0
### Patch Changes
- Updated dependencies
- @backstage/plugin-catalog-react@1.6.0-next.0
- @backstage/core-components@0.13.0
- @backstage/core-plugin-api@1.5.1
- @backstage/catalog-model@1.3.0
- @backstage/errors@1.1.5
- @backstage/theme@0.2.19
- @backstage/plugin-linguist-common@0.1.0
## 0.1.2
### Patch Changes
- 29ba8267d69: Updated dependency `@material-ui/lab` to `4.0.0-alpha.61`.
- e0c6e8b9c3c: Update peer dependencies
- Updated dependencies
- @backstage/core-components@0.13.0
- @backstage/plugin-catalog-react@1.5.0
- @backstage/theme@0.2.19
- @backstage/core-plugin-api@1.5.1
- @backstage/catalog-model@1.3.0
- @backstage/errors@1.1.5
- @backstage/plugin-linguist-common@0.1.0
## 0.1.2-next.3
### Patch Changes
- Updated dependencies
- @backstage/plugin-catalog-react@1.5.0-next.3
- @backstage/catalog-model@1.3.0-next.0
- @backstage/core-components@0.13.0-next.3
- @backstage/core-plugin-api@1.5.1-next.1
- @backstage/errors@1.1.5
- @backstage/theme@0.2.19-next.0
- @backstage/plugin-linguist-common@0.1.0
## 0.1.2-next.2
### Patch Changes
- Updated dependencies
- @backstage/core-components@0.12.6-next.2
- @backstage/plugin-catalog-react@1.4.1-next.2
- @backstage/core-plugin-api@1.5.1-next.1
- @backstage/catalog-model@1.2.1
- @backstage/errors@1.1.5
- @backstage/theme@0.2.19-next.0
- @backstage/plugin-linguist-common@0.1.0
## 0.1.2-next.1
### Patch Changes
- 29ba8267d69: Updated dependency `@material-ui/lab` to `4.0.0-alpha.61`.
- e0c6e8b9c3c: Update peer dependencies
- Updated dependencies
- @backstage/core-components@0.12.6-next.1
- @backstage/core-plugin-api@1.5.1-next.0
- @backstage/plugin-catalog-react@1.4.1-next.1
- @backstage/theme@0.2.19-next.0
- @backstage/catalog-model@1.2.1
- @backstage/errors@1.1.5
- @backstage/plugin-linguist-common@0.1.0
## 0.1.2-next.0
### Patch Changes
- Updated dependencies
- @backstage/core-components@0.12.6-next.0
- @backstage/plugin-catalog-react@1.4.1-next.0
- @backstage/core-plugin-api@1.5.0
- @backstage/catalog-model@1.2.1
- @backstage/errors@1.1.5
- @backstage/theme@0.2.18
- @backstage/plugin-linguist-common@0.1.0
## 0.1.1
### Patch Changes
- 52b0022dab7: Updated dependency `msw` to `^1.0.0`.
- ab997ce243f: Updated `README.md` to removed images with refresh button as this feature was not implemented in the current version
- Updated dependencies
- @backstage/core-components@0.12.5
- @backstage/plugin-catalog-react@1.4.0
- @backstage/errors@1.1.5
- @backstage/core-plugin-api@1.5.0
- @backstage/catalog-model@1.2.1
- @backstage/theme@0.2.18
- @backstage/plugin-linguist-common@0.1.0
## 0.1.1-next.2
### Patch Changes
- Updated dependencies
- @backstage/core-components@0.12.5-next.2
- @backstage/plugin-catalog-react@1.4.0-next.2
- @backstage/core-plugin-api@1.5.0-next.2
## 0.1.1-next.1
### Patch Changes
- 52b0022dab7: Updated dependency `msw` to `^1.0.0`.
- ab997ce243f: Updated `README.md` to removed images with refresh button as this feature was not implemented in the current version
- Updated dependencies
- @backstage/core-components@0.12.5-next.1
- @backstage/errors@1.1.5-next.0
- @backstage/core-plugin-api@1.4.1-next.1
- @backstage/theme@0.2.18-next.0
- @backstage/plugin-catalog-react@1.4.0-next.1
- @backstage/catalog-model@1.2.1-next.1
- @backstage/plugin-linguist-common@0.1.0
## 0.1.1-next.0
### Patch Changes
- Updated dependencies
- @backstage/plugin-catalog-react@1.4.0-next.0
- @backstage/core-plugin-api@1.4.1-next.0
- @backstage/catalog-model@1.2.1-next.0
- @backstage/core-components@0.12.5-next.0
- @backstage/errors@1.1.4
- @backstage/theme@0.2.17
- @backstage/plugin-linguist-common@0.1.0
## 0.1.0
### Minor Changes
- 75cfee5688: Introduced the Linguist plugin, checkout the plugin's `README.md` for more details!
### Patch Changes
- Updated dependencies
- @backstage/core-components@0.12.4
- @backstage/catalog-model@1.2.0
- @backstage/theme@0.2.17
- @backstage/core-plugin-api@1.4.0
- @backstage/plugin-catalog-react@1.3.0
- @backstage/plugin-linguist-common@0.1.0
- @backstage/errors@1.1.4
-3
View File
@@ -1,3 +0,0 @@
# Deprecated
This package has been moved to the [backstage-community/plugins](https://github.com/backstage/community-plugins) repository. Migrate to using `@backstage-community/plugin-linguist` instead.
-23
View File
@@ -1,23 +0,0 @@
## API Report File for "@backstage/plugin-linguist"
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
```ts
import { BackstagePlugin } from '@backstage/frontend-plugin-api';
import { TranslationRef } from '@backstage/core-plugin-api/alpha';
// @alpha (undocumented)
const _default: BackstagePlugin<{}, {}>;
export default _default;
// @alpha (undocumented)
export const linguistTranslationRef: TranslationRef<
'linguist',
{
readonly 'entityCard.title': 'Languages';
readonly 'entityCard.noData': 'There is currently no language data for this entity.';
}
>;
// (No @packageDocumentation comment for this package)
```
-22
View File
@@ -1,22 +0,0 @@
## API Report File for "@backstage/plugin-linguist"
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
```ts
/// <reference types="react" />
import { BackstagePlugin } from '@backstage/core-plugin-api';
import { Entity } from '@backstage/catalog-model';
import { JSX as JSX_2 } from 'react';
// @public (undocumented)
export const EntityLinguistCard: () => JSX_2.Element;
// @public (undocumented)
export const isLinguistAvailable: (entity: Entity) => boolean;
// @public (undocumented)
export const linguistPlugin: BackstagePlugin<{}, {}>;
// (No @packageDocumentation comment for this package)
```
-9
View File
@@ -1,9 +0,0 @@
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: backstage-plugin-linguist
title: '@backstage/plugin-linguist'
spec:
lifecycle: experimental
type: backstage-frontend-plugin
owner: awanlin
-27
View File
@@ -1,27 +0,0 @@
/*
* Copyright 2022 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 React from 'react';
import { createDevApp } from '@backstage/dev-utils';
import { linguistPlugin, EntityLinguistCard } from '../src/plugin';
createDevApp()
.registerPlugin(linguistPlugin)
.addPage({
element: <EntityLinguistCard />,
title: 'Root Page',
path: '/linguist',
})
.render();
Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

-2
View File
@@ -1,2 +0,0 @@
# Knip report
-75
View File
@@ -1,75 +0,0 @@
{
"name": "@backstage/plugin-linguist",
"version": "0.1.19",
"backstage": {
"role": "frontend-plugin",
"moved": "@backstage-community/plugin-linguist"
},
"publishConfig": {
"access": "public"
},
"homepage": "https://backstage.io",
"repository": {
"type": "git",
"url": "https://github.com/backstage/backstage",
"directory": "plugins/linguist"
},
"license": "Apache-2.0",
"sideEffects": false,
"exports": {
".": "./src/index.ts",
"./alpha": "./src/alpha.ts",
"./package.json": "./package.json"
},
"main": "src/index.ts",
"types": "src/index.ts",
"typesVersions": {
"*": {
"alpha": [
"src/alpha.ts"
],
"package.json": [
"package.json"
]
}
},
"files": [
"dist"
],
"scripts": {
"build": "backstage-cli package build",
"clean": "backstage-cli package clean",
"lint": "backstage-cli package lint",
"prepack": "backstage-cli package prepack",
"postpack": "backstage-cli package postpack",
"start": "backstage-cli package start",
"test": "backstage-cli package test"
},
"dependencies": {
"@backstage/catalog-model": "workspace:^",
"@backstage/core-compat-api": "workspace:^",
"@backstage/core-components": "workspace:^",
"@backstage/core-plugin-api": "workspace:^",
"@backstage/errors": "workspace:^",
"@backstage/frontend-plugin-api": "workspace:^",
"@backstage/plugin-catalog-react": "workspace:^",
"@backstage/plugin-linguist-common": "workspace:^",
"@material-ui/core": "^4.9.13",
"@material-ui/lab": "4.0.0-alpha.61",
"@types/react": "^16.13.1 || ^17.0.0 || ^18.0.0",
"luxon": "^3.0.0",
"react-use": "^17.2.4",
"slugify": "^1.6.4"
},
"devDependencies": {
"@backstage/cli": "workspace:^",
"@backstage/dev-utils": "workspace:^",
"@testing-library/jest-dom": "^6.0.0"
},
"peerDependencies": {
"react": "^16.13.1 || ^17.0.0 || ^18.0.0",
"react-dom": "^16.13.1 || ^17.0.0 || ^18.0.0",
"react-router-dom": "6.0.0-beta.0 || ^6.3.0"
},
"deprecated": "This package has been moved to the backstage/community-plugins repository. You should migrate to using @backstage-community/plugin-linguist instead."
}
-19
View File
@@ -1,19 +0,0 @@
/*
* Copyright 2023 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 './alpha/index';
export { default } from './alpha/index';
export * from './translation';
-17
View File
@@ -1,17 +0,0 @@
/*
* Copyright 2023 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 { default } from './plugin';
-56
View File
@@ -1,56 +0,0 @@
/*
* Copyright 2023 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 React from 'react';
import {
createApiExtension,
createApiFactory,
createPlugin,
discoveryApiRef,
fetchApiRef,
} from '@backstage/frontend-plugin-api';
import { LinguistClient, linguistApiRef } from '../api';
import { compatWrapper } from '@backstage/core-compat-api';
import { createEntityCardExtension } from '@backstage/plugin-catalog-react/alpha';
/** @alpha */
export const entityLinguistCard = createEntityCardExtension({
name: 'languages',
loader: async () =>
import('../components/LinguistCard').then(m =>
compatWrapper(<m.LinguistCard />),
),
});
/** @alpha */
export const linguistApi = createApiExtension({
factory: createApiFactory({
api: linguistApiRef,
deps: {
discoveryApi: discoveryApiRef,
fetchApi: fetchApiRef,
},
factory: ({ discoveryApi, fetchApi }) =>
new LinguistClient({ discoveryApi, fetchApi }),
}),
});
/** @alpha */
export default createPlugin({
id: 'linguist',
extensions: [linguistApi, entityLinguistCard],
});
-26
View File
@@ -1,26 +0,0 @@
/*
* Copyright 2022 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 { createApiRef } from '@backstage/core-plugin-api';
import { Languages } from '@backstage/plugin-linguist-common';
export const linguistApiRef = createApiRef<LinguistApi>({
id: 'plugin.linguist.service',
});
export interface LinguistApi {
getLanguages(entityRef: string): Promise<Languages>;
}
@@ -1,56 +0,0 @@
/*
* Copyright 2022 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 { DiscoveryApi, FetchApi } from '@backstage/core-plugin-api';
import { ResponseError } from '@backstage/errors';
import { Languages } from '@backstage/plugin-linguist-common';
import { LinguistApi } from './LinguistApi';
export class LinguistClient implements LinguistApi {
private readonly discoveryApi: DiscoveryApi;
private readonly fetchApi: FetchApi;
public constructor(options: {
discoveryApi: DiscoveryApi;
fetchApi: FetchApi;
}) {
this.discoveryApi = options.discoveryApi;
this.fetchApi = options.fetchApi;
}
public async getLanguages(entityRef: string): Promise<Languages> {
const queryString = new URLSearchParams();
queryString.append('entityRef', entityRef);
const urlSegment = `entity-languages?${queryString}`;
const items = await this.get<Languages>(urlSegment);
return items;
}
private async get<T>(path: string): Promise<T> {
const baseUrl = `${await this.discoveryApi.getBaseUrl('linguist')}/`;
const url = new URL(path, baseUrl);
const response = await this.fetchApi.fetch(url.toString());
if (!response.ok) {
throw await ResponseError.fromResponse(response);
}
return response.json() as Promise<T>;
}
}
-18
View File
@@ -1,18 +0,0 @@
/*
* Copyright 2022 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 './LinguistApi';
export * from './LinguistClient';
@@ -1,151 +0,0 @@
/*
* Copyright 2022 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 Box from '@material-ui/core/Box';
import Chip from '@material-ui/core/Chip';
import Tooltip from '@material-ui/core/Tooltip';
import Typography from '@material-ui/core/Typography';
import Grid from '@material-ui/core/Grid';
import { makeStyles, useTheme } from '@material-ui/core/styles';
import { InfoCard, Progress } from '@backstage/core-components';
import Alert from '@material-ui/lab/Alert';
import { DateTime } from 'luxon';
import React from 'react';
import slugify from 'slugify';
import { useEntity } from '@backstage/plugin-catalog-react';
import { useLanguages } from '../../hooks';
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
import { linguistTranslationRef } from '../../translation';
const useStyles = makeStyles(theme => ({
infoCard: {
marginBottom: theme.spacing(3),
},
barContainer: {
height: theme.spacing(2),
marginBottom: theme.spacing(3),
borderRadius: '4px',
backgroundColor: 'transparent',
overflow: 'hidden',
},
bar: {
height: '100%',
position: 'relative',
},
languageDot: {
width: '10px',
height: '10px',
borderRadius: '50%',
marginRight: theme.spacing(1),
display: 'inline-block',
},
label: {
color: 'inherit',
},
}));
export const LinguistCard = () => {
const { t } = useTranslationRef(linguistTranslationRef);
const classes = useStyles();
const theme = useTheme();
const { entity } = useEntity();
const { items, loading, error } = useLanguages(entity);
let barWidth = 0;
if (loading) {
return <Progress />;
} else if (error) {
return <Alert severity="error">{error.message}</Alert>;
}
if (items && items.languageCount === 0 && items.totalBytes === 0) {
return (
<InfoCard title={t('entityCard.title')} className={classes.infoCard}>
<Grid container spacing={3}>
<Box p={2}>
<Typography>{t('entityCard.noData')}</Typography>
</Box>
</Grid>
</InfoCard>
);
}
const breakdown = items?.breakdown.sort((a, b) =>
a.percentage < b.percentage ? 1 : -1,
);
const processedDate = items?.processedDate;
return breakdown && processedDate ? (
<InfoCard title={t('entityCard.title')} className={classes.infoCard}>
<Box className={classes.barContainer}>
{breakdown.map((language, index: number) => {
barWidth = barWidth + language.percentage;
return (
<Tooltip
title={language.name}
placement="bottom-end"
key={slugify(language.name, { lower: true })}
>
<Box
className={classes.bar}
key={slugify(language.name, { lower: true })}
style={{
marginTop: index === 0 ? '0' : `-16px`,
zIndex: Object.keys(breakdown).length - index,
backgroundColor:
language.color?.toString() ||
theme.palette.background.default,
width: `${barWidth}%`,
}}
/>
</Tooltip>
);
})}
</Box>
<Tooltip
title={`Generated ${DateTime.fromISO(processedDate).toRelative()}`}
>
<Box>
{breakdown.map(languages => (
<Chip
classes={{
label: classes.label,
}}
label={
<Box>
<Box
component="span"
className={classes.languageDot}
style={{
backgroundColor:
languages?.color?.toString() ||
theme.palette.background.default,
}}
/>
{languages.name} - {languages.percentage}%
</Box>
}
variant="outlined"
key={slugify(languages.name, { lower: true })}
/>
))}
</Box>
</Tooltip>
</InfoCard>
) : (
<></>
);
};
@@ -1,17 +0,0 @@
/*
* Copyright 2022 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 { LinguistCard } from './LinguistCard';
-17
View File
@@ -1,17 +0,0 @@
/*
* Copyright 2022 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 './useLanguages';
@@ -1,41 +0,0 @@
/*
* Copyright 2022 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 { Entity, stringifyEntityRef } from '@backstage/catalog-model';
import { linguistApiRef } from '../api';
import { useApi } from '@backstage/core-plugin-api';
import useAsync from 'react-use/esm/useAsync';
import { Languages } from '@backstage/plugin-linguist-common';
export function useLanguages(entity: Entity): {
items?: Languages;
loading: boolean;
error?: Error;
} {
const entityRef = stringifyEntityRef(entity);
const api = useApi(linguistApiRef);
const { value, loading, error } = useAsync(() => {
return api.getLanguages(entityRef);
}, [api, entityRef]);
return {
items: value,
loading,
error,
};
}
-20
View File
@@ -1,20 +0,0 @@
/*
* Copyright 2022 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 {
linguistPlugin,
isLinguistAvailable,
EntityLinguistCard,
} from './plugin';
-22
View File
@@ -1,22 +0,0 @@
/*
* Copyright 2022 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 { linguistPlugin } from './plugin';
describe('linguist', () => {
it('should export plugin', () => {
expect(linguistPlugin).toBeDefined();
});
});
-56
View File
@@ -1,56 +0,0 @@
/*
* Copyright 2022 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 {
createApiFactory,
createComponentExtension,
createPlugin,
discoveryApiRef,
fetchApiRef,
} from '@backstage/core-plugin-api';
import { linguistApiRef, LinguistClient } from './api';
import { LINGUIST_ANNOTATION } from '@backstage/plugin-linguist-common';
import { Entity } from '@backstage/catalog-model';
/** @public */
export const isLinguistAvailable = (entity: Entity) =>
Boolean(entity.metadata.annotations?.[LINGUIST_ANNOTATION]);
/** @public */
export const linguistPlugin = createPlugin({
id: 'linguist',
apis: [
createApiFactory({
api: linguistApiRef,
deps: {
discoveryApi: discoveryApiRef,
fetchApi: fetchApiRef,
},
factory: ({ discoveryApi, fetchApi }) =>
new LinguistClient({ discoveryApi, fetchApi }),
}),
],
});
/** @public */
export const EntityLinguistCard = linguistPlugin.provide(
createComponentExtension({
name: 'EntityLinguistCard',
component: {
lazy: () => import('./components/LinguistCard').then(m => m.LinguistCard),
},
}),
);
-17
View File
@@ -1,17 +0,0 @@
/*
* Copyright 2022 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 '@testing-library/jest-dom';
-27
View File
@@ -1,27 +0,0 @@
/*
* 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 { createTranslationRef } from '@backstage/core-plugin-api/alpha';
/** @alpha */
export const linguistTranslationRef = createTranslationRef({
id: 'linguist',
messages: {
entityCard: {
title: 'Languages',
noData: 'There is currently no language data for this entity.',
},
},
});
-1
View File
@@ -1 +0,0 @@
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
-425
View File
@@ -1,425 +0,0 @@
# @backstage/plugin-nomad-backend
## 0.1.19
### Patch Changes
- d5a1fe1: Replaced winston logger with `LoggerService`
- Updated dependencies
- @backstage/backend-common@0.21.7
- @backstage/backend-plugin-api@0.6.17
- @backstage/config@1.2.0
- @backstage/errors@1.2.4
## 0.1.19-next.1
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.21.7-next.1
- @backstage/backend-plugin-api@0.6.17-next.1
- @backstage/config@1.2.0
- @backstage/errors@1.2.4
## 0.1.19-next.0
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.21.7-next.0
- @backstage/backend-plugin-api@0.6.17-next.0
- @backstage/config@1.2.0
- @backstage/errors@1.2.4
## 0.1.18
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.21.6
- @backstage/backend-plugin-api@0.6.16
- @backstage/config@1.2.0
- @backstage/errors@1.2.4
## 0.1.17
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.21.5
- @backstage/backend-plugin-api@0.6.15
- @backstage/config@1.2.0
- @backstage/errors@1.2.4
## 0.1.16
### Patch Changes
- 4467036: Allow unauthenticated access to health check endpoint.
- Updated dependencies
- @backstage/backend-common@0.21.4
- @backstage/config@1.2.0
- @backstage/errors@1.2.4
- @backstage/backend-plugin-api@0.6.14
## 0.1.16-next.2
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.21.4-next.2
- @backstage/backend-plugin-api@0.6.14-next.2
- @backstage/config@1.2.0-next.1
- @backstage/errors@1.2.4-next.0
## 0.1.16-next.1
### Patch Changes
- Updated dependencies
- @backstage/config@1.2.0-next.1
- @backstage/backend-common@0.21.4-next.1
- @backstage/backend-plugin-api@0.6.14-next.1
- @backstage/errors@1.2.4-next.0
## 0.1.15-next.0
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.21.3-next.0
- @backstage/errors@1.2.4-next.0
- @backstage/backend-plugin-api@0.6.13-next.0
- @backstage/config@1.1.2-next.0
## 0.1.12
### Patch Changes
- 9aac2b0: Use `--cwd` as the first `yarn` argument
- Updated dependencies
- @backstage/backend-common@0.21.0
- @backstage/backend-plugin-api@0.6.10
- @backstage/config@1.1.1
- @backstage/errors@1.2.3
## 0.1.12-next.3
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.21.0-next.3
- @backstage/backend-plugin-api@0.6.10-next.3
- @backstage/config@1.1.1
- @backstage/errors@1.2.3
## 0.1.12-next.2
### Patch Changes
- 9aac2b0: Use `--cwd` as the first `yarn` argument
- Updated dependencies
- @backstage/backend-common@0.21.0-next.2
- @backstage/backend-plugin-api@0.6.10-next.2
- @backstage/config@1.1.1
- @backstage/errors@1.2.3
## 0.1.12-next.1
### Patch Changes
- Updated dependencies
- @backstage/backend-plugin-api@0.6.10-next.1
- @backstage/backend-common@0.21.0-next.1
- @backstage/config@1.1.1
- @backstage/errors@1.2.3
## 0.1.12-next.0
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.21.0-next.0
- @backstage/backend-plugin-api@0.6.10-next.0
- @backstage/config@1.1.1
- @backstage/errors@1.2.3
## 0.1.11
### Patch Changes
- 4016f21: Remove some unused dependencies
- Updated dependencies
- @backstage/backend-common@0.20.1
- @backstage/backend-plugin-api@0.6.9
- @backstage/config@1.1.1
- @backstage/errors@1.2.3
## 0.1.11-next.2
### Patch Changes
- Updated dependencies
- @backstage/backend-plugin-api@0.6.9-next.2
- @backstage/backend-common@0.20.1-next.2
## 0.1.11-next.1
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.20.1-next.1
- @backstage/config@1.1.1
- @backstage/backend-plugin-api@0.6.9-next.1
- @backstage/errors@1.2.3
## 0.1.11-next.0
### Patch Changes
- 4016f21: Remove some unused dependencies
- Updated dependencies
- @backstage/backend-common@0.20.1-next.0
- @backstage/backend-plugin-api@0.6.9-next.0
- @backstage/config@1.1.1
- @backstage/errors@1.2.3
## 0.1.10
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.20.0
- @backstage/backend-plugin-api@0.6.8
- @backstage/config@1.1.1
- @backstage/errors@1.2.3
## 0.1.10-next.3
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.20.0-next.3
- @backstage/backend-plugin-api@0.6.8-next.3
- @backstage/config@1.1.1
- @backstage/errors@1.2.3
## 0.1.10-next.2
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.20.0-next.2
- @backstage/backend-plugin-api@0.6.8-next.2
- @backstage/config@1.1.1
- @backstage/errors@1.2.3
## 0.1.10-next.1
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.20.0-next.1
- @backstage/backend-plugin-api@0.6.8-next.1
- @backstage/config@1.1.1
- @backstage/errors@1.2.3
## 0.1.10-next.0
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.20.0-next.0
- @backstage/backend-plugin-api@0.6.8-next.0
- @backstage/config@1.1.1
- @backstage/errors@1.2.3
## 0.1.9
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.19.9
- @backstage/backend-plugin-api@0.6.7
- @backstage/config@1.1.1
- @backstage/errors@1.2.3
## 0.1.9-next.2
### Patch Changes
- Updated dependencies
- @backstage/backend-plugin-api@0.6.7-next.2
- @backstage/backend-common@0.19.9-next.2
## 0.1.9-next.1
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.19.9-next.1
- @backstage/backend-plugin-api@0.6.7-next.1
- @backstage/config@1.1.1
- @backstage/errors@1.2.3
## 0.1.9-next.0
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.19.9-next.0
- @backstage/backend-plugin-api@0.6.7-next.0
- @backstage/config@1.1.1
- @backstage/errors@1.2.3
## 0.1.8
### Patch Changes
- 6822918c50: Added support for the [new backend system](https://backstage.io/docs/backend-system/)
- Updated dependencies
- @backstage/backend-common@0.19.8
- @backstage/errors@1.2.3
- @backstage/backend-plugin-api@0.6.6
- @backstage/config@1.1.1
## 0.1.8-next.2
### Patch Changes
- 6822918c50: Added support for the [new backend system](https://backstage.io/docs/backend-system/)
- Updated dependencies
- @backstage/backend-common@0.19.8-next.2
- @backstage/errors@1.2.3-next.0
- @backstage/backend-plugin-api@0.6.6-next.2
- @backstage/config@1.1.1-next.0
## 0.1.7-next.1
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.19.7-next.1
- @backstage/config@1.1.0
- @backstage/errors@1.2.2
## 0.1.7-next.0
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.19.7-next.0
- @backstage/config@1.1.0
- @backstage/errors@1.2.2
## 0.1.5
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.19.5
- @backstage/config@1.1.0
- @backstage/errors@1.2.2
## 0.1.5-next.3
### Patch Changes
- Updated dependencies
- @backstage/config@1.1.0-next.2
- @backstage/errors@1.2.2-next.0
- @backstage/backend-common@0.19.5-next.3
## 0.1.5-next.2
### Patch Changes
- Updated dependencies
- @backstage/config@1.1.0-next.1
- @backstage/backend-common@0.19.5-next.2
- @backstage/errors@1.2.1
## 0.1.5-next.1
### Patch Changes
- Updated dependencies
- @backstage/config@1.1.0-next.0
- @backstage/backend-common@0.19.5-next.1
- @backstage/errors@1.2.1
## 0.1.4-next.0
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.19.4-next.0
- @backstage/config@1.0.8
- @backstage/errors@1.2.1
## 0.1.2
### Patch Changes
- 12a8c94eda8d: Add package repository and homepage metadata
- Updated dependencies
- @backstage/backend-common@0.19.2
- @backstage/config@1.0.8
- @backstage/errors@1.2.1
## 0.1.2-next.2
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.19.2-next.2
## 0.1.2-next.1
### Patch Changes
- 12a8c94eda8d: Add package repository and homepage metadata
- Updated dependencies
- @backstage/backend-common@0.19.2-next.1
- @backstage/config@1.0.8
- @backstage/errors@1.2.1
## 0.1.2-next.0
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.19.2-next.0
- @backstage/config@1.0.8
- @backstage/errors@1.2.1
## 0.1.1
### Patch Changes
- Updated dependencies
- @backstage/errors@1.2.1
- @backstage/backend-common@0.19.1
- @backstage/config@1.0.8
## 0.1.1-next.0
### Patch Changes
- Updated dependencies
- @backstage/errors@1.2.1-next.0
- @backstage/backend-common@0.19.1-next.0
- @backstage/config@1.0.8
## 0.1.0
### Minor Changes
- 05bf04ca6863: Initialize Nomad plugin with frontend and backend. List jobs/allocations
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.19.0
- @backstage/errors@1.2.0
- @backstage/config@1.0.8
-3
View File
@@ -1,3 +0,0 @@
# Deprecated
This package has been moved to the [backstage-community/plugins](https://github.com/backstage/community-plugins) repository. Migrate to using `@backstage-community/plugin-nomad-backend` instead.
-27
View File
@@ -1,27 +0,0 @@
## API Report File for "@backstage/plugin-nomad-backend"
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
```ts
import { BackendFeature } from '@backstage/backend-plugin-api';
import { Config } from '@backstage/config';
import express from 'express';
import { LoggerService } from '@backstage/backend-plugin-api';
// @public (undocumented)
export function createRouter(options: RouterOptions): Promise<express.Router>;
// @public
const nomadPlugin: () => BackendFeature;
export default nomadPlugin;
// @public (undocumented)
export interface RouterOptions {
// (undocumented)
config: Config;
// (undocumented)
logger: LoggerService;
}
// (No @packageDocumentation comment for this package)
```
-9
View File
@@ -1,9 +0,0 @@
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: backstage-plugin-nomad-backend
title: '@backstage/plugin-nomad-backend'
spec:
lifecycle: experimental
type: backstage-backend-plugin
owner: maintainers
-2
View File
@@ -1,2 +0,0 @@
# Knip report
-51
View File
@@ -1,51 +0,0 @@
{
"name": "@backstage/plugin-nomad-backend",
"version": "0.1.19",
"backstage": {
"role": "backend-plugin",
"moved": "@backstage-community/plugin-nomad-backend"
},
"publishConfig": {
"access": "public",
"main": "dist/index.cjs.js",
"types": "dist/index.d.ts"
},
"homepage": "https://backstage.io",
"repository": {
"type": "git",
"url": "https://github.com/backstage/backstage",
"directory": "plugins/nomad-backend"
},
"license": "Apache-2.0",
"main": "src/index.ts",
"types": "src/index.ts",
"files": [
"dist"
],
"scripts": {
"build": "backstage-cli package build",
"clean": "backstage-cli package clean",
"lint": "backstage-cli package lint",
"prepack": "backstage-cli package prepack",
"postpack": "backstage-cli package postpack",
"start": "backstage-cli package start",
"test": "backstage-cli package test"
},
"dependencies": {
"@backstage/backend-common": "workspace:^",
"@backstage/backend-plugin-api": "workspace:^",
"@backstage/config": "workspace:^",
"@backstage/errors": "workspace:^",
"@types/express": "*",
"express": "^4.17.1",
"express-promise-router": "^4.1.0",
"node-fetch": "^2.6.7",
"yn": "^4.0.0"
},
"devDependencies": {
"@backstage/cli": "workspace:^",
"@types/supertest": "^2.0.8",
"supertest": "^6.2.4"
},
"deprecated": "This package has been moved to the backstage/community-plugins repository. You should migrate to using @backstage-community/plugin-nomad-backend instead."
}
-17
View File
@@ -1,17 +0,0 @@
/*
* Copyright 2023 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/router';
export { nomadPlugin as default } from './plugin';
-22
View File
@@ -1,22 +0,0 @@
/*
* Copyright 2023 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 { nomadPlugin } from './plugin';
describe('nomad', () => {
it('should export the nomad plugin', () => {
expect(nomadPlugin).toBeDefined();
});
});
-54
View File
@@ -1,54 +0,0 @@
/*
* Copyright 2023 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 {
coreServices,
createBackendPlugin,
} from '@backstage/backend-plugin-api';
import { createRouter } from './service/router';
/**
* Nomad backend plugin
*
* @public
*/
export const nomadPlugin = createBackendPlugin({
pluginId: 'nomad',
register(env) {
env.registerInit({
deps: {
logger: coreServices.logger,
config: coreServices.rootConfig,
httpRouter: coreServices.httpRouter,
},
async init({ logger, config, httpRouter }) {
httpRouter.use(
await createRouter({
/**
* Logger for logging purposes
*/
logger,
config,
}),
);
httpRouter.addAuthPolicy({
path: '/health',
allow: 'unauthenticated',
});
},
});
},
});
-34
View File
@@ -1,34 +0,0 @@
/*
* Copyright 2023 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 { getRootLogger } from '@backstage/backend-common';
import yn from 'yn';
import { startStandaloneServer } from './service/standaloneServer';
import { ConfigReader } from '@backstage/config';
const config = new ConfigReader({});
const port = process.env.PLUGIN_PORT ? Number(process.env.PLUGIN_PORT) : 7007;
const enableCors = yn(process.env.PLUGIN_CORS, { default: false });
const logger = getRootLogger();
startStandaloneServer({ config, port, enableCors, logger }).catch(err => {
logger.error(err);
process.exit(1);
});
process.on('SIGINT', () => {
logger.info('CTRL+C pressed; exiting.');
process.exit(0);
});
@@ -1,51 +0,0 @@
/*
* Copyright 2023 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 { getVoidLogger } from '@backstage/backend-common';
import express from 'express';
import request from 'supertest';
import { createRouter } from './router';
import { ConfigReader } from '@backstage/config';
describe('createRouter', () => {
let app: express.Express;
beforeAll(async () => {
const router = await createRouter({
config: new ConfigReader({
nomad: {
addr: 'http://localhost.com',
token: 'asdf',
},
}),
logger: getVoidLogger(),
});
app = express().use(router);
});
beforeEach(() => {
jest.resetAllMocks();
});
describe('GET /health', () => {
it('returns ok', async () => {
const response = await request(app).get('/health');
expect(response.status).toEqual(200);
expect(response.body).toEqual({ status: 'ok' });
});
});
});
-126
View File
@@ -1,126 +0,0 @@
/*
* Copyright 2023 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 fetch from 'node-fetch';
import { errorHandler, requestLoggingHandler } from '@backstage/backend-common';
import { Config } from '@backstage/config';
import { InputError } from '@backstage/errors';
import express from 'express';
import Router from 'express-promise-router';
import { LoggerService } from '@backstage/backend-plugin-api';
/** @public */
export interface RouterOptions {
logger: LoggerService;
config: Config;
}
/** @public */
export async function createRouter(
options: RouterOptions,
): Promise<express.Router> {
const { config, logger } = options;
// Get Nomad addr and token from config
const addr = config.getString('nomad.addr');
const token = config.getOptionalString('nomad.token');
const router = Router();
router.use(express.json());
router.get('/health', (_, resp) => {
resp.json({ status: 'ok' });
});
router.get('/v1/allocations', async (req, res) => {
// Check namespace argument
const namespace = (req.query.namespace as string) ?? '';
if (!namespace || namespace === '') {
throw new InputError(`Missing "namespace" query parameter`);
}
// Check filter argument
const filter = (req.query.filter as string) ?? '';
logger.debug(`request headers: namespace=${namespace} filter=${filter}`);
// Issue the request
const endpoint = `${addr}/v1/allocations?namespace=${encodeURIComponent(
namespace,
)}&filter=${encodeURIComponent(filter)}`;
const allocationsResp = await fetch(endpoint, {
method: 'GET',
headers: {
Accept: 'application/json',
'X-Nomad-Token': token || '',
},
});
// Check response
if (allocationsResp.status !== 200) {
const body = await allocationsResp.text();
logger.error(`failed to call /v1/allocations endpoint: ${body}`);
res.status(allocationsResp.status).json({ message: body });
return;
}
// Deserialize and return
const allocationsBody = await allocationsResp.json();
logger.debug(`/v1/allocations response: ${allocationsBody}`);
res.json(allocationsBody);
});
router.get('/v1/job/:job_id/versions', async (req, resp) => {
// Check namespace argument
const namespace = (req.query.namespace as string) ?? '';
if (!namespace || namespace === '') {
throw new InputError(`Missing "namespace" query parameter`);
}
// Get job ID
const jobID = (req.params.job_id as string) ?? '';
// Issue the request
const apiResp = await fetch(
`${addr}/v1/job/${jobID}/versions?namespace=${encodeURIComponent(
namespace,
)}`,
{
method: 'GET',
headers: {
Accept: 'application/json',
'X-Nomad-Token': token || '',
},
},
);
// Check response
if (apiResp.status !== 200) {
const body = await apiResp.text();
logger.error(`failed to call /v1/job/:job_id/versions endpoint: ${body}`);
resp.status(apiResp.status).json({ message: body });
return;
}
// Deserialize and return
const versionsBody = await apiResp.json();
logger.debug(`/v1/job/:job_id/versions response: ${versionsBody}`);
resp.json(versionsBody);
});
router.use(requestLoggingHandler());
router.use(errorHandler());
return router;
}
@@ -1,52 +0,0 @@
/*
* Copyright 2023 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 { createServiceBuilder } from '@backstage/backend-common';
import { Server } from 'http';
import { createRouter } from './router';
import { Config } from '@backstage/config';
import { LoggerService } from '@backstage/backend-plugin-api';
export interface ServerOptions {
config: Config;
enableCors: boolean;
logger: LoggerService;
port: number;
}
export async function startStandaloneServer(
options: ServerOptions,
): Promise<Server> {
const logger = options.logger.child({ service: 'nomad' });
logger.debug('Starting application server...');
const router = await createRouter({
logger,
config: options.config,
});
let service = createServiceBuilder(module)
.setPort(options.port)
.addRouter('/nomad', router);
if (options.enableCors) {
service = service.enableCors({ origin: 'http://localhost:3000' });
}
return await service.start().catch(err => {
logger.error(err);
process.exit(1);
});
}
module.hot?.accept();
-16
View File
@@ -1,16 +0,0 @@
/*
* Copyright 2023 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 {};
-5
View File
@@ -1,5 +0,0 @@
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname, {
rules: {
'@backstage/no-top-level-material-ui-4-imports': 'error',
},
});
-525
View File
@@ -1,525 +0,0 @@
# @backstage/plugin-nomad
## 0.1.15
### Patch Changes
- abfbcfc: Updated dependency `@testing-library/react` to `^15.0.0`.
- Updated dependencies
- @backstage/plugin-catalog-react@1.11.3
- @backstage/core-components@0.14.4
- @backstage/core-plugin-api@1.9.2
- @backstage/catalog-model@1.4.5
## 0.1.15-next.1
### Patch Changes
- Updated dependencies
- @backstage/catalog-model@1.4.5
- @backstage/core-components@0.14.4-next.0
- @backstage/core-plugin-api@1.9.1
- @backstage/plugin-catalog-react@1.11.3-next.1
## 0.1.15-next.0
### Patch Changes
- Updated dependencies
- @backstage/core-components@0.14.4-next.0
- @backstage/catalog-model@1.4.5
- @backstage/core-plugin-api@1.9.1
- @backstage/plugin-catalog-react@1.11.3-next.0
## 0.1.14
### Patch Changes
- c116096: Added an optional ESLint rule - no-top-level-material-ui-4-imports -in nomad plugin which has an auto fix function to migrate the imports and used it to migrate the Material UI imports for plugins/nomad.
- e8f026a: Use ESM exports of react-use library
- Updated dependencies
- @backstage/core-components@0.14.3
- @backstage/plugin-catalog-react@1.11.2
- @backstage/core-plugin-api@1.9.1
- @backstage/catalog-model@1.4.5
## 0.1.13
### Patch Changes
- c116096: Added an optional ESLint rule - no-top-level-material-ui-4-imports -in nomad plugin which has an auto fix function to migrate the imports and used it to migrate the Material UI imports for plugins/nomad.
- e8f026a: Use ESM exports of react-use library
- Updated dependencies
- @backstage/core-components@0.14.2
- @backstage/plugin-catalog-react@1.11.1
- @backstage/core-plugin-api@1.9.1
- @backstage/catalog-model@1.4.5
## 0.1.12
### Patch Changes
- Updated dependencies
- @backstage/core-components@0.14.1
- @backstage/plugin-catalog-react@1.11.0
- @backstage/catalog-model@1.4.5
- @backstage/core-plugin-api@1.9.1
## 0.1.12-next.2
### Patch Changes
- Updated dependencies
- @backstage/core-components@0.14.1-next.2
- @backstage/plugin-catalog-react@1.11.0-next.2
- @backstage/catalog-model@1.4.5-next.0
- @backstage/core-plugin-api@1.9.1-next.1
## 0.1.12-next.1
### Patch Changes
- Updated dependencies
- @backstage/core-components@0.14.1-next.1
- @backstage/plugin-catalog-react@1.10.1-next.1
- @backstage/core-plugin-api@1.9.1-next.1
- @backstage/catalog-model@1.4.5-next.0
## 0.1.12-next.0
### Patch Changes
- Updated dependencies
- @backstage/core-components@0.14.1-next.0
- @backstage/plugin-catalog-react@1.10.1-next.0
- @backstage/catalog-model@1.4.5-next.0
- @backstage/core-plugin-api@1.9.1-next.0
## 0.1.11
### Patch Changes
- 9aac2b0: Use `--cwd` as the first `yarn` argument
- 8fe56a8: Widen `@types/react` dependency range to include version 18.
- Updated dependencies
- @backstage/plugin-catalog-react@1.10.0
- @backstage/core-components@0.14.0
- @backstage/catalog-model@1.4.4
- @backstage/core-plugin-api@1.9.0
## 0.1.11-next.3
### Patch Changes
- Updated dependencies
- @backstage/core-components@0.14.0-next.2
- @backstage/plugin-catalog-react@1.10.0-next.3
- @backstage/catalog-model@1.4.4-next.0
- @backstage/core-plugin-api@1.9.0-next.1
## 0.1.11-next.2
### Patch Changes
- 9aac2b0: Use `--cwd` as the first `yarn` argument
- 8fe56a8: Widen `@types/react` dependency range to include version 18.
- Updated dependencies
- @backstage/core-components@0.14.0-next.1
- @backstage/core-plugin-api@1.9.0-next.1
- @backstage/plugin-catalog-react@1.10.0-next.2
- @backstage/catalog-model@1.4.4-next.0
## 0.1.11-next.1
### Patch Changes
- Updated dependencies
- @backstage/core-components@0.14.0-next.0
- @backstage/catalog-model@1.4.4-next.0
- @backstage/core-plugin-api@1.8.3-next.0
- @backstage/plugin-catalog-react@1.9.4-next.1
## 0.1.11-next.0
### Patch Changes
- Updated dependencies
- @backstage/plugin-catalog-react@1.9.4-next.0
- @backstage/core-components@0.13.10
- @backstage/catalog-model@1.4.3
- @backstage/core-plugin-api@1.8.2
## 0.1.10
### Patch Changes
- 4016f21: Remove some unused dependencies
- Updated dependencies
- @backstage/core-components@0.13.10
- @backstage/core-plugin-api@1.8.2
- @backstage/plugin-catalog-react@1.9.3
- @backstage/catalog-model@1.4.3
## 0.1.10-next.2
### Patch Changes
- Updated dependencies
- @backstage/plugin-catalog-react@1.9.3-next.2
## 0.1.10-next.1
### Patch Changes
- Updated dependencies
- @backstage/core-plugin-api@1.8.2-next.0
- @backstage/core-components@0.13.10-next.1
- @backstage/plugin-catalog-react@1.9.3-next.1
- @backstage/catalog-model@1.4.3
## 0.1.10-next.0
### Patch Changes
- 4016f21: Remove some unused dependencies
- Updated dependencies
- @backstage/core-components@0.13.10-next.0
- @backstage/plugin-catalog-react@1.9.3-next.0
- @backstage/catalog-model@1.4.3
- @backstage/core-plugin-api@1.8.1
## 0.1.9
### Patch Changes
- Updated dependencies
- @backstage/core-plugin-api@1.8.1
- @backstage/plugin-catalog-react@1.9.2
- @backstage/core-components@0.13.9
- @backstage/theme@0.5.0
- @backstage/catalog-model@1.4.3
- @backstage/config@1.1.1
## 0.1.9-next.3
### Patch Changes
- Updated dependencies
- @backstage/core-components@0.13.9-next.3
- @backstage/catalog-model@1.4.3
- @backstage/config@1.1.1
- @backstage/core-plugin-api@1.8.1-next.1
- @backstage/theme@0.5.0-next.1
- @backstage/plugin-catalog-react@1.9.2-next.3
## 0.1.9-next.2
### Patch Changes
- Updated dependencies
- @backstage/theme@0.5.0-next.1
- @backstage/plugin-catalog-react@1.9.2-next.2
- @backstage/catalog-model@1.4.3
- @backstage/config@1.1.1
- @backstage/core-components@0.13.9-next.2
- @backstage/core-plugin-api@1.8.1-next.1
## 0.1.9-next.1
### Patch Changes
- Updated dependencies
- @backstage/core-components@0.13.9-next.1
- @backstage/core-plugin-api@1.8.1-next.1
- @backstage/plugin-catalog-react@1.9.2-next.1
- @backstage/catalog-model@1.4.3
- @backstage/config@1.1.1
- @backstage/theme@0.5.0-next.0
## 0.1.9-next.0
### Patch Changes
- Updated dependencies
- @backstage/core-plugin-api@1.8.1-next.0
- @backstage/plugin-catalog-react@1.9.2-next.0
- @backstage/core-components@0.13.9-next.0
- @backstage/theme@0.5.0-next.0
- @backstage/catalog-model@1.4.3
- @backstage/config@1.1.1
## 0.1.7
### Patch Changes
- 6c2b872153: Add official support for React 18.
- 4aa43f62aa: Updated dependency `cross-fetch` to `^4.0.0`.
- fdb5e23602: Import `MissingAnnotationEmptyState` from `@backstage/plugin-catalog-react` to remove the cyclical dependency
- Updated dependencies
- @backstage/plugin-catalog-react@1.9.0
- @backstage/core-components@0.13.8
- @backstage/core-plugin-api@1.8.0
- @backstage/theme@0.4.4
- @backstage/catalog-model@1.4.3
- @backstage/config@1.1.1
## 0.1.7-next.2
### Patch Changes
- [#20842](https://github.com/backstage/backstage/pull/20842) [`fdb5e23602`](https://github.com/backstage/backstage/commit/fdb5e2360299c5faa30f4d4236fc548b94d37446) Thanks [@benjdlambert](https://github.com/benjdlambert)! - Import `MissingAnnotationEmptyState` from `@backstage/plugin-catalog-react` to remove the cyclical dependency
- Updated dependencies
- @backstage/core-components@0.13.8-next.2
- @backstage/plugin-catalog-react@1.9.0-next.2
## 0.1.7-next.1
### Patch Changes
- Updated dependencies
- @backstage/plugin-catalog-react@1.9.0-next.1
- @backstage/core-components@0.13.8-next.1
- @backstage/catalog-model@1.4.3
- @backstage/config@1.1.1
- @backstage/core-plugin-api@1.8.0-next.0
- @backstage/theme@0.4.4-next.0
## 0.1.7-next.0
### Patch Changes
- 6c2b872153: Add official support for React 18.
- Updated dependencies
- @backstage/core-components@0.13.7-next.0
- @backstage/plugin-catalog-react@1.9.0-next.0
- @backstage/core-plugin-api@1.8.0-next.0
- @backstage/theme@0.4.4-next.0
- @backstage/catalog-model@1.4.3
- @backstage/config@1.1.1
## 0.1.6
### Patch Changes
- 9a1fce352e: Updated dependency `@testing-library/jest-dom` to `^6.0.0`.
- Updated dependencies
- @backstage/plugin-catalog-react@1.8.5
- @backstage/core-plugin-api@1.7.0
- @backstage/core-components@0.13.6
- @backstage/catalog-model@1.4.3
- @backstage/theme@0.4.3
- @backstage/config@1.1.1
## 0.1.6-next.2
### Patch Changes
- Updated dependencies
- @backstage/core-components@0.13.6-next.2
- @backstage/core-plugin-api@1.7.0-next.1
- @backstage/catalog-model@1.4.3-next.0
- @backstage/plugin-catalog-react@1.8.5-next.2
- @backstage/theme@0.4.3-next.0
- @backstage/config@1.1.1-next.0
## 0.1.6-next.1
### Patch Changes
- Updated dependencies
- @backstage/core-components@0.13.6-next.1
- @backstage/plugin-catalog-react@1.8.5-next.1
- @backstage/core-plugin-api@1.7.0-next.0
- @backstage/config@1.1.0
- @backstage/catalog-model@1.4.2
- @backstage/theme@0.4.2
## 0.1.6-next.0
### Patch Changes
- Updated dependencies
- @backstage/plugin-catalog-react@1.8.5-next.0
- @backstage/core-plugin-api@1.7.0-next.0
- @backstage/core-components@0.13.6-next.0
- @backstage/config@1.1.0
- @backstage/catalog-model@1.4.2
- @backstage/theme@0.4.2
## 0.1.5
### Patch Changes
- 482bb5c0bbf8: Moved `@types/react` to be a regular dependency
- 406b786a2a2c: Mark package as being free of side effects, allowing more optimized Webpack builds.
- 8cec7664e146: Removed `@types/node` dependency
- Updated dependencies
- @backstage/plugin-catalog-react@1.8.4
- @backstage/core-components@0.13.5
- @backstage/config@1.1.0
- @backstage/catalog-model@1.4.2
- @backstage/core-plugin-api@1.6.0
- @backstage/theme@0.4.2
## 0.1.5-next.3
### Patch Changes
- 406b786a2a2c: Mark package as being free of side effects, allowing more optimized Webpack builds.
- Updated dependencies
- @backstage/catalog-model@1.4.2-next.2
- @backstage/config@1.1.0-next.2
- @backstage/core-components@0.13.5-next.3
- @backstage/core-plugin-api@1.6.0-next.3
- @backstage/plugin-catalog-react@1.8.4-next.3
- @backstage/theme@0.4.2-next.0
## 0.1.5-next.2
### Patch Changes
- 8cec7664e146: Removed `@types/node` dependency
- Updated dependencies
- @backstage/core-components@0.13.5-next.2
- @backstage/core-plugin-api@1.6.0-next.2
- @backstage/config@1.1.0-next.1
- @backstage/plugin-catalog-react@1.8.4-next.2
- @backstage/catalog-model@1.4.2-next.1
- @backstage/theme@0.4.1
## 0.1.5-next.1
### Patch Changes
- Updated dependencies
- @backstage/plugin-catalog-react@1.8.4-next.1
- @backstage/core-components@0.13.5-next.1
- @backstage/config@1.1.0-next.0
- @backstage/catalog-model@1.4.2-next.0
- @backstage/core-plugin-api@1.6.0-next.1
- @backstage/theme@0.4.1
## 0.1.4-next.0
### Patch Changes
- 482bb5c0bbf8: Moved `@types/react` to be a regular dependency
- Updated dependencies
- @backstage/core-plugin-api@1.6.0-next.0
- @backstage/core-components@0.13.5-next.0
- @backstage/catalog-model@1.4.1
- @backstage/config@1.0.8
- @backstage/theme@0.4.1
- @backstage/plugin-catalog-react@1.8.3-next.0
## 0.1.2
### Patch Changes
- 12a8c94eda8d: Add package repository and homepage metadata
- Updated dependencies
- @backstage/core-components@0.13.4
- @backstage/plugin-catalog-react@1.8.1
- @backstage/core-plugin-api@1.5.3
- @backstage/catalog-model@1.4.1
- @backstage/config@1.0.8
- @backstage/theme@0.4.1
## 0.1.2-next.2
### Patch Changes
- Updated dependencies
- @backstage/plugin-catalog-react@1.8.1-next.1
## 0.1.2-next.1
### Patch Changes
- 12a8c94eda8d: Add package repository and homepage metadata
- Updated dependencies
- @backstage/catalog-model@1.4.1
- @backstage/config@1.0.8
- @backstage/core-components@0.13.4-next.0
- @backstage/core-plugin-api@1.5.3
- @backstage/theme@0.4.1
- @backstage/plugin-catalog-react@1.8.1-next.0
## 0.1.2-next.0
### Patch Changes
- Updated dependencies
- @backstage/core-components@0.13.4-next.0
- @backstage/core-plugin-api@1.5.3
- @backstage/plugin-catalog-react@1.8.1-next.0
- @backstage/catalog-model@1.4.1
- @backstage/config@1.0.8
- @backstage/theme@0.4.1
## 0.1.1
### Patch Changes
- 68531cc70e1f: Break some internal circular import chains
- e6f50426333b: update some peer dependencies to silence yarn install
- Updated dependencies
- @backstage/theme@0.4.1
- @backstage/plugin-catalog-react@1.8.0
- @backstage/core-components@0.13.3
- @backstage/core-plugin-api@1.5.3
- @backstage/catalog-model@1.4.1
- @backstage/config@1.0.8
## 0.1.1-next.2
### Patch Changes
- Updated dependencies
- @backstage/plugin-catalog-react@1.8.0-next.2
- @backstage/theme@0.4.1-next.1
- @backstage/core-plugin-api@1.5.3-next.1
- @backstage/core-components@0.13.3-next.2
- @backstage/catalog-model@1.4.1-next.0
- @backstage/config@1.0.8
## 0.1.1-next.1
### Patch Changes
- Updated dependencies
- @backstage/theme@0.4.1-next.0
- @backstage/core-components@0.13.3-next.1
- @backstage/core-plugin-api@1.5.3-next.0
- @backstage/plugin-catalog-react@1.7.1-next.1
- @backstage/config@1.0.8
## 0.1.1-next.0
### Patch Changes
- 68531cc70e1f: Break some internal circular import chains
- Updated dependencies
- @backstage/core-components@0.13.3-next.0
- @backstage/catalog-model@1.4.1-next.0
- @backstage/config@1.0.8
- @backstage/core-plugin-api@1.5.2
- @backstage/theme@0.4.0
- @backstage/plugin-catalog-react@1.7.1-next.0
## 0.1.0
### Minor Changes
- 05bf04ca6863: Initialize Nomad plugin with frontend and backend. List jobs/allocations
### Patch Changes
- Updated dependencies
- @backstage/core-plugin-api@1.5.2
- @backstage/core-components@0.13.2
- @backstage/theme@0.4.0
- @backstage/plugin-catalog-react@1.7.0
- @backstage/catalog-model@1.4.0
- @backstage/config@1.0.8
-3
View File
@@ -1,3 +0,0 @@
# Deprecated
This package has been moved to the [backstage-community/plugins](https://github.com/backstage/community-plugins) repository. Migrate to using `@backstage-community/plugin-nomad` instead.
-35
View File
@@ -1,35 +0,0 @@
## API Report File for "@backstage/plugin-nomad"
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
```ts
/// <reference types="react" />
import { BackstagePlugin } from '@backstage/core-plugin-api';
import { Entity } from '@backstage/catalog-model';
import { JSX as JSX_2 } from 'react';
import { RouteRef } from '@backstage/core-plugin-api';
// @public
export const EntityNomadAllocationListTable: () => JSX_2.Element;
// @public
export const EntityNomadJobVersionListCard: () => JSX_2.Element | null;
// @public (undocumented)
export const isNomadAllocationsAvailable: (entity: Entity) => boolean;
// @public (undocumented)
export const isNomadJobIDAvailable: (entity: Entity) => boolean;
// @public (undocumented)
export const nomadPlugin: BackstagePlugin<
{
root: RouteRef<undefined>;
entityContent: RouteRef<undefined>;
},
{}
>;
// (No @packageDocumentation comment for this package)
```
-9
View File
@@ -1,9 +0,0 @@
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: backstage-plugin-nomad
title: '@backstage/plugin-nomad'
spec:
lifecycle: experimental
type: backstage-frontend-plugin
owner: maintainers
-19
View File
@@ -1,19 +0,0 @@
/*
* Copyright 2023 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 { createDevApp } from '@backstage/dev-utils';
import { nomadPlugin } from '../src/plugin';
createDevApp().registerPlugin(nomadPlugin).render();
Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

-8
View File
@@ -1,8 +0,0 @@
# Knip report
## Unused devDependencies (1)
| Name | Location | Severity |
| :--------------------- | :----------- | :------- |
| @testing-library/react | package.json | error |
-84
View File
@@ -1,84 +0,0 @@
{
"name": "@backstage/plugin-nomad",
"version": "0.1.15",
"backstage": {
"role": "frontend-plugin",
"moved": "@backstage-community/plugin-nomad"
},
"publishConfig": {
"access": "public",
"main": "dist/index.esm.js",
"types": "dist/index.d.ts"
},
"homepage": "https://backstage.io",
"repository": {
"type": "git",
"url": "https://github.com/backstage/backstage",
"directory": "plugins/nomad"
},
"license": "Apache-2.0",
"sideEffects": false,
"main": "src/index.ts",
"types": "src/index.ts",
"files": [
"dist"
],
"scripts": {
"build": "backstage-cli package build",
"clean": "backstage-cli package clean",
"lint": "backstage-cli package lint",
"prepack": "backstage-cli package prepack",
"postpack": "backstage-cli package postpack",
"start": "backstage-cli package start",
"test": "backstage-cli package test"
},
"dependencies": {
"@backstage/catalog-model": "workspace:^",
"@backstage/core-components": "workspace:^",
"@backstage/core-plugin-api": "workspace:^",
"@backstage/plugin-catalog-react": "workspace:^",
"@material-ui/core": "^4.9.13",
"@material-ui/icons": "^4.9.1",
"@types/react": "^16.13.1 || ^17.0.0 || ^18.0.0",
"luxon": "^3.3.0",
"react-use": "^17.2.4"
},
"devDependencies": {
"@backstage/cli": "workspace:^",
"@backstage/dev-utils": "workspace:^",
"@testing-library/jest-dom": "^6.0.0",
"@testing-library/react": "^15.0.0",
"cross-fetch": "^4.0.0"
},
"peerDependencies": {
"react": "^16.13.1 || ^17.0.0 || ^18.0.0",
"react-dom": "^16.13.1 || ^17.0.0 || ^18.0.0",
"react-router-dom": "6.0.0-beta.0 || ^6.3.0"
},
"configSchema": {
"$schema": "https://backstage.io/schema/config-v1",
"title": "@backstage/plugin-nomad",
"type": "object",
"properties": {
"nomad": {
"type": "object",
"properties": {
"addr": {
"type": "string",
"description": "The address of the Nomad API. See: https://developer.hashicorp.com/nomad/api-docs#addressing-and-ports",
"visibility": "frontend"
},
"token": {
"type": "string",
"description": "The token to call the Nomad API with. See: https://developer.hashicorp.com/nomad/api-docs#authentication",
"visibility": "secret"
}
},
"required": [
"addr"
]
}
}
},
"deprecated": "This package has been moved to the backstage/community-plugins repository. You should migrate to using @backstage-community/plugin-nomad instead."
}
-46
View File
@@ -1,46 +0,0 @@
/*
* 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 React from 'react';
import {
useEntity,
MissingAnnotationEmptyState,
} from '@backstage/plugin-catalog-react';
import { Route, Routes } from 'react-router-dom';
import { EntityNomadAllocationListTable } from './components/EntityNomadAllocationListTable/EntityNomadAllocationListTable';
import {
NOMAD_GROUP_ANNOTATION,
NOMAD_JOB_ID_ANNOTATION,
isNomadAllocationsAvailable,
} from './annotations';
export const EmbeddedRouter = () => {
const { entity } = useEntity();
if (!isNomadAllocationsAvailable(entity)) {
return (
<MissingAnnotationEmptyState
annotation={[NOMAD_JOB_ID_ANNOTATION, NOMAD_GROUP_ANNOTATION]}
/>
);
}
return (
<Routes>
<Route path="/" element={<EntityNomadAllocationListTable />} />
</Routes>
);
};
-35
View File
@@ -1,35 +0,0 @@
/*
* Copyright 2023 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 { Entity } from '@backstage/catalog-model';
/** @public */
export const NOMAD_NAMESPACE_ANNOTATION = 'nomad.io/namespace';
/** @public */
export const NOMAD_JOB_ID_ANNOTATION = 'nomad.io/job-id';
/** @public */
export const NOMAD_GROUP_ANNOTATION = 'nomad.io/group';
/** @public */
export const isNomadJobIDAvailable = (entity: Entity) =>
Boolean(entity.metadata.annotations?.[NOMAD_JOB_ID_ANNOTATION]);
/** @public */
export const isNomadAllocationsAvailable = (entity: Entity) =>
Boolean(entity.metadata.annotations?.[NOMAD_JOB_ID_ANNOTATION]) ||
Boolean(entity.metadata.annotations?.[NOMAD_GROUP_ANNOTATION]);
-160
View File
@@ -1,160 +0,0 @@
/*
* 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 {
DiscoveryApi,
FetchApi,
createApiRef,
} from '@backstage/core-plugin-api';
/** @public */
export const nomadApiRef = createApiRef<NomadApi>({
id: 'plugin.nomad.service',
});
/** @public */
export type NomadApi = {
/**
* listAllocations is for listing all allocations matching some part of 'filter'.
*
* See: https://developer.hashicorp.com/nomad/api-docs/allocations#list-allocations
*/
listAllocations: (
options: ListAllocationsRequest,
) => Promise<ListAllocationsResponse>;
/**
* listJobVersions is for listing all deployments matching some part of 'filter'.
*
* See: https://developer.hashicorp.com/nomad/api-docs/deployments#list-deployments
*/
listJobVersions: (
options: ListJobVersionsRequest,
) => Promise<ListJobVersionsResponse>;
};
/** @public */
export interface ListAllocationsRequest {
namespace: string;
filter: string;
}
/** @public */
export interface ListAllocationsResponse {
allocations: Allocation[];
}
/** @public */
export interface Allocation {
ClientStatus: string;
CreateTime: number;
DeploymentStatus: DeploymentStatus;
ID: string;
JobID: string;
JobVersion: string;
ModifyTime: number;
Name: string;
Namespace: string;
NodeID: string;
TaskGroup: string;
}
/** @public */
export interface DeploymentStatus {
Healthy: boolean;
Timestamp: string;
}
/** @public */
export interface ListJobVersionsRequest {
namespace: string;
jobID: string;
}
/** @public */
export interface ListJobVersionsResponse {
versions: Version[];
}
/** @public */
export interface Version {
ID: string;
SubmitTime: number;
Stable: boolean;
Version: number;
}
/** @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 class NomadHttpApi implements NomadApi {
static create(discoveryApi: DiscoveryApi, fetchApi: FetchApi) {
return new NomadHttpApi(discoveryApi, fetchApi);
}
constructor(private discoveryApi: DiscoveryApi, private fetchApi: FetchApi) {}
// TODO: pagination
async listAllocations(
options: ListAllocationsRequest,
): Promise<ListAllocationsResponse> {
const apiUrl = await this.discoveryApi.getBaseUrl('nomad');
const resp = await this.fetchApi.fetch(
`${apiUrl}/v1/allocations?namespace=${encodeURIComponent(
options.namespace,
)}&filter=${encodeURIComponent(options.filter)}`,
);
if (!resp.ok) throw await FetchError.forResponse(resp);
return {
allocations: await resp.json(),
};
}
// TODO: pagination
async listJobVersions(
options: ListJobVersionsRequest,
): Promise<ListJobVersionsResponse> {
const apiUrl = await this.discoveryApi.getBaseUrl('nomad');
const resp = await this.fetchApi.fetch(
`${apiUrl}/v1/job/${
options.jobID
}/versions?namespace=${encodeURIComponent(options.namespace)}`,
);
if (!resp.ok) throw await FetchError.forResponse(resp);
const respJson = await resp.json();
return {
versions: respJson.Versions,
};
}
}
@@ -1,203 +0,0 @@
/*
* 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 { DateTime } from 'luxon';
import {
Link,
ResponseErrorPanel,
StatusError,
StatusOK,
StatusPending,
StatusRunning,
Table,
TableColumn,
} from '@backstage/core-components';
import {
useEntity,
MissingAnnotationEmptyState,
} from '@backstage/plugin-catalog-react';
import React, { useState } from 'react';
import { Allocation, nomadApiRef } from '../../api';
import { configApiRef, useApi } from '@backstage/core-plugin-api';
import {
NOMAD_GROUP_ANNOTATION,
NOMAD_JOB_ID_ANNOTATION,
NOMAD_NAMESPACE_ANNOTATION,
isNomadAllocationsAvailable,
} from '../../annotations';
import useAsync from 'react-use/esm/useAsync';
type rowType = Allocation & { nomadAddr: string };
const columns: TableColumn<rowType>[] = [
{
title: 'ID',
field: 'ID',
render: row => (
<Link to={`${row.nomadAddr}/ui/allocations/${row.ID}`} underline="always">
{row.ID.split('-')[0]}
</Link>
),
},
{
title: 'Task Group',
field: 'TaskGroup',
render: row => (
<Link
to={`${row.nomadAddr}/ui/jobs/${row.JobID}/${row.TaskGroup}`}
underline="always"
>
{row.TaskGroup}
</Link>
),
},
{
title: 'Created',
field: 'CreateTime',
render: row =>
DateTime.fromMillis(row.CreateTime / 1000000).toLocaleString(
DateTime.DATETIME_MED_WITH_SECONDS,
),
},
{
title: 'Status',
field: 'ClientStatus',
render: row =>
({
pending: <StatusPending>pending</StatusPending>,
running: <StatusOK>running</StatusOK>,
failed: <StatusError>failed</StatusError>,
complete: <StatusRunning>complete</StatusRunning>,
}[row.ClientStatus] || <text>{row.ClientStatus}</text>),
},
{
title: 'Version',
field: 'JobVersion',
render: row => row.JobVersion,
},
{
title: 'Client',
field: 'NodeID',
render: row => (
<Link to={`${row.nomadAddr}/ui/clients/${row.NodeID}`} underline="always">
{row.ID.split('-')[0]}
</Link>
),
},
];
/**
* EntityNomadAllocationListTable is roughly based off Nomad's Allocations tab's view.
*/
export const EntityNomadAllocationListTable = () => {
// Wait on entity
const { entity } = useEntity();
// Get ref to the backend API
const [init, setInit] = useState(true);
const configApi = useApi(configApiRef);
const nomadApi = useApi(nomadApiRef);
const nomadAddr = configApi.getString('nomad.addr');
// Store results of calling API
const [allocations, setAllocations] = useState<rowType[]>([]);
const [err, setErr] = useState<Error>();
// Check that attributes are available
if (!isNomadAllocationsAvailable(entity)) {
<MissingAnnotationEmptyState
annotation={[NOMAD_JOB_ID_ANNOTATION, NOMAD_GROUP_ANNOTATION]}
/>;
}
// Get plugin attributes
const namespace =
entity.metadata.annotations?.[NOMAD_NAMESPACE_ANNOTATION] ?? 'default';
const job = entity.metadata.annotations?.[NOMAD_JOB_ID_ANNOTATION] ?? '';
const group = entity.metadata.annotations?.[NOMAD_GROUP_ANNOTATION] ?? '';
// Make filter from attributes
const filter: string[] = [];
if (job) {
filter.push(`(JobID == "${job}")`);
}
if (group) {
filter.push(`(TaskGroup matches "${group}")`);
}
// Create a query to update allocations
const query = async () => {
try {
// Make call to nomad-backend
const resp = await nomadApi.listAllocations({
namespace,
filter: filter.join(' and '),
});
// Sort results
const results = resp.allocations
.sort((a, b) => a.CreateTime - b.CreateTime)
.sort(({ ClientStatus: a }, { ClientStatus: b }) => {
if (a === 'running' || b !== 'running') {
return -1;
}
return 0;
});
setAllocations(results.map(row => ({ ...row, id: row.ID, nomadAddr })));
setErr(undefined);
} catch (e) {
setAllocations([]);
setErr(e);
}
};
// Start querying for allocations every 5s
useAsync(async () => {
if (init) {
setInit(false);
query();
}
const interval = setTimeout(() => {
query();
}, 5_000);
return () => clearTimeout(interval);
}, [allocations, entity]);
// Store a ref to a potential error
if (err) {
return <ResponseErrorPanel error={err} />;
}
return (
<Table<rowType>
title="Allocations"
options={{
search: true,
padding: 'dense',
sorting: true,
draggable: false,
paging: false,
debounceInterval: 500,
filterCellStyle: { padding: '0 16px 0 20px' },
}}
columns={columns}
data={allocations}
/>
);
};
@@ -1,154 +0,0 @@
/*
* 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 { DateTime } from 'luxon';
import {
InfoCard,
ResponseErrorPanel,
Table,
TableColumn,
} from '@backstage/core-components';
import {
useEntity,
MissingAnnotationEmptyState,
} from '@backstage/plugin-catalog-react';
import React, { useEffect, useState } from 'react';
import { Version, nomadApiRef } from '../../api';
import { configApiRef, useApi } from '@backstage/core-plugin-api';
import {
NOMAD_JOB_ID_ANNOTATION,
NOMAD_NAMESPACE_ANNOTATION,
isNomadJobIDAvailable,
} from '../../annotations';
import OpenInNewIcon from '@material-ui/icons/OpenInNew';
import Chip from '@material-ui/core/Chip';
type rowType = Version & { nomadAddr: string };
const columns: TableColumn<rowType>[] = [
{
title: 'Version',
field: 'Version',
render: row => row.Version,
},
{
title: 'Stable',
field: 'Stable',
render: row => <Chip label={`${row.Stable}`} />,
},
{
title: 'Submitted',
field: 'SubmitTime',
render: row =>
DateTime.fromMillis(row.SubmitTime / 1000000).toLocaleString(
DateTime.DATETIME_MED_WITH_SECONDS,
),
},
];
/**
* EntityNomadJobVersionListCard is roughly based on the Nomad UI's versions tab.
*/
export const EntityNomadJobVersionListCard = () => {
// Wait on entity
const { entity } = useEntity();
// Get ref to the backend API
const configApi = useApi(configApiRef);
const nomadApi = useApi(nomadApiRef);
const nomadAddr = configApi.getString('nomad.addr');
// Store results of calling API
const [init, setInit] = useState(true);
const [versions, setVersions] = useState<rowType[]>([]);
const [err, setErr] = useState<Error>();
// Get plugin attributes
const namespace =
entity.metadata.annotations?.[NOMAD_NAMESPACE_ANNOTATION] ?? 'default';
const jobID = entity.metadata.annotations?.[NOMAD_JOB_ID_ANNOTATION] ?? '';
// Start querying for allocations every 10s
useEffect(() => {
// Create a query to update allocations
const query = async () => {
try {
// Make call to nomad-backend
const resp = await nomadApi.listJobVersions({ namespace, jobID });
setVersions(
resp.versions.map(row => ({ ...row, id: row.ID, nomadAddr })),
);
setErr(undefined);
} catch (e) {
setVersions([]);
setErr(e);
}
};
if (init) {
setInit(false);
query();
}
const interval = setTimeout(() => {
query();
}, 10_000);
return () => clearTimeout(interval);
}, [init, jobID, namespace, nomadAddr, nomadApi]);
// Store a ref to a potential error
if (err) {
return <ResponseErrorPanel error={err} />;
}
if (!entity) return null;
// Check that job ID is set
if (!isNomadJobIDAvailable(entity)) {
return (
<InfoCard title="Job Versions">
<MissingAnnotationEmptyState annotation={NOMAD_JOB_ID_ANNOTATION} />
</InfoCard>
);
}
return (
<Table<rowType>
title="Job versions"
actions={[
{
icon: () => <OpenInNewIcon />,
tooltip: 'Open in Nomad UI',
isFreeAction: true,
onClick: () => window.open(`${nomadAddr}/ui/jobs/${jobID}/versions`),
},
]}
options={{
search: false,
padding: 'dense',
sorting: true,
draggable: false,
paging: false,
debounceInterval: 500,
filterCellStyle: { padding: '0 16px 0 20px' },
}}
columns={columns}
data={versions}
/>
);
};
-18
View File
@@ -1,18 +0,0 @@
/*
* Copyright 2023 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 { EntityNomadJobVersionListCard } from './EntityNomadJobVersionListCard/EntityNomadJobVersionListCard';
export { EntityNomadAllocationListTable } from './EntityNomadAllocationListTable/EntityNomadAllocationListTable';
-24
View File
@@ -1,24 +0,0 @@
/*
* Copyright 2023 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 {
nomadPlugin,
EntityNomadJobVersionListCard,
EntityNomadAllocationListTable,
} from './plugin';
export {
isNomadAllocationsAvailable,
isNomadJobIDAvailable,
} from './annotations';
-34
View File
@@ -1,34 +0,0 @@
/*
* Copyright 2023 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 {
nomadPlugin,
entityContentRouteRef,
EntityNomadContent,
} from './plugin';
describe('nomad', () => {
it('should export plugin', () => {
expect(nomadPlugin).toBeDefined();
});
it('should export Nomad entity ref', () => {
expect(entityContentRouteRef).toBeDefined();
});
it('should export Nomad entity', () => {
expect(EntityNomadContent).toBeDefined();
});
});

Some files were not shown because too many files have changed in this diff Show More