Merge branch 'master' into expose-factretrieverengine
This commit is contained in:
@@ -1,5 +1,59 @@
|
||||
# @backstage/plugin-adr-backend
|
||||
|
||||
## 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/backend-common@0.15.0
|
||||
- @backstage/plugin-adr-common@0.2.0
|
||||
- @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
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/plugin-adr-common@0.2.0-next.1
|
||||
- @backstage/backend-common@0.15.0-next.2
|
||||
|
||||
## 0.1.3-next.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/backend-common@0.15.0-next.0
|
||||
- @backstage/integration@1.3.0-next.0
|
||||
- @backstage/plugin-adr-common@0.1.3-next.0
|
||||
|
||||
## 0.1.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- a70869e775: Updated dependency `msw` to `^0.43.0`.
|
||||
- 4e9a90e307: Updated dependency `luxon` to `^3.0.0`.
|
||||
- 8006d0f9bf: Updated dependency `msw` to `^0.44.0`.
|
||||
- Updated dependencies
|
||||
- @backstage/backend-common@0.14.1
|
||||
- @backstage/catalog-model@1.1.0
|
||||
- @backstage/plugin-search-common@1.0.0
|
||||
- @backstage/integration@1.2.2
|
||||
- @backstage/catalog-client@1.0.4
|
||||
- @backstage/errors@1.1.0
|
||||
- @backstage/plugin-adr-common@0.1.2
|
||||
|
||||
## 0.1.2-next.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-adr-backend",
|
||||
"version": "0.1.2-next.2",
|
||||
"version": "0.2.0",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -29,14 +29,14 @@
|
||||
"postpack": "backstage-cli package postpack"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-common": "^0.14.1-next.3",
|
||||
"@backstage/catalog-client": "^1.0.4-next.2",
|
||||
"@backstage/catalog-model": "^1.1.0-next.3",
|
||||
"@backstage/backend-common": "^0.15.0",
|
||||
"@backstage/catalog-client": "^1.0.4",
|
||||
"@backstage/catalog-model": "^1.1.0",
|
||||
"@backstage/config": "^1.0.1",
|
||||
"@backstage/errors": "^1.1.0-next.0",
|
||||
"@backstage/integration": "^1.2.2-next.3",
|
||||
"@backstage/plugin-adr-common": "^0.1.2-next.1",
|
||||
"@backstage/plugin-search-common": "^0.3.6-next.0",
|
||||
"@backstage/errors": "^1.1.0",
|
||||
"@backstage/integration": "^1.3.0",
|
||||
"@backstage/plugin-adr-common": "^0.2.0",
|
||||
"@backstage/plugin-search-common": "^1.0.0",
|
||||
"luxon": "^3.0.0",
|
||||
"marked": "^4.0.14",
|
||||
"winston": "^3.2.1",
|
||||
@@ -44,11 +44,11 @@
|
||||
"yn": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.18.0-next.3",
|
||||
"@backstage/cli": "^0.18.1",
|
||||
"@types/marked": "^4.0.0",
|
||||
"@types/supertest": "^2.0.8",
|
||||
"supertest": "^6.1.3",
|
||||
"msw": "^0.44.0"
|
||||
"msw": "^0.45.0"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
||||
@@ -142,6 +142,7 @@ export class DefaultAdrCollatorFactory implements DocumentCollatorFactory {
|
||||
'metadata.annotations',
|
||||
'metadata.name',
|
||||
'metadata.namespace',
|
||||
'metadata.title',
|
||||
],
|
||||
},
|
||||
{ token },
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
import { DateTime } from 'luxon';
|
||||
import { marked } from 'marked';
|
||||
import { stringifyEntityRef } from '@backstage/catalog-model';
|
||||
import { MADR_DATE_FORMAT } from '@backstage/plugin-adr-common';
|
||||
|
||||
import { AdrParser } from './types';
|
||||
@@ -101,6 +102,8 @@ export const createMadrParser = (
|
||||
text: content,
|
||||
status: adrStatus,
|
||||
date: adrDate,
|
||||
entityRef: stringifyEntityRef(entity),
|
||||
entityTitle: entity.metadata.title,
|
||||
location: applyArgsToFormat(locationTemplate, {
|
||||
namespace: entity.metadata.namespace || 'default',
|
||||
kind: entity.kind,
|
||||
|
||||
@@ -1,5 +1,42 @@
|
||||
# @backstage/plugin-adr-common
|
||||
|
||||
## 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
|
||||
|
||||
@@ -10,6 +10,8 @@ import { ScmIntegrationRegistry } from '@backstage/integration';
|
||||
// @public
|
||||
export interface AdrDocument extends IndexableDocument {
|
||||
date?: string;
|
||||
entityRef: string;
|
||||
entityTitle?: string;
|
||||
status?: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/plugin-adr-common",
|
||||
"description": "Common functionalities for the adr plugin",
|
||||
"version": "0.1.2-next.1",
|
||||
"version": "0.2.0",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -30,12 +30,12 @@
|
||||
"postpack": "backstage-cli package postpack"
|
||||
},
|
||||
"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"
|
||||
"@backstage/catalog-model": "^1.1.0",
|
||||
"@backstage/integration": "^1.3.0",
|
||||
"@backstage/plugin-search-common": "^1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.18.0-next.1"
|
||||
"@backstage/cli": "^0.18.1"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
||||
@@ -84,6 +84,14 @@ export const madrFilePathFilter: AdrFilePathFilterFn = (path: string) =>
|
||||
* @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
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,85 @@
|
||||
# @backstage/plugin-adr
|
||||
|
||||
## 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/plugin-adr-common@0.2.0
|
||||
- @backstage/core-components@0.11.0
|
||||
- @backstage/core-plugin-api@1.0.5
|
||||
- @backstage/plugin-catalog-react@1.1.3
|
||||
- @backstage/integration-react@1.1.3
|
||||
- @backstage/plugin-search-react@1.0.1
|
||||
|
||||
## 0.2.0-next.2
|
||||
|
||||
### 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/plugin-adr-common@0.2.0-next.1
|
||||
|
||||
## 0.1.3-next.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/plugin-catalog-react@1.1.3-next.2
|
||||
- @backstage/core-components@0.11.0-next.2
|
||||
- @backstage/integration-react@1.1.3-next.1
|
||||
- @backstage/plugin-search-react@1.0.1-next.1
|
||||
|
||||
## 0.1.3-next.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/core-plugin-api@1.0.5-next.0
|
||||
- @backstage/integration-react@1.1.3-next.0
|
||||
- @backstage/plugin-adr-common@0.1.3-next.0
|
||||
- @backstage/plugin-catalog-react@1.1.3-next.0
|
||||
- @backstage/core-components@0.10.1-next.0
|
||||
- @backstage/plugin-search-react@1.0.1-next.0
|
||||
|
||||
## 0.1.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- a70869e775: Updated dependency `msw` to `^0.43.0`.
|
||||
- 8006d0f9bf: Updated dependency `msw` to `^0.44.0`.
|
||||
- 511f49ee43: Updated dependency `octokit` to `^2.0.0`.
|
||||
- e2d7b76f43: Upgrade git-url-parse to 12.0.0.
|
||||
|
||||
Motivation for upgrade is transitively upgrading parse-url which is vulnerable
|
||||
to several CVEs detected by Snyk.
|
||||
|
||||
- SNYK-JS-PARSEURL-2935944
|
||||
- SNYK-JS-PARSEURL-2935947
|
||||
- SNYK-JS-PARSEURL-2936249
|
||||
|
||||
- 7d47e7e512: Track discover event and result rank for `AdrSearchResultListItem`
|
||||
- Updated dependencies
|
||||
- @backstage/core-components@0.10.0
|
||||
- @backstage/plugin-search-react@1.0.0
|
||||
- @backstage/plugin-search-common@1.0.0
|
||||
- @backstage/core-plugin-api@1.0.4
|
||||
- @backstage/integration-react@1.1.2
|
||||
- @backstage/plugin-catalog-react@1.1.2
|
||||
- @backstage/theme@0.2.16
|
||||
- @backstage/plugin-adr-common@0.1.2
|
||||
|
||||
## 0.1.2-next.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -25,18 +25,13 @@ export const adrPlugin: BackstagePlugin<
|
||||
{
|
||||
root: RouteRef<undefined>;
|
||||
},
|
||||
{},
|
||||
{}
|
||||
>;
|
||||
|
||||
// @public
|
||||
export const AdrReader: {
|
||||
({
|
||||
adr,
|
||||
decorators,
|
||||
}: {
|
||||
adr: string;
|
||||
decorators?: AdrContentDecorator[] | undefined;
|
||||
}): JSX.Element;
|
||||
(props: { adr: string; decorators?: AdrContentDecorator[] }): JSX.Element;
|
||||
decorators: Readonly<{
|
||||
createRewriteRelativeLinksDecorator(): AdrContentDecorator;
|
||||
createRewriteRelativeEmbedsDecorator(): AdrContentDecorator;
|
||||
@@ -44,23 +39,15 @@ export const AdrReader: {
|
||||
};
|
||||
|
||||
// @public
|
||||
export const AdrSearchResultListItem: ({
|
||||
lineClamp,
|
||||
highlight,
|
||||
rank,
|
||||
result,
|
||||
}: {
|
||||
lineClamp?: number | undefined;
|
||||
highlight?: ResultHighlight | undefined;
|
||||
rank?: number | undefined;
|
||||
export function AdrSearchResultListItem(props: {
|
||||
lineClamp?: number;
|
||||
highlight?: ResultHighlight;
|
||||
rank?: number;
|
||||
result: AdrDocument;
|
||||
}) => JSX.Element;
|
||||
}): JSX.Element;
|
||||
|
||||
// @public
|
||||
export const EntityAdrContent: ({
|
||||
contentDecorators,
|
||||
filePathFilterFn,
|
||||
}: {
|
||||
export const EntityAdrContent: (props: {
|
||||
contentDecorators?: AdrContentDecorator[] | undefined;
|
||||
filePathFilterFn?: AdrFilePathFilterFn | undefined;
|
||||
}) => JSX.Element;
|
||||
|
||||
+15
-14
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-adr",
|
||||
"version": "0.1.2-next.3",
|
||||
"version": "0.2.0",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -22,14 +22,15 @@
|
||||
"postpack": "backstage-cli package postpack"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/core-components": "^0.10.0-next.3",
|
||||
"@backstage/core-plugin-api": "^1.0.4-next.0",
|
||||
"@backstage/integration-react": "^1.1.2-next.3",
|
||||
"@backstage/plugin-adr-common": "^0.1.2-next.1",
|
||||
"@backstage/plugin-catalog-react": "^1.1.2-next.3",
|
||||
"@backstage/plugin-search-common": "^0.3.6-next.0",
|
||||
"@backstage/plugin-search-react": "^0.2.2-next.3",
|
||||
"@backstage/theme": "^0.2.16-next.1",
|
||||
"@backstage/catalog-model": "^1.1.0",
|
||||
"@backstage/core-components": "^0.11.0",
|
||||
"@backstage/core-plugin-api": "^1.0.5",
|
||||
"@backstage/integration-react": "^1.1.3",
|
||||
"@backstage/plugin-adr-common": "^0.2.0",
|
||||
"@backstage/plugin-catalog-react": "^1.1.3",
|
||||
"@backstage/plugin-search-common": "^1.0.0",
|
||||
"@backstage/plugin-search-react": "^1.0.1",
|
||||
"@backstage/theme": "^0.2.16",
|
||||
"@material-ui/core": "^4.12.2",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "4.0.0-alpha.57",
|
||||
@@ -44,10 +45,10 @@
|
||||
"react": "^16.13.1 || ^17.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.18.0-next.3",
|
||||
"@backstage/core-app-api": "^1.0.4-next.1",
|
||||
"@backstage/dev-utils": "^1.0.4-next.3",
|
||||
"@backstage/test-utils": "^1.1.2-next.2",
|
||||
"@backstage/cli": "^0.18.1",
|
||||
"@backstage/core-app-api": "^1.0.5",
|
||||
"@backstage/dev-utils": "^1.0.5",
|
||||
"@backstage/test-utils": "^1.1.3",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^12.1.3",
|
||||
"@testing-library/user-event": "^14.0.0",
|
||||
@@ -55,7 +56,7 @@
|
||||
"@types/jest": "*",
|
||||
"@types/node": "*",
|
||||
"cross-fetch": "^3.1.5",
|
||||
"msw": "^0.44.0"
|
||||
"msw": "^0.45.0"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
||||
@@ -32,15 +32,14 @@ import { AdrContentDecorator } from './types';
|
||||
|
||||
/**
|
||||
* Component to fetch and render an ADR.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const AdrReader = ({
|
||||
adr,
|
||||
decorators,
|
||||
}: {
|
||||
export const AdrReader = (props: {
|
||||
adr: string;
|
||||
decorators?: AdrContentDecorator[];
|
||||
}) => {
|
||||
const { adr, decorators } = props;
|
||||
const { entity } = useEntity();
|
||||
const scmIntegrations = useApi(scmIntegrationsApiRef);
|
||||
const adrLocationUrl = getAdrLocationUrl(entity, scmIntegrations);
|
||||
|
||||
@@ -58,13 +58,11 @@ const useStyles = makeStyles((theme: Theme) => ({
|
||||
* Component for browsing ADRs on an entity page.
|
||||
* @public
|
||||
*/
|
||||
export const EntityAdrContent = ({
|
||||
contentDecorators,
|
||||
filePathFilterFn,
|
||||
}: {
|
||||
export const EntityAdrContent = (props: {
|
||||
contentDecorators?: AdrContentDecorator[];
|
||||
filePathFilterFn?: AdrFilePathFilterFn;
|
||||
}) => {
|
||||
const { contentDecorators, filePathFilterFn } = props;
|
||||
const classes = useStyles();
|
||||
const { entity } = useEntity();
|
||||
const rootLink = useRouteRef(rootRouteRef);
|
||||
|
||||
@@ -23,9 +23,11 @@ import {
|
||||
ListItemText,
|
||||
makeStyles,
|
||||
} from '@material-ui/core';
|
||||
import { parseEntityRef } from '@backstage/catalog-model';
|
||||
import { Link } from '@backstage/core-components';
|
||||
import { useAnalytics } from '@backstage/core-plugin-api';
|
||||
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';
|
||||
|
||||
@@ -41,20 +43,16 @@ const useStyles = makeStyles({
|
||||
});
|
||||
|
||||
/**
|
||||
* A component to display a ADR search result
|
||||
* A component to display an ADR search result.
|
||||
* @public
|
||||
*/
|
||||
export const AdrSearchResultListItem = ({
|
||||
lineClamp = 5,
|
||||
highlight,
|
||||
rank,
|
||||
result,
|
||||
}: {
|
||||
export function AdrSearchResultListItem(props: {
|
||||
lineClamp?: number;
|
||||
highlight?: ResultHighlight;
|
||||
rank?: number;
|
||||
result: AdrDocument;
|
||||
}) => {
|
||||
}) {
|
||||
const { lineClamp = 5, highlight, rank, result } = props;
|
||||
const classes = useStyles();
|
||||
const analytics = useAnalytics();
|
||||
|
||||
@@ -104,6 +102,13 @@ export const AdrSearchResultListItem = ({
|
||||
}
|
||||
/>
|
||||
<Box>
|
||||
<Chip
|
||||
label={`Entity: ${
|
||||
result.entityTitle ??
|
||||
humanizeEntityRef(parseEntityRef(result.entityRef))
|
||||
}`}
|
||||
size="small"
|
||||
/>
|
||||
{result.status && (
|
||||
<Chip label={`Status: ${result.status}`} size="small" />
|
||||
)}
|
||||
@@ -113,4 +118,4 @@ export const AdrSearchResultListItem = ({
|
||||
<Divider component="li" />
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,5 +1,28 @@
|
||||
# @backstage/plugin-airbrake-backend
|
||||
|
||||
## 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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-airbrake-backend",
|
||||
"version": "0.2.7-next.1",
|
||||
"version": "0.2.8",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -22,7 +22,7 @@
|
||||
"clean": "backstage-cli package clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-common": "^0.14.1-next.3",
|
||||
"@backstage/backend-common": "^0.15.0",
|
||||
"@backstage/config": "^1.0.1",
|
||||
"@types/express": "*",
|
||||
"express": "^4.17.1",
|
||||
@@ -33,11 +33,11 @@
|
||||
"yn": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.18.0-next.3",
|
||||
"@backstage/cli": "^0.18.1",
|
||||
"@types/http-proxy-middleware": "^0.19.3",
|
||||
"@types/supertest": "^2.0.8",
|
||||
"supertest": "^6.1.6",
|
||||
"msw": "^0.44.0"
|
||||
"msw": "^0.45.0"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
|
||||
@@ -1,5 +1,51 @@
|
||||
# @backstage/plugin-airbrake
|
||||
|
||||
## 0.3.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/core-components@0.11.0
|
||||
- @backstage/core-plugin-api@1.0.5
|
||||
- @backstage/plugin-catalog-react@1.1.3
|
||||
- @backstage/dev-utils@1.0.5
|
||||
- @backstage/test-utils@1.1.3
|
||||
|
||||
## 0.3.8-next.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/plugin-catalog-react@1.1.3-next.2
|
||||
- @backstage/core-components@0.11.0-next.2
|
||||
- @backstage/dev-utils@1.0.5-next.1
|
||||
|
||||
## 0.3.8-next.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/core-plugin-api@1.0.5-next.0
|
||||
- @backstage/plugin-catalog-react@1.1.3-next.0
|
||||
- @backstage/core-components@0.10.1-next.0
|
||||
- @backstage/dev-utils@1.0.5-next.0
|
||||
- @backstage/test-utils@1.1.3-next.0
|
||||
|
||||
## 0.3.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- a70869e775: Updated dependency `msw` to `^0.43.0`.
|
||||
- 8006d0f9bf: Updated dependency `msw` to `^0.44.0`.
|
||||
- Updated dependencies
|
||||
- @backstage/core-components@0.10.0
|
||||
- @backstage/catalog-model@1.1.0
|
||||
- @backstage/core-plugin-api@1.0.4
|
||||
- @backstage/test-utils@1.1.2
|
||||
- @backstage/plugin-catalog-react@1.1.2
|
||||
- @backstage/theme@0.2.16
|
||||
- @backstage/dev-utils@1.0.4
|
||||
|
||||
## 0.3.7-next.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -13,6 +13,7 @@ export const airbrakePlugin: BackstagePlugin<
|
||||
{
|
||||
root: RouteRef<undefined>;
|
||||
},
|
||||
{},
|
||||
{}
|
||||
>;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-airbrake",
|
||||
"version": "0.3.7-next.3",
|
||||
"version": "0.3.8",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -23,13 +23,13 @@
|
||||
"clean": "backstage-cli package clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^1.1.0-next.3",
|
||||
"@backstage/core-components": "^0.10.0-next.3",
|
||||
"@backstage/core-plugin-api": "^1.0.4-next.0",
|
||||
"@backstage/dev-utils": "^1.0.4-next.3",
|
||||
"@backstage/plugin-catalog-react": "^1.1.2-next.3",
|
||||
"@backstage/test-utils": "^1.1.2-next.2",
|
||||
"@backstage/theme": "^0.2.16-next.1",
|
||||
"@backstage/catalog-model": "^1.1.0",
|
||||
"@backstage/core-components": "^0.11.0",
|
||||
"@backstage/core-plugin-api": "^1.0.5",
|
||||
"@backstage/dev-utils": "^1.0.5",
|
||||
"@backstage/plugin-catalog-react": "^1.1.3",
|
||||
"@backstage/test-utils": "^1.1.3",
|
||||
"@backstage/theme": "^0.2.16",
|
||||
"@material-ui/core": "^4.12.2",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "4.0.0-alpha.57",
|
||||
@@ -40,10 +40,10 @@
|
||||
"react": "^16.13.1 || ^17.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/app-defaults": "^1.0.4-next.3",
|
||||
"@backstage/cli": "^0.18.0-next.3",
|
||||
"@backstage/dev-utils": "^1.0.4-next.3",
|
||||
"@backstage/test-utils": "^1.1.2-next.2",
|
||||
"@backstage/app-defaults": "^1.0.5",
|
||||
"@backstage/cli": "^0.18.1",
|
||||
"@backstage/dev-utils": "^1.0.5",
|
||||
"@backstage/test-utils": "^1.1.3",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^12.1.3",
|
||||
"@testing-library/user-event": "^14.0.0",
|
||||
@@ -51,7 +51,7 @@
|
||||
"@types/node": "^16.11.26",
|
||||
"@types/object-hash": "^2.2.1",
|
||||
"cross-fetch": "^3.1.5",
|
||||
"msw": "^0.44.0",
|
||||
"msw": "^0.45.0",
|
||||
"react-router": "6.0.0-beta.0"
|
||||
},
|
||||
"files": [
|
||||
|
||||
@@ -1,5 +1,44 @@
|
||||
# @backstage/plugin-allure
|
||||
|
||||
## 0.1.24
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/core-components@0.11.0
|
||||
- @backstage/core-plugin-api@1.0.5
|
||||
- @backstage/plugin-catalog-react@1.1.3
|
||||
|
||||
## 0.1.24-next.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/plugin-catalog-react@1.1.3-next.2
|
||||
- @backstage/core-components@0.11.0-next.2
|
||||
|
||||
## 0.1.24-next.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/core-plugin-api@1.0.5-next.0
|
||||
- @backstage/plugin-catalog-react@1.1.3-next.0
|
||||
- @backstage/core-components@0.10.1-next.0
|
||||
|
||||
## 0.1.23
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- a70869e775: Updated dependency `msw` to `^0.43.0`.
|
||||
- 8006d0f9bf: Updated dependency `msw` to `^0.44.0`.
|
||||
- Updated dependencies
|
||||
- @backstage/core-components@0.10.0
|
||||
- @backstage/catalog-model@1.1.0
|
||||
- @backstage/core-plugin-api@1.0.4
|
||||
- @backstage/plugin-catalog-react@1.1.2
|
||||
- @backstage/theme@0.2.16
|
||||
|
||||
## 0.1.23-next.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -21,6 +21,7 @@ export const allurePlugin: BackstagePlugin<
|
||||
{
|
||||
root: RouteRef<undefined>;
|
||||
},
|
||||
{},
|
||||
{}
|
||||
>;
|
||||
|
||||
|
||||
+11
-11
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/plugin-allure",
|
||||
"description": "A Backstage plugin that integrates with Allure",
|
||||
"version": "0.1.23-next.3",
|
||||
"version": "0.1.24",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -25,11 +25,11 @@
|
||||
"clean": "backstage-cli package clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^1.1.0-next.3",
|
||||
"@backstage/core-components": "^0.10.0-next.3",
|
||||
"@backstage/core-plugin-api": "^1.0.4-next.0",
|
||||
"@backstage/plugin-catalog-react": "^1.1.2-next.3",
|
||||
"@backstage/theme": "^0.2.16-next.1",
|
||||
"@backstage/catalog-model": "^1.1.0",
|
||||
"@backstage/core-components": "^0.11.0",
|
||||
"@backstage/core-plugin-api": "^1.0.5",
|
||||
"@backstage/plugin-catalog-react": "^1.1.3",
|
||||
"@backstage/theme": "^0.2.16",
|
||||
"@material-ui/core": "^4.12.2",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "4.0.0-alpha.57",
|
||||
@@ -40,17 +40,17 @@
|
||||
"react": "^16.13.1 || ^17.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.18.0-next.3",
|
||||
"@backstage/core-app-api": "^1.0.4-next.1",
|
||||
"@backstage/dev-utils": "^1.0.4-next.3",
|
||||
"@backstage/test-utils": "^1.1.2-next.2",
|
||||
"@backstage/cli": "^0.18.1",
|
||||
"@backstage/core-app-api": "^1.0.5",
|
||||
"@backstage/dev-utils": "^1.0.5",
|
||||
"@backstage/test-utils": "^1.1.3",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^12.1.3",
|
||||
"@testing-library/user-event": "^14.0.0",
|
||||
"@types/jest": "^26.0.7",
|
||||
"@types/node": "^16.11.26",
|
||||
"cross-fetch": "^3.1.5",
|
||||
"msw": "^0.44.0"
|
||||
"msw": "^0.45.0"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
||||
@@ -1,5 +1,39 @@
|
||||
# @backstage/plugin-analytics-module-ga
|
||||
|
||||
## 0.1.19
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/core-components@0.11.0
|
||||
- @backstage/core-plugin-api@1.0.5
|
||||
|
||||
## 0.1.19-next.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/core-components@0.11.0-next.2
|
||||
|
||||
## 0.1.19-next.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/core-plugin-api@1.0.5-next.0
|
||||
- @backstage/core-components@0.10.1-next.0
|
||||
|
||||
## 0.1.18
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- a70869e775: Updated dependency `msw` to `^0.43.0`.
|
||||
- 8006d0f9bf: Updated dependency `msw` to `^0.44.0`.
|
||||
- Updated dependencies
|
||||
- @backstage/core-components@0.10.0
|
||||
- @backstage/core-plugin-api@1.0.4
|
||||
- @backstage/theme@0.2.16
|
||||
|
||||
## 0.1.18-next.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -10,7 +10,7 @@ import { Config } from '@backstage/config';
|
||||
import { IdentityApi } from '@backstage/core-plugin-api';
|
||||
|
||||
// @public @deprecated (undocumented)
|
||||
export const analyticsModuleGA: BackstagePlugin<{}, {}>;
|
||||
export const analyticsModuleGA: BackstagePlugin<{}, {}, {}>;
|
||||
|
||||
// @public
|
||||
export class GoogleAnalytics implements AnalyticsApi {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-analytics-module-ga",
|
||||
"version": "0.1.18-next.3",
|
||||
"version": "0.1.19",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -25,9 +25,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/config": "^1.0.1",
|
||||
"@backstage/core-components": "^0.10.0-next.3",
|
||||
"@backstage/core-plugin-api": "^1.0.4-next.0",
|
||||
"@backstage/theme": "^0.2.16-next.1",
|
||||
"@backstage/core-components": "^0.11.0",
|
||||
"@backstage/core-plugin-api": "^1.0.5",
|
||||
"@backstage/theme": "^0.2.16",
|
||||
"@material-ui/core": "^4.12.2",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "4.0.0-alpha.57",
|
||||
@@ -38,17 +38,17 @@
|
||||
"react": "^16.13.1 || ^17.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.18.0-next.3",
|
||||
"@backstage/core-app-api": "^1.0.4-next.1",
|
||||
"@backstage/dev-utils": "^1.0.4-next.3",
|
||||
"@backstage/test-utils": "^1.1.2-next.2",
|
||||
"@backstage/cli": "^0.18.1",
|
||||
"@backstage/core-app-api": "^1.0.5",
|
||||
"@backstage/dev-utils": "^1.0.5",
|
||||
"@backstage/test-utils": "^1.1.3",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^12.1.3",
|
||||
"@testing-library/user-event": "^14.0.0",
|
||||
"@types/jest": "^26.0.7",
|
||||
"@types/node": "^16.11.26",
|
||||
"cross-fetch": "^3.1.5",
|
||||
"msw": "^0.44.0"
|
||||
"msw": "^0.45.0"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
|
||||
@@ -1,5 +1,42 @@
|
||||
# @backstage/plugin-apache-airflow
|
||||
|
||||
## 0.2.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/core-components@0.11.0
|
||||
- @backstage/core-plugin-api@1.0.5
|
||||
|
||||
## 0.2.1-next.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/core-components@0.11.0-next.2
|
||||
|
||||
## 0.2.1-next.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/core-plugin-api@1.0.5-next.0
|
||||
- @backstage/core-components@0.10.1-next.0
|
||||
|
||||
## 0.2.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 01f976ea72: Exposed DagTableComponent as standalone component + added a prop to get only select DAGs instead of the full list
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- a70869e775: Updated dependency `msw` to `^0.43.0`.
|
||||
- 8006d0f9bf: Updated dependency `msw` to `^0.44.0`.
|
||||
- Updated dependencies
|
||||
- @backstage/core-components@0.10.0
|
||||
- @backstage/core-plugin-api@1.0.4
|
||||
|
||||
## 0.2.0-next.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -9,9 +9,7 @@ import { BackstagePlugin } from '@backstage/core-plugin-api';
|
||||
import { RouteRef } from '@backstage/core-plugin-api';
|
||||
|
||||
// @public
|
||||
export const ApacheAirflowDagTable: ({
|
||||
dagIds,
|
||||
}: {
|
||||
export const ApacheAirflowDagTable: (props: {
|
||||
dagIds?: string[] | undefined;
|
||||
}) => JSX.Element;
|
||||
|
||||
@@ -27,6 +25,7 @@ export const apacheAirflowPlugin: BackstagePlugin<
|
||||
{
|
||||
root: RouteRef<undefined>;
|
||||
},
|
||||
{},
|
||||
{}
|
||||
>;
|
||||
```
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-apache-airflow",
|
||||
"version": "0.2.0-next.3",
|
||||
"version": "0.2.1",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -23,8 +23,8 @@
|
||||
"clean": "backstage-cli package clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/core-components": "^0.10.0-next.3",
|
||||
"@backstage/core-plugin-api": "^1.0.4-next.0",
|
||||
"@backstage/core-components": "^0.11.0",
|
||||
"@backstage/core-plugin-api": "^1.0.5",
|
||||
"@material-ui/core": "^4.12.2",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "4.0.0-alpha.57",
|
||||
@@ -36,16 +36,16 @@
|
||||
"react": "^16.13.1 || ^17.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.18.0-next.3",
|
||||
"@backstage/core-app-api": "^1.0.4-next.1",
|
||||
"@backstage/dev-utils": "^1.0.4-next.3",
|
||||
"@backstage/test-utils": "^1.1.2-next.2",
|
||||
"@backstage/cli": "^0.18.1",
|
||||
"@backstage/core-app-api": "^1.0.5",
|
||||
"@backstage/dev-utils": "^1.0.5",
|
||||
"@backstage/test-utils": "^1.1.3",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^12.1.3",
|
||||
"@testing-library/user-event": "^14.0.0",
|
||||
"@types/jest": "^26.0.7",
|
||||
"@types/node": "^16.11.26",
|
||||
"msw": "^0.44.0"
|
||||
"msw": "^0.45.0"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
|
||||
@@ -134,7 +134,8 @@ type DagTableComponentProps = {
|
||||
dagIds?: string[];
|
||||
};
|
||||
|
||||
export const DagTableComponent = ({ dagIds }: DagTableComponentProps) => {
|
||||
export const DagTableComponent = (props: DagTableComponentProps) => {
|
||||
const { dagIds } = props;
|
||||
const apiClient = useApi(apacheAirflowApiRef);
|
||||
|
||||
const { value, loading, error } = useAsync(async (): Promise<Dag[]> => {
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @backstage/plugin-api-docs-module-protoc-gen-doc
|
||||
|
||||
## 0.1.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- e0328f2107: Added the new `grpcDocsApiWidget` to render `protoc-gen-doc` generated descriptors by the `grpc-docs` package.
|
||||
|
||||
## 0.1.0-next.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/plugin-api-docs-module-protoc-gen-doc",
|
||||
"description": "Additional functionalities for the api-docs plugin that renders the output of the protoc-gen-doc",
|
||||
"version": "0.1.0-next.0",
|
||||
"version": "0.1.0",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -37,7 +37,7 @@
|
||||
"react": "^16.13.1 || ^17.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.18.0-next.1",
|
||||
"@backstage/cli": "^0.18.1-next.0",
|
||||
"@testing-library/jest-dom": "^5.16.4",
|
||||
"@types/react": "^16.13.1 || ^17.0.0"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,62 @@
|
||||
# @backstage/plugin-api-docs
|
||||
|
||||
## 0.8.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- dae12c71cf: Updated dependency `@asyncapi/react-component` to `1.0.0-next.40`.
|
||||
- Updated dependencies
|
||||
- @backstage/core-components@0.11.0
|
||||
- @backstage/core-plugin-api@1.0.5
|
||||
- @backstage/plugin-catalog@1.5.0
|
||||
- @backstage/plugin-catalog-react@1.1.3
|
||||
|
||||
## 0.8.8-next.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/plugin-catalog-react@1.1.3-next.2
|
||||
- @backstage/core-components@0.11.0-next.2
|
||||
- @backstage/plugin-catalog@1.5.0-next.2
|
||||
|
||||
## 0.8.8-next.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- dae12c71cf: Updated dependency `@asyncapi/react-component` to `1.0.0-next.40`.
|
||||
- Updated dependencies
|
||||
- @backstage/core-components@0.10.1-next.1
|
||||
- @backstage/plugin-catalog@1.5.0-next.1
|
||||
- @backstage/plugin-catalog-react@1.1.3-next.1
|
||||
|
||||
## 0.8.8-next.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/core-plugin-api@1.0.5-next.0
|
||||
- @backstage/plugin-catalog@1.5.0-next.0
|
||||
- @backstage/plugin-catalog-react@1.1.3-next.0
|
||||
- @backstage/core-components@0.10.1-next.0
|
||||
|
||||
## 0.8.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- a70869e775: Updated dependency `msw` to `^0.43.0`.
|
||||
- 8006d0f9bf: Updated dependency `msw` to `^0.44.0`.
|
||||
- b76eea25ed: Updated dependency `@asyncapi/react-component` to `1.0.0-next.39`.
|
||||
- a274fe38b9: Add hidden title column to catalog and API table to enable filtering by title.
|
||||
- 9432a05cf3: Set font colors correctly for descriptions containing HTML
|
||||
- Updated dependencies
|
||||
- @backstage/core-components@0.10.0
|
||||
- @backstage/catalog-model@1.1.0
|
||||
- @backstage/plugin-catalog@1.4.0
|
||||
- @backstage/core-plugin-api@1.0.4
|
||||
- @backstage/plugin-catalog-react@1.1.2
|
||||
- @backstage/theme@0.2.16
|
||||
|
||||
## 0.8.7-next.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -17,13 +17,9 @@ import { TableColumn } from '@backstage/core-components';
|
||||
import { TableProps } from '@backstage/core-components';
|
||||
import { UserListFilterKind } from '@backstage/plugin-catalog-react';
|
||||
|
||||
// Warning: (ae-missing-release-tag) "ApiDefinitionCard" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const ApiDefinitionCard: () => JSX.Element;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "ApiDefinitionWidget" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export type ApiDefinitionWidget = {
|
||||
type: string;
|
||||
@@ -32,14 +28,17 @@ export type ApiDefinitionWidget = {
|
||||
rawLanguage?: string;
|
||||
};
|
||||
|
||||
// Warning: (ae-forgotten-export) The symbol "ApiDocsConfig" needs to be exported by the entry point index.d.ts
|
||||
// Warning: (ae-missing-release-tag) "apiDocsConfigRef" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export interface ApiDocsConfig {
|
||||
// (undocumented)
|
||||
getApiDefinitionWidget: (
|
||||
apiEntity: ApiEntity,
|
||||
) => ApiDefinitionWidget | undefined;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export const apiDocsConfigRef: ApiRef<ApiDocsConfig>;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "apiDocsPlugin" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
const apiDocsPlugin: BackstagePlugin<
|
||||
{
|
||||
@@ -47,7 +46,8 @@ const apiDocsPlugin: BackstagePlugin<
|
||||
},
|
||||
{
|
||||
registerApi: ExternalRouteRef<undefined, true>;
|
||||
}
|
||||
},
|
||||
{}
|
||||
>;
|
||||
export { apiDocsPlugin };
|
||||
export { apiDocsPlugin as plugin };
|
||||
@@ -57,54 +57,38 @@ export const ApiExplorerIndexPage: (
|
||||
props: DefaultApiExplorerPageProps,
|
||||
) => JSX.Element;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "ApiExplorerPage" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const ApiExplorerPage: (
|
||||
props: DefaultApiExplorerPageProps,
|
||||
) => JSX.Element;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "ApiTypeTitle" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const ApiTypeTitle: ({
|
||||
apiEntity,
|
||||
}: {
|
||||
apiEntity: ApiEntity;
|
||||
}) => JSX.Element;
|
||||
export const ApiTypeTitle: (props: { apiEntity: ApiEntity }) => JSX.Element;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "AsyncApiDefinitionWidget" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const AsyncApiDefinitionWidget: (
|
||||
props: AsyncApiDefinitionWidgetProps,
|
||||
) => JSX.Element;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "AsyncApiDefinitionWidgetProps" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export type AsyncApiDefinitionWidgetProps = {
|
||||
definition: string;
|
||||
};
|
||||
|
||||
// Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts
|
||||
// Warning: (ae-missing-release-tag) "ConsumedApisCard" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const ConsumedApisCard: ({ variant }: Props) => JSX.Element;
|
||||
export const ConsumedApisCard: (props: {
|
||||
variant?: InfoCardVariants;
|
||||
}) => JSX.Element;
|
||||
|
||||
// Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts
|
||||
// Warning: (ae-missing-release-tag) "ConsumingComponentsCard" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const ConsumingComponentsCard: ({ variant }: Props_4) => JSX.Element;
|
||||
export const ConsumingComponentsCard: (props: {
|
||||
variant?: InfoCardVariants;
|
||||
}) => JSX.Element;
|
||||
|
||||
// @public
|
||||
export const DefaultApiExplorerPage: ({
|
||||
initiallySelectedFilter,
|
||||
columns,
|
||||
actions,
|
||||
}: DefaultApiExplorerPageProps) => JSX.Element;
|
||||
export const DefaultApiExplorerPage: (
|
||||
props: DefaultApiExplorerPageProps,
|
||||
) => JSX.Element;
|
||||
|
||||
// @public
|
||||
export type DefaultApiExplorerPageProps = {
|
||||
@@ -113,119 +97,80 @@ export type DefaultApiExplorerPageProps = {
|
||||
actions?: TableProps<CatalogTableRow>['actions'];
|
||||
};
|
||||
|
||||
// Warning: (ae-missing-release-tag) "defaultDefinitionWidgets" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export function defaultDefinitionWidgets(): ApiDefinitionWidget[];
|
||||
|
||||
// Warning: (ae-missing-release-tag) "EntityApiDefinitionCard" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const EntityApiDefinitionCard: () => JSX.Element;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "EntityConsumedApisCard" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const EntityConsumedApisCard: ({
|
||||
variant,
|
||||
}: {
|
||||
export const EntityConsumedApisCard: (props: {
|
||||
variant?: InfoCardVariants | undefined;
|
||||
}) => JSX.Element;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "EntityConsumingComponentsCard" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const EntityConsumingComponentsCard: ({
|
||||
variant,
|
||||
}: {
|
||||
export const EntityConsumingComponentsCard: (props: {
|
||||
variant?: InfoCardVariants | undefined;
|
||||
}) => JSX.Element;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "EntityHasApisCard" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const EntityHasApisCard: ({
|
||||
variant,
|
||||
}: {
|
||||
export const EntityHasApisCard: (props: {
|
||||
variant?: InfoCardVariants | undefined;
|
||||
}) => JSX.Element;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "EntityProvidedApisCard" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const EntityProvidedApisCard: ({
|
||||
variant,
|
||||
}: {
|
||||
export const EntityProvidedApisCard: (props: {
|
||||
variant?: InfoCardVariants | undefined;
|
||||
}) => JSX.Element;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "EntityProvidingComponentsCard" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const EntityProvidingComponentsCard: ({
|
||||
variant,
|
||||
}: {
|
||||
export const EntityProvidingComponentsCard: (props: {
|
||||
variant?: InfoCardVariants | undefined;
|
||||
}) => JSX.Element;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "GraphQlDefinitionWidget" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const GraphQlDefinitionWidget: (
|
||||
props: GraphQlDefinitionWidgetProps,
|
||||
) => JSX.Element;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "GraphQlDefinitionWidgetProps" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export type GraphQlDefinitionWidgetProps = {
|
||||
definition: string;
|
||||
};
|
||||
|
||||
// Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts
|
||||
// Warning: (ae-missing-release-tag) "HasApisCard" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const HasApisCard: ({ variant }: Props_2) => JSX.Element;
|
||||
export const HasApisCard: (props: {
|
||||
variant?: InfoCardVariants;
|
||||
}) => JSX.Element;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "OpenApiDefinitionWidget" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const OpenApiDefinitionWidget: (
|
||||
props: OpenApiDefinitionWidgetProps,
|
||||
) => JSX.Element;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "OpenApiDefinitionWidgetProps" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export type OpenApiDefinitionWidgetProps = {
|
||||
definition: string;
|
||||
};
|
||||
|
||||
// Warning: (ae-missing-release-tag) "PlainApiDefinitionWidget" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const PlainApiDefinitionWidget: (
|
||||
props: PlainApiDefinitionWidgetProps,
|
||||
) => JSX.Element;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "PlainApiDefinitionWidgetProps" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export type PlainApiDefinitionWidgetProps = {
|
||||
definition: any;
|
||||
language: string;
|
||||
};
|
||||
|
||||
// Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts
|
||||
// Warning: (ae-missing-release-tag) "ProvidedApisCard" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const ProvidedApisCard: ({ variant }: Props_3) => JSX.Element;
|
||||
export const ProvidedApisCard: (props: {
|
||||
variant?: InfoCardVariants;
|
||||
}) => JSX.Element;
|
||||
|
||||
// Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts
|
||||
// Warning: (ae-missing-release-tag) "ProvidingComponentsCard" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const ProvidingComponentsCard: ({ variant }: Props_5) => JSX.Element;
|
||||
export const ProvidingComponentsCard: (props: {
|
||||
variant?: InfoCardVariants;
|
||||
}) => JSX.Element;
|
||||
```
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/plugin-api-docs",
|
||||
"description": "A Backstage plugin that helps represent API entities in the frontend",
|
||||
"version": "0.8.7-next.3",
|
||||
"version": "0.8.8",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -33,13 +33,13 @@
|
||||
"clean": "backstage-cli package clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@asyncapi/react-component": "1.0.0-next.39",
|
||||
"@backstage/catalog-model": "^1.1.0-next.3",
|
||||
"@backstage/core-components": "^0.10.0-next.3",
|
||||
"@backstage/core-plugin-api": "^1.0.4-next.0",
|
||||
"@backstage/plugin-catalog": "^1.4.0-next.3",
|
||||
"@backstage/plugin-catalog-react": "^1.1.2-next.3",
|
||||
"@backstage/theme": "^0.2.16-next.1",
|
||||
"@asyncapi/react-component": "1.0.0-next.40",
|
||||
"@backstage/catalog-model": "^1.1.0",
|
||||
"@backstage/core-components": "^0.11.0",
|
||||
"@backstage/core-plugin-api": "^1.0.5",
|
||||
"@backstage/plugin-catalog": "^1.5.0",
|
||||
"@backstage/plugin-catalog-react": "^1.1.3",
|
||||
"@backstage/theme": "^0.2.16",
|
||||
"@material-ui/core": "^4.12.2",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "4.0.0-alpha.57",
|
||||
@@ -57,10 +57,10 @@
|
||||
"react": "^16.13.1 || ^17.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.18.0-next.3",
|
||||
"@backstage/core-app-api": "^1.0.4-next.1",
|
||||
"@backstage/dev-utils": "^1.0.4-next.3",
|
||||
"@backstage/test-utils": "^1.1.2-next.2",
|
||||
"@backstage/cli": "^0.18.1",
|
||||
"@backstage/core-app-api": "^1.0.5",
|
||||
"@backstage/dev-utils": "^1.0.5",
|
||||
"@backstage/test-utils": "^1.1.3",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^12.1.3",
|
||||
"@testing-library/user-event": "^14.0.0",
|
||||
@@ -68,7 +68,7 @@
|
||||
"@types/node": "^16.11.26",
|
||||
"@types/swagger-ui-react": "^4.1.1",
|
||||
"cross-fetch": "^3.1.5",
|
||||
"msw": "^0.44.0"
|
||||
"msw": "^0.45.0"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
||||
@@ -24,6 +24,7 @@ import { PlainApiDefinitionWidget } from '../PlainApiDefinitionWidget';
|
||||
import { CardTab, TabbedCard } from '@backstage/core-components';
|
||||
import { useApi } from '@backstage/core-plugin-api';
|
||||
|
||||
/** @public */
|
||||
export const ApiDefinitionCard = () => {
|
||||
const { entity } = useEntity<ApiEntity>();
|
||||
const config = useApi(apiDocsConfigRef);
|
||||
|
||||
@@ -19,6 +19,7 @@ import { GraphQlDefinitionWidget } from '../GraphQlDefinitionWidget';
|
||||
import { OpenApiDefinitionWidget } from '../OpenApiDefinitionWidget';
|
||||
import { GrpcApiDefinitionWidget } from '../GrpcApiDefinitionWidget';
|
||||
|
||||
/** @public */
|
||||
export type ApiDefinitionWidget = {
|
||||
type: string;
|
||||
title: string;
|
||||
@@ -26,6 +27,7 @@ export type ApiDefinitionWidget = {
|
||||
rawLanguage?: string;
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export function defaultDefinitionWidgets(): ApiDefinitionWidget[] {
|
||||
return [
|
||||
{
|
||||
|
||||
@@ -19,7 +19,11 @@ import React from 'react';
|
||||
import { apiDocsConfigRef } from '../../config';
|
||||
import { useApi } from '@backstage/core-plugin-api';
|
||||
|
||||
export const ApiTypeTitle = ({ apiEntity }: { apiEntity: ApiEntity }) => {
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const ApiTypeTitle = (props: { apiEntity: ApiEntity }) => {
|
||||
const { apiEntity } = props;
|
||||
const config = useApi(apiDocsConfigRef);
|
||||
const definition = config.getApiDefinitionWidget(apiEntity);
|
||||
const type = definition ? definition.title : apiEntity.spec.type;
|
||||
|
||||
@@ -64,11 +64,9 @@ export type DefaultApiExplorerPageProps = {
|
||||
* DefaultApiExplorerPage
|
||||
* @public
|
||||
*/
|
||||
export const DefaultApiExplorerPage = ({
|
||||
initiallySelectedFilter = 'all',
|
||||
columns,
|
||||
actions,
|
||||
}: DefaultApiExplorerPageProps) => {
|
||||
export const DefaultApiExplorerPage = (props: DefaultApiExplorerPageProps) => {
|
||||
const { initiallySelectedFilter = 'all', columns, actions } = props;
|
||||
|
||||
const configApi = useApi(configApiRef);
|
||||
const generatedSubtitle = `${
|
||||
configApi.getOptionalString('organization.name') ?? 'Backstage'
|
||||
|
||||
@@ -32,11 +32,11 @@ import {
|
||||
WarningPanel,
|
||||
} from '@backstage/core-components';
|
||||
|
||||
type Props = {
|
||||
variant?: InfoCardVariants;
|
||||
};
|
||||
|
||||
export const ConsumedApisCard = ({ variant = 'gridItem' }: Props) => {
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const ConsumedApisCard = (props: { variant?: InfoCardVariants }) => {
|
||||
const { variant = 'gridItem' } = props;
|
||||
const { entity } = useEntity();
|
||||
const { entities, loading, error } = useRelatedEntities(entity, {
|
||||
type: RELATION_CONSUMES_API,
|
||||
|
||||
@@ -33,10 +33,6 @@ import {
|
||||
WarningPanel,
|
||||
} from '@backstage/core-components';
|
||||
|
||||
type Props = {
|
||||
variant?: InfoCardVariants;
|
||||
};
|
||||
|
||||
const columns: TableColumn<ApiEntity>[] = [
|
||||
EntityTable.columns.createEntityRefColumn({ defaultKind: 'API' }),
|
||||
EntityTable.columns.createOwnerColumn(),
|
||||
@@ -45,7 +41,11 @@ const columns: TableColumn<ApiEntity>[] = [
|
||||
EntityTable.columns.createMetadataDescriptionColumn(),
|
||||
];
|
||||
|
||||
export const HasApisCard = ({ variant = 'gridItem' }: Props) => {
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const HasApisCard = (props: { variant?: InfoCardVariants }) => {
|
||||
const { variant = 'gridItem' } = props;
|
||||
const { entity } = useEntity();
|
||||
const { entities, loading, error } = useRelatedEntities(entity, {
|
||||
type: RELATION_HAS_PART,
|
||||
|
||||
@@ -32,11 +32,11 @@ import {
|
||||
WarningPanel,
|
||||
} from '@backstage/core-components';
|
||||
|
||||
type Props = {
|
||||
variant?: InfoCardVariants;
|
||||
};
|
||||
|
||||
export const ProvidedApisCard = ({ variant = 'gridItem' }: Props) => {
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const ProvidedApisCard = (props: { variant?: InfoCardVariants }) => {
|
||||
const { variant = 'gridItem' } = props;
|
||||
const { entity } = useEntity();
|
||||
const { entities, loading, error } = useRelatedEntities(entity, {
|
||||
type: RELATION_PROVIDES_API,
|
||||
|
||||
@@ -25,10 +25,12 @@ const LazyAsyncApiDefinition = React.lazy(() =>
|
||||
})),
|
||||
);
|
||||
|
||||
/** @public */
|
||||
export type AsyncApiDefinitionWidgetProps = {
|
||||
definition: string;
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export const AsyncApiDefinitionWidget = (
|
||||
props: AsyncApiDefinitionWidgetProps,
|
||||
) => {
|
||||
|
||||
@@ -34,11 +34,13 @@ import {
|
||||
WarningPanel,
|
||||
} from '@backstage/core-components';
|
||||
|
||||
type Props = {
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const ConsumingComponentsCard = (props: {
|
||||
variant?: InfoCardVariants;
|
||||
};
|
||||
|
||||
export const ConsumingComponentsCard = ({ variant = 'gridItem' }: Props) => {
|
||||
}) => {
|
||||
const { variant = 'gridItem' } = props;
|
||||
const { entity } = useEntity();
|
||||
const { entities, loading, error } = useRelatedEntities(entity, {
|
||||
type: RELATION_API_CONSUMED_BY,
|
||||
|
||||
@@ -34,11 +34,11 @@ import {
|
||||
WarningPanel,
|
||||
} from '@backstage/core-components';
|
||||
|
||||
type Props = {
|
||||
/** @public */
|
||||
export const ProvidingComponentsCard = (props: {
|
||||
variant?: InfoCardVariants;
|
||||
};
|
||||
|
||||
export const ProvidingComponentsCard = ({ variant = 'gridItem' }: Props) => {
|
||||
}) => {
|
||||
const { variant = 'gridItem' } = props;
|
||||
const { entity } = useEntity();
|
||||
const { entities, loading, error } = useRelatedEntities(entity, {
|
||||
type: RELATION_API_PROVIDED_BY,
|
||||
|
||||
@@ -25,10 +25,12 @@ const LazyGraphQlDefinition = React.lazy(() =>
|
||||
})),
|
||||
);
|
||||
|
||||
/** @public */
|
||||
export type GraphQlDefinitionWidgetProps = {
|
||||
definition: string;
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export const GraphQlDefinitionWidget = (
|
||||
props: GraphQlDefinitionWidgetProps,
|
||||
) => {
|
||||
|
||||
@@ -19,10 +19,12 @@ import { CodeSnippet } from '@backstage/core-components';
|
||||
import { useTheme } from '@material-ui/core/styles';
|
||||
import { BackstageTheme } from '@backstage/theme';
|
||||
|
||||
/** @public */
|
||||
export type GrpcApiDefinitionWidgetProps = {
|
||||
definition: string;
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export const GrpcApiDefinitionWidget = (
|
||||
props: GrpcApiDefinitionWidgetProps,
|
||||
) => {
|
||||
|
||||
@@ -25,10 +25,12 @@ const LazyOpenApiDefinition = React.lazy(() =>
|
||||
})),
|
||||
);
|
||||
|
||||
/** @public */
|
||||
export type OpenApiDefinitionWidgetProps = {
|
||||
definition: string;
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export const OpenApiDefinitionWidget = (
|
||||
props: OpenApiDefinitionWidgetProps,
|
||||
) => {
|
||||
|
||||
@@ -17,11 +17,13 @@
|
||||
import React from 'react';
|
||||
import { CodeSnippet } from '@backstage/core-components';
|
||||
|
||||
/** @public */
|
||||
export type PlainApiDefinitionWidgetProps = {
|
||||
definition: any;
|
||||
language: string;
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export const PlainApiDefinitionWidget = (
|
||||
props: PlainApiDefinitionWidgetProps,
|
||||
) => {
|
||||
|
||||
@@ -18,10 +18,12 @@ import { ApiEntity } from '@backstage/catalog-model';
|
||||
import { ApiDefinitionWidget } from './components/ApiDefinitionCard/ApiDefinitionWidget';
|
||||
import { createApiRef } from '@backstage/core-plugin-api';
|
||||
|
||||
/** @public */
|
||||
export const apiDocsConfigRef = createApiRef<ApiDocsConfig>({
|
||||
id: 'plugin.api-docs.config',
|
||||
});
|
||||
|
||||
/** @public */
|
||||
export interface ApiDocsConfig {
|
||||
getApiDefinitionWidget: (
|
||||
apiEntity: ApiEntity,
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
*/
|
||||
|
||||
export * from './components';
|
||||
export type { ApiDocsConfig } from './config';
|
||||
export { apiDocsConfigRef } from './config';
|
||||
export {
|
||||
apiDocsPlugin,
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
createRoutableExtension,
|
||||
} from '@backstage/core-plugin-api';
|
||||
|
||||
/** @public */
|
||||
export const apiDocsPlugin = createPlugin({
|
||||
id: 'api-docs',
|
||||
routes: {
|
||||
@@ -49,6 +50,7 @@ export const apiDocsPlugin = createPlugin({
|
||||
},
|
||||
});
|
||||
|
||||
/** @public */
|
||||
export const ApiExplorerPage = apiDocsPlugin.provide(
|
||||
createRoutableExtension({
|
||||
name: 'ApiExplorerPage',
|
||||
@@ -58,6 +60,7 @@ export const ApiExplorerPage = apiDocsPlugin.provide(
|
||||
}),
|
||||
);
|
||||
|
||||
/** @public */
|
||||
export const EntityApiDefinitionCard = apiDocsPlugin.provide(
|
||||
createComponentExtension({
|
||||
name: 'EntityApiDefinitionCard',
|
||||
@@ -68,6 +71,7 @@ export const EntityApiDefinitionCard = apiDocsPlugin.provide(
|
||||
}),
|
||||
);
|
||||
|
||||
/** @public */
|
||||
export const EntityConsumedApisCard = apiDocsPlugin.provide(
|
||||
createComponentExtension({
|
||||
name: 'EntityConsumedApisCard',
|
||||
@@ -78,6 +82,7 @@ export const EntityConsumedApisCard = apiDocsPlugin.provide(
|
||||
}),
|
||||
);
|
||||
|
||||
/** @public */
|
||||
export const EntityConsumingComponentsCard = apiDocsPlugin.provide(
|
||||
createComponentExtension({
|
||||
name: 'EntityConsumingComponentsCard',
|
||||
@@ -90,6 +95,7 @@ export const EntityConsumingComponentsCard = apiDocsPlugin.provide(
|
||||
}),
|
||||
);
|
||||
|
||||
/** @public */
|
||||
export const EntityProvidedApisCard = apiDocsPlugin.provide(
|
||||
createComponentExtension({
|
||||
name: 'EntityProvidedApisCard',
|
||||
@@ -100,6 +106,7 @@ export const EntityProvidedApisCard = apiDocsPlugin.provide(
|
||||
}),
|
||||
);
|
||||
|
||||
/** @public */
|
||||
export const EntityProvidingComponentsCard = apiDocsPlugin.provide(
|
||||
createComponentExtension({
|
||||
name: 'EntityProvidingComponentsCard',
|
||||
@@ -112,6 +119,7 @@ export const EntityProvidingComponentsCard = apiDocsPlugin.provide(
|
||||
}),
|
||||
);
|
||||
|
||||
/** @public */
|
||||
export const EntityHasApisCard = apiDocsPlugin.provide(
|
||||
createComponentExtension({
|
||||
name: 'EntityHasApisCard',
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
|
||||
@@ -0,0 +1,37 @@
|
||||
# @backstage/plugin-apollo-explorer
|
||||
|
||||
## 0.1.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/core-components@0.11.0
|
||||
- @backstage/core-plugin-api@1.0.5
|
||||
|
||||
## 0.1.1-next.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/core-components@0.11.0-next.2
|
||||
|
||||
## 0.1.1-next.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/core-plugin-api@1.0.5-next.0
|
||||
- @backstage/core-components@0.10.1-next.0
|
||||
|
||||
## 0.1.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- d8fec19542: Apollo Explorer plugin now available! Installation instructions can be found in the plugin README
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/core-components@0.10.0
|
||||
- @backstage/core-plugin-api@1.0.4
|
||||
- @backstage/theme@0.2.16
|
||||
@@ -0,0 +1,66 @@
|
||||
# apollo-explorer
|
||||
|
||||
Welcome to the Apollo Explorer plugin!
|
||||
|
||||
This plugin allows users to directly embed an [Apollo](https://www.apollographql.com) graph explorer directly into
|
||||
Backstage!
|
||||
|
||||
## Getting started
|
||||
|
||||
### Getting an Apollo Graph Reference
|
||||
|
||||
First things first, you will need an Apollo account, and a graph imported into your account. This is beyond the scope of
|
||||
this plugin, so if you are totally new to Apollo, please reference their official
|
||||
documentation [here](https://www.apollographql.com/docs).
|
||||
|
||||
Once you have a graph set up in Apollo, we need to grab the graph reference. First, go to your Apollo graphs home page and choose the graph you wish to embed.
|
||||
|
||||

|
||||
|
||||
Once you are in your graph explorer, click the dropdown next to the share icon and select `Share as Embedded`
|
||||
|
||||

|
||||
|
||||
This modal contains a number of useful properties, all of which can be passed to the plugin via the component properties, but the only mandatory input we need from here is the `graphRef`.
|
||||
|
||||

|
||||
|
||||
Hold on to this snippet for a second while we set up the plugin ✨
|
||||
|
||||
### Installing the Backstage Plugin
|
||||
|
||||
First, add the plugin to your Backstage app
|
||||
|
||||
```shell
|
||||
yarn --cwd packages/app add @backstage/plugin-apollo-explorer
|
||||
```
|
||||
|
||||
Then, in `packages/app/src/App.tsx` add the plugin as a `Route`
|
||||
|
||||
```typescript
|
||||
<Route
|
||||
path="/apollo-explorer"
|
||||
element={
|
||||
<ApolloExplorerPage
|
||||
endpoints={[
|
||||
{ title: 'Github', graphRef: 'my-github-graph-ref@current' },
|
||||
{ title: 'Linear', graphRef: 'my-linear-graph-ref@current' },
|
||||
]}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
```
|
||||
|
||||
Then, in `packages/app/src/components/Root/Root.tsx` add a sidebar item so users can find your beautiful plugin!
|
||||
|
||||
```typescript
|
||||
<SidebarItem icon={GraphiQLIcon} to="apollo-explorer" text="Apollo Explorer" />
|
||||
```
|
||||
|
||||
That's it! You should now see an `Apollo Explorer` item in your sidebar, and if you click it, you should see your graph(s) load and direct you to authenticate via Apollo!
|
||||
|
||||

|
||||
|
||||
Once you authenticate, your graph is ready to use 🚀
|
||||
|
||||

|
||||
@@ -0,0 +1,45 @@
|
||||
## API Report File for "@backstage/plugin-apollo-explorer"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
/// <reference types="react" />
|
||||
|
||||
import { BackstagePlugin } from '@backstage/core-plugin-api';
|
||||
import { JSONObject } from '@apollo/explorer/src/helpers/types';
|
||||
import { RouteRef } from '@backstage/core-plugin-api';
|
||||
|
||||
// @public
|
||||
export const ApolloExplorerPage: (props: {
|
||||
title?: string | undefined;
|
||||
subtitle?: string | undefined;
|
||||
endpoints: {
|
||||
title: string;
|
||||
graphRef: string;
|
||||
persistExplorerState?: boolean | undefined;
|
||||
initialState?:
|
||||
| {
|
||||
document?: string | undefined;
|
||||
variables?: JSONObject | undefined;
|
||||
headers?: Record<string, string> | undefined;
|
||||
displayOptions: {
|
||||
docsPanelState?: 'closed' | 'open' | undefined;
|
||||
showHeadersAndEnvVars?: boolean | undefined;
|
||||
theme?: 'dark' | 'light' | undefined;
|
||||
};
|
||||
}
|
||||
| undefined;
|
||||
}[];
|
||||
}) => JSX.Element;
|
||||
|
||||
// @public
|
||||
export const apolloExplorerPlugin: BackstagePlugin<
|
||||
{
|
||||
root: RouteRef<undefined>;
|
||||
},
|
||||
{},
|
||||
{}
|
||||
>;
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
```
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { createDevApp } from '@backstage/dev-utils';
|
||||
import { apolloExplorerPlugin, ApolloExplorerPage } from '../src/plugin';
|
||||
|
||||
createDevApp()
|
||||
.registerPlugin(apolloExplorerPlugin)
|
||||
.addPage({
|
||||
element: <ApolloExplorerPage endpoints={[]} />,
|
||||
title: 'Root Page',
|
||||
path: '/apollo-explorer',
|
||||
})
|
||||
.render();
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 436 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.0 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.1 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.5 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 803 KiB |
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"name": "@backstage/plugin-apollo-explorer",
|
||||
"version": "0.1.1",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"main": "dist/index.esm.js",
|
||||
"types": "dist/index.d.ts"
|
||||
},
|
||||
"backstage": {
|
||||
"role": "frontend-plugin"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "backstage-cli package start",
|
||||
"build": "backstage-cli package build",
|
||||
"lint": "backstage-cli package lint",
|
||||
"test": "backstage-cli package test",
|
||||
"clean": "backstage-cli package clean",
|
||||
"prepack": "backstage-cli package prepack",
|
||||
"postpack": "backstage-cli package postpack"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/core-components": "^0.11.0",
|
||||
"@backstage/core-plugin-api": "^1.0.5",
|
||||
"@backstage/theme": "^0.2.16",
|
||||
"@material-ui/core": "^4.9.13",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "^4.0.0-alpha.57",
|
||||
"react-use": "^17.2.4",
|
||||
"@apollo/explorer": "^1.1.1",
|
||||
"use-deep-compare-effect": "^1.8.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.13.1 || ^17.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.18.1",
|
||||
"@backstage/core-app-api": "^1.0.5",
|
||||
"@backstage/dev-utils": "^1.0.5",
|
||||
"@backstage/test-utils": "^1.1.3",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^12.1.3",
|
||||
"@testing-library/user-event": "^14.0.0",
|
||||
"@types/jest": "*",
|
||||
"@types/node": "*",
|
||||
"msw": "^0.45.0",
|
||||
"cross-fetch": "^3.1.5"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* 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, { useState } from 'react';
|
||||
import { Divider, makeStyles, Tab, Tabs } from '@material-ui/core';
|
||||
import { BackstageTheme } from '@backstage/theme';
|
||||
import { JSONObject } from '@apollo/explorer/src/helpers/types';
|
||||
import { ApolloExplorer } from '@apollo/explorer/react';
|
||||
import { Content } from '@backstage/core-components';
|
||||
|
||||
const useStyles = makeStyles<BackstageTheme>(theme => ({
|
||||
tabs: {
|
||||
background: theme.palette.background.paper,
|
||||
},
|
||||
root: {
|
||||
height: '100%',
|
||||
},
|
||||
content: {
|
||||
height: '100%',
|
||||
},
|
||||
explorer: {
|
||||
height: '95%',
|
||||
},
|
||||
}));
|
||||
|
||||
export type ApolloEndpointProps = {
|
||||
title: string;
|
||||
graphRef: string;
|
||||
persistExplorerState?: boolean;
|
||||
initialState?: {
|
||||
document?: string;
|
||||
variables?: JSONObject;
|
||||
headers?: Record<string, string>;
|
||||
displayOptions: {
|
||||
docsPanelState?: 'open' | 'closed';
|
||||
showHeadersAndEnvVars?: boolean;
|
||||
theme?: 'dark' | 'light';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
type Props = {
|
||||
endpoints: ApolloEndpointProps[];
|
||||
};
|
||||
|
||||
export const ApolloExplorerBrowser = ({ endpoints }: Props) => {
|
||||
const classes = useStyles();
|
||||
const [tabIndex, setTabIndex] = useState(0);
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<Tabs
|
||||
classes={{ root: classes.tabs }}
|
||||
value={tabIndex}
|
||||
onChange={(_, value) => setTabIndex(value)}
|
||||
indicatorColor="primary"
|
||||
>
|
||||
{endpoints.map(({ title }, index) => (
|
||||
<Tab key={index} label={title} value={index} />
|
||||
))}
|
||||
</Tabs>
|
||||
<Divider />
|
||||
<Content className={classes.content}>
|
||||
<ApolloExplorer
|
||||
className={classes.explorer}
|
||||
graphRef={endpoints[tabIndex].graphRef}
|
||||
persistExplorerState={endpoints[tabIndex].persistExplorerState}
|
||||
initialState={endpoints[tabIndex].initialState}
|
||||
/>
|
||||
</Content>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
+1
-5
@@ -13,8 +13,4 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
export {
|
||||
useTemplateSecrets,
|
||||
SecretsContext,
|
||||
SecretsContextProvider,
|
||||
} from './SecretsContext';
|
||||
export { ApolloExplorerBrowser } from './ApolloExplorerBrowser';
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* 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 { Content, Header, Page } from '@backstage/core-components';
|
||||
import { ApolloExplorerBrowser } from '../ApolloExplorerBrowser';
|
||||
import { JSONObject } from '@apollo/explorer/src/helpers/types';
|
||||
|
||||
type EndpointProps = {
|
||||
title: string;
|
||||
graphRef: string;
|
||||
persistExplorerState?: boolean;
|
||||
initialState?: {
|
||||
document?: string;
|
||||
variables?: JSONObject;
|
||||
headers?: Record<string, string>;
|
||||
displayOptions: {
|
||||
docsPanelState?: 'open' | 'closed';
|
||||
showHeadersAndEnvVars?: boolean;
|
||||
theme?: 'dark' | 'light';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
type Props = {
|
||||
title?: string | undefined;
|
||||
subtitle?: string | undefined;
|
||||
endpoints: EndpointProps[];
|
||||
};
|
||||
|
||||
export const ApolloExplorerPage = (props: Props) => {
|
||||
const { title, subtitle, endpoints } = props;
|
||||
return (
|
||||
<Page themeId="tool">
|
||||
<Header title={title ?? 'Apollo Explorer 👩🚀'} subtitle={subtitle ?? ''} />
|
||||
<Content noPadding>
|
||||
<ApolloExplorerBrowser endpoints={endpoints} />
|
||||
</Content>
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* 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 { ApolloExplorerPage } from './ApolloExplorerPage';
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* 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 { apolloExplorerPlugin, ApolloExplorerPage } from './plugin';
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* 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 { apolloExplorerPlugin } from './plugin';
|
||||
|
||||
describe('apollo-explorer', () => {
|
||||
it('should export plugin', () => {
|
||||
expect(apolloExplorerPlugin).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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 {
|
||||
createPlugin,
|
||||
createRoutableExtension,
|
||||
} from '@backstage/core-plugin-api';
|
||||
|
||||
import { rootRouteRef } from './routes';
|
||||
|
||||
/**
|
||||
* Plugin that allows Apollo Explorer instances to be directly embedded into Backstage
|
||||
* @public
|
||||
*/
|
||||
export const apolloExplorerPlugin = createPlugin({
|
||||
id: 'apollo-explorer',
|
||||
routes: {
|
||||
root: rootRouteRef,
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Main component that wraps the embedded graph(s)
|
||||
* @public
|
||||
*/
|
||||
export const ApolloExplorerPage = apolloExplorerPlugin.provide(
|
||||
createRoutableExtension({
|
||||
name: 'ApolloExplorerPage',
|
||||
component: () =>
|
||||
import('./components/ApolloExplorerPage').then(m => m.ApolloExplorerPage),
|
||||
mountPoint: rootRouteRef,
|
||||
}),
|
||||
);
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* 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';
|
||||
|
||||
export const rootRouteRef = createRouteRef({
|
||||
id: 'apollo-explorer',
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* 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';
|
||||
import 'cross-fetch/polyfill';
|
||||
@@ -1,5 +1,31 @@
|
||||
# @backstage/plugin-app-backend
|
||||
|
||||
## 0.3.35
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/backend-common@0.15.0
|
||||
|
||||
## 0.3.35-next.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/backend-common@0.15.0-next.0
|
||||
|
||||
## 0.3.34
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- a70869e775: Updated dependency `msw` to `^0.43.0`.
|
||||
- 4e9a90e307: Updated dependency `luxon` to `^3.0.0`.
|
||||
- 8006d0f9bf: Updated dependency `msw` to `^0.44.0`.
|
||||
- 679b32172e: Updated dependency `knex` to `^2.0.0`.
|
||||
- Updated dependencies
|
||||
- @backstage/backend-common@0.14.1
|
||||
- @backstage/config-loader@1.1.3
|
||||
|
||||
## 0.3.34-next.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/plugin-app-backend",
|
||||
"description": "A Backstage backend plugin that serves the Backstage frontend app",
|
||||
"version": "0.3.34-next.3",
|
||||
"version": "0.3.35",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -33,8 +33,8 @@
|
||||
"clean": "backstage-cli package clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-common": "^0.14.1-next.3",
|
||||
"@backstage/config-loader": "^1.1.3-next.1",
|
||||
"@backstage/backend-common": "^0.15.0",
|
||||
"@backstage/config-loader": "^1.1.3",
|
||||
"@backstage/config": "^1.0.1",
|
||||
"@backstage/types": "^1.0.0",
|
||||
"@types/express": "^4.17.6",
|
||||
@@ -50,12 +50,12 @@
|
||||
"yn": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/backend-test-utils": "^0.1.26-next.3",
|
||||
"@backstage/cli": "^0.18.0-next.3",
|
||||
"@backstage/backend-test-utils": "^0.1.27",
|
||||
"@backstage/cli": "^0.18.1",
|
||||
"@backstage/types": "^1.0.0",
|
||||
"@types/supertest": "^2.0.8",
|
||||
"mock-fs": "^5.1.0",
|
||||
"msw": "^0.44.0",
|
||||
"msw": "^0.45.0",
|
||||
"supertest": "^6.1.3"
|
||||
},
|
||||
"files": [
|
||||
|
||||
@@ -14,12 +14,25 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Knex as KnexType } from 'knex';
|
||||
import { getVoidLogger } from '@backstage/backend-common';
|
||||
import { TestDatabases } from '@backstage/backend-test-utils';
|
||||
import { StaticAssetsStore } from './StaticAssetsStore';
|
||||
|
||||
const logger = getVoidLogger();
|
||||
|
||||
function createDatabaseManager(
|
||||
client: KnexType,
|
||||
skipMigrations: boolean = false,
|
||||
) {
|
||||
return {
|
||||
getClient: async () => client,
|
||||
migrations: {
|
||||
skip: skipMigrations,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
describe('StaticAssetsStore', () => {
|
||||
const databases = TestDatabases.create({
|
||||
ids: ['POSTGRES_13', 'POSTGRES_9', 'SQLITE_3'],
|
||||
@@ -28,9 +41,11 @@ describe('StaticAssetsStore', () => {
|
||||
it.each(databases.eachSupportedId())(
|
||||
'should store and retrieve assets, %p',
|
||||
async databaseId => {
|
||||
const client = await databases.init(databaseId);
|
||||
const database = createDatabaseManager(client);
|
||||
const store = await StaticAssetsStore.create({
|
||||
logger,
|
||||
database: await databases.init(databaseId),
|
||||
database,
|
||||
});
|
||||
|
||||
await store.storeAssets([
|
||||
@@ -69,9 +84,11 @@ describe('StaticAssetsStore', () => {
|
||||
it.each(databases.eachSupportedId())(
|
||||
'should update assets timestamps, but not contents, %p',
|
||||
async databaseId => {
|
||||
const client = await databases.init(databaseId);
|
||||
const database = createDatabaseManager(client);
|
||||
const store = await StaticAssetsStore.create({
|
||||
logger,
|
||||
database: await databases.init(databaseId),
|
||||
database,
|
||||
});
|
||||
|
||||
await store.storeAssets([
|
||||
@@ -119,7 +136,8 @@ describe('StaticAssetsStore', () => {
|
||||
it.each(databases.eachSupportedId())(
|
||||
'should trim old assets, %p',
|
||||
async databaseId => {
|
||||
const database = await databases.init(databaseId);
|
||||
const knex = await databases.init(databaseId);
|
||||
const database = createDatabaseManager(knex);
|
||||
const store = await StaticAssetsStore.create({
|
||||
logger,
|
||||
database,
|
||||
@@ -137,12 +155,12 @@ describe('StaticAssetsStore', () => {
|
||||
]);
|
||||
|
||||
// Rewrite modified time of "old" to be 1h in the past
|
||||
const updated = await database('static_assets_cache')
|
||||
const updated = await knex('static_assets_cache')
|
||||
.where({ path: 'old' })
|
||||
.update({
|
||||
last_modified_at: database.client.config.client.includes('sqlite3')
|
||||
? database.raw(`datetime('now', '-3600 seconds')`)
|
||||
: database.raw(`now() + interval '-3600 seconds'`),
|
||||
last_modified_at: knex.client.config.client.includes('sqlite3')
|
||||
? knex.raw(`datetime('now', '-3600 seconds')`)
|
||||
: knex.raw(`now() + interval '-3600 seconds'`),
|
||||
});
|
||||
expect(updated).toBe(1);
|
||||
|
||||
|
||||
@@ -14,7 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { resolvePackagePath } from '@backstage/backend-common';
|
||||
import {
|
||||
PluginDatabaseManager,
|
||||
resolvePackagePath,
|
||||
} from '@backstage/backend-common';
|
||||
import { Knex } from 'knex';
|
||||
import { Logger } from 'winston';
|
||||
import { DateTime } from 'luxon';
|
||||
@@ -34,7 +37,7 @@ interface StaticAssetRow {
|
||||
|
||||
/** @internal */
|
||||
export interface StaticAssetsStoreOptions {
|
||||
database: Knex;
|
||||
database: PluginDatabaseManager;
|
||||
logger: Logger;
|
||||
}
|
||||
|
||||
@@ -48,15 +51,21 @@ export class StaticAssetsStore implements StaticAssetProvider {
|
||||
#logger: Logger;
|
||||
|
||||
static async create(options: StaticAssetsStoreOptions) {
|
||||
await options.database.migrate.latest({
|
||||
directory: migrationsDir,
|
||||
});
|
||||
return new StaticAssetsStore(options);
|
||||
const { database } = options;
|
||||
const client = await database.getClient();
|
||||
|
||||
if (!database.migrations?.skip) {
|
||||
await client.migrate.latest({
|
||||
directory: migrationsDir,
|
||||
});
|
||||
}
|
||||
|
||||
return new StaticAssetsStore(client, options.logger);
|
||||
}
|
||||
|
||||
private constructor(options: StaticAssetsStoreOptions) {
|
||||
this.#db = options.database;
|
||||
this.#logger = options.logger;
|
||||
private constructor(client: Knex, logger: Logger) {
|
||||
this.#db = client;
|
||||
this.#logger = logger;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -130,7 +130,7 @@ export async function createRouter(
|
||||
if (options.database) {
|
||||
const store = await StaticAssetsStore.create({
|
||||
logger,
|
||||
database: await options.database.getClient(),
|
||||
database: options.database,
|
||||
});
|
||||
|
||||
const assets = await findStaticAssets(staticDir);
|
||||
|
||||
@@ -1,5 +1,61 @@
|
||||
# @backstage/plugin-auth-backend
|
||||
|
||||
## 0.15.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- c676a9e07b: Fixed a bug in auth plugin on the backend where it ignores the skip migration database options when using the database provider.
|
||||
- 2d7d6028e1: Updated dependency `@google-cloud/firestore` to `^6.0.0`.
|
||||
- Updated dependencies
|
||||
- @backstage/backend-common@0.15.0
|
||||
- @backstage/plugin-auth-node@0.2.4
|
||||
|
||||
## 0.15.1-next.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 2d7d6028e1: Updated dependency `@google-cloud/firestore` to `^6.0.0`.
|
||||
|
||||
## 0.15.1-next.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/backend-common@0.15.0-next.0
|
||||
- @backstage/plugin-auth-node@0.2.4-next.0
|
||||
|
||||
## 0.15.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 9d4040777e: **BREAKING**: Removed all directly exported auth provider factories, option types, and sign-in resolvers. For example: `AwsAlbProviderOptions`, `bitbucketUserIdSignInResolver`, `createGithubProvider`. These are all still accessible via the `providers` export. For example, use `providers.github.create()` rather than `createGithubProvider()`, and `providers.bitbucket.resolvers.userIdMatchingUserEntityAnnotation()` rather than `bitbucketUserIdSignInResolver`.
|
||||
|
||||
**BREAKING**: Removed the exported `AuthProviderFactoryOptions` type as well as the deprecated option fields of the `AuthProviderFactory` callback. This includes the `tokenManager`, `tokenIssuer`, `discovery`, and `catalogApi` fields. Existing usage of these should be replaced with the new utilities in the `resolverContext` field. The deprecated `TokenIssuer` type is now also removed, since it is no longer used.
|
||||
|
||||
**BREAKING**: Removed `getEntityClaims`, use `getDefaultOwnershipEntityRefs` instead.
|
||||
|
||||
**DEPRECATION**: Deprecated `AtlassianAuthProvider` as it was unintentionally exported.
|
||||
|
||||
- fe8e025af5: Allowed post method on /refresh path
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 3cedfd8365: add Cloudflare Access auth provider to auth-backend
|
||||
- f2cf79d62e: Added an option for the auth backend router to select the algorithm for the JWT token signing keys
|
||||
- 8e03db907a: Auth provider now also export createAuthProviderIntegration
|
||||
- a70869e775: Updated dependency `msw` to `^0.43.0`.
|
||||
- 4e9a90e307: Updated dependency `luxon` to `^3.0.0`.
|
||||
- 8006d0f9bf: Updated dependency `msw` to `^0.44.0`.
|
||||
- 679b32172e: Updated dependency `knex` to `^2.0.0`.
|
||||
- 859346bfbb: Updated dependency `google-auth-library` to `^8.0.0`.
|
||||
- 3a014730dc: Add new config option for okta auth server and IDP
|
||||
- Updated dependencies
|
||||
- @backstage/backend-common@0.14.1
|
||||
- @backstage/catalog-model@1.1.0
|
||||
- @backstage/catalog-client@1.0.4
|
||||
- @backstage/plugin-auth-node@0.2.3
|
||||
- @backstage/errors@1.1.0
|
||||
|
||||
## 0.15.0-next.3
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
import { BackstageIdentityResponse } from '@backstage/plugin-auth-node';
|
||||
import { BackstageSignInResult } from '@backstage/plugin-auth-node';
|
||||
import { CacheClient } from '@backstage/backend-common';
|
||||
import { CatalogApi } from '@backstage/catalog-client';
|
||||
import { Config } from '@backstage/config';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
@@ -170,6 +171,41 @@ export class CatalogIdentityClient {
|
||||
resolveCatalogMembership(query: MemberClaimQuery): Promise<string[]>;
|
||||
}
|
||||
|
||||
// @public
|
||||
export type CloudflareAccessClaims = {
|
||||
aud: string[];
|
||||
email: string;
|
||||
exp: number;
|
||||
iat: number;
|
||||
nonce: string;
|
||||
identity_nonce: string;
|
||||
sub: string;
|
||||
iss: string;
|
||||
custom: string;
|
||||
};
|
||||
|
||||
// @public
|
||||
export type CloudflareAccessGroup = {
|
||||
id: string;
|
||||
name: string;
|
||||
email: string;
|
||||
};
|
||||
|
||||
// @public
|
||||
export type CloudflareAccessIdentityProfile = {
|
||||
id: string;
|
||||
name: string;
|
||||
email: string;
|
||||
groups: CloudflareAccessGroup[];
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type CloudflareAccessResult = {
|
||||
claims: CloudflareAccessClaims;
|
||||
cfIdentity: CloudflareAccessIdentityProfile;
|
||||
expiresInSeconds?: number;
|
||||
};
|
||||
|
||||
// @public
|
||||
export type CookieConfigurer = (ctx: {
|
||||
providerId: string;
|
||||
@@ -472,6 +508,18 @@ export const providers: Readonly<{
|
||||
userIdMatchingUserEntityAnnotation(): SignInResolver<OAuthResult>;
|
||||
}>;
|
||||
}>;
|
||||
cfAccess: Readonly<{
|
||||
create: (options: {
|
||||
authHandler?: AuthHandler<CloudflareAccessResult> | undefined;
|
||||
signIn: {
|
||||
resolver: SignInResolver<CloudflareAccessResult>;
|
||||
};
|
||||
cache?: CacheClient | undefined;
|
||||
}) => AuthProviderFactory;
|
||||
resolvers: Readonly<{
|
||||
emailMatchingUserEntityProfileEmail: () => SignInResolver<unknown>;
|
||||
}>;
|
||||
}>;
|
||||
gcpIap: Readonly<{
|
||||
create: (options: {
|
||||
authHandler?: AuthHandler<GcpIapResult> | undefined;
|
||||
|
||||
Vendored
+3
@@ -116,6 +116,9 @@ export interface Config {
|
||||
issuer?: string;
|
||||
region: string;
|
||||
};
|
||||
cfaccess?: {
|
||||
teamName: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/plugin-auth-backend",
|
||||
"description": "A Backstage backend plugin that handles authentication",
|
||||
"version": "0.15.0-next.3",
|
||||
"version": "0.15.1",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -33,15 +33,15 @@
|
||||
"clean": "backstage-cli package clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/plugin-auth-node": "^0.2.3-next.2",
|
||||
"@backstage/backend-common": "^0.14.1-next.3",
|
||||
"@backstage/catalog-client": "^1.0.4-next.2",
|
||||
"@backstage/catalog-model": "^1.1.0-next.3",
|
||||
"@backstage/plugin-auth-node": "^0.2.4",
|
||||
"@backstage/backend-common": "^0.15.0",
|
||||
"@backstage/catalog-client": "^1.0.4",
|
||||
"@backstage/catalog-model": "^1.1.0",
|
||||
"@backstage/config": "^1.0.1",
|
||||
"@backstage/errors": "^1.1.0-next.0",
|
||||
"@backstage/errors": "^1.1.0",
|
||||
"@backstage/types": "^1.0.0",
|
||||
"@davidzemon/passport-okta-oauth": "^0.0.5",
|
||||
"@google-cloud/firestore": "^5.0.2",
|
||||
"@google-cloud/firestore": "^6.0.0",
|
||||
"@types/express": "^4.17.6",
|
||||
"@types/passport": "^1.0.3",
|
||||
"compression": "^1.7.4",
|
||||
@@ -76,8 +76,8 @@
|
||||
"yn": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/backend-test-utils": "^0.1.26-next.3",
|
||||
"@backstage/cli": "^0.18.0-next.3",
|
||||
"@backstage/backend-test-utils": "^0.1.27",
|
||||
"@backstage/cli": "^0.18.1",
|
||||
"@types/body-parser": "^1.19.0",
|
||||
"@types/cookie-parser": "^1.4.2",
|
||||
"@types/express-session": "^1.17.2",
|
||||
@@ -88,7 +88,7 @@
|
||||
"@types/passport-saml": "^1.1.3",
|
||||
"@types/passport-strategy": "^0.2.35",
|
||||
"@types/xml2js": "^0.4.7",
|
||||
"msw": "^0.44.0",
|
||||
"msw": "^0.45.0",
|
||||
"supertest": "^6.1.3"
|
||||
},
|
||||
"files": [
|
||||
|
||||
@@ -14,10 +14,22 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import Knex from 'knex';
|
||||
import Knex, { Knex as KnexType } from 'knex';
|
||||
import { DatabaseKeyStore } from './DatabaseKeyStore';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
function createDatabaseManager(
|
||||
client: KnexType,
|
||||
skipMigrations: boolean = false,
|
||||
) {
|
||||
return {
|
||||
getClient: async () => client,
|
||||
migrations: {
|
||||
skip: skipMigrations,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function createDB() {
|
||||
const knex = Knex({
|
||||
client: 'better-sqlite3',
|
||||
@@ -38,8 +50,10 @@ const keyBase = {
|
||||
|
||||
describe('DatabaseKeyStore', () => {
|
||||
it('should store a key', async () => {
|
||||
const database = createDB();
|
||||
const store = await DatabaseKeyStore.create({ database });
|
||||
const client = createDB();
|
||||
const store = await DatabaseKeyStore.create({
|
||||
database: createDatabaseManager(client),
|
||||
});
|
||||
|
||||
const key = {
|
||||
kid: '123',
|
||||
@@ -59,8 +73,10 @@ describe('DatabaseKeyStore', () => {
|
||||
});
|
||||
|
||||
it('should remove stored keys', async () => {
|
||||
const database = createDB();
|
||||
const store = await DatabaseKeyStore.create({ database });
|
||||
const client = createDB();
|
||||
const store = await DatabaseKeyStore.create({
|
||||
database: createDatabaseManager(client),
|
||||
});
|
||||
|
||||
const key1 = { kid: '1', ...keyBase };
|
||||
const key2 = { kid: '2', ...keyBase };
|
||||
|
||||
@@ -14,7 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { resolvePackagePath } from '@backstage/backend-common';
|
||||
import {
|
||||
PluginDatabaseManager,
|
||||
resolvePackagePath,
|
||||
} from '@backstage/backend-common';
|
||||
import { Knex } from 'knex';
|
||||
import { DateTime } from 'luxon';
|
||||
import { AnyJWK, KeyStore, StoredKey } from './types';
|
||||
@@ -33,7 +36,7 @@ type Row = {
|
||||
};
|
||||
|
||||
type Options = {
|
||||
database: Knex;
|
||||
database: PluginDatabaseManager;
|
||||
};
|
||||
|
||||
const parseDate = (date: string | Date) => {
|
||||
@@ -54,29 +57,32 @@ const parseDate = (date: string | Date) => {
|
||||
export class DatabaseKeyStore implements KeyStore {
|
||||
static async create(options: Options): Promise<DatabaseKeyStore> {
|
||||
const { database } = options;
|
||||
const client = await database.getClient();
|
||||
|
||||
await database.migrate.latest({
|
||||
directory: migrationsDir,
|
||||
});
|
||||
if (!database.migrations?.skip) {
|
||||
await client.migrate.latest({
|
||||
directory: migrationsDir,
|
||||
});
|
||||
}
|
||||
|
||||
return new DatabaseKeyStore(options);
|
||||
return new DatabaseKeyStore(client);
|
||||
}
|
||||
|
||||
private readonly database: Knex;
|
||||
private readonly client: Knex;
|
||||
|
||||
private constructor(options: Options) {
|
||||
this.database = options.database;
|
||||
private constructor(client: Knex) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
async addKey(key: AnyJWK): Promise<void> {
|
||||
await this.database<Row>(TABLE).insert({
|
||||
await this.client<Row>(TABLE).insert({
|
||||
kid: key.kid,
|
||||
key: JSON.stringify(key),
|
||||
});
|
||||
}
|
||||
|
||||
async listKeys(): Promise<{ items: StoredKey[] }> {
|
||||
const rows = await this.database<Row>(TABLE).select();
|
||||
const rows = await this.client<Row>(TABLE).select();
|
||||
|
||||
return {
|
||||
items: rows.map(row => ({
|
||||
@@ -87,6 +93,6 @@ export class DatabaseKeyStore implements KeyStore {
|
||||
}
|
||||
|
||||
async removeKeys(kids: string[]): Promise<void> {
|
||||
await this.database(TABLE).delete().whereIn('kid', kids);
|
||||
await this.client(TABLE).delete().whereIn('kid', kids);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,9 +53,7 @@ export class KeyStores {
|
||||
throw new Error('This KeyStore provider requires a database');
|
||||
}
|
||||
|
||||
return await DatabaseKeyStore.create({
|
||||
database: await database.getClient(),
|
||||
});
|
||||
return await DatabaseKeyStore.create({ database });
|
||||
}
|
||||
|
||||
if (provider === 'memory') {
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* 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 { cfAccess } from './provider';
|
||||
export type {
|
||||
CloudflareAccessClaims,
|
||||
CloudflareAccessGroup,
|
||||
CloudflareAccessResult,
|
||||
CloudflareAccessIdentityProfile,
|
||||
} from './provider';
|
||||
@@ -0,0 +1,267 @@
|
||||
/*
|
||||
* 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 express from 'express';
|
||||
import { jwtVerify } from 'jose';
|
||||
import {
|
||||
CF_JWT_HEADER,
|
||||
CF_AUTH_IDENTITY,
|
||||
CloudflareAccessAuthProvider,
|
||||
} from './provider';
|
||||
import { AuthResolverContext } from '../types';
|
||||
import fetch from 'node-fetch';
|
||||
|
||||
const jwtMock = jwtVerify as jest.Mocked<any>;
|
||||
const mockJwt =
|
||||
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IktFWV9JRCIsImlzcyI6IklTU1VFUl9VUkwifQ.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IlVzZXIgTmFtZSIsImlhdCI6MTUxNjIzOTAyMn0.uMCSBGhij1xn5pnot8XgD-huQuTIBOFGs6kkW_p_X94';
|
||||
const mockClaims = {
|
||||
sub: '1234567890',
|
||||
email: 'user.name@email.test',
|
||||
iat: 1632833760,
|
||||
exp: 1632833763,
|
||||
iss: 'ISSUER_URL',
|
||||
};
|
||||
const mockCfIdentity = {
|
||||
name: 'foo',
|
||||
id: '123',
|
||||
email: 'foo@bar.com',
|
||||
groups: [
|
||||
{
|
||||
id: '123',
|
||||
email: 'foo@bar.com',
|
||||
name: 'foo',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const identityOkResponse = {
|
||||
backstageIdentity: {
|
||||
identity: {
|
||||
ownershipEntityRefs: ['user:default/jimmymarkum'],
|
||||
type: 'user',
|
||||
userEntityRef: 'user:default/jimmymarkum',
|
||||
},
|
||||
token:
|
||||
'eyblob.eyJzdWIiOiJ1c2VyOmRlZmF1bHQvamltbXltYXJrdW0iLCJlbnQiOlsidXNlcjpkZWZhdWx0L2ppbW15bWFya3VtIl19.eyblob',
|
||||
},
|
||||
profile: {
|
||||
email: 'user.name@email.test',
|
||||
},
|
||||
providerInfo: {
|
||||
cfAccessIdentityProfile: {
|
||||
email: 'foo@bar.com',
|
||||
groups: [
|
||||
{
|
||||
email: 'foo@bar.com',
|
||||
id: '123',
|
||||
name: 'foo',
|
||||
},
|
||||
],
|
||||
id: '123',
|
||||
name: 'foo',
|
||||
},
|
||||
claims: mockClaims,
|
||||
expiresInSeconds: 3,
|
||||
},
|
||||
};
|
||||
|
||||
const mockAuthenticatedUserEmail = 'user.name@email.test';
|
||||
const mockCacheClient = {
|
||||
get: jest.fn(),
|
||||
set: jest.fn(),
|
||||
delete: jest.fn(),
|
||||
};
|
||||
|
||||
jest.mock('jose');
|
||||
jest.mock('node-fetch', () => {
|
||||
const original = jest.requireActual('node-fetch');
|
||||
return {
|
||||
__esModule: true,
|
||||
default: jest.fn(),
|
||||
Headers: original.Headers,
|
||||
};
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('CloudflareAccessAuthProvider', () => {
|
||||
// Cloudflare access provides jwt in two ways.
|
||||
const mockRequestWithJwtHeader = {
|
||||
header: jest.fn(name => {
|
||||
if (name === CF_JWT_HEADER) {
|
||||
return mockJwt;
|
||||
} else if (name === CF_AUTH_IDENTITY) {
|
||||
return mockAuthenticatedUserEmail;
|
||||
}
|
||||
return undefined;
|
||||
}),
|
||||
} as unknown as express.Request;
|
||||
const mockRequestWithJwtCookie = {
|
||||
header: jest.fn(_ => {
|
||||
return undefined;
|
||||
}),
|
||||
cookies: {
|
||||
CF_Authorization: `${mockJwt}`,
|
||||
},
|
||||
} as unknown as express.Request;
|
||||
|
||||
const mockRequestWithoutJwt = {
|
||||
header: jest.fn(_ => {
|
||||
return undefined;
|
||||
}),
|
||||
} as unknown as express.Request;
|
||||
|
||||
const mockResponse = {
|
||||
end: jest.fn(),
|
||||
header: () => jest.fn(),
|
||||
json: jest.fn(),
|
||||
status: jest.fn(),
|
||||
} as unknown as express.Response;
|
||||
|
||||
const mockFetch = fetch as unknown as jest.Mocked<any>;
|
||||
|
||||
const provider = new CloudflareAccessAuthProvider({
|
||||
teamName: 'foobar',
|
||||
resolverContext: {} as AuthResolverContext,
|
||||
authHandler: async ({ claims }) => ({
|
||||
profile: {
|
||||
email: claims.email,
|
||||
},
|
||||
}),
|
||||
signInResolver: async () => {
|
||||
return {
|
||||
token:
|
||||
'eyblob.eyJzdWIiOiJ1c2VyOmRlZmF1bHQvamltbXltYXJrdW0iLCJlbnQiOlsidXNlcjpkZWZhdWx0L2ppbW15bWFya3VtIl19.eyblob',
|
||||
};
|
||||
},
|
||||
cache: mockCacheClient,
|
||||
});
|
||||
|
||||
describe('when JWT is valid', () => {
|
||||
it('returns cfidentity also when get-identity succeeds', async () => {
|
||||
jwtMock.mockReturnValue(Promise.resolve({ payload: mockClaims }));
|
||||
mockFetch.mockReturnValueOnce(
|
||||
Promise.resolve({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: () => {
|
||||
return mockCfIdentity;
|
||||
},
|
||||
}),
|
||||
);
|
||||
await provider.refresh(mockRequestWithJwtHeader, mockResponse);
|
||||
expect(mockResponse.json).toHaveBeenCalledWith(identityOkResponse);
|
||||
});
|
||||
|
||||
it('should resolve when passed in cookie', async () => {
|
||||
jwtMock.mockReturnValue(Promise.resolve({ payload: mockClaims }));
|
||||
// when mockFetch resolves and there nothing gets returned from /get-identity
|
||||
mockFetch.mockReturnValueOnce(
|
||||
Promise.resolve({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: () => {
|
||||
return mockCfIdentity;
|
||||
},
|
||||
}),
|
||||
);
|
||||
await provider.refresh(mockRequestWithJwtCookie, mockResponse);
|
||||
expect(mockResponse.json).toHaveBeenCalledWith(identityOkResponse);
|
||||
});
|
||||
|
||||
it('should resolve an identity and populate access groups when there are groups', async () => {
|
||||
// when get-identity api responds and responds with status 200
|
||||
mockFetch.mockResolvedValueOnce(
|
||||
Promise.resolve({
|
||||
ok: () => {
|
||||
return true;
|
||||
},
|
||||
status: 200,
|
||||
json: () => {
|
||||
return Promise.resolve({
|
||||
name: 'foo',
|
||||
id: '123',
|
||||
email: 'foo@bar.com',
|
||||
groups: [
|
||||
{
|
||||
id: '123',
|
||||
email: 'foo@bar.com',
|
||||
name: 'foo',
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
}),
|
||||
);
|
||||
jwtMock.mockReturnValueOnce(Promise.resolve({ payload: mockClaims }));
|
||||
await provider.refresh(mockRequestWithJwtCookie, mockResponse);
|
||||
expect(mockResponse.json).toHaveBeenCalledWith(identityOkResponse);
|
||||
});
|
||||
|
||||
it('should throw an error when get-identity fails', async () => {
|
||||
mockFetch.mockReturnValue(Promise.reject());
|
||||
await expect(
|
||||
provider.refresh(mockRequestWithJwtCookie, mockResponse),
|
||||
).rejects.toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('should fail when', () => {
|
||||
it('JWT is missing', async () => {
|
||||
await expect(
|
||||
provider.refresh(mockRequestWithoutJwt, mockResponse),
|
||||
).rejects.toThrow();
|
||||
});
|
||||
|
||||
it('JWT is invalid', async () => {
|
||||
jwtMock.mockImplementation(() => {
|
||||
throw new Error('bad JWT');
|
||||
});
|
||||
await expect(
|
||||
provider.refresh(mockRequestWithJwtCookie, mockResponse),
|
||||
).rejects.toThrow();
|
||||
await expect(
|
||||
provider.refresh(mockRequestWithJwtHeader, mockResponse),
|
||||
).rejects.toThrow();
|
||||
jwtMock.mockReset();
|
||||
});
|
||||
|
||||
it('SignInResolver rejects', async () => {
|
||||
jwtMock.mockReturnValue(mockClaims);
|
||||
await expect(
|
||||
provider.refresh(mockRequestWithJwtCookie, mockResponse),
|
||||
).rejects.toThrow();
|
||||
await expect(
|
||||
provider.refresh(mockRequestWithJwtHeader, mockResponse),
|
||||
).rejects.toThrow();
|
||||
jwtMock.mockReset();
|
||||
});
|
||||
|
||||
it('AuthHandler rejects', async () => {
|
||||
jwtMock.mockReturnValue(mockClaims);
|
||||
|
||||
await expect(
|
||||
provider.refresh(mockRequestWithJwtCookie, mockResponse),
|
||||
).rejects.toThrow();
|
||||
await expect(
|
||||
provider.refresh(mockRequestWithJwtHeader, mockResponse),
|
||||
).rejects.toThrow();
|
||||
jwtMock.mockReset();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,379 @@
|
||||
/*
|
||||
* 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 {
|
||||
AuthHandler,
|
||||
AuthProviderRouteHandlers,
|
||||
AuthResolverContext,
|
||||
AuthResponse,
|
||||
SignInResolver,
|
||||
} from '../types';
|
||||
import fetch, { Headers } from 'node-fetch';
|
||||
import express from 'express';
|
||||
import * as _ from 'lodash';
|
||||
import { jwtVerify, createRemoteJWKSet } from 'jose';
|
||||
import {
|
||||
AuthenticationError,
|
||||
ResponseError,
|
||||
ForwardedError,
|
||||
} from '@backstage/errors';
|
||||
import { CacheClient } from '@backstage/backend-common';
|
||||
import { createAuthProviderIntegration } from '../createAuthProviderIntegration';
|
||||
import { prepareBackstageIdentityResponse } from '../prepareBackstageIdentityResponse';
|
||||
import { commonByEmailResolver } from '../resolvers';
|
||||
|
||||
// JWT Web Token definitions are in the URL below
|
||||
// https://developers.cloudflare.com/cloudflare-one/identity/users/validating-json/
|
||||
export const CF_JWT_HEADER = 'cf-access-jwt-assertion';
|
||||
export const CF_AUTH_IDENTITY = 'cf-access-authenticated-user-email';
|
||||
const COOKIE_AUTH_NAME = 'CF_Authorization';
|
||||
const CACHE_PREFIX = 'providers/cloudflare-access/profile-v1';
|
||||
|
||||
/**
|
||||
* Default cache TTL
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const CF_DEFAULT_CACHE_TTL = 3600;
|
||||
|
||||
/** @public */
|
||||
export type Options = {
|
||||
/**
|
||||
* Access team name
|
||||
*
|
||||
* When you configure Access, the public certificates are available at this
|
||||
* URL, where your-team-name is your team name:
|
||||
* https://<your-team-name>.cloudflareaccess.com/cdn-cgi/access/certs
|
||||
*/
|
||||
teamName: string;
|
||||
authHandler: AuthHandler<CloudflareAccessResult>;
|
||||
signInResolver: SignInResolver<CloudflareAccessResult>;
|
||||
resolverContext: AuthResolverContext;
|
||||
cache?: CacheClient;
|
||||
};
|
||||
|
||||
/**
|
||||
* CloudflareAccessClaims
|
||||
*
|
||||
* Can be used in externally provided auth handler or sign in resolver to
|
||||
* enrich user profile for sign-in user entity
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type CloudflareAccessClaims = {
|
||||
/**
|
||||
* `aud` identifies the application to which the JWT is issued.
|
||||
*/
|
||||
aud: string[];
|
||||
/**
|
||||
* `email` contains the email address of the authenticated user.
|
||||
*/
|
||||
email: string;
|
||||
/**
|
||||
* iat and exp are the issuance and expiration timestamps.
|
||||
*/
|
||||
exp: number;
|
||||
iat: number;
|
||||
/**
|
||||
* `nonce` is the session identifier.
|
||||
*/
|
||||
nonce: string;
|
||||
/**
|
||||
* `identity_nonce` is available in the Application Token and can be used to
|
||||
* query all group membership for a given user.
|
||||
*/
|
||||
identity_nonce: string;
|
||||
/**
|
||||
* `sub` contains the identifier of the authenticated user.
|
||||
*/
|
||||
sub: string;
|
||||
/**
|
||||
* `iss` the issuer is the application’s Cloudflare Access Domain URL.
|
||||
*/
|
||||
iss: string;
|
||||
/**
|
||||
* `custom` contains SAML attributes in the Application Token specified by an
|
||||
* administrator in the identity provider configuration.
|
||||
*/
|
||||
custom: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* CloudflareAccessGroup
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type CloudflareAccessGroup = {
|
||||
/**
|
||||
* Group id
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* Name of group as defined in Cloudflare zero trust dashboard
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* Access group email address
|
||||
*/
|
||||
email: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* CloudflareAccessIdentityProfile
|
||||
*
|
||||
* Can be used in externally provided auth handler or sign in resolver to
|
||||
* enrich user profile for sign-in user entity
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type CloudflareAccessIdentityProfile = {
|
||||
id: string;
|
||||
name: string;
|
||||
email: string;
|
||||
groups: CloudflareAccessGroup[];
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type CloudflareAccessResult = {
|
||||
claims: CloudflareAccessClaims;
|
||||
cfIdentity: CloudflareAccessIdentityProfile;
|
||||
expiresInSeconds?: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type CloudflareAccessProviderInfo = {
|
||||
/**
|
||||
* Expiry of the access token in seconds.
|
||||
*/
|
||||
expiresInSeconds?: number;
|
||||
/**
|
||||
* Cloudflare access identity profile with cloudflare access groups
|
||||
*/
|
||||
cfAccessIdentityProfile?: CloudflareAccessIdentityProfile;
|
||||
/**
|
||||
* Cloudflare access claims
|
||||
*/
|
||||
claims: CloudflareAccessClaims;
|
||||
};
|
||||
|
||||
export type CloudflareAccessResponse =
|
||||
AuthResponse<CloudflareAccessProviderInfo>;
|
||||
|
||||
export class CloudflareAccessAuthProvider implements AuthProviderRouteHandlers {
|
||||
private readonly teamName: string;
|
||||
private readonly resolverContext: AuthResolverContext;
|
||||
private readonly authHandler: AuthHandler<CloudflareAccessResult>;
|
||||
private readonly signInResolver: SignInResolver<CloudflareAccessResult>;
|
||||
private readonly jwtKeySet: any;
|
||||
private readonly cache?: CacheClient;
|
||||
|
||||
constructor(options: Options) {
|
||||
this.teamName = options.teamName;
|
||||
this.authHandler = options.authHandler;
|
||||
this.signInResolver = options.signInResolver;
|
||||
this.resolverContext = options.resolverContext;
|
||||
this.jwtKeySet = createRemoteJWKSet(
|
||||
new URL(
|
||||
`https://${this.teamName}.cloudflareaccess.com/cdn-cgi/access/certs`,
|
||||
),
|
||||
);
|
||||
this.cache = options.cache;
|
||||
}
|
||||
|
||||
frameHandler(): Promise<void> {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
async refresh(req: express.Request, res: express.Response): Promise<void> {
|
||||
// ProxiedSignInPage calls `/refresh` implicitly each time the backstage
|
||||
// app is refreshed on the browser.
|
||||
// User authentication is then checked here.
|
||||
const result = await this.getResult(req);
|
||||
const response = await this.handleResult(result);
|
||||
res.json(response);
|
||||
}
|
||||
|
||||
start(): Promise<void> {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
private async getIdentityProfile(
|
||||
jwt: string,
|
||||
): Promise<CloudflareAccessIdentityProfile> {
|
||||
const headers = new Headers();
|
||||
// set both headers just the way inbound responses are set
|
||||
headers.set(CF_JWT_HEADER, jwt);
|
||||
headers.set('cookie', `${COOKIE_AUTH_NAME}=${jwt}`);
|
||||
try {
|
||||
const res = await fetch(
|
||||
`https://${this.teamName}.cloudflareaccess.com/cdn-cgi/access/get-identity`,
|
||||
{ headers },
|
||||
);
|
||||
if (!res.ok) {
|
||||
throw ResponseError.fromResponse(res);
|
||||
}
|
||||
const cfIdentity = await res.json();
|
||||
return cfIdentity as unknown as CloudflareAccessIdentityProfile;
|
||||
} catch (err) {
|
||||
throw new ForwardedError('getIdentityProfile failed', err);
|
||||
}
|
||||
}
|
||||
|
||||
private async getResult(
|
||||
req: express.Request,
|
||||
): Promise<CloudflareAccessResult> {
|
||||
// JWTs generated by Access are available in a request header as
|
||||
// Cf-Access-Jwt-Assertion and as cookies as CF_Authorization.
|
||||
let jwt = req.header(CF_JWT_HEADER);
|
||||
if (!jwt) {
|
||||
jwt = req.cookies.CF_Authorization;
|
||||
}
|
||||
if (!jwt) {
|
||||
// Only throw if both are not provided by Cloudflare Access since either
|
||||
// can be used.
|
||||
throw new AuthenticationError(
|
||||
`Missing ${CF_JWT_HEADER} from Cloudflare Access`,
|
||||
);
|
||||
}
|
||||
|
||||
// Cloudflare signs the JWT using the RSA Signature with SHA-256 (RS256).
|
||||
// RS256 follows an asymmetric algorithm; a private key signs the JWTs and
|
||||
// a separate public key verifies the signature.
|
||||
const verifyResult = await jwtVerify(jwt, this.jwtKeySet, {
|
||||
issuer: `https://${this.teamName}.cloudflareaccess.com`,
|
||||
});
|
||||
const sub = verifyResult.payload.sub;
|
||||
const cfAccessResultStr = await this.cache?.get(`${CACHE_PREFIX}/${sub}`);
|
||||
if (typeof cfAccessResultStr === 'string') {
|
||||
return JSON.parse(cfAccessResultStr) as CloudflareAccessResult;
|
||||
}
|
||||
const claims = verifyResult.payload as CloudflareAccessClaims;
|
||||
// Builds a passport profile from JWT claims first
|
||||
try {
|
||||
// If we successfully fetch the get-identity endpoint,
|
||||
// We supplement the passport profile with richer user identity
|
||||
// information here.
|
||||
const cfIdentity = await this.getIdentityProfile(jwt);
|
||||
// Stores a stringified JSON object in cfaccess provider cache only when
|
||||
// we complete all steps
|
||||
const cfAccessResult: CloudflareAccessResult = {
|
||||
claims,
|
||||
cfIdentity,
|
||||
expiresInSeconds: claims.exp - claims.iat,
|
||||
};
|
||||
this.cache?.set(`${CACHE_PREFIX}/${sub}`, JSON.stringify(cfAccessResult));
|
||||
return cfAccessResult;
|
||||
} catch (err) {
|
||||
throw new ForwardedError(
|
||||
'Failed to populate access identity information',
|
||||
err,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async handleResult(
|
||||
result: CloudflareAccessResult,
|
||||
): Promise<CloudflareAccessResponse> {
|
||||
const { profile } = await this.authHandler(result, this.resolverContext);
|
||||
const backstageIdentity = await this.signInResolver(
|
||||
{
|
||||
result,
|
||||
profile,
|
||||
},
|
||||
this.resolverContext,
|
||||
);
|
||||
|
||||
return {
|
||||
providerInfo: {
|
||||
expiresInSeconds: result.expiresInSeconds,
|
||||
claims: result.claims,
|
||||
cfAccessIdentityProfile: result.cfIdentity,
|
||||
},
|
||||
backstageIdentity: prepareBackstageIdentityResponse(backstageIdentity),
|
||||
profile,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Auth provider integration for Cloudflare Access auth
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const cfAccess = createAuthProviderIntegration({
|
||||
create(options: {
|
||||
/**
|
||||
* The profile transformation function used to verify and convert the auth response
|
||||
* into the profile that will be presented to the user.
|
||||
*/
|
||||
authHandler?: AuthHandler<CloudflareAccessResult>;
|
||||
|
||||
/**
|
||||
* Configure sign-in for this provider, without it the provider can not be used to sign users in.
|
||||
*/
|
||||
signIn: {
|
||||
/**
|
||||
* Maps an auth result to a Backstage identity for the user.
|
||||
*/
|
||||
resolver: SignInResolver<CloudflareAccessResult>;
|
||||
};
|
||||
/**
|
||||
* CacheClient object that was configured for the Backstage backend,
|
||||
* should be provided via the backend auth plugin.
|
||||
*/
|
||||
cache?: CacheClient;
|
||||
}) {
|
||||
return ({ config, resolverContext }) => {
|
||||
const teamName = config.getString('teamName');
|
||||
|
||||
if (!options.signIn.resolver) {
|
||||
throw new Error(
|
||||
'SignInResolver is required to use this authentication provider',
|
||||
);
|
||||
}
|
||||
|
||||
const authHandler: AuthHandler<CloudflareAccessResult> =
|
||||
options?.authHandler
|
||||
? options.authHandler
|
||||
: async ({ claims, cfIdentity }) => {
|
||||
return {
|
||||
profile: {
|
||||
email: claims.email,
|
||||
displayName: cfIdentity.name,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
return new CloudflareAccessAuthProvider({
|
||||
teamName,
|
||||
signInResolver: options?.signIn.resolver,
|
||||
authHandler,
|
||||
resolverContext,
|
||||
...(options.cache && { cache: options.cache }),
|
||||
});
|
||||
};
|
||||
},
|
||||
resolvers: {
|
||||
/**
|
||||
* Looks up the user by matching their email to the entity email.
|
||||
*/
|
||||
emailMatchingUserEntityProfileEmail: () => commonByEmailResolver,
|
||||
},
|
||||
});
|
||||
@@ -20,6 +20,12 @@ export type {
|
||||
BitbucketOAuthResult,
|
||||
BitbucketPassportProfile,
|
||||
} from './bitbucket';
|
||||
export type {
|
||||
CloudflareAccessClaims,
|
||||
CloudflareAccessGroup,
|
||||
CloudflareAccessResult,
|
||||
CloudflareAccessIdentityProfile,
|
||||
} from './cloudflare-access';
|
||||
export type { GithubOAuthResult } from './github';
|
||||
export type { OAuth2ProxyResult } from './oauth2-proxy';
|
||||
export type { OidcAuthResult } from './oidc';
|
||||
|
||||
@@ -18,6 +18,7 @@ import { atlassian } from './atlassian';
|
||||
import { auth0 } from './auth0';
|
||||
import { awsAlb } from './aws-alb';
|
||||
import { bitbucket } from './bitbucket';
|
||||
import { cfAccess } from './cloudflare-access';
|
||||
import { gcpIap } from './gcp-iap';
|
||||
import { github } from './github';
|
||||
import { gitlab } from './gitlab';
|
||||
@@ -41,6 +42,7 @@ export const providers = Object.freeze({
|
||||
auth0,
|
||||
awsAlb,
|
||||
bitbucket,
|
||||
cfAccess,
|
||||
gcpIap,
|
||||
github,
|
||||
gitlab,
|
||||
|
||||
@@ -1,5 +1,29 @@
|
||||
# @backstage/plugin-auth-node
|
||||
|
||||
## 0.2.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/backend-common@0.15.0
|
||||
|
||||
## 0.2.4-next.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/backend-common@0.15.0-next.0
|
||||
|
||||
## 0.2.3
|
||||
|
||||
### 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
|
||||
- @backstage/errors@1.1.0
|
||||
|
||||
## 0.2.3-next.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-auth-node",
|
||||
"version": "0.2.3-next.2",
|
||||
"version": "0.2.4",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -23,17 +23,17 @@
|
||||
"start": "backstage-cli package start"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-common": "^0.14.1-next.3",
|
||||
"@backstage/backend-common": "^0.15.0",
|
||||
"@backstage/config": "^1.0.1",
|
||||
"@backstage/errors": "^1.1.0-next.0",
|
||||
"@backstage/errors": "^1.1.0",
|
||||
"jose": "^4.6.0",
|
||||
"node-fetch": "^2.6.7",
|
||||
"winston": "^3.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.18.0-next.3",
|
||||
"@backstage/cli": "^0.18.1",
|
||||
"lodash": "^4.17.21",
|
||||
"msw": "^0.44.0",
|
||||
"msw": "^0.45.0",
|
||||
"uuid": "^8.0.0"
|
||||
},
|
||||
"files": [
|
||||
|
||||
@@ -1,5 +1,34 @@
|
||||
# @backstage/plugin-azure-devops-backend
|
||||
|
||||
## 0.3.14
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/backend-common@0.15.0
|
||||
|
||||
## 0.3.14-next.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/backend-common@0.15.0-next.0
|
||||
|
||||
## 0.3.13
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- a70869e775: Updated dependency `msw` to `^0.43.0`.
|
||||
- 8006d0f9bf: Updated dependency `msw` to `^0.44.0`.
|
||||
- 13a232ec22: Added comments to example to help avoid confusion as to where lines need to be added
|
||||
- e67c4b7d5a: Adding getProjects endpoint to list out all projects associated with the Azure DevOps organization.
|
||||
|
||||
It can be accessed by using this endpoint `{backendUrl}/api/azure-devops/projects`
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/backend-common@0.14.1
|
||||
- @backstage/plugin-azure-devops-common@0.2.4
|
||||
|
||||
## 0.3.13-next.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -18,13 +18,12 @@ import { PullRequestOptions } from '@backstage/plugin-azure-devops-common';
|
||||
import { RepoBuild } from '@backstage/plugin-azure-devops-common';
|
||||
import { Team } from '@backstage/plugin-azure-devops-common';
|
||||
import { TeamMember } from '@backstage/plugin-azure-devops-common';
|
||||
import { UrlReader } from '@backstage/backend-common';
|
||||
import { WebApi } from 'azure-devops-node-api';
|
||||
|
||||
// Warning: (ae-missing-release-tag) "AzureDevOpsApi" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export class AzureDevOpsApi {
|
||||
constructor(logger: Logger, webApi: WebApi);
|
||||
constructor(logger: Logger, webApi: WebApi, urlReader: UrlReader);
|
||||
// (undocumented)
|
||||
getAllTeams(): Promise<Team[]>;
|
||||
// (undocumented)
|
||||
@@ -73,28 +72,31 @@ export class AzureDevOpsApi {
|
||||
options: PullRequestOptions,
|
||||
): Promise<PullRequest[]>;
|
||||
// (undocumented)
|
||||
getReadme(
|
||||
host: string,
|
||||
org: string,
|
||||
project: string,
|
||||
repo: string,
|
||||
): Promise<{
|
||||
url: string;
|
||||
content: string;
|
||||
}>;
|
||||
// (undocumented)
|
||||
getRepoBuilds(
|
||||
projectName: string,
|
||||
repoName: string,
|
||||
top: number,
|
||||
): Promise<RepoBuild[]>;
|
||||
// (undocumented)
|
||||
getTeamMembers({
|
||||
projectId,
|
||||
teamId,
|
||||
}: {
|
||||
getTeamMembers(options: {
|
||||
projectId: string;
|
||||
teamId: string;
|
||||
}): Promise<TeamMember[] | undefined>;
|
||||
}
|
||||
|
||||
// Warning: (ae-missing-release-tag) "createRouter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export function createRouter(options: RouterOptions): Promise<express.Router>;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "RouterOptions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export interface RouterOptions {
|
||||
// (undocumented)
|
||||
@@ -103,6 +105,8 @@ export interface RouterOptions {
|
||||
config: Config;
|
||||
// (undocumented)
|
||||
logger: Logger;
|
||||
// (undocumented)
|
||||
reader: UrlReader;
|
||||
}
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-azure-devops-backend",
|
||||
"version": "0.3.13-next.1",
|
||||
"version": "0.3.14",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -23,22 +23,23 @@
|
||||
"clean": "backstage-cli package clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-common": "^0.14.1-next.3",
|
||||
"@backstage/backend-common": "^0.15.0",
|
||||
"@backstage/config": "^1.0.1",
|
||||
"@backstage/plugin-azure-devops-common": "^0.2.4-next.0",
|
||||
"@backstage/plugin-azure-devops-common": "^0.2.4",
|
||||
"@types/express": "^4.17.6",
|
||||
"azure-devops-node-api": "^11.0.1",
|
||||
"express": "^4.17.1",
|
||||
"express-promise-router": "^4.1.0",
|
||||
"p-limit": "^3.1.0",
|
||||
"winston": "^3.2.1",
|
||||
"yn": "^4.0.0"
|
||||
"yn": "^4.0.0",
|
||||
"mime-types": "^2.1.27"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.18.0-next.3",
|
||||
"@backstage/cli": "^0.18.1",
|
||||
"@types/supertest": "^2.0.8",
|
||||
"supertest": "^6.1.6",
|
||||
"msw": "^0.44.0"
|
||||
"msw": "^0.45.0"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
|
||||
@@ -42,6 +42,8 @@ import {
|
||||
convertDashboardPullRequest,
|
||||
convertPolicy,
|
||||
getArtifactId,
|
||||
replaceReadme,
|
||||
buildEncodedUrl,
|
||||
} from '../utils';
|
||||
|
||||
import { TeamMember as AdoTeamMember } from 'azure-devops-node-api/interfaces/common/VSSInterfaces';
|
||||
@@ -52,11 +54,14 @@ import {
|
||||
TeamProjectReference,
|
||||
WebApiTeam,
|
||||
} from 'azure-devops-node-api/interfaces/CoreInterfaces';
|
||||
import { UrlReader } from '@backstage/backend-common';
|
||||
|
||||
/** @public */
|
||||
export class AzureDevOpsApi {
|
||||
public constructor(
|
||||
private readonly logger: Logger,
|
||||
private readonly webApi: WebApi,
|
||||
private readonly urlReader: UrlReader,
|
||||
) {}
|
||||
|
||||
public async getProjects(): Promise<Project[]> {
|
||||
@@ -73,6 +78,7 @@ export class AzureDevOpsApi {
|
||||
a.name && b.name ? a.name.localeCompare(b.name) : 0,
|
||||
);
|
||||
}
|
||||
|
||||
public async getGitRepository(
|
||||
projectName: string,
|
||||
repoName: string,
|
||||
@@ -294,13 +300,11 @@ export class AzureDevOpsApi {
|
||||
);
|
||||
}
|
||||
|
||||
public async getTeamMembers({
|
||||
projectId,
|
||||
teamId,
|
||||
}: {
|
||||
public async getTeamMembers(options: {
|
||||
projectId: string;
|
||||
teamId: string;
|
||||
}): Promise<TeamMember[] | undefined> {
|
||||
const { projectId, teamId } = options;
|
||||
this.logger?.debug(`Getting team member ids for team '${teamId}'.`);
|
||||
|
||||
const client = await this.webApi.getCoreApi();
|
||||
@@ -393,6 +397,28 @@ export class AzureDevOpsApi {
|
||||
|
||||
return buildRuns;
|
||||
}
|
||||
|
||||
public async getReadme(
|
||||
host: string,
|
||||
org: string,
|
||||
project: string,
|
||||
repo: string,
|
||||
): Promise<{
|
||||
url: string;
|
||||
content: string;
|
||||
}> {
|
||||
const url = buildEncodedUrl(host, org, project, repo, 'README.md');
|
||||
const response = await this.urlReader.read(url);
|
||||
const content = await replaceReadme(
|
||||
this.urlReader,
|
||||
host,
|
||||
org,
|
||||
project,
|
||||
repo,
|
||||
response.toString(),
|
||||
);
|
||||
return { url, content };
|
||||
}
|
||||
}
|
||||
|
||||
export function mappedRepoBuild(build: Build): RepoBuild {
|
||||
|
||||
@@ -33,7 +33,7 @@ import { ConfigReader } from '@backstage/config';
|
||||
import { GitRepository } from 'azure-devops-node-api/interfaces/GitInterfaces';
|
||||
import { createRouter } from './router';
|
||||
import express from 'express';
|
||||
import { getVoidLogger } from '@backstage/backend-common';
|
||||
import { getVoidLogger, UrlReaders } from '@backstage/backend-common';
|
||||
import request from 'supertest';
|
||||
|
||||
describe('createRouter', () => {
|
||||
@@ -53,19 +53,30 @@ describe('createRouter', () => {
|
||||
getBuildRuns: jest.fn(),
|
||||
getAllTeams: jest.fn(),
|
||||
getTeamMembers: jest.fn(),
|
||||
getReadme: jest.fn(),
|
||||
} as any;
|
||||
|
||||
const config = new ConfigReader({
|
||||
azureDevOps: {
|
||||
token: 'foo',
|
||||
host: 'host.com',
|
||||
organization: 'myOrg',
|
||||
top: 5,
|
||||
},
|
||||
});
|
||||
|
||||
const logger = getVoidLogger();
|
||||
|
||||
const router = await createRouter({
|
||||
config,
|
||||
logger,
|
||||
azureDevOpsApi,
|
||||
logger: getVoidLogger(),
|
||||
config: new ConfigReader({
|
||||
azureDevOps: {
|
||||
token: 'foo',
|
||||
host: 'host.com',
|
||||
organization: 'myOrg',
|
||||
top: 5,
|
||||
},
|
||||
reader: UrlReaders.default({
|
||||
config,
|
||||
logger,
|
||||
}),
|
||||
});
|
||||
|
||||
app = express().use(router);
|
||||
});
|
||||
|
||||
@@ -455,4 +466,67 @@ describe('createRouter', () => {
|
||||
expect(response.status).toEqual(200);
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /readme/:projectName/:repoName', () => {
|
||||
it('fetches readme file', async () => {
|
||||
const content = getReadmeMock();
|
||||
const url = `https://host.com/myOrg/myProject/_git/myRepo?path=README.md`;
|
||||
|
||||
azureDevOpsApi.getReadme.mockResolvedValueOnce({
|
||||
content,
|
||||
url,
|
||||
});
|
||||
|
||||
const response = await request(app).get(
|
||||
'/readme/myProject/myRepo?path=README.md',
|
||||
);
|
||||
expect(azureDevOpsApi.getReadme).toHaveBeenCalledWith(
|
||||
'host.com',
|
||||
'myOrg',
|
||||
'myProject',
|
||||
'myRepo',
|
||||
);
|
||||
expect(response.status).toEqual(200);
|
||||
expect(response.body).toEqual({
|
||||
content,
|
||||
url,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function getReadmeMock() {
|
||||
return `
|
||||
# Introduction
|
||||
TODO: Give a short introduction of your project. Let this section explain the objectives or the motivation behind this project.
|
||||
|
||||
# Getting Started
|
||||
TODO: Guide users through getting your code up and running on their own system. In this section you can talk about:
|
||||
1. Installation process
|
||||
2. Software dependencies
|
||||
3. Latest releases
|
||||
4. API references
|
||||
|
||||
# Build and Test
|
||||
TODO: Describe and show how to build your code and run the tests.
|
||||
|
||||
# Contribute
|
||||
TODO: Explain how other users and developers can contribute to make your code better.
|
||||
|
||||
If you want to learn more about creating good readme files then refer the following [guidelines](https://docs.microsoft.com/en-us/azure/devops/repos/git/create-a-readme?view=azure-devops). You can also seek inspiration from the below readme files:
|
||||
- [ASP.NET Core](https://github.com/aspnet/Home)
|
||||
- [Visual Studio Code](https://github.com/Microsoft/vscode)
|
||||
- [Chakra Core](https://github.com/Microsoft/ChakraCore)
|
||||
|
||||
|
||||
- 
|
||||
- 
|
||||
- 
|
||||
- 
|
||||
- 
|
||||
- 
|
||||
- 
|
||||
- 
|
||||
- 
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -26,21 +26,24 @@ import { Config } from '@backstage/config';
|
||||
import { Logger } from 'winston';
|
||||
import { PullRequestsDashboardProvider } from '../api/PullRequestsDashboardProvider';
|
||||
import Router from 'express-promise-router';
|
||||
import { errorHandler } from '@backstage/backend-common';
|
||||
import { errorHandler, UrlReader } from '@backstage/backend-common';
|
||||
import express from 'express';
|
||||
|
||||
const DEFAULT_TOP = 10;
|
||||
|
||||
/** @public */
|
||||
export interface RouterOptions {
|
||||
azureDevOpsApi?: AzureDevOpsApi;
|
||||
logger: Logger;
|
||||
config: Config;
|
||||
reader: UrlReader;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export async function createRouter(
|
||||
options: RouterOptions,
|
||||
): Promise<express.Router> {
|
||||
const { logger } = options;
|
||||
const { logger, reader } = options;
|
||||
const config = options.config.getConfig('azureDevOps');
|
||||
|
||||
const token = config.getString('token');
|
||||
@@ -51,7 +54,7 @@ export async function createRouter(
|
||||
const webApi = new WebApi(`https://${host}/${organization}`, authHandler);
|
||||
|
||||
const azureDevOpsApi =
|
||||
options.azureDevOpsApi || new AzureDevOpsApi(logger, webApi);
|
||||
options.azureDevOpsApi || new AzureDevOpsApi(logger, webApi, reader);
|
||||
|
||||
const pullRequestsDashboardProvider =
|
||||
await PullRequestsDashboardProvider.create(logger, azureDevOpsApi);
|
||||
@@ -191,6 +194,17 @@ export async function createRouter(
|
||||
res.status(200).json(teamIds);
|
||||
});
|
||||
|
||||
router.get('/readme/:projectName/:repoName', async (req, res) => {
|
||||
const { projectName, repoName } = req.params;
|
||||
const readme = await azureDevOpsApi.getReadme(
|
||||
host,
|
||||
organization,
|
||||
projectName,
|
||||
repoName,
|
||||
);
|
||||
res.status(200).json(readme);
|
||||
});
|
||||
|
||||
router.use(errorHandler());
|
||||
return router;
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
import {
|
||||
createServiceBuilder,
|
||||
loadBackendConfig,
|
||||
UrlReaders,
|
||||
} from '@backstage/backend-common';
|
||||
import { Server } from 'http';
|
||||
import { Logger } from 'winston';
|
||||
@@ -39,6 +40,7 @@ export async function startStandaloneServer(
|
||||
const router = await createRouter({
|
||||
logger,
|
||||
config,
|
||||
reader: UrlReaders.default({ logger, config }),
|
||||
});
|
||||
|
||||
let service = createServiceBuilder(module)
|
||||
|
||||
@@ -21,12 +21,15 @@ import {
|
||||
} from '@backstage/plugin-azure-devops-common';
|
||||
import {
|
||||
convertDashboardPullRequest,
|
||||
extractAssets,
|
||||
extractPartsFromAsset,
|
||||
getArtifactId,
|
||||
getAvatarUrl,
|
||||
getPullRequestLink,
|
||||
replaceReadme,
|
||||
} from './azure-devops-utils';
|
||||
|
||||
import { GitPullRequest } from 'azure-devops-node-api/interfaces/GitInterfaces';
|
||||
import { UrlReader } from '@backstage/backend-common';
|
||||
|
||||
describe('convertDashboardPullRequest', () => {
|
||||
it('should return DashboardPullRequest', () => {
|
||||
@@ -159,3 +162,115 @@ describe('getArtifactId', () => {
|
||||
expect(result).toBe('vstfs:///CodeReview/CodeReviewId/project1/1');
|
||||
});
|
||||
});
|
||||
|
||||
describe('extractAssets', () => {
|
||||
it('should return assets', () => {
|
||||
const readme = `
|
||||
## Images
|
||||
.png)
|
||||

|
||||
))).jpeg)
|
||||

|
||||
mple.gif)
|
||||
`;
|
||||
const result = extractAssets(readme);
|
||||
expect(result).toEqual([
|
||||
'[Image 1](./images/sample-4(2).png)',
|
||||
'[Image 2](./images/cdCSj+-012340.jpg)',
|
||||
'[Image 3](/images/test-4(2)))).jpeg)',
|
||||
'[Image 4](./images/test-2211jd.webp)',
|
||||
'[Image 5](/images/sa)mple.gif)',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('extractPartsFromAsset', () => {
|
||||
it('should return parts from asset - PNG', () => {
|
||||
const result = extractPartsFromAsset('[Image 1](./images/sample-4(2).png)');
|
||||
expect(result).toEqual({
|
||||
label: 'Image 1',
|
||||
path: '/images/sample-4(2)',
|
||||
ext: '.png',
|
||||
});
|
||||
});
|
||||
|
||||
it('should return parts from asset - JPG', () => {
|
||||
const result = extractPartsFromAsset(
|
||||
'[Image 2](./images/cdCSj+-012340.jpg)',
|
||||
);
|
||||
expect(result).toEqual({
|
||||
label: 'Image 2',
|
||||
path: '/images/cdCSj+-012340',
|
||||
ext: '.jpg',
|
||||
});
|
||||
});
|
||||
|
||||
it('should return parts from asset - JPEG', () => {
|
||||
const result = extractPartsFromAsset(
|
||||
'[Image 2](/images/test-4(2)))).jpeg)',
|
||||
);
|
||||
expect(result).toEqual({
|
||||
label: 'Image 2',
|
||||
path: '/images/test-4(2))))',
|
||||
ext: '.jpeg',
|
||||
});
|
||||
});
|
||||
|
||||
it('should return parts from asset - WEBP', () => {
|
||||
const result = extractPartsFromAsset('[Image 2](/images/test-2211jd.webp)');
|
||||
expect(result).toEqual({
|
||||
label: 'Image 2',
|
||||
path: '/images/test-2211jd',
|
||||
ext: '.webp',
|
||||
});
|
||||
});
|
||||
|
||||
it('should return parts from asset - GIF', () => {
|
||||
const result = extractPartsFromAsset('[Image 2](/images/test-4(2)))).gif)');
|
||||
expect(result).toEqual({
|
||||
label: 'Image 2',
|
||||
path: '/images/test-4(2))))',
|
||||
ext: '.gif',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('replaceReadme', () => {
|
||||
it('should return mime type', async () => {
|
||||
const readme = `
|
||||
## Images
|
||||
.png)
|
||||

|
||||
))).jpeg)
|
||||

|
||||
mple.gif)
|
||||
`;
|
||||
|
||||
const reader: UrlReader = {
|
||||
read: url => new Promise<Buffer>(resolve => resolve(Buffer.from(url))),
|
||||
readTree: jest.fn(),
|
||||
search: jest.fn(),
|
||||
readUrl: jest.fn(),
|
||||
};
|
||||
|
||||
const result = await replaceReadme(
|
||||
reader,
|
||||
'host',
|
||||
'org',
|
||||
'project',
|
||||
'repo',
|
||||
readme,
|
||||
);
|
||||
|
||||
const expected = `
|
||||
## Images
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
`;
|
||||
|
||||
expect(expected).toBe(result);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -30,9 +30,11 @@ import {
|
||||
GitRepository,
|
||||
IdentityRefWithVote,
|
||||
} from 'azure-devops-node-api/interfaces/GitInterfaces';
|
||||
import mime from 'mime-types';
|
||||
|
||||
import { IdentityRef } from 'azure-devops-node-api/interfaces/common/VSSInterfaces';
|
||||
import { PolicyEvaluationRecord } from 'azure-devops-node-api/interfaces/PolicyInterfaces';
|
||||
import { UrlReader } from '@backstage/backend-common';
|
||||
|
||||
export function convertDashboardPullRequest(
|
||||
pullRequest: GitPullRequest,
|
||||
@@ -205,6 +207,48 @@ export function convertPolicy(
|
||||
};
|
||||
}
|
||||
|
||||
export async function replaceReadme(
|
||||
urlReader: UrlReader,
|
||||
host: string,
|
||||
org: string,
|
||||
project: string,
|
||||
repo: string,
|
||||
readmeContent: string,
|
||||
) {
|
||||
const filesPath = extractAssets(readmeContent);
|
||||
if (!filesPath) return readmeContent;
|
||||
return await filesPath.reduce(
|
||||
async (promise: Promise<string>, filePath: string) =>
|
||||
promise.then(async content => {
|
||||
const { label, path, ext } = extractPartsFromAsset(filePath);
|
||||
const data = mime.lookup(ext);
|
||||
const url = buildEncodedUrl(host, org, project, repo, path + ext);
|
||||
const buffer = await urlReader.read(url);
|
||||
const file = await buffer.toString('base64');
|
||||
return content.replace(
|
||||
filePath,
|
||||
`[${label}](data:${data};base64,${file})`,
|
||||
);
|
||||
}),
|
||||
Promise.resolve(readmeContent),
|
||||
);
|
||||
}
|
||||
|
||||
export function buildEncodedUrl(
|
||||
host: string,
|
||||
org: string,
|
||||
project: string,
|
||||
repo: string,
|
||||
path: string,
|
||||
): string {
|
||||
const encodedHost = encodeURIComponent(host);
|
||||
const encodedOrg = encodeURIComponent(org);
|
||||
const encodedProject = encodeURIComponent(project);
|
||||
const encodedRepo = encodeURIComponent(repo);
|
||||
const encodedPath = encodeURIComponent(path);
|
||||
return `https://${encodedHost}/${encodedOrg}/${encodedProject}/_git/${encodedRepo}?path=${encodedPath}`;
|
||||
}
|
||||
|
||||
function convertReviewer(
|
||||
identityRef?: IdentityRefWithVote,
|
||||
): Reviewer | undefined {
|
||||
@@ -263,3 +307,24 @@ function convertCreatedBy(identityRef?: IdentityRef): CreatedBy | undefined {
|
||||
function hasAutoComplete(pullRequest: GitPullRequest): boolean {
|
||||
return pullRequest.isDraft !== true && !!pullRequest.completionOptions;
|
||||
}
|
||||
|
||||
export function extractAssets(content: string) {
|
||||
const regExp =
|
||||
/\[([^\[\]]*)\]\((?!https?:\/\/)(.*?)(\.png|\.jpg|\.jpeg|\.gif|\.webp)(.*)\)/gim;
|
||||
return content.match(regExp);
|
||||
}
|
||||
|
||||
export function extractPartsFromAsset(content: string): {
|
||||
label: string;
|
||||
path: string;
|
||||
ext: string;
|
||||
} {
|
||||
const regExp =
|
||||
/\[(.*?)\]\((?!https?:\/\/)(.*?)(\.png|\.jpg|\.jpeg|\.gif|\.webp)(.*)\)/;
|
||||
const [_, label, path, ext] = regExp.exec(content) || [];
|
||||
return {
|
||||
ext,
|
||||
label,
|
||||
path: path.startsWith('.') ? path.substring(1, path.length) : path,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# @backstage/plugin-azure-devops-common
|
||||
|
||||
## 0.2.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- e67c4b7d5a: Adding getProjects endpoint to list out all projects associated with the Azure DevOps organization.
|
||||
|
||||
It can be accessed by using this endpoint `{backendUrl}/api/azure-devops/projects`
|
||||
|
||||
## 0.2.4-next.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user