chore: remove deprecated plugins

Signed-off-by: blam <ben@blam.sh>
This commit is contained in:
blam
2024-04-19 14:35:21 +02:00
parent 3a85821e76
commit 7a33f95483
3085 changed files with 0 additions and 326283 deletions
-1
View File
@@ -1 +0,0 @@
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
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-adr-backend` instead.
-87
View File
@@ -1,87 +0,0 @@
## API Report File for "@backstage/plugin-adr-backend"
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
```ts
/// <reference types="node" />
import { AdrDocument } from '@backstage/plugin-adr-common';
import { AdrFilePathFilterFn } from '@backstage/plugin-adr-common';
import { AuthService } from '@backstage/backend-plugin-api';
import { BackendFeature } from '@backstage/backend-plugin-api';
import { CacheClient } from '@backstage/backend-common';
import { CatalogApi } from '@backstage/catalog-client';
import { Config } from '@backstage/config';
import { DocumentCollatorFactory } from '@backstage/plugin-search-common';
import { Entity } from '@backstage/catalog-model';
import express from 'express';
import { LoggerService } from '@backstage/backend-plugin-api';
import { PluginCacheManager } from '@backstage/backend-common';
import { PluginEndpointDiscovery } from '@backstage/backend-common';
import { Readable } from 'stream';
import { TokenManager } from '@backstage/backend-common';
import { UrlReader } from '@backstage/backend-common';
// @public
export type AdrCollatorFactoryOptions = {
adrFilePathFilterFn?: AdrFilePathFilterFn;
cache: PluginCacheManager;
config: Config;
catalogClient?: CatalogApi;
discovery: PluginEndpointDiscovery;
logger: LoggerService;
parser?: AdrParser;
reader: UrlReader;
tokenManager: TokenManager;
auth?: AuthService;
};
// @public
export type AdrParser = (ctx: AdrParserContext) => Promise<AdrDocument>;
// @public
export type AdrParserContext = {
entity: Entity;
content: string;
path: string;
};
// @public
const adrPlugin: () => BackendFeature;
export default adrPlugin;
// @public (undocumented)
export type AdrRouterOptions = {
reader: UrlReader;
cacheClient: CacheClient;
logger: LoggerService;
};
// @public
export const createMadrParser: (options?: MadrParserOptions) => AdrParser;
// @public (undocumented)
export function createRouter(
options: AdrRouterOptions,
): Promise<express.Router>;
// @public
export class DefaultAdrCollatorFactory implements DocumentCollatorFactory {
// (undocumented)
execute(): AsyncGenerator<AdrDocument>;
// (undocumented)
static fromConfig(
options: AdrCollatorFactoryOptions,
): DefaultAdrCollatorFactory;
// (undocumented)
getCollator(): Promise<Readable>;
// (undocumented)
readonly type: string;
}
// @public
export type MadrParserOptions = {
locationTemplate?: string;
dateFormat?: string;
};
```
-9
View File
@@ -1,9 +0,0 @@
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: backstage-plugin-adr-backend
title: '@backstage/plugin-adr-backend'
spec:
lifecycle: experimental
type: backstage-backend-plugin
owner: kuangp
-9
View File
@@ -1,9 +0,0 @@
# Knip report
## Unused dependencies (2)
| Name | Location | Severity |
| :--------- | :----------- | :------- |
| node-fetch | package.json | error |
| yn | package.json | error |
-58
View File
@@ -1,58 +0,0 @@
{
"name": "@backstage/plugin-adr-backend",
"version": "0.4.14",
"backstage": {
"role": "backend-plugin",
"moved": "@backstage-community/plugin-adr-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/adr-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/catalog-client": "workspace:^",
"@backstage/catalog-model": "workspace:^",
"@backstage/config": "workspace:^",
"@backstage/errors": "workspace:^",
"@backstage/integration": "workspace:^",
"@backstage/plugin-adr-common": "workspace:^",
"@backstage/plugin-search-common": "workspace:^",
"@types/express": "^4.17.6",
"express": "^4.17.1",
"express-promise-router": "^4.1.0",
"luxon": "^3.0.0",
"marked": "^12.0.0",
"node-fetch": "^2.6.5",
"yn": "^4.0.0"
},
"devDependencies": {
"@backstage/cli": "workspace:^",
"@types/supertest": "^2.0.8",
"supertest": "^6.1.3"
},
"deprecated": "This package has been moved to the backstage/community-plugins repository. You should migrate to using @backstage-community/plugin-adr-backend instead."
}
-25
View File
@@ -1,25 +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.
*/
/**
* Architecture Decision Records backend plugin.
*
* @packageDocumentation
*/
export * from './search';
export * from './service';
export { adrPlugin as default } from './plugin';
-48
View File
@@ -1,48 +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';
/**
* ADR backend plugin
*
* @public
*/
export const adrPlugin = createBackendPlugin({
pluginId: 'adr',
register(env) {
env.registerInit({
deps: {
logger: coreServices.logger,
reader: coreServices.urlReader,
cache: coreServices.cache,
httpRouter: coreServices.httpRouter,
},
async init({ httpRouter, logger, reader, cache }) {
httpRouter.use(
await createRouter({
logger,
reader,
cacheClient: cache,
}),
);
},
});
},
});
@@ -1,228 +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 { Readable } from 'stream';
import {
CacheClient,
createLegacyAuthAdapters,
PluginCacheManager,
PluginEndpointDiscovery,
TokenManager,
UrlReader,
} from '@backstage/backend-common';
import {
CATALOG_FILTER_EXISTS,
CatalogApi,
CatalogClient,
} from '@backstage/catalog-client';
import { stringifyEntityRef } from '@backstage/catalog-model';
import { Config } from '@backstage/config';
import { NotModifiedError, stringifyError } from '@backstage/errors';
import {
ScmIntegrationRegistry,
ScmIntegrations,
} from '@backstage/integration';
import {
AdrDocument,
AdrFilePathFilterFn,
ANNOTATION_ADR_LOCATION,
getAdrLocationUrl,
madrFilePathFilter,
} from '@backstage/plugin-adr-common';
import { DocumentCollatorFactory } from '@backstage/plugin-search-common';
import { createMadrParser } from './createMadrParser';
import { AdrParser } from './types';
import { AuthService, LoggerService } from '@backstage/backend-plugin-api';
/**
* Options to configure the AdrCollatorFactory
* @public
*/
export type AdrCollatorFactoryOptions = {
/**
* Function used to filter ADR file paths.
* Defaults to filtering paths following MADR filename format.
*/
adrFilePathFilterFn?: AdrFilePathFilterFn;
/**
* Plugin cache manager
*/
cache: PluginCacheManager;
/**
* App Config
*/
config: Config;
/**
* Catalog API client. Defaults to CatalogClient.
*/
catalogClient?: CatalogApi;
/**
* Plugin Endpoint Discovery client
*/
discovery: PluginEndpointDiscovery;
/**
* Logger
*/
logger: LoggerService;
/**
* ADR content parser. Defaults to built in MADR parser.
*/
parser?: AdrParser;
/**
* URL Reader
*/
reader: UrlReader;
/**
* Token Manager
*/
tokenManager: TokenManager;
/**
* Auth Service
*/
auth?: AuthService;
};
/**
* Default collator to index ADR documents for Backstage search.
* @public
*/
export class DefaultAdrCollatorFactory implements DocumentCollatorFactory {
public readonly type: string = 'adr';
private readonly adrFilePathFilterFn: AdrFilePathFilterFn;
private readonly cacheClient: CacheClient;
private readonly catalogClient: CatalogApi;
private readonly logger: LoggerService;
private readonly parser: AdrParser;
private readonly reader: UrlReader;
private readonly auth: AuthService;
private readonly scmIntegrations: ScmIntegrationRegistry;
private constructor(options: AdrCollatorFactoryOptions) {
this.adrFilePathFilterFn =
options.adrFilePathFilterFn ?? madrFilePathFilter;
this.cacheClient = options.cache.getClient();
this.catalogClient =
options.catalogClient ??
new CatalogClient({ discoveryApi: options.discovery });
this.logger = options.logger.child({ documentType: this.type });
this.parser = options.parser ?? createMadrParser();
this.reader = options.reader;
this.scmIntegrations = ScmIntegrations.fromConfig(options.config);
this.auth = createLegacyAuthAdapters(options).auth;
}
static fromConfig(options: AdrCollatorFactoryOptions) {
return new DefaultAdrCollatorFactory(options);
}
async getCollator() {
return Readable.from(this.execute());
}
async *execute(): AsyncGenerator<AdrDocument> {
const { token } = await this.auth.getPluginRequestToken({
onBehalfOf: await this.auth.getOwnServiceCredentials(),
targetPluginId: 'catalog',
});
const entities = (
await this.catalogClient.getEntities(
{
filter: {
[`metadata.annotations.${ANNOTATION_ADR_LOCATION}`]:
CATALOG_FILTER_EXISTS,
},
fields: [
'kind',
'metadata.annotations',
'metadata.name',
'metadata.namespace',
'metadata.title',
],
},
{ token },
)
).items;
for (const ent of entities) {
let adrsUrl: string;
try {
adrsUrl = getAdrLocationUrl(ent, this.scmIntegrations);
} catch (e: any) {
this.logger.error(
`Unable to get ADR location URL for ${stringifyEntityRef(
ent,
)}: ${stringifyError(e)}`,
);
continue;
}
const cacheItem = (await this.cacheClient.get(adrsUrl)) as {
adrFiles: {
path: string;
content: string;
}[];
etag: string;
};
let adrFiles = cacheItem?.adrFiles;
try {
const tree = await this.reader.readTree(adrsUrl, {
etag: cacheItem?.etag,
filter: filePath => this.adrFilePathFilterFn(filePath),
});
adrFiles = await Promise.all(
(
await tree.files()
).map(async f => ({
path: f.path,
content: (await f.content()).toString('utf8'),
})),
);
await this.cacheClient.set(adrsUrl, { adrFiles, etag: tree.etag });
} catch (error: any) {
// Ignore error if we're able to use cached response
if (!cacheItem || error.name !== NotModifiedError.name) {
this.logger.error(
`Unable to fetch ADRs from ${adrsUrl}: ${stringifyError(error)}`,
);
continue;
}
}
for (const { content, path } of adrFiles) {
try {
const adrDoc = await this.parser({
entity: ent,
path,
content,
});
yield adrDoc;
} catch (e: any) {
this.logger.error(
`Unable to parse ADR ${path}: ${stringifyError(e)}`,
);
}
}
this.logger.info(`Indexed ${adrFiles.length} ADRs from ${adrsUrl}`);
}
}
}
@@ -1,83 +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 { stringifyEntityRef } from '@backstage/catalog-model';
import { MADR_DATE_FORMAT } from '@backstage/plugin-adr-common';
import { AdrParser } from './types';
import { madrParser } from './madrParser';
const applyArgsToFormat = (
format: string,
args: Record<string, string>,
): string => {
let formatted = format;
for (const [key, value] of Object.entries(args)) {
formatted = formatted.replace(`:${key}`, value);
}
return formatted.toLowerCase();
};
const DEFAULT_LOCATION_TEMPLATE =
'/catalog/:namespace/:kind/:name/adrs?record=:record';
/**
*
* Options for the default MADR content parser
* @public
*/
export type MadrParserOptions = {
/**
* Location template for the route of the frontend plugin
* Defaults to '/catalog/:namespace/:kind/:name/adrs?record=:record'
*/
locationTemplate?: string;
/**
* luxon DateTime format string to parse ADR dates with.
* Defaults to 'yyyy-MM-dd'
*/
dateFormat?: string;
};
/**
* Default content parser for ADRs following the MADR template (https://adr.github.io/madr/)
* @public
*/
export const createMadrParser = (
options: MadrParserOptions = {},
): AdrParser => {
const locationTemplate =
options.locationTemplate ?? DEFAULT_LOCATION_TEMPLATE;
const dateFormat = options.dateFormat ?? MADR_DATE_FORMAT;
return async ({ entity, content, path }) => {
const madr = madrParser(content, dateFormat);
return {
title: madr.title ?? path.replace(/\.md$/, ''),
text: content,
status: madr.status,
date: madr.date,
entityRef: stringifyEntityRef(entity),
entityTitle: entity.metadata.title,
location: applyArgsToFormat(locationTemplate, {
namespace: entity.metadata.namespace || 'default',
kind: entity.kind,
name: entity.metadata.name,
record: path,
}),
};
};
};
-19
View File
@@ -1,19 +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 './DefaultAdrCollatorFactory';
export * from './createMadrParser';
export * from './types';
@@ -1,78 +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 { DateTime } from 'luxon';
import { marked, Tokens, TokensList } from 'marked';
import {
MADR_DATE_FORMAT,
parseMadrWithFrontmatter,
} from '@backstage/plugin-adr-common';
const getTitle = (tokens: TokensList): string | undefined => {
return (
tokens.find(t => t.type === 'heading' && t.depth === 1) as Tokens.Heading
)?.text;
};
const getStatusForV2Format = (tokens: TokensList): string | undefined =>
(tokens.find(t => t.type === 'list') as Tokens.List)?.items
?.find(t => /^status:/i.test(t.text))
?.text.replace(/^status:/i, '')
.trim()
.toLocaleLowerCase('en-US');
const getDateForV2Format = (tokens: TokensList): string | undefined => {
const listTokens = (tokens.find(t => t.type === 'list') as Tokens.List)
?.items;
const adrDateTime = listTokens
?.find(t => /^date:/i.test(t.text))
?.text.replace(/^date:/i, '')
.trim();
return adrDateTime;
};
const getStatus = (
tokens: TokensList,
frontMatterStatus?: string,
): string | undefined => {
return frontMatterStatus ?? getStatusForV2Format(tokens);
};
const getDate = (
tokens: TokensList,
dateFormat: string,
frontMatterDate?: string,
): string | undefined => {
const dateString = frontMatterDate ?? getDateForV2Format(tokens);
if (!dateString) {
return undefined;
}
const date = DateTime.fromFormat(dateString, dateFormat);
return date?.isValid ? date.toFormat(MADR_DATE_FORMAT) : undefined;
};
export const madrParser = (content: string, dateFormat = MADR_DATE_FORMAT) => {
const preparsed = parseMadrWithFrontmatter(content);
const tokens = marked.lexer(preparsed.content);
if (!tokens.length) {
throw new Error('ADR has no content');
}
return {
title: getTitle(tokens),
status: getStatus(tokens, preparsed.status),
date: getDate(tokens, dateFormat, preparsed.date),
};
};
-43
View File
@@ -1,43 +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 } from '@backstage/catalog-model';
import { AdrDocument } from '@backstage/plugin-adr-common';
/**
* Context passed to a AdrParser.
* @public
*/
export type AdrParserContext = {
/**
* The entity associated with the ADR.
*/
entity: Entity;
/**
* The ADR content string.
*/
content: string;
/**
* The ADR file path.
*/
path: string;
};
/**
* ADR parser function type.
* @public
*/
export type AdrParser = (ctx: AdrParserContext) => Promise<AdrDocument>;
-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 { createRouter } from './router';
export type { AdrRouterOptions } from './router';
@@ -1,231 +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 {
CacheClient,
ReadTreeResponse,
ReadTreeResponseFile,
ReadUrlResponse,
UrlReader,
} from '@backstage/backend-common';
import express from 'express';
import request from 'supertest';
import { createRouter } from './router';
import { LoggerService } from '@backstage/backend-plugin-api';
const listEndpointName = '/list';
const fileEndpointName = '/file';
const makeBufferFromString = (string: string) => async () =>
Buffer.from(string);
const testingUrlFakeFileTree: ReadTreeResponseFile[] = [
{
path: 'folder/testFile001.txt',
content: makeBufferFromString('folder/testFile001.txt content'),
},
{
path: 'testFile001.txt',
content: makeBufferFromString('testFile002.txt content'),
},
{
path: 'testFile002.txt',
content: makeBufferFromString('testFile001.txt content'),
},
];
const makeFileContent = async (fileContent: string) => {
const result: ReadUrlResponse = {
buffer: makeBufferFromString(fileContent),
};
return result;
};
const testFileOneContent = 'testFileOne content';
const testFileTwoContent = 'testFileTwo content';
const genericFileContent = 'file content';
const mockUrlReader: UrlReader = {
readUrl(url: string) {
switch (url) {
case 'testFileOne':
return makeFileContent(testFileOneContent);
case 'testFileTwo':
return makeFileContent(testFileTwoContent);
default:
return makeFileContent(genericFileContent);
}
},
readTree() {
const result: ReadTreeResponse = {
files: async () => testingUrlFakeFileTree,
archive() {
throw new Error('Function not implemented.');
},
dir() {
throw new Error('Function not implemented.');
},
etag: '',
};
const resultPromise = async () => result;
return resultPromise();
},
search() {
throw new Error('search not implemented.');
},
};
class MockCacheClient implements CacheClient {
private itemRegistry: { [key: string]: any };
constructor() {
this.itemRegistry = {};
}
async get(key: string) {
return this.itemRegistry[key];
}
async set(key: string, value: any) {
this.itemRegistry[key] = value;
}
async delete(key: string) {
delete this.itemRegistry[key];
}
withOptions = () => this;
}
describe('createRouter', () => {
let app: express.Express;
beforeEach(async () => {
jest.resetAllMocks();
const router = await createRouter({
reader: mockUrlReader,
cacheClient: new MockCacheClient(),
logger: {
error: (message: any) => message,
} as LoggerService,
});
app = express().use(router);
});
describe(`GET ${listEndpointName}`, () => {
it('returns bad request (400) when no url is provided', async () => {
const urlNotSpecifiedRequest = await request(app).get(listEndpointName);
const urlNotSpecifiedStatus = urlNotSpecifiedRequest.status;
const urlNotSpecifiedMessage = urlNotSpecifiedRequest.body.message;
const urlNotFilledRequest = await request(app).get(
`${listEndpointName}?url=`,
);
const urlNotFilledStatus = urlNotFilledRequest.status;
const urlNotFilledMessage = urlNotFilledRequest.body.message;
const expectedStatusCode = 400;
const expectedErrorMessage = 'No URL provided';
expect(urlNotSpecifiedStatus).toBe(expectedStatusCode);
expect(urlNotSpecifiedMessage).toBe(expectedErrorMessage);
expect(urlNotFilledStatus).toBe(expectedStatusCode);
expect(urlNotFilledMessage).toBe(expectedErrorMessage);
});
it('returns the correct listing when reading a url', async () => {
const result = await request(app).get(`${listEndpointName}?url=testing`);
const { status, body, error } = result;
const expectedStatusCode = 200;
const expectedBody = {
data: [
{
type: 'file',
name: 'testFile002.txt',
path: 'testFile002.txt',
},
{
type: 'file',
name: 'testFile001.txt',
path: 'testFile001.txt',
},
{
type: 'file',
name: 'testFile001.txt',
path: 'folder/testFile001.txt',
},
],
};
expect(error).toBeFalsy();
expect(status).toBe(expectedStatusCode);
expect(body).toEqual(expectedBody);
});
});
describe(`GET ${fileEndpointName}`, () => {
it('returns bad request (400) when no url is provided', async () => {
const urlNotSpecifiedRequest = await request(app).get(fileEndpointName);
const urlNotSpecifiedStatus = urlNotSpecifiedRequest.status;
const urlNotSpecifiedMessage = urlNotSpecifiedRequest.body.message;
const urlNotFilledRequest = await request(app).get(
`${fileEndpointName}?url=`,
);
const urlNotFilledStatus = urlNotFilledRequest.status;
const urlNotFilledMessage = urlNotFilledRequest.body.message;
const expectedStatusCode = 400;
const expectedErrorMessage = 'No URL provided';
expect(urlNotSpecifiedStatus).toBe(expectedStatusCode);
expect(urlNotSpecifiedMessage).toBe(expectedErrorMessage);
expect(urlNotFilledStatus).toBe(expectedStatusCode);
expect(urlNotFilledMessage).toBe(expectedErrorMessage);
});
it('returns the correct file contents when reading a url', async () => {
const fileOneResponse = await request(app).get(
`${fileEndpointName}?url=testFileOne`,
);
const fileOneStatus = fileOneResponse.status;
const fileOneBody = fileOneResponse.body;
const fileOneError = fileOneResponse.error;
const fileTwoResponse = await request(app).get(
`${fileEndpointName}?url=testFileTwo`,
);
const fileTwoStatus = fileTwoResponse.status;
const fileTwoBody = fileTwoResponse.body;
const fileTwoError = fileTwoResponse.error;
const expectedStatusCode = 200;
expect(fileOneError).toBeFalsy();
expect(fileOneStatus).toBe(expectedStatusCode);
expect(fileOneBody.data).toBe(testFileOneContent);
expect(fileTwoError).toBeFalsy();
expect(fileTwoStatus).toBe(expectedStatusCode);
expect(fileTwoBody.data).toBe(testFileTwoContent);
});
});
});
-137
View File
@@ -1,137 +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 { CacheClient, UrlReader } from '@backstage/backend-common';
import { NotModifiedError, stringifyError } from '@backstage/errors';
import express from 'express';
import Router from 'express-promise-router';
import { madrParser } from '../search/madrParser';
import { LoggerService } from '@backstage/backend-plugin-api';
/** @public */
export type AdrRouterOptions = {
reader: UrlReader;
cacheClient: CacheClient;
logger: LoggerService;
};
/** @public */
export async function createRouter(
options: AdrRouterOptions,
): Promise<express.Router> {
const { reader, cacheClient, logger } = options;
const router = Router();
router.use(express.json());
router.get('/list', async (req, res) => {
const urlToProcess = req.query.url as string;
if (!urlToProcess) {
res.statusCode = 400;
res.json({ message: 'No URL provided' });
return;
}
const cachedTree = (await cacheClient.get(urlToProcess)) as {
data: {
type: string;
name: string;
path: string;
}[];
etag: string;
};
const cachedData = cachedTree?.data;
try {
const treeGetResponse = await reader.readTree(urlToProcess, {
etag: cachedTree?.etag,
});
const files = await treeGetResponse.files();
const data = await Promise.all(
files
.map(async file => {
const fileContent = await file.content();
const adrInfo = madrParser(fileContent.toString());
return {
type: 'file',
name: file.path.substring(file.path.lastIndexOf('/') + 1),
path: file.path,
...adrInfo,
};
})
.reverse(),
);
await cacheClient.set(urlToProcess, {
data,
etag: treeGetResponse.etag,
});
res.json({ data });
} catch (error: any) {
if (cachedData && error.name === NotModifiedError.name) {
res.json({ data: cachedData });
return;
}
const message = stringifyError(error);
logger.error(`Unable to fetch ADRs from ${urlToProcess}: ${message}`);
res.statusCode = 500;
res.json({ message });
}
});
router.get('/file', async (req, res) => {
const urlToProcess = req.query.url as string;
if (!urlToProcess) {
res.statusCode = 400;
res.json({ message: 'No URL provided' });
return;
}
const cachedFileContent = (await cacheClient.get(urlToProcess)) as {
data: string;
etag: string;
};
try {
const fileGetResponse = await reader.readUrl(urlToProcess, {
etag: cachedFileContent?.etag,
});
const fileBuffer = await fileGetResponse.buffer();
const data = fileBuffer.toString();
await cacheClient.set(urlToProcess, {
data,
etag: fileGetResponse.etag,
});
res.json({ data });
} catch (error) {
if (cachedFileContent && error.name === NotModifiedError.name) {
res.json({ data: cachedFileContent.data });
return;
}
const message = stringifyError(error);
logger.error(`Unable to fetch ADRs from ${urlToProcess}: ${message}`);
res.statusCode = 500;
res.json({ message });
}
});
return router;
}
-1
View File
@@ -1 +0,0 @@
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
-636
View File
@@ -1,636 +0,0 @@
# @backstage/plugin-adr-common
## 0.2.22
### Patch Changes
- Updated dependencies
- @backstage/integration@1.10.0
- @backstage/catalog-model@1.4.5
- @backstage/plugin-search-common@1.2.11
## 0.2.22-next.0
### Patch Changes
- Updated dependencies
- @backstage/integration@1.10.0-next.0
- @backstage/catalog-model@1.4.5
- @backstage/plugin-search-common@1.2.11
## 0.2.21
### Patch Changes
- 5335634: Fixed Azure DevOps ADR file path reading
- Updated dependencies
- @backstage/integration@1.9.1
- @backstage/plugin-search-common@1.2.11
- @backstage/catalog-model@1.4.5
## 0.2.21-next.2
### Patch Changes
- Updated dependencies
- @backstage/integration@1.9.1-next.2
- @backstage/catalog-model@1.4.5-next.0
- @backstage/plugin-search-common@1.2.11-next.1
## 0.2.21-next.1
### Patch Changes
- Updated dependencies
- @backstage/integration@1.9.1-next.1
- @backstage/catalog-model@1.4.5-next.0
- @backstage/plugin-search-common@1.2.11-next.1
## 0.2.21-next.0
### Patch Changes
- 5335634: Fixed Azure DevOps ADR file path reading
- Updated dependencies
- @backstage/plugin-search-common@1.2.11-next.0
- @backstage/catalog-model@1.4.5-next.0
- @backstage/integration@1.9.1-next.0
## 0.2.20
### Patch Changes
- Updated dependencies
- @backstage/catalog-model@1.4.4
- @backstage/integration@1.9.0
- @backstage/plugin-search-common@1.2.10
## 0.2.20-next.1
### Patch Changes
- Updated dependencies
- @backstage/integration@1.9.0-next.1
- @backstage/catalog-model@1.4.4-next.0
- @backstage/plugin-search-common@1.2.10
## 0.2.20-next.0
### Patch Changes
- Updated dependencies
- @backstage/catalog-model@1.4.4-next.0
- @backstage/integration@1.9.0-next.0
- @backstage/plugin-search-common@1.2.10
## 0.2.19
### Patch Changes
- Updated dependencies
- @backstage/catalog-model@1.4.3
- @backstage/integration@1.8.0
- @backstage/plugin-search-common@1.2.10
## 0.2.18
### Patch Changes
- Updated dependencies
- @backstage/integration@1.8.0
- @backstage/catalog-model@1.4.3
- @backstage/plugin-search-common@1.2.9
## 0.2.18-next.1
### Patch Changes
- Updated dependencies
- @backstage/integration@1.8.0-next.1
- @backstage/catalog-model@1.4.3
- @backstage/plugin-search-common@1.2.8
## 0.2.18-next.0
### Patch Changes
- Updated dependencies
- @backstage/integration@1.8.0-next.0
- @backstage/catalog-model@1.4.3
- @backstage/plugin-search-common@1.2.8
## 0.2.17
### Patch Changes
- Updated dependencies
- @backstage/integration@1.7.2
- @backstage/catalog-model@1.4.3
- @backstage/plugin-search-common@1.2.8
## 0.2.17-next.0
### Patch Changes
- Updated dependencies
- @backstage/integration@1.7.2-next.0
- @backstage/catalog-model@1.4.3
- @backstage/plugin-search-common@1.2.7
## 0.2.16
### Patch Changes
- Updated dependencies
- @backstage/integration@1.7.1
- @backstage/catalog-model@1.4.3
- @backstage/plugin-search-common@1.2.7
## 0.2.16-next.1
### Patch Changes
- Updated dependencies
- @backstage/catalog-model@1.4.3-next.0
- @backstage/integration@1.7.1-next.1
- @backstage/plugin-search-common@1.2.7-next.0
## 0.2.16-next.0
### Patch Changes
- Updated dependencies
- @backstage/integration@1.7.1-next.0
- @backstage/catalog-model@1.4.2
- @backstage/plugin-search-common@1.2.6
## 0.2.15
### Patch Changes
- 406b786a2a2c: Mark package as being free of side effects, allowing more optimized Webpack builds.
- Updated dependencies
- @backstage/catalog-model@1.4.2
- @backstage/integration@1.7.0
- @backstage/plugin-search-common@1.2.6
## 0.2.15-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/integration@1.7.0-next.3
- @backstage/plugin-search-common@1.2.6-next.2
## 0.2.15-next.2
### Patch Changes
- Updated dependencies
- @backstage/integration@1.7.0-next.2
- @backstage/catalog-model@1.4.2-next.1
- @backstage/plugin-search-common@1.2.6-next.1
## 0.2.15-next.1
### Patch Changes
- Updated dependencies
- @backstage/integration@1.7.0-next.1
- @backstage/catalog-model@1.4.2-next.0
- @backstage/plugin-search-common@1.2.6-next.0
## 0.2.14-next.0
### Patch Changes
- Updated dependencies
- @backstage/integration@1.7.0-next.0
- @backstage/catalog-model@1.4.1
- @backstage/plugin-search-common@1.2.5
## 0.2.12
### Patch Changes
- Updated dependencies
- @backstage/integration@1.6.0
- @backstage/catalog-model@1.4.1
- @backstage/plugin-search-common@1.2.5
## 0.2.11
### Patch Changes
- 2b4513abb784: fixed error with date parsing.
- Updated dependencies
- @backstage/catalog-model@1.4.1
- @backstage/integration@1.5.1
- @backstage/plugin-search-common@1.2.5
## 0.2.11-next.1
### Patch Changes
- 2b4513abb784: fixed error with date parsing.
- Updated dependencies
- @backstage/catalog-model@1.4.1-next.0
- @backstage/integration@1.5.1-next.0
- @backstage/plugin-search-common@1.2.5-next.0
## 0.2.11-next.0
### Patch Changes
- Updated dependencies
- @backstage/catalog-model@1.4.1-next.0
- @backstage/integration@1.5.1-next.0
- @backstage/plugin-search-common@1.2.5-next.0
## 0.2.10
### Patch Changes
- 58524588448c: Add utility function for parsing MADR front matter
- Updated dependencies
- @backstage/integration@1.5.0
- @backstage/catalog-model@1.4.0
- @backstage/plugin-search-common@1.2.4
## 0.2.10-next.1
### Patch Changes
- Updated dependencies
- @backstage/catalog-model@1.4.0-next.1
- @backstage/integration@1.5.0-next.0
- @backstage/plugin-search-common@1.2.4-next.0
## 0.2.10-next.0
### Patch Changes
- 58524588448c: Add utility function for parsing MADR front matter
- Updated dependencies
- @backstage/integration@1.5.0-next.0
- @backstage/catalog-model@1.4.0-next.0
- @backstage/plugin-search-common@1.2.4-next.0
## 0.2.9
### Patch Changes
- Updated dependencies
- @backstage/integration@1.4.5
- @backstage/catalog-model@1.3.0
- @backstage/plugin-search-common@1.2.3
## 0.2.9-next.0
### Patch Changes
- Updated dependencies
- @backstage/integration@1.4.5-next.0
- @backstage/catalog-model@1.3.0
- @backstage/plugin-search-common@1.2.3
## 0.2.8
### Patch Changes
- 77aa3a4d47e: use path attribute to fetch files instead of name and update the UI to navigate over subdirectories
- Updated dependencies
- @backstage/catalog-model@1.3.0
- @backstage/integration@1.4.4
- @backstage/plugin-search-common@1.2.3
## 0.2.8-next.1
### Patch Changes
- 77aa3a4d47e: use path attribute to fetch files instead of name and update the UI to navigate over subdirectories
- Updated dependencies
- @backstage/catalog-model@1.3.0-next.0
- @backstage/integration@1.4.4-next.0
- @backstage/plugin-search-common@1.2.3-next.0
## 0.2.8-next.0
### Patch Changes
- Updated dependencies
- @backstage/integration@1.4.4-next.0
- @backstage/catalog-model@1.2.1
- @backstage/plugin-search-common@1.2.3-next.0
## 0.2.7
### Patch Changes
- Updated dependencies
- @backstage/catalog-model@1.2.1
- @backstage/integration@1.4.3
- @backstage/plugin-search-common@1.2.2
## 0.2.7-next.1
### Patch Changes
- Updated dependencies
- @backstage/integration@1.4.3-next.0
- @backstage/catalog-model@1.2.1-next.1
- @backstage/plugin-search-common@1.2.2-next.0
## 0.2.7-next.0
### Patch Changes
- Updated dependencies
- @backstage/catalog-model@1.2.1-next.0
- @backstage/integration@1.4.2
- @backstage/plugin-search-common@1.2.1
## 0.2.6
### Patch Changes
- Updated dependencies
- @backstage/catalog-model@1.2.0
- @backstage/integration@1.4.2
- @backstage/plugin-search-common@1.2.1
## 0.2.6-next.1
### Patch Changes
- Updated dependencies
- @backstage/catalog-model@1.2.0-next.1
- @backstage/integration@1.4.2
- @backstage/plugin-search-common@1.2.1
## 0.2.6-next.0
### Patch Changes
- Updated dependencies
- @backstage/catalog-model@1.1.6-next.0
## 0.2.5
### Patch Changes
- Updated dependencies
- @backstage/catalog-model@1.1.5
- @backstage/integration@1.4.2
- @backstage/plugin-search-common@1.2.1
## 0.2.5-next.1
### Patch Changes
- Updated dependencies
- @backstage/catalog-model@1.1.5-next.1
- @backstage/integration@1.4.2-next.0
- @backstage/plugin-search-common@1.2.1-next.0
## 0.2.5-next.0
### Patch Changes
- Updated dependencies
- @backstage/catalog-model@1.1.5-next.0
- @backstage/integration@1.4.1
- @backstage/plugin-search-common@1.2.0
## 0.2.4
### Patch Changes
- Updated dependencies
- @backstage/integration@1.4.1
- @backstage/plugin-search-common@1.2.0
- @backstage/catalog-model@1.1.4
## 0.2.4-next.3
### Patch Changes
- Updated dependencies
- @backstage/catalog-model@1.1.4-next.1
- @backstage/integration@1.4.1-next.1
- @backstage/plugin-search-common@1.2.0-next.3
## 0.2.4-next.2
### Patch Changes
- Updated dependencies
- @backstage/plugin-search-common@1.2.0-next.2
- @backstage/catalog-model@1.1.4-next.1
- @backstage/integration@1.4.1-next.1
## 0.2.4-next.1
### Patch Changes
- Updated dependencies
- @backstage/integration@1.4.1-next.1
- @backstage/catalog-model@1.1.4-next.1
- @backstage/plugin-search-common@1.1.2-next.1
## 0.2.4-next.0
### Patch Changes
- Updated dependencies
- @backstage/integration@1.4.1-next.0
- @backstage/catalog-model@1.1.4-next.0
- @backstage/plugin-search-common@1.1.2-next.0
## 0.2.3
### Patch Changes
- Updated dependencies
- @backstage/integration@1.4.0
- @backstage/catalog-model@1.1.3
- @backstage/plugin-search-common@1.1.1
## 0.2.3-next.0
### Patch Changes
- Updated dependencies
- @backstage/integration@1.4.0-next.0
- @backstage/catalog-model@1.1.3-next.0
- @backstage/plugin-search-common@1.1.1-next.0
## 0.2.2
### Patch Changes
- Updated dependencies
- @backstage/catalog-model@1.1.2
- @backstage/plugin-search-common@1.1.0
- @backstage/integration@1.3.2
## 0.2.2-next.2
### Patch Changes
- Updated dependencies
- @backstage/plugin-search-common@1.1.0-next.2
- @backstage/catalog-model@1.1.2-next.2
- @backstage/integration@1.3.2-next.2
## 0.2.2-next.1
### Patch Changes
- Updated dependencies
- @backstage/plugin-search-common@1.1.0-next.1
- @backstage/catalog-model@1.1.2-next.1
- @backstage/integration@1.3.2-next.1
## 0.2.2-next.0
### Patch Changes
- Updated dependencies
- @backstage/catalog-model@1.1.2-next.0
- @backstage/integration@1.3.2-next.0
- @backstage/plugin-search-common@1.0.2-next.0
## 0.2.1
### Patch Changes
- Updated dependencies
- @backstage/integration@1.3.1
- @backstage/catalog-model@1.1.1
- @backstage/plugin-search-common@1.0.1
## 0.2.1-next.1
### Patch Changes
- Updated dependencies
- @backstage/catalog-model@1.1.1-next.0
- @backstage/integration@1.3.1-next.2
## 0.2.1-next.0
### Patch Changes
- Updated dependencies
- @backstage/integration@1.3.1-next.0
- @backstage/plugin-search-common@1.0.1-next.0
## 0.2.0
### Minor Changes
- bfc7c50a09: Display associated entity as a chip in `AdrSearchResultListItem`
BREAKING: `AdrDocument` now includes a `entityRef` property, if you have a custom `AdrParser` you will have to supply this property in your returned documents
### Patch Changes
- Updated dependencies
- @backstage/integration@1.3.0
## 0.2.0-next.1
### Minor Changes
- bfc7c50a09: Display associated entity as a chip in `AdrSearchResultListItem`
BREAKING: `AdrDocument` now includes a `entityRef` property, if you have a custom `AdrParser` you will have to supply this property in your returned documents
## 0.1.3-next.0
### Patch Changes
- Updated dependencies
- @backstage/integration@1.3.0-next.0
## 0.1.2
### Patch Changes
- Updated dependencies
- @backstage/catalog-model@1.1.0
- @backstage/plugin-search-common@1.0.0
- @backstage/integration@1.2.2
## 0.1.2-next.1
### Patch Changes
- Updated dependencies
- @backstage/catalog-model@1.1.0-next.1
- @backstage/integration@1.2.2-next.1
- @backstage/plugin-search-common@0.3.6-next.0
## 0.1.2-next.0
### Patch Changes
- Updated dependencies
- @backstage/catalog-model@1.1.0-next.0
- @backstage/integration@1.2.2-next.0
## 0.1.1
### Patch Changes
- Updated dependencies
- @backstage/plugin-search-common@0.3.5
- @backstage/integration@1.2.1
- @backstage/catalog-model@1.0.3
## 0.1.1-next.1
### Patch Changes
- Updated dependencies
- @backstage/integration@1.2.1-next.1
- @backstage/catalog-model@1.0.3-next.0
- @backstage/plugin-search-common@0.3.5-next.0
## 0.1.1-next.0
### Patch Changes
- Updated dependencies
- @backstage/integration@1.2.1-next.0
## 0.1.0
### Minor Changes
- e73075a301: Implement ADR plugin
### Patch Changes
- Updated dependencies
- @backstage/integration@1.2.0
- @backstage/plugin-search-common@0.3.4
- @backstage/catalog-model@1.0.2
## 0.1.0-next.1
### Patch Changes
- Updated dependencies
- @backstage/plugin-search-common@0.3.4-next.0
- @backstage/catalog-model@1.0.2-next.0
- @backstage/integration@1.2.0-next.1
## 0.1.0-next.0
### Minor Changes
- e73075a301: Implement ADR plugin
-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-adr-common` instead.
-50
View File
@@ -1,50 +0,0 @@
## API Report File for "@backstage/plugin-adr-common"
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
```ts
import { Entity } from '@backstage/catalog-model';
import { IndexableDocument } from '@backstage/plugin-search-common';
import { ScmIntegrationRegistry } from '@backstage/integration';
// @public
export interface AdrDocument extends IndexableDocument {
date?: string;
entityRef: string;
entityTitle?: string;
status?: string;
}
// @public
export type AdrFilePathFilterFn = (path: string) => boolean;
// @public
export const ANNOTATION_ADR_LOCATION = 'backstage.io/adr-location';
// @public
export const getAdrLocationUrl: (
entity: Entity,
scmIntegration: ScmIntegrationRegistry,
adrFilePath?: String,
) => string;
// @public
export const isAdrAvailable: (entity: Entity) => boolean;
// @public
export const MADR_DATE_FORMAT = 'yyyy-MM-dd';
// @public
export const madrFilePathFilter: AdrFilePathFilterFn;
// @public
export interface ParsedMadr {
attributes: Record<string, unknown>;
content: string;
date?: string;
status?: string;
}
// @public
export const parseMadrWithFrontmatter: (content: string) => ParsedMadr;
```
-10
View File
@@ -1,10 +0,0 @@
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: backstage-plugin-adr-common
title: '@backstage/plugin-adr-common'
description: Common functionalities for the adr plugin
spec:
lifecycle: experimental
type: backstage-common-library
owner: kuangp
-2
View File
@@ -1,2 +0,0 @@
# Knip report
-47
View File
@@ -1,47 +0,0 @@
{
"name": "@backstage/plugin-adr-common",
"version": "0.2.22",
"description": "Common functionalities for the adr plugin",
"backstage": {
"role": "common-library",
"moved": "@backstage-community/plugin-adr-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/adr-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"
},
"dependencies": {
"@backstage/catalog-model": "workspace:^",
"@backstage/integration": "workspace:^",
"@backstage/plugin-search-common": "workspace:^",
"front-matter": "^4.0.2",
"luxon": "^3.0.0"
},
"devDependencies": {
"@backstage/cli": "workspace:^"
},
"deprecated": "This package has been moved to the backstage/community-plugins repository. You should migrate to using @backstage-community/plugin-adr-common instead."
}
-154
View File
@@ -1,154 +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.
*/
/**
* Common types and functionalities for the ADR plugin.
* @packageDocumentation
*/
import { Entity, getEntitySourceLocation } from '@backstage/catalog-model';
import { IndexableDocument } from '@backstage/plugin-search-common';
import { ScmIntegrationRegistry } from '@backstage/integration';
import frontMatter from 'front-matter';
import { DateTime } from 'luxon';
/**
* ADR plugin annotation.
* @public
*/
export const ANNOTATION_ADR_LOCATION = 'backstage.io/adr-location';
/**
* Standard luxon DateTime format string for MADR dates.
* @public
*/
export const MADR_DATE_FORMAT = 'yyyy-MM-dd';
/**
* Utility function to get the value of an entity ADR annotation.
* @public
*/
const getAdrLocationDir = (entity: Entity) =>
entity.metadata.annotations?.[ANNOTATION_ADR_LOCATION]?.trim();
/**
* Utility function to determine if the given entity has ADRs.
* @public
*/
export const isAdrAvailable = (entity: Entity) =>
Boolean(getAdrLocationDir(entity));
/**
* Utility function to extract the ADR location URL from an entity based off
* its ADR annotation and relative to the entity source location.
* @public
*/
export const getAdrLocationUrl = (
entity: Entity,
scmIntegration: ScmIntegrationRegistry,
adrFilePath?: String,
) => {
if (!isAdrAvailable(entity)) {
throw new Error(`Missing ADR annotation: ${ANNOTATION_ADR_LOCATION}`);
}
let url = getAdrLocationDir(entity)!.replace(/\/$/, '');
if (adrFilePath) {
url = `${url}/${adrFilePath}`;
}
return scmIntegration.resolveUrl({
url,
base: getEntitySourceLocation(entity).target,
});
};
/**
* File path filter function type for ADR filenames
* @public
*/
export type AdrFilePathFilterFn = (path: string) => boolean;
/**
* File path filter for MADR filename formats
* @public
*/
export const madrFilePathFilter: AdrFilePathFilterFn = (path: string) =>
/^(?:.*\/)?\d{4}-.+\.md$/.test(path);
/**
* ADR indexable document interface
* @public
*/
export interface AdrDocument extends IndexableDocument {
/**
* Ref of the entity associated with this ADR
*/
entityRef: string;
/**
* Title of the entity associated with this ADR
*/
entityTitle?: string;
/**
* ADR status label
*/
status?: string;
/**
* ADR date
*/
date?: string;
}
/**
* Parsed MADR document with front matter (if present) parsed and extracted from the main markdown content.
* @public
*/
export interface ParsedMadr {
/**
* Main body of ADR content (with any front matter removed)
*/
content: string;
/**
* ADR status
*/
status?: string;
/**
* ADR date
*/
date?: string;
/**
* All attributes parsed from front matter
*/
attributes: Record<string, unknown>;
}
/**
* Utility function to parse raw markdown content for an ADR and extract any metadata found as "front matter" at the top of the Markdown document.
* @param content - Raw markdown content which may (optionally) include front matter
* @public
*/
export const parseMadrWithFrontmatter = (content: string): ParsedMadr => {
const parsed = frontMatter<Record<string, unknown>>(content);
const status = parsed.attributes.status;
const date = parsed.attributes.date;
const luxdate = DateTime.fromJSDate(new Date(`${date}`));
const formattedDate = luxdate.toISODate();
return {
content: parsed.body,
status: status ? String(status) : undefined,
date: date ? String(formattedDate) : undefined,
attributes: parsed.attributes,
};
};
-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',
},
});
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-adr` instead.
-31
View File
@@ -1,31 +0,0 @@
## API Report File for "@backstage/plugin-adr"
> 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 { ExtensionDefinition } from '@backstage/frontend-plugin-api';
import { TranslationRef } from '@backstage/core-plugin-api/alpha';
// @alpha (undocumented)
export const adrSearchResultListItemExtension: ExtensionDefinition<{
lineClamp: number;
noTrack: boolean;
}>;
// @alpha (undocumented)
export const adrTranslationRef: TranslationRef<
'adr',
{
readonly contentHeaderTitle: 'Architecture Decision Records';
readonly failedToFetch: 'Failed to fetch ADRs';
readonly notFound: 'No ADRs found';
}
>;
// @alpha (undocumented)
const _default: BackstagePlugin<{}, {}>;
export default _default;
// (No @packageDocumentation comment for this package)
```
-117
View File
@@ -1,117 +0,0 @@
## API Report File for "@backstage/plugin-adr"
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
```ts
/// <reference types="react" />
import { AdrDocument } from '@backstage/plugin-adr-common';
import { AdrFilePathFilterFn } from '@backstage/plugin-adr-common';
import { ApiRef } from '@backstage/core-plugin-api';
import { BackstagePlugin } from '@backstage/core-plugin-api';
import { DiscoveryApi } from '@backstage/core-plugin-api';
import { FetchApi } from '@backstage/core-plugin-api';
import { isAdrAvailable } from '@backstage/plugin-adr-common';
import { JSX as JSX_2 } from 'react';
import { default as React_2 } from 'react';
import { ReactNode } from 'react';
import { ResultHighlight } from '@backstage/plugin-search-common';
import { RouteRef } from '@backstage/core-plugin-api';
// @public
export interface AdrApi {
listAdrs(url: string): Promise<AdrListResult>;
readAdr(url: string): Promise<AdrReadResult>;
}
// @public
export const adrApiRef: ApiRef<AdrApi>;
// @public
export class AdrClient implements AdrApi {
constructor(options: AdrClientOptions);
// (undocumented)
listAdrs(url: string): Promise<AdrListResult>;
// (undocumented)
readAdr(url: string): Promise<AdrReadResult>;
}
// @public
export interface AdrClientOptions {
// (undocumented)
discoveryApi: DiscoveryApi;
// (undocumented)
fetchApi: FetchApi;
}
// @public
export type AdrContentDecorator = (adrInfo: {
baseUrl: string;
content: string;
}) => {
content: string;
};
// @public
export type AdrFileInfo = {
type: string;
path: string;
name: string;
title?: string;
status?: string;
date?: string;
};
// @public
export type AdrListResult = {
data: AdrFileInfo[];
};
// @public
export const adrPlugin: BackstagePlugin<
{
root: RouteRef<undefined>;
},
{}
>;
// @public
export const AdrReader: {
(props: {
adr: string;
decorators?: AdrContentDecorator[];
}): React_2.JSX.Element;
decorators: Readonly<{
createRewriteRelativeLinksDecorator(): AdrContentDecorator;
createRewriteRelativeEmbedsDecorator(): AdrContentDecorator;
createFrontMatterFormatterDecorator(): AdrContentDecorator;
}>;
};
// @public
export type AdrReadResult = {
data: string;
};
// @public
export const AdrSearchResultListItem: (
props: AdrSearchResultListItemProps,
) => JSX.Element | null;
// @public (undocumented)
export type AdrSearchResultListItemProps = {
lineClamp?: number;
highlight?: ResultHighlight;
icon?: ReactNode;
rank?: number;
result?: AdrDocument;
};
// @public
export const EntityAdrContent: (props: {
contentDecorators?: AdrContentDecorator[] | undefined;
filePathFilterFn?: AdrFilePathFilterFn | undefined;
}) => JSX_2.Element;
export { isAdrAvailable };
```
-9
View File
@@ -1,9 +0,0 @@
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: backstage-plugin-adr
title: '@backstage/plugin-adr'
spec:
lifecycle: experimental
type: backstage-frontend-plugin
owner: kuangp
Binary file not shown.

Before

Width:  |  Height:  |  Size: 756 KiB

@@ -1,13 +0,0 @@
---
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: component-with-adrs
title: Component With ADRs
description: A Service with ADRs registerd
annotations:
backstage.io/adr-location: https://github.com/adr/madr/tree/develop/docs/decisions
spec:
type: service
lifecycle: experimental
owner: user:guest
-9
View File
@@ -1,9 +0,0 @@
# Knip report
## Unused devDependencies (2)
| Name | Location | Severity |
| :--------------------- | :----------- | :------- |
| @testing-library/react | package.json | error |
| @testing-library/dom | package.json | error |
-76
View File
@@ -1,76 +0,0 @@
{
"name": "@backstage/plugin-adr",
"version": "0.6.17",
"backstage": {
"role": "frontend-plugin",
"moved": "@backstage-community/plugin-adr"
},
"publishConfig": {
"access": "public"
},
"homepage": "https://backstage.io",
"repository": {
"type": "git",
"url": "https://github.com/backstage/backstage",
"directory": "plugins/adr"
},
"license": "Apache-2.0",
"sideEffects": false,
"exports": {
".": "./src/index.ts",
"./alpha": "./src/alpha.tsx",
"./package.json": "./package.json"
},
"main": "src/index.ts",
"types": "src/index.ts",
"typesVersions": {
"*": {
"alpha": [
"src/alpha.tsx"
],
"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-components": "workspace:^",
"@backstage/core-plugin-api": "workspace:^",
"@backstage/frontend-plugin-api": "workspace:^",
"@backstage/integration-react": "workspace:^",
"@backstage/plugin-adr-common": "workspace:^",
"@backstage/plugin-catalog-react": "workspace:^",
"@backstage/plugin-search-common": "workspace:^",
"@backstage/plugin-search-react": "workspace:^",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
"@types/react": "^16.13.1 || ^17.0.0 || ^18.0.0",
"lodash": "^4.17.21",
"react-use": "^17.2.4"
},
"devDependencies": {
"@backstage/cli": "workspace:^",
"@testing-library/dom": "^10.0.0",
"@testing-library/jest-dom": "^6.0.0",
"@testing-library/react": "^15.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-adr instead."
}
-57
View File
@@ -1,57 +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 {
createPlugin,
createSchemaFromZod,
} from '@backstage/frontend-plugin-api';
import { createSearchResultListItemExtension } from '@backstage/plugin-search-react/alpha';
import { AdrDocument } from '@backstage/plugin-adr-common';
export * from './translations';
function isAdrDocument(result: any): result is AdrDocument {
return result.entityRef;
}
/** @alpha */
export const adrSearchResultListItemExtension =
createSearchResultListItemExtension({
configSchema: createSchemaFromZod(z =>
z.object({
// TODO: Define how the icon can be configurable
noTrack: z.boolean().default(false),
lineClamp: z.number().default(5),
}),
),
predicate: result => result.type === 'adr',
component: async ({ config }) => {
const { AdrSearchResultListItem } = await import(
'./search/AdrSearchResultListItem'
);
return ({ result, ...rest }) =>
isAdrDocument(result) ? (
<AdrSearchResultListItem {...rest} {...config} result={result} />
) : null;
},
});
/** @alpha */
export default createPlugin({
id: 'adr',
extensions: [adrSearchResultListItemExtension],
});
-69
View File
@@ -1,69 +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 { AdrApi, AdrListResult, AdrReadResult } from './types';
/**
* Options for creating an AdrClient.
*
* @public
*/
export interface AdrClientOptions {
discoveryApi: DiscoveryApi;
fetchApi: FetchApi;
}
const readEndpoint = 'file';
const listEndpoint = 'list';
/**
* An implementation of the AdrApi that communicates with the ADR backend plugin.
*
* @public
*/
export class AdrClient implements AdrApi {
private readonly discoveryApi: DiscoveryApi;
private readonly fetchApi: FetchApi;
constructor(options: AdrClientOptions) {
this.discoveryApi = options.discoveryApi;
this.fetchApi = options.fetchApi;
}
private async fetchAdrApi<T>(endpoint: string, fileUrl: string): Promise<T> {
const baseUrl = await this.discoveryApi.getBaseUrl('adr');
const targetUrl = `${baseUrl}/${endpoint}?url=${encodeURIComponent(
fileUrl,
)}`;
const result = await this.fetchApi.fetch(targetUrl);
const data = await result.json();
if (!result.ok) {
throw new Error(`${data.message}`);
}
return data;
}
async listAdrs(url: string): Promise<AdrListResult> {
return this.fetchAdrApi<AdrListResult>(listEndpoint, url);
}
async readAdr(url: string): Promise<AdrReadResult> {
return this.fetchAdrApi<AdrReadResult>(readEndpoint, url);
}
}
-25
View File
@@ -1,25 +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 { AdrClient } from './AdrClient';
export type { AdrClientOptions } from './AdrClient';
export { adrApiRef } from './types';
export type {
AdrApi,
AdrFileInfo,
AdrListResult,
AdrReadResult,
} from './types';
-83
View File
@@ -1,83 +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';
/**
* Contains information about an ADR file.
*
* @public
*/
export type AdrFileInfo = {
/** The file type. */
type: string;
/** The relative path of the ADR file. */
path: string;
/** The name of the ADR file. */
name: string;
/** The title of the ADR. */
title?: string;
/** The status of the ADR. */
status?: string;
/** The date of the ADR. */
date?: string;
};
/**
* The result of listing ADRs.
*
* @public
*/
export type AdrListResult = {
data: AdrFileInfo[];
};
/**
* The result of reading an ADR.
*
* @public
*/
export type AdrReadResult = {
/** The contents of the read ADR file. */
data: string;
};
/**
* The API used by the adr plugin to list and read ADRs.
*
* @public
*/
export interface AdrApi {
/** Lists the ADRs at the provided url. */
listAdrs(url: string): Promise<AdrListResult>;
/** Reads the contents of the ADR at the provided url. */
readAdr(url: string): Promise<AdrReadResult>;
}
/**
* ApiRef for the AdrApi.
*
* @public
*/
export const adrApiRef = createApiRef<AdrApi>({
id: 'plugin.adr.api',
});
@@ -1,87 +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, { useMemo } from 'react';
import {
InfoCard,
MarkdownContent,
Progress,
WarningPanel,
} from '@backstage/core-components';
import { useApi } from '@backstage/core-plugin-api';
import { scmIntegrationsApiRef } from '@backstage/integration-react';
import { getAdrLocationUrl } from '@backstage/plugin-adr-common';
import { useEntity } from '@backstage/plugin-catalog-react';
import { adrDecoratorFactories } from './decorators';
import { AdrContentDecorator } from './types';
import { adrApiRef } from '../../api';
import useAsync from 'react-use/esm/useAsync';
/**
* Component to fetch and render an ADR.
*
* @public
*/
export const AdrReader = (props: {
adr: string;
decorators?: AdrContentDecorator[];
}) => {
const { adr, decorators } = props;
const { entity } = useEntity();
const scmIntegrations = useApi(scmIntegrationsApiRef);
const adrApi = useApi(adrApiRef);
const adrLocationUrl = getAdrLocationUrl(entity, scmIntegrations);
const adrFileLocationUrl = getAdrLocationUrl(entity, scmIntegrations, adr);
const { value, loading, error } = useAsync(
async () => adrApi.readAdr(adrFileLocationUrl),
[adrFileLocationUrl],
);
const adrContent = useMemo(() => {
if (!value?.data) {
return '';
}
const adrDecorators = decorators ?? [
adrDecoratorFactories.createRewriteRelativeLinksDecorator(),
adrDecoratorFactories.createRewriteRelativeEmbedsDecorator(),
adrDecoratorFactories.createFrontMatterFormatterDecorator(),
];
return adrDecorators.reduce(
(content, decorator) =>
decorator({ baseUrl: adrLocationUrl, content }).content,
value.data,
);
}, [adrLocationUrl, decorators, value]);
return (
<InfoCard>
{loading && <Progress />}
{!loading && error && (
<WarningPanel title="Failed to fetch ADR" message={error?.message} />
)}
{!loading && !error && value?.data && (
<MarkdownContent content={adrContent} linkTarget="_blank" />
)}
</InfoCard>
);
};
AdrReader.decorators = adrDecoratorFactories;
@@ -1,67 +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 { parseMadrWithFrontmatter } from '@backstage/plugin-adr-common';
import { AdrContentDecorator } from './types';
/**
*
* Factory for creating default ADR content decorators. The adrDecoratorFactories
* symbol is not directly exported, but through the AdrReader.decorators field.
* @public
*/
export const adrDecoratorFactories = Object.freeze({
/**
* Rewrites relative Markdown links as absolute links.
*/
createRewriteRelativeLinksDecorator(): AdrContentDecorator {
return ({ baseUrl, content }) => ({
content: content.replace(
/\[([^\[\]]*)\]\((?!https?:\/\/)(.*?)(\.md)\)/gim,
`[$1](${baseUrl}/$2$3)`,
),
});
},
/**
* Rewrites relative Markdown embeds using absolute URLs.
*/
createRewriteRelativeEmbedsDecorator(): AdrContentDecorator {
return ({ baseUrl, content }) => ({
content: content.replace(
/!\[([^\[\]]*)\]\((?!https?:\/\/)(.*?)(\.png|\.jpg|\.jpeg|\.gif|\.webp)(.*)\)/gim,
`![$1](${baseUrl}/$2$3$4)`,
),
});
},
/**
* Formats YAML front-matter into a table format (if any exists in the markdown document)
*/
createFrontMatterFormatterDecorator(): AdrContentDecorator {
return ({ content }) => {
const parsedFrontmatter = parseMadrWithFrontmatter(content);
let table = '';
const attrs = parsedFrontmatter.attributes;
if (Object.keys(attrs).length > 0) {
const stripNewLines = (val: unknown) =>
String(val).replaceAll('\n', '<br/>');
const row = (vals: string[]) => `|${vals.join('|')}|\n`;
table = `${row(Object.keys(attrs))}`;
table += `${row(Object.keys(attrs).map(() => '---'))}`;
table += `${row(Object.values(attrs).map(stripNewLines))}\n\n`;
}
return { content: table + parsedFrontmatter.content };
};
},
});
@@ -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 './AdrReader';
export * from './types';
@@ -1,25 +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.
*/
/**
* ADR content decorator function type. Decorators are responsible for
* performing any necessary transformations on the ADR content before rendering.
* @public
*/
export type AdrContentDecorator = (adrInfo: {
baseUrl: string;
content: string;
}) => { content: string };
@@ -1,261 +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, { useEffect, useState } from 'react';
import { Link, useSearchParams } from 'react-router-dom';
import useAsync from 'react-use/esm/useAsync';
import groupBy from 'lodash/groupBy';
import {
Content,
ContentHeader,
InfoCard,
Progress,
SupportButton,
WarningPanel,
} from '@backstage/core-components';
import { configApiRef, useApi, useRouteRef } from '@backstage/core-plugin-api';
import { scmIntegrationsApiRef } from '@backstage/integration-react';
import {
AdrFilePathFilterFn,
ANNOTATION_ADR_LOCATION,
getAdrLocationUrl,
isAdrAvailable,
madrFilePathFilter,
} from '@backstage/plugin-adr-common';
import {
useEntity,
MissingAnnotationEmptyState,
} from '@backstage/plugin-catalog-react';
import Box from '@material-ui/core/Box';
import Chip from '@material-ui/core/Chip';
import Collapse from '@material-ui/core/Collapse';
import Grid from '@material-ui/core/Grid';
import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem';
import ListItemIcon from '@material-ui/core/ListItemIcon';
import ListItemText from '@material-ui/core/ListItemText';
import Typography from '@material-ui/core/Typography';
import { makeStyles, Theme } from '@material-ui/core/styles';
import ExpandLess from '@material-ui/icons/ExpandLess';
import ExpandMore from '@material-ui/icons/ExpandMore';
import FolderIcon from '@material-ui/icons/Folder';
import { adrApiRef, AdrFileInfo } from '../../api';
import { rootRouteRef } from '../../routes';
import { AdrContentDecorator, AdrReader } from '../AdrReader';
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
import { adrTranslationRef } from '../../translations';
const useStyles = makeStyles((theme: Theme) => ({
adrMenu: {
backgroundColor: theme.palette.background.paper,
},
adrContainerTitle: {
color: theme.palette.grey[700],
marginBottom: theme.spacing(1),
},
adrBox: {
display: 'flex',
justifyContent: 'space-between',
marginTop: '10px',
},
}));
const AdrListContainer = (props: {
adrs: AdrFileInfo[];
selectedAdr: string;
title: string;
}) => {
const { adrs, selectedAdr, title } = props;
const classes = useStyles();
const rootLink = useRouteRef(rootRouteRef);
const [open, setOpen] = React.useState(true);
const getChipColor = (status: string) => {
switch (status) {
case 'accepted':
return 'primary';
case 'rejected':
case 'deprecated':
return 'secondary';
default:
return 'default';
}
};
const handleClick = () => {
setOpen(!open);
};
return (
<>
{title && (
<ListItem
button
className={classes.adrContainerTitle}
onClick={handleClick}
>
<ListItemIcon>
<FolderIcon />
</ListItemIcon>
<ListItemText primary={title} />
{open ? <ExpandLess /> : <ExpandMore />}
</ListItem>
)}
<Collapse in={open} timeout="auto" unmountOnExit>
<List dense>
{adrs.map((adr, idx) => (
<ListItem
button
component={Link}
key={idx}
selected={selectedAdr === adr.path}
to={`${rootLink()}?record=${adr.path}`}
>
<ListItemText
primary={adr.title ?? adr?.name.replace(/\.md$/, '')}
primaryTypographyProps={{
style: { whiteSpace: 'normal' },
}}
secondary={
<Box className={classes.adrBox}>
{adr.date}
{adr.status && (
<Chip
color={getChipColor(adr.status)}
label={adr.status}
size="small"
variant="outlined"
/>
)}
</Box>
}
/>
</ListItem>
))}
</List>
</Collapse>
</>
);
};
/**
* Component for browsing ADRs on an entity page.
* @public
*/
export const EntityAdrContent = (props: {
contentDecorators?: AdrContentDecorator[];
filePathFilterFn?: AdrFilePathFilterFn;
}) => {
const { contentDecorators, filePathFilterFn } = props;
const classes = useStyles();
const { entity } = useEntity();
const [adrList, setAdrList] = useState<AdrFileInfo[]>([]);
const [searchParams, setSearchParams] = useSearchParams();
const scmIntegrations = useApi(scmIntegrationsApiRef);
const adrApi = useApi(adrApiRef);
const entityHasAdrs = isAdrAvailable(entity);
const { t } = useTranslationRef(adrTranslationRef);
const config = useApi(configApiRef);
const appSupportConfigured = config?.getOptionalConfig('app.support');
const { value, loading, error } = useAsync(async () => {
const url = getAdrLocationUrl(entity, scmIntegrations);
return adrApi.listAdrs(url);
}, [entity, scmIntegrations]);
const selectedAdr =
adrList.find(adr => adr.path === searchParams.get('record'))?.path ?? '';
const adrSubDirectoryFunc = (adr: AdrFileInfo) => {
return adr.path.split('/').slice(0, -1).join('/');
};
useEffect(() => {
if (adrList.length && !selectedAdr) {
searchParams.set('record', adrList[0].path);
setSearchParams(searchParams, { replace: true });
}
});
useEffect(() => {
if (!value?.data) {
return;
}
const adrs: AdrFileInfo[] = value.data.filter(
(item: AdrFileInfo) =>
item.type === 'file' &&
(filePathFilterFn
? filePathFilterFn(item.path)
: madrFilePathFilter(item.path)),
);
setAdrList(adrs);
}, [filePathFilterFn, value]);
const adrListGrouped = Object.entries(
groupBy(adrList, adrSubDirectoryFunc),
).sort();
return (
<Content>
<ContentHeader title={t('contentHeaderTitle')}>
{appSupportConfigured && <SupportButton />}
</ContentHeader>
{!entityHasAdrs && (
<MissingAnnotationEmptyState annotation={ANNOTATION_ADR_LOCATION} />
)}
{loading && <Progress />}
{entityHasAdrs && !loading && error && (
<WarningPanel title={t('failedToFetch')} message={error?.message} />
)}
{entityHasAdrs &&
!loading &&
!error &&
(adrList.length ? (
<Grid container direction="row">
<Grid item xs={3}>
<InfoCard>
<List className={classes.adrMenu} dense>
{adrListGrouped.map(([title, adrs], idx) => (
<AdrListContainer
adrs={adrs}
key={idx}
selectedAdr={selectedAdr}
title={title}
/>
))}
</List>
</InfoCard>
</Grid>
<Grid item xs={9}>
<AdrReader adr={selectedAdr} decorators={contentDecorators} />
</Grid>
</Grid>
) : (
<Typography>{t('notFound')}</Typography>
))}
</Content>
);
};
@@ -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 { EntityAdrContent } from './EntityAdrContent';
-34
View File
@@ -1,34 +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.
*/
/**
* Architecture Decision Records frontend plugin.
*
* @packageDocumentation
*/
export { adrApiRef, AdrClient } from './api';
export type {
AdrApi,
AdrClientOptions,
AdrFileInfo,
AdrListResult,
AdrReadResult,
} from './api';
export { isAdrAvailable } from '@backstage/plugin-adr-common';
export * from './components/AdrReader';
export { adrPlugin, AdrSearchResultListItem, EntityAdrContent } from './plugin';
export type { AdrSearchResultListItemProps } from './search';
-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 { adrPlugin } from './plugin';
describe('adr', () => {
it('should export plugin', () => {
expect(adrPlugin).toBeDefined();
});
});
-81
View File
@@ -1,81 +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 { adrApiRef, AdrClient } from './api';
import {
createApiFactory,
createPlugin,
createRoutableExtension,
discoveryApiRef,
fetchApiRef,
} from '@backstage/core-plugin-api';
import { createSearchResultListItemExtension } from '@backstage/plugin-search-react';
import { rootRouteRef } from './routes';
import { AdrSearchResultListItemProps } from './search';
/**
* The Backstage plugin that holds ADR specific components
* @public
*/
export const adrPlugin = createPlugin({
id: 'adr',
apis: [
createApiFactory({
api: adrApiRef,
deps: {
discoveryApi: discoveryApiRef,
fetchApi: fetchApiRef,
},
factory({ discoveryApi, fetchApi }) {
return new AdrClient({ discoveryApi, fetchApi });
},
}),
],
routes: {
root: rootRouteRef,
},
});
/**
* An extension for browsing ADRs on an entity page.
* @public
*/
export const EntityAdrContent = adrPlugin.provide(
createRoutableExtension({
name: 'EntityAdrContent',
component: () =>
import('./components/EntityAdrContent').then(m => m.EntityAdrContent),
mountPoint: rootRouteRef,
}),
);
/**
* React extension used to render results on Search page or modal
*
* @public
*/
export const AdrSearchResultListItem: (
props: AdrSearchResultListItemProps,
) => JSX.Element | null = adrPlugin.provide(
createSearchResultListItemExtension({
name: 'AdrSearchResultListItem',
component: () =>
import('./search/AdrSearchResultListItem').then(
m => m.AdrSearchResultListItem,
),
predicate: result => result.type === 'adr',
}),
);
-24
View File
@@ -1,24 +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 { createRouteRef } from '@backstage/core-plugin-api';
/**
* Root route ref for the ADR plugin
* @public
*/
export const rootRouteRef = createRouteRef({
id: 'adr',
});
@@ -1,126 +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, { ReactNode } from 'react';
import Box from '@material-ui/core/Box';
import Chip from '@material-ui/core/Chip';
import ListItemIcon from '@material-ui/core/ListItemIcon';
import ListItemText from '@material-ui/core/ListItemText';
import { makeStyles } from '@material-ui/core/styles';
import Typography from '@material-ui/core/Typography';
import { parseEntityRef } from '@backstage/catalog-model';
import { Link } from '@backstage/core-components';
import { AdrDocument } from '@backstage/plugin-adr-common';
import { humanizeEntityRef } from '@backstage/plugin-catalog-react';
import { ResultHighlight } from '@backstage/plugin-search-common';
import { HighlightedSearchResultText } from '@backstage/plugin-search-react';
const useStyles = makeStyles({
item: {
display: 'flex',
},
flexContainer: {
flexWrap: 'wrap',
},
itemText: {
width: '100%',
wordBreak: 'break-all',
marginBottom: '1rem',
},
});
/**
* @public
*/
export type AdrSearchResultListItemProps = {
lineClamp?: number;
highlight?: ResultHighlight;
icon?: ReactNode;
rank?: number;
result?: AdrDocument;
};
/**
* A component to display an ADR search result.
* @public
*/
export function AdrSearchResultListItem(props: AdrSearchResultListItemProps) {
const { lineClamp = 5, highlight, icon, result } = props;
const classes = useStyles();
if (!result) return null;
return (
<div className={classes.item}>
{icon && <ListItemIcon>{icon}</ListItemIcon>}
<div className={classes.flexContainer}>
<ListItemText
className={classes.itemText}
primaryTypographyProps={{ variant: 'h6' }}
primary={
<Link noTrack to={result.location}>
{highlight?.fields.title ? (
<HighlightedSearchResultText
text={highlight?.fields.title || ''}
preTag={highlight?.preTag || ''}
postTag={highlight?.postTag || ''}
/>
) : (
result.title
)}
</Link>
}
secondary={
<Typography
component="span"
style={{
display: '-webkit-box',
WebkitBoxOrient: 'vertical',
WebkitLineClamp: lineClamp,
overflow: 'hidden',
}}
color="textSecondary"
variant="body2"
>
{highlight?.fields.text ? (
<HighlightedSearchResultText
text={highlight.fields.text}
preTag={highlight.preTag}
postTag={highlight.postTag}
/>
) : (
result.text
)}
</Typography>
}
/>
<Box>
<Chip
label={`Entity: ${
result.entityTitle ??
humanizeEntityRef(parseEntityRef(result.entityRef))
}`}
size="small"
/>
{result.status && (
<Chip label={`Status: ${result.status}`} size="small" />
)}
{result.date && <Chip label={`Date: ${result.date}`} size="small" />}
</Box>
</div>
</div>
);
}
-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 type { AdrSearchResultListItemProps } from './AdrSearchResultListItem';
-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.
*/
import '@testing-library/jest-dom';
-26
View File
@@ -1,26 +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 { createTranslationRef } from '@backstage/core-plugin-api/alpha';
/** @alpha */
export const adrTranslationRef = createTranslationRef({
id: 'adr',
messages: {
contentHeaderTitle: 'Architecture Decision Records',
failedToFetch: 'Failed to fetch ADRs',
notFound: 'No ADRs found',
},
});
-1
View File
@@ -1 +0,0 @@
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
-914
View File
@@ -1,914 +0,0 @@
# @backstage/plugin-airbrake-backend
## 0.3.14
### Patch Changes
- d5a1fe1: Replaced winston logger with `LoggerService`
- e5a2ccc: Updated dependency `@types/http-proxy-middleware` to `^1.0.0`.
- Updated dependencies
- @backstage/backend-common@0.21.7
- @backstage/backend-plugin-api@0.6.17
- @backstage/config@1.2.0
## 0.3.14-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
## 0.3.14-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
## 0.3.13
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.21.6
- @backstage/backend-plugin-api@0.6.16
- @backstage/config@1.2.0
## 0.3.12
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.21.5
- @backstage/backend-plugin-api@0.6.15
- @backstage/config@1.2.0
## 0.3.11
### Patch Changes
- 4467036: Allow unauthenticated access to health check endpoint.
- Updated dependencies
- @backstage/backend-common@0.21.4
- @backstage/config@1.2.0
- @backstage/backend-plugin-api@0.6.14
## 0.3.11-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
## 0.3.11-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
## 0.3.10-next.0
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.21.3-next.0
- @backstage/backend-plugin-api@0.6.13-next.0
- @backstage/config@1.1.2-next.0
## 0.3.7
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.21.0
- @backstage/backend-plugin-api@0.6.10
- @backstage/config@1.1.1
## 0.3.7-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
## 0.3.7-next.2
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.21.0-next.2
- @backstage/backend-plugin-api@0.6.10-next.2
- @backstage/config@1.1.1
## 0.3.7-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
## 0.3.7-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
## 0.3.6
### 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
## 0.3.6-next.2
### Patch Changes
- Updated dependencies
- @backstage/backend-plugin-api@0.6.9-next.2
- @backstage/backend-common@0.20.1-next.2
## 0.3.6-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
## 0.3.6-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
## 0.3.5
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.20.0
- @backstage/backend-plugin-api@0.6.8
- @backstage/config@1.1.1
## 0.3.5-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
## 0.3.5-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
## 0.3.5-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
## 0.3.5-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
## 0.3.4
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.19.9
- @backstage/backend-plugin-api@0.6.7
- @backstage/config@1.1.1
## 0.3.4-next.2
### Patch Changes
- Updated dependencies
- @backstage/backend-plugin-api@0.6.7-next.2
- @backstage/backend-common@0.19.9-next.2
## 0.3.4-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
## 0.3.4-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
## 0.3.3
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.19.8
- @backstage/backend-plugin-api@0.6.6
- @backstage/config@1.1.1
## 0.3.3-next.2
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.19.8-next.2
- @backstage/backend-plugin-api@0.6.6-next.2
- @backstage/config@1.1.1-next.0
## 0.3.2-next.1
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.19.7-next.1
- @backstage/backend-plugin-api@0.6.5-next.1
- @backstage/config@1.1.0
## 0.3.2-next.0
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.19.7-next.0
- @backstage/config@1.1.0
- @backstage/backend-plugin-api@0.6.5-next.0
## 0.3.0
### Minor Changes
- 71114ac50e02: **BREAKING**: The export for the new backend system has been moved to be the `default` export.
For example, if you are currently importing the plugin using the following pattern:
```ts
import { examplePlugin } from '@backstage/plugin-example-backend';
backend.add(examplePlugin);
```
It should be migrated to this:
```ts
backend.add(import('@backstage/plugin-example-backend'));
```
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.19.5
- @backstage/config@1.1.0
- @backstage/backend-plugin-api@0.6.3
## 0.3.0-next.3
### Minor Changes
- 71114ac50e02: **BREAKING**: The export for the new backend system has been moved to be the `default` export.
For example, if you are currently importing the plugin using the following pattern:
```ts
import { examplePlugin } from '@backstage/plugin-example-backend';
backend.add(examplePlugin);
```
It should be migrated to this:
```ts
backend.add(import('@backstage/plugin-example-backend'));
```
### Patch Changes
- Updated dependencies
- @backstage/config@1.1.0-next.2
- @backstage/backend-plugin-api@0.6.3-next.3
- @backstage/backend-common@0.19.5-next.3
## 0.2.24-next.2
### Patch Changes
- Updated dependencies
- @backstage/config@1.1.0-next.1
- @backstage/backend-common@0.19.5-next.2
- @backstage/backend-plugin-api@0.6.3-next.2
## 0.2.24-next.1
### Patch Changes
- Updated dependencies
- @backstage/config@1.1.0-next.0
- @backstage/backend-common@0.19.5-next.1
- @backstage/backend-plugin-api@0.6.3-next.1
## 0.2.23-next.0
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.19.4-next.0
- @backstage/backend-plugin-api@0.6.2-next.0
- @backstage/config@1.0.8
## 0.2.21
### Patch Changes
- 629cbd194a87: Use `coreServices.rootConfig` instead of `coreService.config`
- 12a8c94eda8d: Add package repository and homepage metadata
- Updated dependencies
- @backstage/backend-common@0.19.2
- @backstage/backend-plugin-api@0.6.0
- @backstage/config@1.0.8
## 0.2.21-next.2
### Patch Changes
- Updated dependencies
- @backstage/backend-plugin-api@0.6.0-next.2
- @backstage/backend-common@0.19.2-next.2
## 0.2.21-next.1
### Patch Changes
- 629cbd194a87: Use `coreServices.rootConfig` instead of `coreService.config`
- 12a8c94eda8d: Add package repository and homepage metadata
- Updated dependencies
- @backstage/backend-common@0.19.2-next.1
- @backstage/backend-plugin-api@0.6.0-next.1
- @backstage/config@1.0.8
## 0.2.21-next.0
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.19.2-next.0
- @backstage/backend-plugin-api@0.5.5-next.0
- @backstage/config@1.0.8
## 0.2.20
### Patch Changes
- a95bb64e461a: Added support for the [new backend system](https://backstage.io/docs/backend-system/)
- Updated dependencies
- @backstage/backend-common@0.19.1
- @backstage/backend-plugin-api@0.5.4
- @backstage/config@1.0.8
## 0.2.20-next.1
### Patch Changes
- a95bb64e461a: Added support for the [new backend system](https://backstage.io/docs/backend-system/)
- Updated dependencies
- @backstage/backend-common@0.19.1-next.0
- @backstage/backend-plugin-api@0.5.4-next.0
- @backstage/config@1.0.8
## 0.2.20-next.0
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.19.1-next.0
- @backstage/config@1.0.8
## 0.2.19
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.19.0
- @backstage/config@1.0.8
## 0.2.19-next.2
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.19.0-next.2
- @backstage/config@1.0.7
## 0.2.19-next.1
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.19.0-next.1
- @backstage/config@1.0.7
## 0.2.19-next.0
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.18.6-next.0
- @backstage/config@1.0.7
## 0.2.18
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.18.5
- @backstage/config@1.0.7
## 0.2.18-next.1
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.18.5-next.1
- @backstage/config@1.0.7
## 0.2.18-next.0
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.18.5-next.0
- @backstage/config@1.0.7
## 0.2.17
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.18.4
- @backstage/config@1.0.7
## 0.2.17-next.2
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.18.4-next.2
- @backstage/config@1.0.7
## 0.2.17-next.1
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.18.4-next.1
- @backstage/config@1.0.7
## 0.2.17-next.0
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.18.4-next.0
- @backstage/config@1.0.7
## 0.2.16
### Patch Changes
- 52b0022dab7: Updated dependency `msw` to `^1.0.0`.
- Updated dependencies
- @backstage/backend-common@0.18.3
- @backstage/config@1.0.7
## 0.2.16-next.2
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.18.3-next.2
- @backstage/config@1.0.7-next.0
## 0.2.16-next.1
### Patch Changes
- 52b0022dab7: Updated dependency `msw` to `^1.0.0`.
- Updated dependencies
- @backstage/backend-common@0.18.3-next.1
- @backstage/config@1.0.7-next.0
## 0.2.16-next.0
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.18.3-next.0
- @backstage/config@1.0.6
## 0.2.15
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.18.2
- @backstage/config@1.0.6
## 0.2.15-next.2
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.18.2-next.2
- @backstage/config@1.0.6
## 0.2.15-next.1
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.18.2-next.1
- @backstage/config@1.0.6
## 0.2.15-next.0
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.18.2-next.0
## 0.2.13
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.18.0
- @backstage/config@1.0.6
## 0.2.13-next.1
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.18.0-next.1
- @backstage/config@1.0.6-next.0
## 0.2.13-next.0
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.18.0-next.0
- @backstage/config@1.0.6-next.0
## 0.2.12
### Patch Changes
- 3280711113: Updated dependency `msw` to `^0.49.0`.
- Updated dependencies
- @backstage/backend-common@0.17.0
- @backstage/config@1.0.5
## 0.2.12-next.3
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.17.0-next.3
- @backstage/config@1.0.5-next.1
## 0.2.12-next.2
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.17.0-next.2
- @backstage/config@1.0.5-next.1
## 0.2.12-next.1
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.17.0-next.1
- @backstage/config@1.0.5-next.1
## 0.2.12-next.0
### Patch Changes
- 3280711113: Updated dependency `msw` to `^0.49.0`.
- Updated dependencies
- @backstage/backend-common@0.16.1-next.0
- @backstage/config@1.0.5-next.0
## 0.2.11
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.16.0
- @backstage/config@1.0.4
## 0.2.11-next.1
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.16.0-next.1
- @backstage/config@1.0.4-next.0
## 0.2.11-next.0
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.16.0-next.0
- @backstage/config@1.0.4-next.0
## 0.2.10
### Patch Changes
- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md`
- Updated dependencies
- @backstage/backend-common@0.15.2
- @backstage/config@1.0.3
## 0.2.10-next.2
### Patch Changes
- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md`
- Updated dependencies
- @backstage/backend-common@0.15.2-next.2
- @backstage/config@1.0.3-next.2
## 0.2.10-next.1
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.15.2-next.1
- @backstage/config@1.0.3-next.1
## 0.2.10-next.0
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.15.2-next.0
- @backstage/config@1.0.3-next.0
## 0.2.9
### Patch Changes
- 148568b5c2: Switched to using node-fetch instead of cross-fetch as is standard for our backend packages
- 667d917488: Updated dependency `msw` to `^0.47.0`.
- 87ec2ba4d6: Updated dependency `msw` to `^0.46.0`.
- bf5e9030eb: Updated dependency `msw` to `^0.45.0`.
- Updated dependencies
- @backstage/backend-common@0.15.1
- @backstage/config@1.0.2
## 0.2.9-next.3
### Patch Changes
- Updated dependencies
- @backstage/config@1.0.2-next.0
- @backstage/backend-common@0.15.1-next.3
## 0.2.9-next.2
### Patch Changes
- 667d917488: Updated dependency `msw` to `^0.47.0`.
- 87ec2ba4d6: Updated dependency `msw` to `^0.46.0`.
- Updated dependencies
- @backstage/backend-common@0.15.1-next.2
## 0.2.9-next.1
### Patch Changes
- 148568b5c2: Switched to using node-fetch instead of cross-fetch as is standard for our backend packages
- Updated dependencies
- @backstage/backend-common@0.15.1-next.1
## 0.2.9-next.0
### Patch Changes
- bf5e9030eb: Updated dependency `msw` to `^0.45.0`.
- Updated dependencies
- @backstage/backend-common@0.15.1-next.0
## 0.2.8
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.15.0
## 0.2.8-next.0
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.15.0-next.0
## 0.2.7
### Patch Changes
- a70869e775: Updated dependency `msw` to `^0.43.0`.
- 8006d0f9bf: Updated dependency `msw` to `^0.44.0`.
- Updated dependencies
- @backstage/backend-common@0.14.1
## 0.2.7-next.1
### Patch Changes
- a70869e775: Updated dependency `msw` to `^0.43.0`.
- Updated dependencies
- @backstage/backend-common@0.14.1-next.3
## 0.2.7-next.0
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.14.1-next.0
## 0.2.6
### Patch Changes
- 8f7b1835df: Updated dependency `msw` to `^0.41.0`.
- Updated dependencies
- @backstage/backend-common@0.14.0
## 0.2.6-next.2
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.14.0-next.2
## 0.2.6-next.1
### Patch Changes
- 8f7b1835df: Updated dependency `msw` to `^0.41.0`.
- Updated dependencies
- @backstage/backend-common@0.13.6-next.1
## 0.2.6-next.0
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.13.6-next.0
## 0.2.5
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.13.3
- @backstage/config@1.0.1
## 0.2.5-next.1
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.13.3-next.2
- @backstage/config@1.0.1-next.0
## 0.2.5-next.0
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.13.3-next.0
## 0.2.4
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.13.2
## 0.2.4-next.0
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.13.2-next.0
## 0.2.3
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.13.1
- @backstage/config@1.0.0
## 0.2.2
### Patch Changes
- 4f79204c47: Added `backstage.role` to `package.json`
- Updated dependencies
- @backstage/backend-common@0.13.0
## 0.2.2-next.0
### Patch Changes
- 4f79204c47: Added `backstage.role` to `package.json`
- Updated dependencies
- @backstage/backend-common@0.13.0-next.0
## 0.2.1
### Patch Changes
- 3c1d3cb07e: The Airbrake plugin installation instructions have been updated to work better and conform to how the frontend and backend plugins are supposed to be integrated into a Backstage instance.
- Updated dependencies
- @backstage/backend-common@0.12.0
## 0.2.0
### Minor Changes
- da78e79a94: This marks the first release where the Airbrake plugin is useable. Airbrake frontend and Airbrake backend work with each other. Currently just a summary of the latest Airbrakes is shown on Backstage.
### Patch Changes
- 0045270527: Make config optional
- Updated dependencies
- @backstage/backend-common@0.11.0
## 0.1.0
### Minor Changes
- a5aebe3e30: Created the Airbrake backend plugin that proxies requests to the Airbrake API
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.10.8
- @backstage/config@0.1.14
-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-airbrake-backend` instead.
-31
View File
@@ -1,31 +0,0 @@
## API Report File for "@backstage/plugin-airbrake-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
export interface AirbrakeConfig {
apiKey: string;
}
// @public
const airbrakePlugin: () => BackendFeature;
export default airbrakePlugin;
// @public
export function createRouter(options: RouterOptions): Promise<express.Router>;
// @public
export function extractAirbrakeConfig(config: Config): AirbrakeConfig;
// @public
export interface RouterOptions {
airbrakeConfig: AirbrakeConfig;
logger: LoggerService;
}
```
@@ -1,9 +0,0 @@
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: backstage-plugin-airbrake-backend
title: '@backstage/plugin-airbrake-backend'
spec:
lifecycle: experimental
type: backstage-backend-plugin
owner: maintainers
-28
View File
@@ -1,28 +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.
*/
export interface Config {
/** Configuration options for the Airbrake plugin */
airbrake?: {
/**
* The API Key to authenticate requests. More details on how to get this
* here: https://airbrake.io/docs/api/#authentication
*
* @visibility secret
*/
apiKey: string;
};
}
-2
View File
@@ -1,2 +0,0 @@
# Knip report
-54
View File
@@ -1,54 +0,0 @@
{
"name": "@backstage/plugin-airbrake-backend",
"version": "0.3.14",
"backstage": {
"role": "backend-plugin",
"moved": "@backstage-community/plugin-airbrake-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/airbrake-backend"
},
"license": "Apache-2.0",
"main": "src/index.ts",
"types": "src/index.ts",
"files": [
"dist",
"config.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/config": "workspace:^",
"@types/express": "*",
"express": "^4.17.1",
"express-promise-router": "^4.1.0",
"http-proxy-middleware": "^2.0.0",
"yn": "^4.0.0"
},
"devDependencies": {
"@backstage/backend-test-utils": "workspace:^",
"@backstage/cli": "workspace:^",
"@types/http-proxy-middleware": "^1.0.0",
"@types/supertest": "^2.0.8",
"supertest": "^6.1.6"
},
"configSchema": "config.d.ts",
"deprecated": "This package has been moved to the backstage/community-plugins repository. You should migrate to using @backstage-community/plugin-airbrake-backend instead."
}
@@ -1,38 +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 { ConfigReader } from '@backstage/config';
import { extractAirbrakeConfig } from './ExtractAirbrakeConfig';
describe('ExtractAirbrakeConfig', () => {
it('gets the API key', () => {
const config = new ConfigReader({
airbrake: {
apiKey: 'fakeApiKey',
},
});
const airbrakeConfig = extractAirbrakeConfig(config);
expect(airbrakeConfig.apiKey).toStrictEqual('fakeApiKey');
});
it('fails for missing keys', () => {
expect(() => extractAirbrakeConfig(new ConfigReader({}))).toThrow();
expect(() =>
extractAirbrakeConfig(new ConfigReader({ airbrake: {} })),
).toThrow();
});
});
@@ -1,42 +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 { Config } from '@backstage/config';
/**
* The configuration needed for the airbrake-backend plugin
*
* @public
*/
export interface AirbrakeConfig {
/**
* The API Key to authenticate requests. More details on how to get this here: https://airbrake.io/docs/api/#authentication
*/
apiKey: string;
}
/**
* Extract the Airbrake config from a config object
*
* @public
*
* @param config - The config object to extract from
*/
export function extractAirbrakeConfig(config: Config): AirbrakeConfig {
return {
apiKey: config.getString('airbrake.apiKey'),
};
}
@@ -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 './ExtractAirbrakeConfig';
-25
View File
@@ -1,25 +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.
*/
/**
* The Airbrake Backend plugin used by \@backstage/plugin-airbrake
*
* @packageDocumentation
*/
export * from './service/router';
export * from './config';
export { airbrakePlugin as default } from './plugin';
-52
View File
@@ -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 {
coreServices,
createBackendPlugin,
} from '@backstage/backend-plugin-api';
import { createRouter } from './service/router';
import { extractAirbrakeConfig } from './config';
/**
* The Airbrake Backend plugin.
*
* @public
*/
export const airbrakePlugin = createBackendPlugin({
pluginId: 'airbrake',
register(env) {
env.registerInit({
deps: {
config: coreServices.rootConfig,
logger: coreServices.logger,
httpRouter: coreServices.httpRouter,
},
async init({ logger, httpRouter, config }) {
httpRouter.use(
await createRouter({
airbrakeConfig: extractAirbrakeConfig(config),
logger,
}),
);
httpRouter.addAuthPolicy({
path: '/health',
allow: 'unauthenticated',
});
},
});
},
});
-33
View File
@@ -1,33 +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 { 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,92 +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 express from 'express';
import request from 'supertest';
import { ConfigReader } from '@backstage/config';
import {
createRouter,
generateAirbrakePathRewrite,
RouterOptions,
} from './router';
import { AirbrakeConfig, extractAirbrakeConfig } from '../config';
import { LoggerService } from '@backstage/backend-plugin-api';
import { mockServices } from '@backstage/backend-test-utils';
describe('createRouter', () => {
let app: express.Express;
let airbrakeConfig: AirbrakeConfig;
let voidLogger: LoggerService;
beforeEach(async () => {
jest.resetAllMocks();
voidLogger = mockServices.logger.mock();
const config = new ConfigReader({
airbrake: {
apiKey: 'fakeApiKey',
},
});
airbrakeConfig = extractAirbrakeConfig(config);
const router = await createRouter({
logger: voidLogger,
airbrakeConfig,
});
app = express().use(router);
});
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' });
});
});
describe('GET /api', () => {
it('appends the API Key properly with no other url parameters', () => {
const options: RouterOptions = {
logger: voidLogger,
airbrakeConfig,
};
const pathRewrite = generateAirbrakePathRewrite(options) as (
path: string,
) => string;
expect(pathRewrite('/airbrake-backend/api/v4/random/endpoint')).toBe(
'/v4/random/endpoint?key=fakeApiKey',
);
});
it('appends the API Key properly despite there being other URL parameters', () => {
const options: RouterOptions = {
logger: voidLogger,
airbrakeConfig,
};
const pathRewrite = generateAirbrakePathRewrite(options) as (
path: string,
) => string;
expect(
pathRewrite(
'/airbrake-backend/api/v4/random/endpoint?param1=123&param2=abc',
),
).toBe('/v4/random/endpoint?param1=123&param2=abc&key=fakeApiKey');
});
});
});
@@ -1,96 +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 { errorHandler } from '@backstage/backend-common';
import express from 'express';
import Router from 'express-promise-router';
import { createProxyMiddleware } from 'http-proxy-middleware';
import { AirbrakeConfig } from '../config';
import { Options } from 'http-proxy-middleware/dist/types';
import { LoggerService } from '@backstage/backend-plugin-api';
/**
* The router options that are needed when creating a router.
*
* @public
*/
export interface RouterOptions {
/**
* A logger object
*/
logger: LoggerService;
/**
* The Airbrake config obtained from {@link extractAirbrakeConfig}
*/
airbrakeConfig: AirbrakeConfig;
}
/**
* Mainly used internally to generate the path.
*
* @internal
*
* @param options - Router options
*/
export const generateAirbrakePathRewrite = (
options: RouterOptions,
): Options['pathRewrite'] => {
const apiKey = options.airbrakeConfig.apiKey;
return path => {
let newPath = path.replace(/.+?(\/api)/g, '');
if (newPath.includes('?')) {
newPath += `&key=${apiKey}`;
} else {
newPath += `?key=${apiKey}`;
}
return newPath;
};
};
/**
* Create the Airbrake Router, used for making API calls to the Airbrake API.
*
* @public
*
* @param options - Router options
*/
export async function createRouter(
options: RouterOptions,
): Promise<express.Router> {
const { logger } = options;
const router = Router();
router.use(express.json());
router.get('/health', (_, response) => {
logger.info('PONG!');
response.json({ status: 'ok' });
});
router.use(
'/api',
createProxyMiddleware({
target: 'https://api.airbrake.io/api',
changeOrigin: true,
pathRewrite: generateAirbrakePathRewrite(options),
}),
);
router.use(errorHandler());
return router;
}
@@ -1,61 +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 {
createServiceBuilder,
loadBackendConfig,
} from '@backstage/backend-common';
import { Server } from 'http';
import { createRouter } from './router';
import { extractAirbrakeConfig } from '../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: 'airbrake-backend' });
const config = await loadBackendConfig({ logger, argv: process.argv });
const airbrakeConfig = extractAirbrakeConfig(config);
logger.debug('Starting application server...');
const router = await createRouter({
logger,
airbrakeConfig,
});
let service = createServiceBuilder(module)
.setPort(options.port)
.addRouter('/api/airbrake', router);
if (options.enableCors) {
logger.info('CORS is enabled, limiting to localhost with port 3000');
service = service.enableCors({ origin: 'http://localhost:3000' });
} else {
logger.info('CORS is disabled, allowing all origins');
service = service.enableCors({ origin: '*' });
}
return await service.start().catch(err => {
logger.error(err);
process.exit(1);
});
}
module.hot?.accept();
@@ -1,17 +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.
*/
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',
},
});
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-airbrake` instead.
-24
View File
@@ -1,24 +0,0 @@
## API Report File for "@backstage/plugin-airbrake"
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
```ts
import { BackstagePlugin } from '@backstage/core-plugin-api';
import { Entity } from '@backstage/catalog-model';
import { default as React_2 } from 'react';
import { RouteRef } from '@backstage/core-plugin-api';
// @public
export const airbrakePlugin: BackstagePlugin<
{
root: RouteRef<undefined>;
},
{}
>;
// @public
export const EntityAirbrakeContent: () => React_2.JSX.Element;
// @public
export const isAirbrakeAvailable: (entity: Entity) => boolean;
```
-9
View File
@@ -1,9 +0,0 @@
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: backstage-plugin-airbrake
title: '@backstage/plugin-airbrake'
spec:
lifecycle: experimental
type: backstage-frontend-plugin
owner: maintainers
@@ -1,57 +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 TextField from '@material-ui/core/TextField';
import { makeStyles } from '@material-ui/core/styles';
import { Context } from '../ContextProvider';
const useStyles = makeStyles(theme => ({
root: {
display: 'flex',
gap: '1em',
flexWrap: 'wrap',
},
label: {
color: `${theme.palette.common.white} !important`,
},
outline: {
color: `${theme.palette.common.white} !important`,
borderColor: `${theme.palette.common.white} !important`,
},
}));
export const ApiBar = () => {
const classes = useStyles();
return (
<Context.Consumer>
{value => (
<div className={classes.root}>
<TextField
label="Project ID"
variant="outlined"
defaultValue={value.projectId}
InputLabelProps={{ classes: { root: classes.label } }}
InputProps={{ classes: { notchedOutline: classes.outline } }}
onChange={e =>
value.setProjectId?.(parseInt(e.target.value, 10) || undefined)
}
/>
</div>
)}
</Context.Consumer>
);
};
@@ -1,17 +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.
*/
export { ApiBar } from './ApiBar';
@@ -1,43 +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, {
Dispatch,
PropsWithChildren,
SetStateAction,
useState,
} from 'react';
interface ContextInterface {
projectId?: number;
setProjectId?: Dispatch<SetStateAction<number | undefined>>;
}
export const Context = React.createContext<ContextInterface>({});
export const ContextProvider = ({ children }: PropsWithChildren<{}>) => {
const [projectId, setProjectId] = useState<number>();
return (
<Context.Provider
value={{
projectId,
setProjectId,
}}
>
{children}
</Context.Provider>
);
};
@@ -1,17 +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.
*/
export { ContextProvider, Context } from './ContextProvider';
-88
View File
@@ -1,88 +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 React from 'react';
import { createDevApp } from '@backstage/dev-utils';
import { TestApiProvider } from '@backstage/test-utils';
import { airbrakePlugin, EntityAirbrakeContent } from '../src';
import {
airbrakeApiRef,
localDiscoveryApi,
MockAirbrakeApi,
ProductionAirbrakeApi,
} from '../src/api';
import { ApiBar } from './components/ApiBar';
import { Content, Header, Page } from '@backstage/core-components';
import { EntityProvider } from '@backstage/plugin-catalog-react';
import { createEntity } from '../src/api';
import CloudOffIcon from '@material-ui/icons/CloudOff';
import CloudIcon from '@material-ui/icons/Cloud';
import { Context, ContextProvider } from './components/ContextProvider';
createDevApp()
.registerPlugin(airbrakePlugin)
.addPage({
element: (
<Page themeId="tool">
<Header title="Airbrake demo application" subtitle="Mock API" />
<Content>
<TestApiProvider apis={[[airbrakeApiRef, new MockAirbrakeApi(800)]]}>
<EntityProvider entity={createEntity(1234)}>
<EntityAirbrakeContent />
</EntityProvider>
</TestApiProvider>
</Content>
</Page>
),
title: 'Mock API',
path: '/airbrake-mock-api',
icon: CloudOffIcon,
})
.addPage({
element: (
<ContextProvider>
<Page themeId="tool">
<Header
title="Airbrake demo application"
subtitle="Real API (The Airbrake backend plugin must be running for this to work)"
>
<ApiBar />
</Header>
<Content>
<Context.Consumer>
{value => (
<TestApiProvider
apis={[
[
airbrakeApiRef,
new ProductionAirbrakeApi(localDiscoveryApi),
],
]}
>
<EntityProvider entity={createEntity(value.projectId)}>
<EntityAirbrakeContent />
</EntityProvider>
</TestApiProvider>
)}
</Context.Consumer>
</Content>
</Page>
</ContextProvider>
),
title: 'Real API',
path: '/airbrake-real-api',
icon: CloudIcon,
})
.render();
-10
View File
@@ -1,10 +0,0 @@
# Knip report
## Unused devDependencies (3)
| Name | Location | Severity |
| :---------------------- | :----------- | :------- |
| @backstage/app-defaults | package.json | error |
| @testing-library/react | package.json | error |
| @testing-library/dom | package.json | error |
-63
View File
@@ -1,63 +0,0 @@
{
"name": "@backstage/plugin-airbrake",
"version": "0.3.34",
"backstage": {
"role": "frontend-plugin",
"moved": "@backstage-community/plugin-airbrake"
},
"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/airbrake"
},
"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/dev-utils": "workspace:^",
"@backstage/plugin-catalog-react": "workspace:^",
"@backstage/test-utils": "workspace:^",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
"@types/react": "^16.13.1 || ^17.0.0 || ^18.0.0",
"react-use": "^17.2.4"
},
"devDependencies": {
"@backstage/app-defaults": "workspace:^",
"@backstage/cli": "workspace:^",
"@backstage/dev-utils": "workspace:^",
"@backstage/test-utils": "workspace:^",
"@testing-library/dom": "^10.0.0",
"@testing-library/jest-dom": "^6.0.0",
"@testing-library/react": "^15.0.0",
"msw": "^1.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-airbrake instead."
}
-26
View File
@@ -1,26 +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 { Groups } from './airbrakeGroups';
import { createApiRef } from '@backstage/core-plugin-api';
export const airbrakeApiRef = createApiRef<AirbrakeApi>({
id: 'plugin.airbrake.service',
});
export interface AirbrakeApi {
fetchGroups(projectId: string): Promise<Groups>;
}
@@ -1,55 +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 { ProductionAirbrakeApi } from './ProductionApi';
import { rest } from 'msw';
import mockGroupsData from './mock/airbrakeGroupsApiMock.json';
import { setupServer } from 'msw/node';
import { setupRequestMockHandlers } from '@backstage/test-utils';
import { localDiscoveryApi } from './mock';
describe('The production Airbrake API', () => {
const productionApi = new ProductionAirbrakeApi(localDiscoveryApi);
const worker = setupServer();
setupRequestMockHandlers(worker);
it('fetches groups using the provided project ID', async () => {
worker.use(
rest.get(
'http://localhost:7007/api/airbrake/api/v4/projects/123456/groups',
(_, res, ctx) => {
return res(ctx.status(200), ctx.json(mockGroupsData));
},
),
);
const groups = await productionApi.fetchGroups('123456');
expect(groups).toStrictEqual(mockGroupsData);
});
it('throws if fetching groups was unsuccessful', async () => {
worker.use(
rest.get(
'http://localhost:7007/api/airbrake/api/v4/projects/123456/groups',
(_, res, ctx) => {
return res(ctx.status(500));
},
),
);
await expect(productionApi.fetchGroups('123456')).rejects.toThrow();
});
});
-36
View File
@@ -1,36 +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 { Groups } from './airbrakeGroups';
import { AirbrakeApi } from './AirbrakeApi';
import { DiscoveryApi } from '@backstage/core-plugin-api';
export class ProductionAirbrakeApi implements AirbrakeApi {
constructor(private readonly discoveryApi: DiscoveryApi) {}
async fetchGroups(projectId: string): Promise<Groups> {
const baseUrl = await this.discoveryApi.getBaseUrl('airbrake');
const apiUrl = `${baseUrl}/api/v4/projects/${projectId}/groups`;
const response = await fetch(apiUrl);
if (response.status >= 400 && response.status < 600) {
throw new Error('Failed fetching Airbrake groups');
}
return (await response.json()) as Groups;
}
}
-65
View File
@@ -1,65 +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 interface Groups {
count: number;
end: string;
groups?: Group[] | null;
page: number;
resolvedCount: number;
unresolvedCount: number;
}
export interface Group {
id: string;
projectId: number;
resolved: boolean;
muted: boolean;
mutedBy: number;
mutedAt?: string | null;
errors?: Error[];
attributes?: string | null;
context: Context;
lastDeployId: string;
lastDeployAt?: string | null;
lastNoticeId: string;
lastNoticeAt: string;
noticeCount: number;
noticeTotalCount: number;
commentCount: number;
createdAt: string;
}
export interface Error {
type: string;
message: string;
backtrace?: Backtrace[];
}
export interface Backtrace {
file: string;
function: string;
line: number;
column: number;
code?: string | null;
}
export interface Context {
action: string;
component: string;
environment: string;
severity: string;
}
-21
View File
@@ -1,21 +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.
*/
export * from './mock';
export type { AirbrakeApi } from './AirbrakeApi';
export { airbrakeApiRef } from './AirbrakeApi';
export type { Groups } from './airbrakeGroups';
export { ProductionAirbrakeApi } from './ProductionApi';
@@ -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 { DiscoveryApi } from '@backstage/core-plugin-api';
export const localDiscoveryApi: DiscoveryApi = {
async getBaseUrl(pluginId: string): Promise<string> {
return `http://localhost:7007/api/${pluginId}`;
},
};
-33
View File
@@ -1,33 +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 { Groups } from '../airbrakeGroups';
import { AirbrakeApi } from '../AirbrakeApi';
import mockGroupsData from './airbrakeGroupsApiMock.json';
export class MockAirbrakeApi implements AirbrakeApi {
waitTimeInMillis: number;
constructor(waitTimeInMillis = 10) {
this.waitTimeInMillis = waitTimeInMillis;
}
fetchGroups(): Promise<Groups> {
return new Promise(resolve => {
setTimeout(() => resolve(mockGroupsData), this.waitTimeInMillis);
});
}
}
@@ -1,32 +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 { AIRBRAKE_PROJECT_ID_ANNOTATION } from '../../components/useProjectId';
import { Entity } from '@backstage/catalog-model';
export const createEntity = (projectId?: number) => {
const projectIdString = projectId?.toString();
return {
apiVersion: 'backstage.io/v1alpha1',
kind: 'Component',
metadata: {
annotations: {
[AIRBRAKE_PROJECT_ID_ANNOTATION]: projectIdString,
},
name: projectIdString,
},
} as Entity;
};
@@ -1,85 +0,0 @@
{
"count": 2,
"end": "",
"groups": [
{
"id": "1",
"projectId": 123,
"resolved": false,
"muted": false,
"mutedBy": 0,
"mutedAt": null,
"errors": [
{
"type": "Error",
"message": "useSearch must be used within a SearchContextProvider",
"backtrace": [
{
"file": "webpack-internal:///../../node_modules/@backstage/plugin-search/dist/esm/index-893ec2f5.esm.js",
"function": "useSearch",
"line": 303,
"column": 11,
"code": null
}
]
}
],
"attributes": null,
"context": {
"action": "",
"component": "",
"environment": "local",
"severity": "error"
},
"lastDeployId": "0",
"lastDeployAt": null,
"lastNoticeId": "234",
"lastNoticeAt": "2021-12-19T09:59:00.124Z",
"noticeCount": 5,
"noticeTotalCount": 5,
"commentCount": 0,
"createdAt": "2021-12-19T09:44:30.067447Z"
},
{
"id": "2",
"projectId": 123,
"resolved": true,
"muted": false,
"mutedBy": 0,
"mutedAt": null,
"errors": [
{
"type": "ChunkLoadError",
"message": "Loading chunk 7764 failed.",
"backtrace": [
{
"file": "/PROJECT_ROOT/static/runtime.069c874d.js",
"function": "Object._.f.j",
"line": 1,
"column": 19465,
"code": null
}
]
}
],
"attributes": null,
"context": {
"action": "",
"component": "",
"environment": "local",
"severity": "error"
},
"lastDeployId": "0",
"lastDeployAt": null,
"lastNoticeId": "345",
"lastNoticeAt": "2021-12-15T17:16:38.419Z",
"noticeCount": 1,
"noticeTotalCount": 1,
"commentCount": 0,
"createdAt": "2021-12-15T17:16:38.41983Z"
}
],
"page": 1,
"resolvedCount": 1,
"unresolvedCount": 1
}
-19
View File
@@ -1,19 +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.
*/
export * from './MockApi';
export * from './MockEntity';
export * from './LocalDiscoveryApi';
@@ -1,96 +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 { EntityAirbrakeWidget } from './EntityAirbrakeWidget';
import exampleData from '../../api/mock/airbrakeGroupsApiMock.json';
import {
renderInTestApp,
setupRequestMockHandlers,
TestApiProvider,
} from '@backstage/test-utils';
import {
airbrakeApiRef,
createEntity,
localDiscoveryApi,
MockAirbrakeApi,
ProductionAirbrakeApi,
} from '../../api';
import { rest } from 'msw';
import { setupServer } from 'msw/node';
describe('EntityAirbrakeWidget', () => {
const worker = setupServer();
setupRequestMockHandlers(worker);
it('renders all errors sent from Airbrake', async () => {
const widget = await renderInTestApp(
<TestApiProvider apis={[[airbrakeApiRef, new MockAirbrakeApi()]]}>
<EntityAirbrakeWidget entity={createEntity(123)} />
</TestApiProvider>,
);
expect(exampleData.groups.length).toBeGreaterThan(0);
for (const group of exampleData.groups) {
expect(
await widget.findByText(group.errors[0].message),
).toBeInTheDocument();
}
});
it('states that the annotation is missing if no project ID annotation is provided but does not error', async () => {
const widget = await renderInTestApp(
<TestApiProvider
apis={[[airbrakeApiRef, new ProductionAirbrakeApi(localDiscoveryApi)]]}
>
<EntityAirbrakeWidget entity={createEntity()} />
</TestApiProvider>,
);
await expect(
widget.findByText('Missing Annotation'),
).resolves.toBeInTheDocument();
expect(
widget.queryByText(/.*Failed fetching Airbrake groups.*/),
).not.toBeInTheDocument();
});
it('states that an error occurred if the API call fails', async () => {
worker.use(
rest.get(
'http://localhost:7007/api/airbrake/api/v4/projects/123/groups',
(_, res, ctx) => {
return res(ctx.status(500));
},
),
);
const widget = await renderInTestApp(
<TestApiProvider
apis={[[airbrakeApiRef, new ProductionAirbrakeApi(localDiscoveryApi)]]}
>
<EntityAirbrakeWidget entity={createEntity(123)} />
</TestApiProvider>,
);
await expect(
widget.findByText(/.*there was an issue communicating with Airbrake.*/),
).resolves.toBeInTheDocument();
expect(
widget.getByRole('heading', {
name: /.*Failed fetching Airbrake groups.*/,
}),
).toBeInTheDocument();
});
});
@@ -1,90 +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 { Entity } from '@backstage/catalog-model';
import {
EmptyState,
ErrorPanel,
InfoCard,
Progress,
} from '@backstage/core-components';
import { useApi } from '@backstage/core-plugin-api';
import Grid from '@material-ui/core/Grid';
import Typography from '@material-ui/core/Typography';
import { makeStyles } from '@material-ui/core/styles';
import React from 'react';
import useAsync from 'react-use/esm/useAsync';
import { airbrakeApiRef } from '../../api';
import { MissingAnnotationEmptyState } from '@backstage/plugin-catalog-react';
import { AIRBRAKE_PROJECT_ID_ANNOTATION, useProjectId } from '../useProjectId';
const useStyles = makeStyles({
multilineText: {
whiteSpace: 'pre-wrap',
},
});
export const EntityAirbrakeWidget = ({ entity }: { entity: Entity }) => {
const classes = useStyles();
const projectId = useProjectId(entity);
const airbrakeApi = useApi(airbrakeApiRef);
const { loading, value, error } = useAsync(() => {
if (!projectId) {
return Promise.resolve(undefined);
}
return airbrakeApi.fetchGroups(projectId);
}, [airbrakeApi, projectId]);
if (!projectId) {
return (
<MissingAnnotationEmptyState
annotation={AIRBRAKE_PROJECT_ID_ANNOTATION}
/>
);
} else if (loading) {
return <Progress />;
} else if (value) {
return (
<Grid container spacing={3} direction="column">
{value.groups?.map(group => (
<Grid item key={group.id}>
{group.errors?.map((groupError, i) => (
<InfoCard title={groupError.type} key={i}>
<Typography variant="body1" className={classes.multilineText}>
{groupError.message}
</Typography>
</InfoCard>
))}
</Grid>
))}
</Grid>
);
}
return (
<>
{error && <ErrorPanel error={error} />}
<EmptyState
missing="info"
title="No information to display"
description={`There is no Airbrake project with id '${projectId}' or there was an issue communicating with Airbrake.`}
/>
</>
);
};
@@ -1,17 +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.
*/
export { EntityAirbrakeWidget } from './EntityAirbrakeWidget';
@@ -1,25 +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 { Entity } from '@backstage/catalog-model';
import { AIRBRAKE_PROJECT_ID_ANNOTATION } from './useProjectId';
/**
* Utility function to determine if the given entity has an Airbrake ID set in the repos catalog-info.yml .
* @public
*/
export const isAirbrakeAvailable = (entity: Entity) =>
Boolean(entity.metadata.annotations?.[AIRBRAKE_PROJECT_ID_ANNOTATION]);
@@ -1,23 +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 { Entity } from '@backstage/catalog-model';
export const AIRBRAKE_PROJECT_ID_ANNOTATION = 'airbrake.io/project-id';
export const useProjectId = (entity: Entity) => {
return entity?.metadata.annotations?.[AIRBRAKE_PROJECT_ID_ANNOTATION] ?? '';
};
-39
View File
@@ -1,39 +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 React from 'react';
import { EntityAirbrakeContent } from './extensions';
import { Route, Routes } from 'react-router-dom';
import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
import { airbrakeApiRef, MockAirbrakeApi } from './api';
import { createEntity } from './api';
import { EntityProvider } from '@backstage/plugin-catalog-react';
describe('The Airbrake entity', () => {
it('should render the content properly', async () => {
const rendered = await renderInTestApp(
<TestApiProvider apis={[[airbrakeApiRef, new MockAirbrakeApi()]]}>
<EntityProvider entity={createEntity(123)}>
<Routes>
<Route path="/" element={<EntityAirbrakeContent />} />
</Routes>
</EntityProvider>
</TestApiProvider>,
);
await expect(
rendered.findByText('ChunkLoadError'),
).resolves.toBeInTheDocument();
});
});
-42
View File
@@ -1,42 +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 { useEntity } from '@backstage/plugin-catalog-react';
import React from 'react';
import { airbrakePlugin } from './plugin';
import { createRoutableExtension } from '@backstage/core-plugin-api';
import { rootRouteRef } from './routes';
/**
* This is the widget that shows up on a component page
*
* @public
*/
export const EntityAirbrakeContent = airbrakePlugin.provide(
createRoutableExtension({
name: 'EntityAirbrakeContent',
mountPoint: rootRouteRef,
component: () =>
import('./components/EntityAirbrakeWidget').then(
({ EntityAirbrakeWidget }) => {
return () => {
const { entity } = useEntity();
return <EntityAirbrakeWidget entity={entity} />;
};
},
),
}),
);

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