Merge branch 'master' into kubernetes-common
This commit is contained in:
@@ -1,5 +1,19 @@
|
||||
# @backstage/plugin-api-docs
|
||||
|
||||
## 0.4.12
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 1ce80ff02: Resolve issues with AsyncAPI rendering by updating `@asyncapi/react-component`
|
||||
to `0.23.0`. The theming of the component is adjusted to the latest styling
|
||||
changes.
|
||||
- c614ede9a: Updated README to have up-to-date install instructions.
|
||||
- 07a7806c3: Added fields filtering in get API entities to avoid the requesting of unused data
|
||||
- Updated dependencies [9afcac5af]
|
||||
- Updated dependencies [e0c9ed759]
|
||||
- Updated dependencies [6eaecbd81]
|
||||
- @backstage/core@0.7.7
|
||||
|
||||
## 0.4.11
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -15,6 +15,7 @@ Right now, the following API formats are supported:
|
||||
- [OpenAPI](https://swagger.io/specification/) 2 & 3
|
||||
- [AsyncAPI](https://www.asyncapi.com/docs/specifications/latest/)
|
||||
- [GraphQL](https://graphql.org/learn/schema/)
|
||||
- [JSON Schema](https://json-schema.org/)
|
||||
|
||||
Other formats are displayed as plain text, but this can easily be extended.
|
||||
|
||||
@@ -28,15 +29,15 @@ To link that a component provides or consumes an API, see the [`providesApis`](h
|
||||
1. Install the API docs plugin
|
||||
|
||||
```bash
|
||||
# packages/app
|
||||
|
||||
# From your Backstage root directory
|
||||
cd packages/app
|
||||
yarn add @backstage/plugin-api-docs
|
||||
```
|
||||
|
||||
2. Add the `ApiExplorerPage` extension to the app:
|
||||
|
||||
```tsx
|
||||
// packages/app/src/App.tsx
|
||||
// In packages/app/src/App.tsx
|
||||
|
||||
import { ApiExplorerPage } from '@backstage/plugin-api-docs';
|
||||
|
||||
@@ -56,7 +57,7 @@ import {
|
||||
} from '@backstage/plugin-api-docs';
|
||||
|
||||
const apiPage = (
|
||||
<EntityLayoutWrapper>
|
||||
<EntityLayout>
|
||||
<EntityLayout.Route path="/" title="Overview">
|
||||
<Grid container spacing={3}>
|
||||
<Grid item md={6}>
|
||||
@@ -80,7 +81,7 @@ const apiPage = (
|
||||
</Grid>
|
||||
</Grid>
|
||||
</EntityLayout.Route>
|
||||
</EntityLayoutWrapper>
|
||||
</EntityLayout>
|
||||
);
|
||||
|
||||
// ...
|
||||
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
} from '../src';
|
||||
import asyncapiApiEntity from './asyncapi-example-api.yaml';
|
||||
import graphqlApiEntity from './graphql-example-api.yaml';
|
||||
import jsonschemaApiEntity from './jsonschema-example-api.yaml';
|
||||
import openapiApiEntity from './openapi-example-api.yaml';
|
||||
import otherApiEntity from './other-example-api.yaml';
|
||||
|
||||
@@ -41,6 +42,7 @@ createDevApp()
|
||||
items: [
|
||||
openapiApiEntity,
|
||||
asyncapiApiEntity,
|
||||
jsonschemaApiEntity,
|
||||
graphqlApiEntity,
|
||||
otherApiEntity,
|
||||
],
|
||||
@@ -87,6 +89,19 @@ createDevApp()
|
||||
</Page>
|
||||
),
|
||||
})
|
||||
.addPage({
|
||||
title: 'JSON Schema',
|
||||
element: (
|
||||
<Page themeId="home">
|
||||
<Header title="JSON Schema" />
|
||||
<Content>
|
||||
<EntityProvider entity={(jsonschemaApiEntity as any) as Entity}>
|
||||
<EntityApiDefinitionCard />
|
||||
</EntityProvider>
|
||||
</Content>
|
||||
</Page>
|
||||
),
|
||||
})
|
||||
.addPage({
|
||||
title: 'GraphQL',
|
||||
element: (
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
apiVersion: backstage.io/v1alpha1
|
||||
kind: API
|
||||
metadata:
|
||||
name: persons
|
||||
description: Person dataset
|
||||
spec:
|
||||
type: jsonschema
|
||||
lifecycle: experimental
|
||||
owner: team-c
|
||||
# From https://json-schema.org/learn/miscellaneous-examples.html
|
||||
definition: |
|
||||
{
|
||||
"$id": "https://example.com/person.schema.json",
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"title": "Person",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"firstName": {
|
||||
"type": "string",
|
||||
"description": "The person's first name."
|
||||
},
|
||||
"lastName": {
|
||||
"type": "string",
|
||||
"description": "The person's last name."
|
||||
},
|
||||
"age": {
|
||||
"description": "Age in years which must be equal to or greater than zero.",
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-api-docs",
|
||||
"version": "0.4.11",
|
||||
"version": "0.4.12",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -31,13 +31,16 @@
|
||||
"dependencies": {
|
||||
"@asyncapi/react-component": "^0.23.0",
|
||||
"@backstage/catalog-model": "^0.7.7",
|
||||
"@backstage/core": "^0.7.6",
|
||||
"@backstage/core": "^0.7.7",
|
||||
"@backstage/plugin-catalog-react": "^0.1.4",
|
||||
"@backstage/theme": "^0.2.6",
|
||||
"@material-icons/font": "^1.0.2",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "4.0.0-alpha.45",
|
||||
"@stoplight/json-schema-viewer": "^4.0.0-beta.16",
|
||||
"@stoplight/mosaic": "^1.0.0-beta.46",
|
||||
"@stoplight/reporter": "^1.10.0",
|
||||
"@types/react": "^16.9",
|
||||
"graphiql": "^1.0.0-alpha.10",
|
||||
"graphql": "^15.3.0",
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
import React from 'react';
|
||||
import { AsyncApiDefinitionWidget } from '../AsyncApiDefinitionWidget';
|
||||
import { GraphQlDefinitionWidget } from '../GraphQlDefinitionWidget';
|
||||
import { JsonSchemaDefinitionWidget } from '../JsonSchemaDefinitionWidget';
|
||||
import { OpenApiDefinitionWidget } from '../OpenApiDefinitionWidget';
|
||||
|
||||
export type ApiDefinitionWidget = {
|
||||
@@ -51,5 +52,13 @@ export function defaultDefinitionWidgets(): ApiDefinitionWidget[] {
|
||||
<GraphQlDefinitionWidget definition={definition} />
|
||||
),
|
||||
},
|
||||
{
|
||||
type: 'jsonschema',
|
||||
title: 'JSON Schema',
|
||||
rawLanguage: 'json',
|
||||
component: definition => (
|
||||
<JsonSchemaDefinitionWidget definition={definition} />
|
||||
),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* 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 { renderInTestApp } from '@backstage/test-utils';
|
||||
import React from 'react';
|
||||
import { JsonSchemaDefinitionWidget } from './JsonSchemaDefinitionWidget';
|
||||
|
||||
describe('<JsonSchemaDefinitionWidget />', () => {
|
||||
it('renders json schema', async () => {
|
||||
// From https://json-schema.org/learn/miscellaneous-examples.html
|
||||
const definition = `
|
||||
{
|
||||
"$id": "https://example.com/person.schema.json",
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"title": "Person",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"firstName": {
|
||||
"type": "string",
|
||||
"description": "The person's first name."
|
||||
},
|
||||
"lastName": {
|
||||
"type": "string",
|
||||
"description": "The person's last name."
|
||||
},
|
||||
"age": {
|
||||
"description": "Age in years which must be equal to or greater than zero.",
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
const { getByText } = await renderInTestApp(
|
||||
<JsonSchemaDefinitionWidget definition={definition} />,
|
||||
);
|
||||
|
||||
expect(getByText(/lastName/i)).toBeInTheDocument();
|
||||
expect(getByText(/The person's last name./i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders error if definition is missing', async () => {
|
||||
const { getByText } = await renderInTestApp(
|
||||
<JsonSchemaDefinitionWidget definition="{}" />,
|
||||
);
|
||||
expect(getByText(/No schema defined/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* 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 { useTheme } from '@material-ui/core';
|
||||
import { JsonSchemaViewer } from '@stoplight/json-schema-viewer';
|
||||
import { injectStyles, useThemeStore } from '@stoplight/mosaic';
|
||||
import React, { useMemo } from 'react';
|
||||
import { useEffectOnce } from 'react-use';
|
||||
|
||||
injectStyles();
|
||||
|
||||
type Props = {
|
||||
definition: any;
|
||||
};
|
||||
|
||||
export const JsonSchemaDefinitionWidget = ({ definition }: Props) => {
|
||||
const schema = useMemo(() => JSON.parse(definition), [definition]);
|
||||
const theme = useTheme();
|
||||
const themeStore = useThemeStore();
|
||||
|
||||
useEffectOnce(() => {
|
||||
themeStore.setColor('background', theme.palette.background.paper);
|
||||
themeStore.setColor('text', theme.palette.text.primary);
|
||||
themeStore.setColor('primary', theme.palette.primary.main);
|
||||
themeStore.setColor('success', theme.palette.success.main);
|
||||
themeStore.setColor('warning', theme.palette.warning.main);
|
||||
themeStore.setColor('danger', theme.palette.error.main);
|
||||
themeStore.setMode(theme.palette.type);
|
||||
});
|
||||
return (
|
||||
<JsonSchemaViewer
|
||||
schema={schema}
|
||||
emptyText="No schema defined"
|
||||
defaultExpandedDepth={5}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* 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 { JsonSchemaDefinitionWidget } from './JsonSchemaDefinitionWidget';
|
||||
@@ -20,3 +20,4 @@ export * from './AsyncApiDefinitionWidget';
|
||||
export * from './ComponentsCards';
|
||||
export * from './OpenApiDefinitionWidget';
|
||||
export * from './PlainApiDefinitionWidget';
|
||||
export * from './JsonSchemaDefinitionWidget';
|
||||
|
||||
@@ -7,7 +7,9 @@ This backend plugin can be installed to serve static content of a Backstage app.
|
||||
Add both this package and your local frontend app package as dependencies to your backend, for example
|
||||
|
||||
```bash
|
||||
yarn add @backstage/plugin-app-backend example-app
|
||||
# From your Backstage root directory
|
||||
cd packages/backend
|
||||
yarn add @backstage/plugin-app-backend app
|
||||
```
|
||||
|
||||
By adding the app package as a dependency we ensure that it is built as part of the backend, and that it can be resolved at runtime.
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.7.6",
|
||||
"@backstage/core": "^0.7.6",
|
||||
"@backstage/core": "^0.7.7",
|
||||
"@backstage/errors": "^0.1.1",
|
||||
"@backstage/plugin-catalog-react": "^0.1.3",
|
||||
"@backstage/theme": "^0.2.6",
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
# @backstage/plugin-bitrise
|
||||
|
||||
## 0.1.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- c614ede9a: Updated README to have up-to-date install instructions.
|
||||
- Updated dependencies [9afcac5af]
|
||||
- Updated dependencies [e0c9ed759]
|
||||
- Updated dependencies [6eaecbd81]
|
||||
- @backstage/core@0.7.7
|
||||
|
||||
## 0.1.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -8,9 +8,9 @@ Welcome to the Bitrise plugin!
|
||||
## Installation
|
||||
|
||||
```sh
|
||||
# The plugin must be added in the app package
|
||||
$ cd packages/app
|
||||
$ yarn add @backstage/plugin-bitrise
|
||||
# From your Backstage root directory
|
||||
cd packages/app
|
||||
yarn add @backstage/plugin-bitrise
|
||||
```
|
||||
|
||||
Bitrise Plugin exposes an entity tab component named `EntityBitriseContent`. You can include it in the
|
||||
@@ -22,7 +22,7 @@ import { EntityBitriseContent } from '@backstage/plugin-bitrise';
|
||||
|
||||
// Farther down at the website declaration
|
||||
const websiteEntityPage = (
|
||||
<EntityLayoutWrapper>
|
||||
<EntityLayout>
|
||||
{/* Place the following section where you want the tab to appear */}
|
||||
<EntityLayout.Route path="/bitrise" title="Bitrise">
|
||||
<EntityBitriseContent />
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-bitrise",
|
||||
"version": "0.1.1",
|
||||
"version": "0.1.2",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -21,7 +21,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.7.2",
|
||||
"@backstage/core": "^0.7.6",
|
||||
"@backstage/core": "^0.7.7",
|
||||
"@backstage/plugin-catalog-react": "^0.1.2",
|
||||
"@backstage/theme": "^0.2.6",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# @backstage/plugin-catalog-backend
|
||||
|
||||
## 0.8.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- a99e0bc42: Entity lifecycle and owner are now indexed by the `DefaultCatalogCollator`. A `locationTemplate` may now optionally be provided to its constructor to reflect a custom catalog entity path in the Backstage frontend.
|
||||
- Updated dependencies [e1e757569]
|
||||
- @backstage/plugin-search-backend-node@0.1.4
|
||||
|
||||
## 0.8.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-catalog-backend",
|
||||
"version": "0.8.0",
|
||||
"version": "0.8.1",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -35,7 +35,7 @@
|
||||
"@backstage/config": "^0.1.4",
|
||||
"@backstage/errors": "^0.1.1",
|
||||
"@backstage/integration": "^0.5.1",
|
||||
"@backstage/plugin-search-backend-node": "^0.1.3",
|
||||
"@backstage/plugin-search-backend-node": "^0.1.4",
|
||||
"@backstage/search-common": "^0.1.1",
|
||||
"@octokit/graphql": "^4.5.8",
|
||||
"@types/express": "^4.17.6",
|
||||
|
||||
@@ -156,7 +156,7 @@ describe('github', () => {
|
||||
describe('getOrganizationRepositories', () => {
|
||||
it('read repositories', async () => {
|
||||
const input: QueryResponse = {
|
||||
organization: {
|
||||
repositoryOwner: {
|
||||
repositories: {
|
||||
nodes: [
|
||||
{
|
||||
|
||||
@@ -20,7 +20,8 @@ import { graphql } from '@octokit/graphql';
|
||||
// Graphql types
|
||||
|
||||
export type QueryResponse = {
|
||||
organization: Organization;
|
||||
organization?: Organization;
|
||||
repositoryOwner?: Organization | User;
|
||||
};
|
||||
|
||||
export type Organization = {
|
||||
@@ -41,6 +42,7 @@ export type User = {
|
||||
avatarUrl?: string;
|
||||
email?: string;
|
||||
name?: string;
|
||||
repositories?: Connection<Repository>;
|
||||
};
|
||||
|
||||
export type Team = {
|
||||
@@ -228,28 +230,27 @@ export async function getOrganizationRepositories(
|
||||
org: string,
|
||||
): Promise<{ repositories: Repository[] }> {
|
||||
const query = `
|
||||
query repositories($org: String!, $cursor: String) {
|
||||
organization(login: $org) {
|
||||
name
|
||||
repositories(first: 100, after: $cursor) {
|
||||
nodes {
|
||||
name
|
||||
url
|
||||
isArchived
|
||||
}
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
endCursor
|
||||
query repositories($org: String!, $cursor: String) {
|
||||
repositoryOwner(login: $org) {
|
||||
login
|
||||
repositories(first: 100, after: $cursor) {
|
||||
nodes {
|
||||
name
|
||||
url
|
||||
isArchived
|
||||
}
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
endCursor
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
}`;
|
||||
|
||||
const repositories = await queryWithPaging(
|
||||
client,
|
||||
query,
|
||||
r => r.organization?.repositories,
|
||||
r => r.repositoryOwner?.repositories,
|
||||
x => x,
|
||||
{ org },
|
||||
);
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
# @backstage/plugin-catalog-import
|
||||
|
||||
## 0.5.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- c614ede9a: Updated README to have up-to-date install instructions.
|
||||
- Updated dependencies [9afcac5af]
|
||||
- Updated dependencies [e0c9ed759]
|
||||
- Updated dependencies [6eaecbd81]
|
||||
- @backstage/core@0.7.7
|
||||
|
||||
## 0.5.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -18,8 +18,8 @@ Some features are not yet available for all supported Git providers.
|
||||
1. Install the Catalog Import Plugin:
|
||||
|
||||
```bash
|
||||
# packages/app
|
||||
|
||||
# From your Backstage root directory
|
||||
cd packages/app
|
||||
yarn add @backstage/plugin-catalog-import
|
||||
```
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-catalog-import",
|
||||
"version": "0.5.3",
|
||||
"version": "0.5.4",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -32,7 +32,7 @@
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.7.6",
|
||||
"@backstage/catalog-client": "^0.3.9",
|
||||
"@backstage/core": "^0.7.6",
|
||||
"@backstage/core": "^0.7.7",
|
||||
"@backstage/integration": "^0.5.0",
|
||||
"@backstage/integration-react": "^0.1.1",
|
||||
"@backstage/plugin-catalog-react": "^0.1.4",
|
||||
@@ -40,7 +40,7 @@
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "4.0.0-alpha.45",
|
||||
"@octokit/rest": "^18.0.12",
|
||||
"@octokit/rest": "^18.5.3",
|
||||
"@types/react": "^16.9",
|
||||
"git-url-parse": "^11.4.4",
|
||||
"js-base64": "^3.6.0",
|
||||
|
||||
@@ -1,5 +1,18 @@
|
||||
# @backstage/plugin-catalog
|
||||
|
||||
## 0.5.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 19a4dd710: Removed unused `swr` dependency.
|
||||
- da546ce00: Support `gridItem` variant for `EntityLinksCard`.
|
||||
- e0c9ed759: Add `if` prop to `EntityLayout.Route` to conditionally render tabs
|
||||
- 1a142ae8a: Switch out the time-based personal greeting for a plain title on the catalog index page, and remove the clocks for different timezones.
|
||||
- Updated dependencies [9afcac5af]
|
||||
- Updated dependencies [e0c9ed759]
|
||||
- Updated dependencies [6eaecbd81]
|
||||
- @backstage/core@0.7.7
|
||||
|
||||
## 0.5.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-catalog",
|
||||
"version": "0.5.5",
|
||||
"version": "0.5.6",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -32,7 +32,7 @@
|
||||
"dependencies": {
|
||||
"@backstage/catalog-client": "^0.3.10",
|
||||
"@backstage/catalog-model": "^0.7.7",
|
||||
"@backstage/core": "^0.7.6",
|
||||
"@backstage/core": "^0.7.7",
|
||||
"@backstage/errors": "^0.1.1",
|
||||
"@backstage/integration": "^0.5.1",
|
||||
"@backstage/integration-react": "^0.1.1",
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
# @backstage/plugin-circleci
|
||||
|
||||
## 0.2.13
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- c614ede9a: Updated README to have up-to-date install instructions.
|
||||
- Updated dependencies [9afcac5af]
|
||||
- Updated dependencies [e0c9ed759]
|
||||
- Updated dependencies [6eaecbd81]
|
||||
- @backstage/core@0.7.7
|
||||
|
||||
## 0.2.12
|
||||
|
||||
### Patch Changes
|
||||
|
||||
+18
-16
@@ -7,34 +7,35 @@ Website: [https://circleci.com/](https://circleci.com/)
|
||||
|
||||
## Setup
|
||||
|
||||
1. If you have standalone app (you didn't clone this repo), then do
|
||||
1. If you have a standalone app (you didn't clone this repo), then do
|
||||
|
||||
```bash
|
||||
# From your Backstage root directory
|
||||
cd packages/app
|
||||
yarn add @backstage/plugin-circleci
|
||||
```
|
||||
|
||||
2. Add the `EntityCircleCIContent` extension to the entity page in the app:
|
||||
2. Add the `EntityCircleCIContent` extension to the entity page in your app:
|
||||
|
||||
```tsx
|
||||
// packages/app/src/components/catalog/EntityPage.tsx
|
||||
import { EntityCircleCIContent } from '@backstage/plugin-circleci';
|
||||
// In packages/app/src/components/catalog/EntityPage.tsx
|
||||
import {
|
||||
EntityCircleCIContent,
|
||||
isCircleCIAvailable,
|
||||
} from '@backstage/plugin-circleci';
|
||||
|
||||
// ...
|
||||
const serviceEntityPage = (
|
||||
<EntityPageLayout>
|
||||
...
|
||||
<EntityLayout.Route path="/circle-ci" title="Circle CI">
|
||||
// For example in the CI/CD section
|
||||
const cicdContent = (
|
||||
<EntitySwitch>
|
||||
<EntitySwitch.Case if={isCircleCIAvailable}>
|
||||
<EntityCircleCIContent />
|
||||
</EntityLayout.Route>
|
||||
...
|
||||
</EntityPageLayout>
|
||||
);
|
||||
</EntitySwitch.Case>
|
||||
```
|
||||
|
||||
4. Add proxy config:
|
||||
|
||||
```yaml
|
||||
// app-config.yaml
|
||||
# In app-config.yaml
|
||||
proxy:
|
||||
'/circleci/api':
|
||||
target: https://circleci.com/api/v1.1
|
||||
@@ -42,10 +43,11 @@ proxy:
|
||||
Circle-Token: ${CIRCLECI_AUTH_TOKEN}
|
||||
```
|
||||
|
||||
5. Get and provide `CIRCLECI_AUTH_TOKEN` as env variable (https://circleci.com/docs/api/#add-an-api-token)
|
||||
6. Add `circleci.com/project-slug` annotation to your catalog-info.yaml file in format <git-provider>/<owner>/<project> (https://backstage.io/docs/architecture-decisions/adrs-adr002#format)
|
||||
5. Get and provide a `CIRCLECI_AUTH_TOKEN` as an environment variable (see the [CircleCI docs](https://circleci.com/docs/api/#add-an-api-token)).
|
||||
6. Add a `circleci.com/project-slug` annotation to your respective `catalog-info.yaml` files, on the format <git-provider>/<owner>/<project> (https://backstage.io/docs/architecture-decisions/adrs-adr002#format).
|
||||
|
||||
```yaml
|
||||
# Example catalog-info.yaml entity definition file
|
||||
apiVersion: backstage.io/v1alpha1
|
||||
kind: Component
|
||||
metadata:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-circleci",
|
||||
"version": "0.2.12",
|
||||
"version": "0.2.13",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -32,7 +32,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.7.3",
|
||||
"@backstage/core": "^0.7.6",
|
||||
"@backstage/core": "^0.7.7",
|
||||
"@backstage/plugin-catalog-react": "^0.1.2",
|
||||
"@backstage/theme": "^0.2.6",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.7.3",
|
||||
"@backstage/plugin-catalog-react": "^0.1.2",
|
||||
"@backstage/core": "^0.7.6",
|
||||
"@backstage/core": "^0.7.7",
|
||||
"@backstage/theme": "^0.2.6",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @backstage/plugin-code-coverage-backend
|
||||
|
||||
## 0.1.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- d47c2628b: Include migrations
|
||||
|
||||
## 0.1.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-code-coverage-backend",
|
||||
"version": "0.1.2",
|
||||
"version": "0.1.3",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.7.7",
|
||||
"@backstage/config": "^0.1.4",
|
||||
"@backstage/core": "^0.7.6",
|
||||
"@backstage/core": "^0.7.7",
|
||||
"@backstage/errors": "^0.1.1",
|
||||
"@backstage/plugin-catalog-react": "^0.1.4",
|
||||
"@backstage/theme": "^0.2.6",
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/config": "^0.1.4",
|
||||
"@backstage/core": "^0.7.6",
|
||||
"@backstage/core": "^0.7.7",
|
||||
"@backstage/errors": "^0.1.1",
|
||||
"@backstage/theme": "^0.2.6",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
# @backstage/plugin-cost-insights
|
||||
|
||||
## 0.8.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- b98de52ae: Support a `name` prop for Projects for display purposes
|
||||
- c614ede9a: Updated README to have up-to-date install instructions.
|
||||
- Updated dependencies [9afcac5af]
|
||||
- Updated dependencies [e0c9ed759]
|
||||
- Updated dependencies [6eaecbd81]
|
||||
- @backstage/core@0.7.7
|
||||
|
||||
## 0.8.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -16,6 +16,8 @@ Learn more with the Backstage blog post [New Cost Insights plugin: The engineer'
|
||||
## Install
|
||||
|
||||
```bash
|
||||
# From your Backstage root directory
|
||||
cd packages/app
|
||||
yarn add @backstage/plugin-cost-insights
|
||||
```
|
||||
|
||||
|
||||
@@ -33,6 +33,8 @@ Cost Explorer permission policy:
|
||||
Install the AWS Cost Explorer SDK. The AWS docs recommend using the SDK over making calls to the API directly as it simplifies authentication and provides direct access to commands.
|
||||
|
||||
```bash
|
||||
# From your Backstage root directory
|
||||
cd packages/app
|
||||
yarn add @aws-sdk/client-cost-explorer
|
||||
```
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-cost-insights",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -31,7 +31,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/config": "^0.1.3",
|
||||
"@backstage/core": "^0.7.6",
|
||||
"@backstage/core": "^0.7.7",
|
||||
"@backstage/theme": "^0.2.6",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
|
||||
@@ -46,6 +46,7 @@ describe.each`
|
||||
engineerCost | ratio | amount | expected
|
||||
${200_000} | ${0} | ${0} | ${'Negligible'}
|
||||
${200_000} | ${0} | ${8_333} | ${'Negligible'}
|
||||
${200_000} | ${undefined} | ${10_000} | ${`~1 ${engineers.unit}`}
|
||||
${200_000} | ${0.000000001} | ${8_334} | ${`0% or ~1 ${engineers.unit}`}
|
||||
${200_000} | ${-0.000000001} | ${10_000} | ${`0% or ~1 ${engineers.unit}`}
|
||||
${200_000} | ${-0.8} | ${10_000} | ${`80% or ~1 ${engineers.unit}`}
|
||||
@@ -65,6 +66,9 @@ describe.each`
|
||||
engineerCost | ratio | amount | expected
|
||||
${200_000} | ${0} | ${0} | ${'Negligible'}
|
||||
${200_000} | ${0} | ${8_333} | ${'Negligible'}
|
||||
${200_000} | ${undefined} | ${-1_000} | ${'Negligible'}
|
||||
${200_000} | ${undefined} | ${1_000} | ${'Negligible'}
|
||||
${200_000} | ${undefined} | ${10_000} | ${'~$10,000'}
|
||||
${200_000} | ${0.000000001} | ${8_334} | ${'0% or ~$8,334'}
|
||||
${200_000} | ${-0.000000001} | ${10_000} | ${'0% or ~$10,000'}
|
||||
${200_000} | ${-0.8} | ${10_000} | ${'80% or ~$10,000'}
|
||||
@@ -84,6 +88,8 @@ describe.each`
|
||||
engineerCost | ratio | amount | expected
|
||||
${200_000} | ${0} | ${0} | ${'Negligible'}
|
||||
${200_000} | ${0} | ${8_333} | ${'Negligible'}
|
||||
${200_000} | ${undefined} | ${1_000} | ${'Negligible'}
|
||||
${200_000} | ${undefined} | ${10_000} | ${`~2,857 ${carbon.unit}s`}
|
||||
${200_000} | ${0.000000001} | ${8_334} | ${`0% or ~2,381 ${carbon.unit}s`}
|
||||
${200_000} | ${-0.000000001} | ${10_000} | ${`0% or ~2,857 ${carbon.unit}s`}
|
||||
${200_000} | ${-0.8} | ${10_000} | ${`80% or ~2,857 ${carbon.unit}s`}
|
||||
|
||||
@@ -29,6 +29,7 @@ import { useCostGrowthStyles as useStyles } from '../../utils/styles';
|
||||
import { formatPercent, formatCurrency } from '../../utils/formatters';
|
||||
import { indefiniteArticleOf } from '../../utils/grammar';
|
||||
import { useConfig, useCurrency } from '../../hooks';
|
||||
import { notEmpty } from '../../utils/assert';
|
||||
|
||||
export type CostGrowthProps = {
|
||||
change: ChangeStatistic;
|
||||
@@ -42,31 +43,65 @@ export const CostGrowth = ({ change, duration }: CostGrowthProps) => {
|
||||
|
||||
// Only display costs in absolute values
|
||||
const amount = Math.abs(change.amount);
|
||||
const ratio = Math.abs(change.ratio);
|
||||
const ratio = Math.abs(change.ratio ?? NaN);
|
||||
|
||||
const rate = rateOf(engineerCost, duration);
|
||||
const engineers = amount / rate;
|
||||
const converted = amount / (currency.rate ?? rate);
|
||||
|
||||
// If a ratio cannot be calculated, don't format.
|
||||
const growth = notEmpty(change.ratio)
|
||||
? growthOf({ ratio: change.ratio, amount: engineers })
|
||||
: null;
|
||||
// Determine if growth is significant enough to highlight
|
||||
const growth = growthOf(change.ratio, engineers);
|
||||
const classes = classnames({
|
||||
[styles.excess]: growth === GrowthType.Excess,
|
||||
[styles.savings]: growth === GrowthType.Savings,
|
||||
});
|
||||
|
||||
const percent = formatPercent(ratio);
|
||||
|
||||
let cost = `${percent} or ~${formatCurrency(converted, currency.unit)}`;
|
||||
// Always display the converted value but use the cost in engineers
|
||||
// to determine negligibility, as costs should be time-period aware
|
||||
if (engineers < EngineerThreshold) {
|
||||
cost = 'Negligible';
|
||||
} else if (currency.kind === CurrencyType.USD) {
|
||||
cost = `${percent} or ~${currency.prefix}${formatCurrency(converted)}`;
|
||||
} else if (amount < 1) {
|
||||
cost = `less than ${indefiniteArticleOf(['a', 'an'], currency.unit)}`;
|
||||
return <span className={classes}>Negligible</span>;
|
||||
}
|
||||
|
||||
return <span className={classes}>{cost}</span>;
|
||||
if (currency.kind === CurrencyType.USD) {
|
||||
// Do not display percentage if ratio cannot be calculated
|
||||
if (isNaN(ratio)) {
|
||||
return (
|
||||
<span className={classes}>
|
||||
~{currency.prefix}
|
||||
{formatCurrency(converted)}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<span className={classes}>
|
||||
{formatPercent(ratio)} or ~{currency.prefix}
|
||||
{formatCurrency(converted)}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
if (amount < 1) {
|
||||
return (
|
||||
<span className={classes}>
|
||||
less than {indefiniteArticleOf(['a', 'an'], currency.unit)}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
// Do not display percentage if ratio cannot be calculated
|
||||
if (isNaN(ratio)) {
|
||||
return (
|
||||
<span className={classes}>
|
||||
~{formatCurrency(converted, currency.unit)}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<span className={classes}>
|
||||
{formatPercent(ratio)} or ~{formatCurrency(converted, currency.unit)}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -21,8 +21,6 @@ import { ChangeThreshold, EngineerThreshold } from '../../types';
|
||||
|
||||
describe.each`
|
||||
ratio | amount | ariaLabel
|
||||
${-0.1} | ${undefined} | ${'savings'}
|
||||
${0.01} | ${undefined} | ${'excess'}
|
||||
${ChangeThreshold.lower} | ${EngineerThreshold} | ${'savings'}
|
||||
${ChangeThreshold.lower - 0.01} | ${EngineerThreshold} | ${'savings'}
|
||||
${ChangeThreshold.lower - 0.01} | ${EngineerThreshold + 0.1} | ${'savings'}
|
||||
@@ -32,7 +30,7 @@ describe.each`
|
||||
`('growthOf', ({ ratio, amount, ariaLabel }) => {
|
||||
it(`should display the correct indicator for ${ariaLabel}`, async () => {
|
||||
const { getByLabelText } = await renderInTestApp(
|
||||
<CostGrowthIndicator ratio={ratio} amount={amount} />,
|
||||
<CostGrowthIndicator change={{ ratio, amount }} />,
|
||||
);
|
||||
expect(getByLabelText(ariaLabel)).toBeInTheDocument();
|
||||
});
|
||||
@@ -40,7 +38,8 @@ describe.each`
|
||||
|
||||
describe.each`
|
||||
ratio | amount
|
||||
${0} | ${undefined}
|
||||
${undefined} | ${0}
|
||||
${0} | ${0}
|
||||
${ChangeThreshold.lower} | ${0}
|
||||
${ChangeThreshold.lower + 0.01} | ${EngineerThreshold}
|
||||
${ChangeThreshold.lower + 0.01} | ${EngineerThreshold + 0.1}
|
||||
@@ -49,7 +48,7 @@ describe.each`
|
||||
`('growthOf', ({ ratio, amount }) => {
|
||||
it('should display the correct indicator for negligible growth', async () => {
|
||||
const { queryByLabelText } = await renderInTestApp(
|
||||
<CostGrowthIndicator ratio={ratio} amount={amount} />,
|
||||
<CostGrowthIndicator change={{ ratio, amount }} />,
|
||||
);
|
||||
expect(queryByLabelText('savings')).not.toBeInTheDocument();
|
||||
expect(queryByLabelText('excess')).not.toBeInTheDocument();
|
||||
|
||||
@@ -20,49 +20,33 @@ import { Typography, TypographyProps } from '@material-ui/core';
|
||||
import { default as ArrowDropUp } from '@material-ui/icons/ArrowDropUp';
|
||||
import { default as ArrowDropDown } from '@material-ui/icons/ArrowDropDown';
|
||||
import { growthOf } from '../../utils/change';
|
||||
import { GrowthType } from '../../types';
|
||||
import { ChangeStatistic, GrowthType, Maybe } from '../../types';
|
||||
import { useCostGrowthStyles as useStyles } from '../../utils/styles';
|
||||
|
||||
export type CostGrowthIndicatorProps = TypographyProps & {
|
||||
ratio: number;
|
||||
amount?: number;
|
||||
formatter?: (amount: number) => string;
|
||||
change: ChangeStatistic;
|
||||
formatter?: (change: ChangeStatistic) => Maybe<string>;
|
||||
};
|
||||
|
||||
export const CostGrowthIndicator = ({
|
||||
ratio,
|
||||
amount,
|
||||
change,
|
||||
formatter,
|
||||
className,
|
||||
...props
|
||||
}: CostGrowthIndicatorProps) => {
|
||||
const classes = useStyles();
|
||||
const growth = growthOf(ratio, amount);
|
||||
const growth = growthOf(change);
|
||||
|
||||
const classNames = classnames(classes.indicator, className, {
|
||||
[classes.savings]: growth === GrowthType.Savings,
|
||||
[classes.excess]: growth === GrowthType.Excess,
|
||||
[classes.savings]: growth === GrowthType.Savings,
|
||||
});
|
||||
|
||||
// Display cost as a factor of engineer cost growth and percentage growth
|
||||
if (typeof amount === 'number') {
|
||||
return (
|
||||
<Typography className={classNames} component="span" {...props}>
|
||||
{formatter ? formatter(amount) : amount}
|
||||
{growth === GrowthType.Savings && (
|
||||
<ArrowDropDown aria-label="savings" />
|
||||
)}
|
||||
{growth === GrowthType.Excess && <ArrowDropUp aria-label="excess" />}
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
|
||||
// Display cost as a factor of percent change
|
||||
return (
|
||||
<Typography className={classNames} component="span" {...props}>
|
||||
{formatter ? formatter(ratio) : ratio}
|
||||
{ratio < 0 && <ArrowDropDown aria-label="savings" />}
|
||||
{ratio > 0 && <ArrowDropUp aria-label="excess" />}
|
||||
{formatter ? formatter(change) : change.ratio}
|
||||
{growth === GrowthType.Excess && <ArrowDropUp aria-label="excess" />}
|
||||
{growth === GrowthType.Savings && <ArrowDropDown aria-label="savings" />}
|
||||
</Typography>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
* Copyright 2021 Spotify AB
|
||||
*
|
||||
* 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 { render } from '@testing-library/react';
|
||||
import { wrapInTestApp } from '@backstage/test-utils';
|
||||
import { CostOverviewLegend } from './CostOverviewLegend';
|
||||
import {
|
||||
MockBillingDateProvider,
|
||||
MockConfigProvider,
|
||||
MockFilterProvider,
|
||||
MockCurrencyProvider,
|
||||
} from '../../testUtils';
|
||||
|
||||
function renderInTestApp(children: JSX.Element) {
|
||||
return render(
|
||||
wrapInTestApp(
|
||||
<MockConfigProvider>
|
||||
<MockCurrencyProvider>
|
||||
<MockBillingDateProvider>
|
||||
<MockFilterProvider>{children}</MockFilterProvider>
|
||||
</MockBillingDateProvider>
|
||||
</MockCurrencyProvider>
|
||||
</MockConfigProvider>,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
describe('<CostOverviewLegend />', () => {
|
||||
it('displays the legend without exploding', async () => {
|
||||
const { findByText } = renderInTestApp(
|
||||
<CostOverviewLegend
|
||||
metric={{
|
||||
kind: 'msc',
|
||||
name: 'MSC',
|
||||
default: false,
|
||||
}}
|
||||
metricData={{
|
||||
id: 'msc',
|
||||
format: 'number',
|
||||
aggregation: [],
|
||||
change: {
|
||||
ratio: 0,
|
||||
amount: 0,
|
||||
},
|
||||
}}
|
||||
dailyCostData={{
|
||||
id: 'mock-id',
|
||||
aggregation: [],
|
||||
change: {
|
||||
amount: 0,
|
||||
},
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(await findByText('Cost Trend')).toBeInTheDocument();
|
||||
expect(await findByText('MSC Trend')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('does not display metric legend if metric data is not provided', async () => {
|
||||
const { findByText, queryByText } = renderInTestApp(
|
||||
<CostOverviewLegend
|
||||
metric={{
|
||||
kind: 'msc',
|
||||
name: 'MSC',
|
||||
default: false,
|
||||
}}
|
||||
metricData={null}
|
||||
dailyCostData={{
|
||||
id: 'mock-id',
|
||||
aggregation: [],
|
||||
change: {
|
||||
amount: 0,
|
||||
},
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(await findByText('Cost Trend')).toBeInTheDocument();
|
||||
expect(queryByText('MSC Trend')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe.each`
|
||||
ratio | amount | title | expected
|
||||
${undefined} | ${1_000} | ${'∞'} | ${'Your Excess'}
|
||||
${undefined} | ${-1_000} | ${'-∞'} | ${'Your Savings'}
|
||||
`('<CostOverviewLegend />', ({ ratio, amount, title, expected }) => {
|
||||
it('displays the correct legend if ratio cannot be calculated and costs are within time period', async () => {
|
||||
const { findByText, findAllByText } = renderInTestApp(
|
||||
<CostOverviewLegend
|
||||
metric={{
|
||||
kind: 'msc',
|
||||
name: 'MSC',
|
||||
default: false,
|
||||
}}
|
||||
metricData={{
|
||||
id: 'msc',
|
||||
format: 'number',
|
||||
change: {
|
||||
ratio: ratio,
|
||||
amount: amount,
|
||||
},
|
||||
aggregation: [
|
||||
{
|
||||
date: '2020-01-01',
|
||||
amount: 0,
|
||||
},
|
||||
{
|
||||
date: '2020-07-01', // within default P90D period
|
||||
amount: amount,
|
||||
},
|
||||
],
|
||||
}}
|
||||
dailyCostData={{
|
||||
id: 'mock-id',
|
||||
change: {
|
||||
ratio,
|
||||
amount,
|
||||
},
|
||||
aggregation: [
|
||||
{
|
||||
date: '2020-01-01',
|
||||
amount: 0,
|
||||
},
|
||||
{
|
||||
date: '2020-07-01', // within default P90D period
|
||||
amount: amount,
|
||||
},
|
||||
],
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(await findByText('Cost Trend')).toBeInTheDocument();
|
||||
expect(await findByText('MSC Trend')).toBeInTheDocument();
|
||||
expect(await findAllByText(title).then(res => res.length)).toBe(2);
|
||||
expect(await findByText(expected)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -25,9 +25,9 @@ import {
|
||||
Metric,
|
||||
} from '../../types';
|
||||
import { useLastCompleteBillingDate, useFilters } from '../../hooks';
|
||||
import { getComparedChange } from '../../utils/change';
|
||||
import { getComparedChange, choose } from '../../utils/change';
|
||||
import { mapFiltersToProps } from './selector';
|
||||
import { formatPercent } from '../../utils/formatters';
|
||||
import { formatChange } from '../../utils/formatters';
|
||||
import { CostGrowth } from '../CostGrowth';
|
||||
|
||||
type CostOverviewLegendProps = {
|
||||
@@ -42,9 +42,8 @@ export const CostOverviewLegend = ({
|
||||
metricData,
|
||||
}: PropsWithChildren<CostOverviewLegendProps>) => {
|
||||
const theme = useTheme<CostInsightsTheme>();
|
||||
|
||||
const lastCompleteBillingDate = useLastCompleteBillingDate();
|
||||
const { duration } = useFilters(mapFiltersToProps);
|
||||
const lastCompleteBillingDate = useLastCompleteBillingDate();
|
||||
|
||||
const comparedChange = metricData
|
||||
? getComparedChange(
|
||||
@@ -57,23 +56,25 @@ export const CostOverviewLegend = ({
|
||||
|
||||
return (
|
||||
<Box display="flex" flexDirection="row">
|
||||
<Box mr={2}>
|
||||
<LegendItem title="Cost Trend" markerColor={theme.palette.blue}>
|
||||
{formatPercent(dailyCostData.change!.ratio)}
|
||||
</LegendItem>
|
||||
</Box>
|
||||
{metric && metricData && comparedChange && (
|
||||
{dailyCostData.change && (
|
||||
<Box mr={2}>
|
||||
<LegendItem title="Cost Trend" markerColor={theme.palette.blue}>
|
||||
{formatChange(dailyCostData.change)}
|
||||
</LegendItem>
|
||||
</Box>
|
||||
)}
|
||||
{metricData && metric && comparedChange && (
|
||||
<>
|
||||
<Box mr={2}>
|
||||
<LegendItem
|
||||
title={`${metric.name} Trend`}
|
||||
markerColor={theme.palette.magenta}
|
||||
>
|
||||
{formatPercent(metricData.change.ratio)}
|
||||
{formatChange(metricData.change)}
|
||||
</LegendItem>
|
||||
</Box>
|
||||
<LegendItem
|
||||
title={comparedChange.ratio <= 0 ? 'Your Savings' : 'Your Excess'}
|
||||
title={choose(['Your Savings', 'Your Excess'], comparedChange)}
|
||||
>
|
||||
<CostGrowth change={comparedChange} duration={duration} />
|
||||
</LegendItem>
|
||||
|
||||
@@ -18,10 +18,10 @@ import React from 'react';
|
||||
import classnames from 'classnames';
|
||||
import { Table, TableColumn } from '@backstage/core';
|
||||
import { Typography } from '@material-ui/core';
|
||||
import { costFormatter, formatPercent } from '../../utils/formatters';
|
||||
import { costFormatter, formatChange } from '../../utils/formatters';
|
||||
import { useEntityDialogStyles as useStyles } from '../../utils/styles';
|
||||
import { CostGrowthIndicator } from '../CostGrowth';
|
||||
import { BarChartOptions, Entity } from '../../types';
|
||||
import { BarChartOptions, ChangeStatistic, Entity } from '../../types';
|
||||
|
||||
export type ProductEntityTableOptions = Partial<
|
||||
Pick<BarChartOptions, 'previousName' | 'currentName'>
|
||||
@@ -32,7 +32,7 @@ type RowData = {
|
||||
label: string;
|
||||
previous: number;
|
||||
current: number;
|
||||
ratio: number;
|
||||
change: ChangeStatistic;
|
||||
};
|
||||
|
||||
function createRenderer(col: keyof RowData, classes: Record<string, string>) {
|
||||
@@ -41,7 +41,7 @@ function createRenderer(col: keyof RowData, classes: Record<string, string>) {
|
||||
const rowStyles = classnames(classes.row, {
|
||||
[classes.rowTotal]: row.id === 'total',
|
||||
[classes.colFirst]: col === 'label',
|
||||
[classes.colLast]: col === 'ratio',
|
||||
[classes.colLast]: col === 'change',
|
||||
});
|
||||
|
||||
switch (col) {
|
||||
@@ -52,12 +52,12 @@ function createRenderer(col: keyof RowData, classes: Record<string, string>) {
|
||||
{costFormatter.format(row[col])}
|
||||
</Typography>
|
||||
);
|
||||
case 'ratio':
|
||||
case 'change':
|
||||
return (
|
||||
<CostGrowthIndicator
|
||||
className={rowStyles}
|
||||
ratio={row.ratio}
|
||||
formatter={amount => formatPercent(Math.abs(amount))}
|
||||
change={row.change}
|
||||
formatter={formatChange}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
@@ -75,10 +75,15 @@ function createSorter(field?: keyof Omit<RowData, 'id'>) {
|
||||
if (a.id === 'total') return 1;
|
||||
if (b.id === 'total') return 1;
|
||||
if (field === 'label') return a.label.localeCompare(b.label);
|
||||
if (field === 'change') {
|
||||
if (formatChange(a[field]) === '∞' || formatChange(b[field]) === '-∞')
|
||||
return 1;
|
||||
if (formatChange(a[field]) === '-∞' || formatChange(b[field]) === '∞')
|
||||
return -1;
|
||||
return a[field].ratio! - b[field].ratio!;
|
||||
}
|
||||
|
||||
return field
|
||||
? a[field] - b[field]
|
||||
: b.previous + b.current - (a.previous + a.current);
|
||||
return b.previous + b.current - (a.previous + a.current);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -134,11 +139,11 @@ export const ProductEntityTable = ({
|
||||
customSort: createSorter('current'),
|
||||
},
|
||||
{
|
||||
field: 'ratio',
|
||||
field: 'change',
|
||||
title: <Typography className={lastColClasses}>Change</Typography>,
|
||||
align: 'right',
|
||||
render: createRenderer('ratio', classes),
|
||||
customSort: createSorter('ratio'),
|
||||
render: createRenderer('change', classes),
|
||||
customSort: createSorter('change'),
|
||||
},
|
||||
];
|
||||
|
||||
@@ -148,14 +153,14 @@ export const ProductEntityTable = ({
|
||||
label: e.id || 'Unknown',
|
||||
previous: e.aggregation[0],
|
||||
current: e.aggregation[1],
|
||||
ratio: e.change.ratio,
|
||||
change: e.change,
|
||||
}))
|
||||
.concat({
|
||||
id: 'total',
|
||||
label: 'Total',
|
||||
previous: entity.aggregation[0],
|
||||
current: entity.aggregation[1],
|
||||
ratio: entity.change.ratio,
|
||||
change: entity.change,
|
||||
})
|
||||
.sort(createSorter());
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ import {
|
||||
findAnyKey,
|
||||
assertAlways,
|
||||
} from '../../utils/assert';
|
||||
import { formatPeriod, formatPercent } from '../../utils/formatters';
|
||||
import { formatPeriod, formatChange } from '../../utils/formatters';
|
||||
import {
|
||||
titleOf,
|
||||
tooltipItemOf,
|
||||
@@ -54,6 +54,7 @@ import {
|
||||
useBarChartLayoutStyles as useLayoutStyles,
|
||||
} from '../../utils/styles';
|
||||
import { Duration, Entity, Maybe } from '../../types';
|
||||
import { choose } from '../../utils/change';
|
||||
|
||||
export type ProductInsightsChartProps = {
|
||||
billingDate: string;
|
||||
@@ -86,7 +87,6 @@ export const ProductInsightsChart = ({
|
||||
return breakdowns.length > 0;
|
||||
}, [entities, activeLabel]);
|
||||
|
||||
const legendTitle = `Cost ${entity.change.ratio <= 0 ? 'Savings' : 'Growth'}`;
|
||||
const costStart = entity.aggregation[0];
|
||||
const costEnd = entity.aggregation[1];
|
||||
const resources = entities.map(resourceOf);
|
||||
@@ -136,7 +136,6 @@ export const ProductInsightsChart = ({
|
||||
const items = payload.map(tooltipItemOf).filter(notEmpty);
|
||||
|
||||
const activeEntity = findAlways(entities, e => e.id === id);
|
||||
const ratio = activeEntity.change.ratio;
|
||||
const breakdowns = Object.keys(activeEntity.entities);
|
||||
|
||||
if (breakdowns.length) {
|
||||
@@ -148,11 +147,13 @@ export const ProductInsightsChart = ({
|
||||
title={title}
|
||||
subtitle={subtitle}
|
||||
topRight={
|
||||
<CostGrowthIndicator
|
||||
className={classes.indicator}
|
||||
ratio={ratio}
|
||||
formatter={formatPercent}
|
||||
/>
|
||||
!!activeEntity.change.ratio && (
|
||||
<CostGrowthIndicator
|
||||
formatter={formatChange}
|
||||
change={activeEntity.change}
|
||||
className={classes.indicator}
|
||||
/>
|
||||
)
|
||||
}
|
||||
actions={
|
||||
<Box className={classes.actions}>
|
||||
@@ -173,11 +174,13 @@ export const ProductInsightsChart = ({
|
||||
<BarChartTooltip
|
||||
title={title}
|
||||
topRight={
|
||||
<CostGrowthIndicator
|
||||
className={classes.indicator}
|
||||
ratio={ratio}
|
||||
formatter={formatPercent}
|
||||
/>
|
||||
!!activeEntity.change.ratio && (
|
||||
<CostGrowthIndicator
|
||||
formatter={formatChange}
|
||||
change={activeEntity.change}
|
||||
className={classes.indicator}
|
||||
/>
|
||||
)
|
||||
}
|
||||
content={
|
||||
id
|
||||
@@ -197,7 +200,9 @@ export const ProductInsightsChart = ({
|
||||
return (
|
||||
<Box className={layoutClasses.wrapper}>
|
||||
<BarChartLegend costStart={costStart} costEnd={costEnd} options={options}>
|
||||
<LegendItem title={legendTitle}>
|
||||
<LegendItem
|
||||
title={choose(['Cost Savings', 'Cost Excess'], entity.change)}
|
||||
>
|
||||
<CostGrowth change={entity.change} duration={duration} />
|
||||
</LegendItem>
|
||||
</BarChartLegend>
|
||||
|
||||
@@ -294,7 +294,6 @@ export const MockBigQueryInsights: Entity = {
|
||||
id: 'dataset-c',
|
||||
aggregation: [0, 10_000],
|
||||
change: {
|
||||
ratio: 10_000,
|
||||
amount: 10_000,
|
||||
},
|
||||
entities: {},
|
||||
@@ -415,7 +414,6 @@ export const MockCloudDataflowInsights: Entity = {
|
||||
id: 'pipeline-c',
|
||||
aggregation: [0, 10_000],
|
||||
change: {
|
||||
ratio: 10_000,
|
||||
amount: 10_000,
|
||||
},
|
||||
entities: {},
|
||||
@@ -503,7 +501,6 @@ export const MockCloudStorageInsights: Entity = {
|
||||
id: 'Mock SKU C',
|
||||
aggregation: [2_000, 0],
|
||||
change: {
|
||||
ratio: -1,
|
||||
amount: -2000,
|
||||
},
|
||||
entities: {},
|
||||
@@ -515,7 +512,6 @@ export const MockCloudStorageInsights: Entity = {
|
||||
id: 'bucket-c',
|
||||
aggregation: [0, 0],
|
||||
change: {
|
||||
ratio: 0,
|
||||
amount: 0,
|
||||
},
|
||||
entities: {},
|
||||
@@ -655,7 +651,6 @@ export const MockComputeEngineInsights: Entity = {
|
||||
id: 'service-c',
|
||||
aggregation: [0, 10_000],
|
||||
change: {
|
||||
ratio: 10_000,
|
||||
amount: 10_000,
|
||||
},
|
||||
entities: {},
|
||||
|
||||
@@ -93,10 +93,16 @@ export function changeOf(aggregation: DateAggregation[]): ChangeStatistic {
|
||||
const lastAmount = aggregation.length
|
||||
? aggregation[aggregation.length - 1].amount
|
||||
: 0;
|
||||
const ratio =
|
||||
firstAmount !== 0 ? (lastAmount - firstAmount) / firstAmount : 0;
|
||||
|
||||
// if either the first or last amounts are zero, the rate of increase/decrease is infinite
|
||||
if (!firstAmount || !lastAmount) {
|
||||
return {
|
||||
amount: lastAmount - firstAmount,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
ratio: ratio,
|
||||
ratio: (lastAmount - firstAmount) / firstAmount,
|
||||
amount: lastAmount - firstAmount,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -16,7 +16,9 @@
|
||||
|
||||
export interface ChangeStatistic {
|
||||
// The ratio of change from one duration to another, expressed as: (newSum - oldSum) / oldSum
|
||||
ratio: number;
|
||||
// If a ratio cannot be calculated - such as when a new or old sum is zero,
|
||||
// the ratio can be omitted and where applicable, ∞ or -∞ will display based on amount.
|
||||
ratio?: number;
|
||||
// The actual USD change between time periods (can be negative if costs decreased)
|
||||
amount: number;
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ export function notEmpty<TValue>(
|
||||
return !isNull(value) && !isUndefined(value);
|
||||
}
|
||||
|
||||
export function isUndefined(value: any): boolean {
|
||||
export function isUndefined(value: any): value is undefined {
|
||||
return value === undefined;
|
||||
}
|
||||
|
||||
|
||||
@@ -32,23 +32,23 @@ const GrowthMap = {
|
||||
|
||||
describe.each`
|
||||
ratio | amount | expected
|
||||
${0.0} | ${undefined} | ${GrowthType.Negligible}
|
||||
${undefined} | ${0} | ${GrowthType.Negligible}
|
||||
${0.0} | ${0} | ${GrowthType.Negligible}
|
||||
${0.0} | ${EngineerThreshold} | ${GrowthType.Negligible}
|
||||
${ChangeThreshold.lower} | ${0} | ${GrowthType.Negligible}
|
||||
${ChangeThreshold.lower + 0.01} | ${undefined} | ${GrowthType.Negligible}
|
||||
${ChangeThreshold.lower + 0.01} | ${0} | ${GrowthType.Negligible}
|
||||
${ChangeThreshold.lower + 0.01} | ${EngineerThreshold} | ${GrowthType.Negligible}
|
||||
${ChangeThreshold.lower + 0.01} | ${EngineerThreshold + 0.1} | ${GrowthType.Negligible}
|
||||
${ChangeThreshold.lower - 0.01} | ${EngineerThreshold - 0.1} | ${GrowthType.Negligible}
|
||||
${ChangeThreshold.upper - 0.01} | ${undefined} | ${GrowthType.Negligible}
|
||||
${ChangeThreshold.lower - 0.01} | ${0} | ${GrowthType.Negligible}
|
||||
${ChangeThreshold.upper} | ${0} | ${GrowthType.Negligible}
|
||||
${ChangeThreshold.upper - 0.01} | ${0} | ${GrowthType.Negligible}
|
||||
${ChangeThreshold.upper + 0.01} | ${EngineerThreshold - 0.1} | ${GrowthType.Negligible}
|
||||
${ChangeThreshold.lower} | ${undefined} | ${GrowthType.Savings}
|
||||
${ChangeThreshold.upper + 0.01} | ${0} | ${GrowthType.Negligible}
|
||||
${ChangeThreshold.lower} | ${EngineerThreshold} | ${GrowthType.Savings}
|
||||
${ChangeThreshold.lower - 0.01} | ${undefined} | ${GrowthType.Savings}
|
||||
${ChangeThreshold.lower - 0.01} | ${EngineerThreshold} | ${GrowthType.Savings}
|
||||
${ChangeThreshold.lower - 0.01} | ${EngineerThreshold + 0.1} | ${GrowthType.Savings}
|
||||
${ChangeThreshold.upper} | ${undefined} | ${GrowthType.Excess}
|
||||
${ChangeThreshold.upper} | ${EngineerThreshold} | ${GrowthType.Excess}
|
||||
${ChangeThreshold.upper + 0.01} | ${undefined} | ${GrowthType.Excess}
|
||||
${ChangeThreshold.upper + 0.01} | ${EngineerThreshold} | ${GrowthType.Excess}
|
||||
${ChangeThreshold.upper + 0.01} | ${EngineerThreshold + 0.1} | ${GrowthType.Excess}
|
||||
`(
|
||||
@@ -63,7 +63,7 @@ describe.each`
|
||||
expected: GrowthType;
|
||||
}) => {
|
||||
it(`should display ${GrowthMap[expected]}`, () => {
|
||||
expect(growthOf(ratio, amount)).toBe(expected);
|
||||
expect(growthOf({ ratio, amount })).toBe(expected);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
@@ -27,21 +27,26 @@ import {
|
||||
import dayjs, { OpUnitType } from 'dayjs';
|
||||
import durationPlugin from 'dayjs/plugin/duration';
|
||||
import { inclusiveStartDateOf } from './duration';
|
||||
import { notEmpty } from './assert';
|
||||
|
||||
dayjs.extend(durationPlugin);
|
||||
|
||||
// Used for displaying status colors
|
||||
export function growthOf(ratio: number, amount?: number) {
|
||||
if (typeof amount === 'number') {
|
||||
if (amount >= EngineerThreshold && ratio >= ChangeThreshold.upper) {
|
||||
export function growthOf(change: ChangeStatistic): GrowthType {
|
||||
const exceedsEngineerThreshold = Math.abs(change.amount) >= EngineerThreshold;
|
||||
|
||||
if (notEmpty(change.ratio)) {
|
||||
if (exceedsEngineerThreshold && change.ratio >= ChangeThreshold.upper) {
|
||||
return GrowthType.Excess;
|
||||
}
|
||||
if (amount >= EngineerThreshold && ratio <= ChangeThreshold.lower) {
|
||||
|
||||
if (exceedsEngineerThreshold && change.ratio <= ChangeThreshold.lower) {
|
||||
return GrowthType.Savings;
|
||||
}
|
||||
} else {
|
||||
if (ratio >= ChangeThreshold.upper) return GrowthType.Excess;
|
||||
if (ratio <= ChangeThreshold.lower) return GrowthType.Savings;
|
||||
if (exceedsEngineerThreshold && change.amount > 0) return GrowthType.Excess;
|
||||
if (exceedsEngineerThreshold && change.amount < 0)
|
||||
return GrowthType.Savings;
|
||||
}
|
||||
|
||||
return GrowthType.Negligible;
|
||||
@@ -54,15 +59,24 @@ export function getComparedChange(
|
||||
duration: Duration,
|
||||
lastCompleteBillingDate: string, // YYYY-MM-DD,
|
||||
): ChangeStatistic {
|
||||
const ratio = dailyCost.change!.ratio - metricData.change.ratio;
|
||||
const dailyCostRatio = dailyCost.change?.ratio;
|
||||
const metricDataRatio = metricData.change?.ratio;
|
||||
const previousPeriodTotal = getPreviousPeriodTotalCost(
|
||||
dailyCost.aggregation,
|
||||
duration,
|
||||
lastCompleteBillingDate,
|
||||
);
|
||||
|
||||
// if either ratio cannot be calculated, no compared ratio can be calculated
|
||||
if (!notEmpty(dailyCostRatio) || !notEmpty(metricDataRatio)) {
|
||||
return {
|
||||
amount: previousPeriodTotal,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
ratio: ratio,
|
||||
amount: previousPeriodTotal * ratio,
|
||||
ratio: dailyCostRatio - metricDataRatio,
|
||||
amount: previousPeriodTotal * (dailyCostRatio - metricDataRatio),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -78,7 +92,6 @@ export function getPreviousPeriodTotalCost(
|
||||
? [dayjsDuration.days(), 'day']
|
||||
: [dayjsDuration.months(), 'month'];
|
||||
const nextPeriodStart = dayjs(startDate).add(amount, type);
|
||||
|
||||
// Add up costs that incurred before the start of the next period.
|
||||
return aggregation.reduce((acc, costByDate) => {
|
||||
return dayjs(costByDate.date).isBefore(nextPeriodStart)
|
||||
@@ -86,3 +99,11 @@ export function getPreviousPeriodTotalCost(
|
||||
: acc;
|
||||
}, 0);
|
||||
}
|
||||
|
||||
export function choose<T>(
|
||||
[savings, excess]: [T, T],
|
||||
change: ChangeStatistic,
|
||||
): T {
|
||||
const isSavings = (change.ratio ?? change.amount) <= 0;
|
||||
return isSavings ? savings : excess;
|
||||
}
|
||||
|
||||
@@ -16,8 +16,9 @@
|
||||
|
||||
import moment from 'moment';
|
||||
import pluralize from 'pluralize';
|
||||
import { Duration } from '../types';
|
||||
import { ChangeStatistic, Duration } from '../types';
|
||||
import { inclusiveEndDateOf, inclusiveStartDateOf } from '../utils/duration';
|
||||
import { notEmpty } from './assert';
|
||||
|
||||
export type Period = {
|
||||
periodStart: string;
|
||||
@@ -78,6 +79,13 @@ export function formatCurrency(amount: number, currency?: string): string {
|
||||
return currency ? `${numString} ${pluralize(currency, n)}` : numString;
|
||||
}
|
||||
|
||||
export function formatChange(change: ChangeStatistic): string {
|
||||
if (notEmpty(change.ratio)) {
|
||||
return formatPercent(Math.abs(change.ratio));
|
||||
}
|
||||
return change.amount >= 0 ? '∞' : '-∞';
|
||||
}
|
||||
|
||||
export function formatPercent(n: number): string {
|
||||
// Number.toFixed shows scientific notation for extreme numbers
|
||||
if (isNaN(n) || Math.abs(n) < 0.01) {
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
# @backstage/plugin-explore
|
||||
|
||||
## 0.3.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- c614ede9a: Updated README to have up-to-date install instructions.
|
||||
- Updated dependencies [9afcac5af]
|
||||
- Updated dependencies [e0c9ed759]
|
||||
- Updated dependencies [6eaecbd81]
|
||||
- @backstage/core@0.7.7
|
||||
|
||||
## 0.3.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-explore",
|
||||
"version": "0.3.3",
|
||||
"version": "0.3.4",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -31,7 +31,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.7.5",
|
||||
"@backstage/core": "^0.7.6",
|
||||
"@backstage/core": "^0.7.7",
|
||||
"@backstage/plugin-catalog-react": "^0.1.4",
|
||||
"@backstage/plugin-explore-react": "^0.0.4",
|
||||
"@backstage/theme": "^0.2.6",
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
# @backstage/plugin-fossa
|
||||
|
||||
## 0.2.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- c614ede9a: Updated README to have up-to-date install instructions.
|
||||
- Updated dependencies [9afcac5af]
|
||||
- Updated dependencies [e0c9ed759]
|
||||
- Updated dependencies [6eaecbd81]
|
||||
- @backstage/core@0.7.7
|
||||
|
||||
## 0.2.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -9,8 +9,8 @@ The FOSSA Plugin displays code statistics from [FOSSA](https://fossa.com/).
|
||||
1. Install the FOSSA Plugin:
|
||||
|
||||
```bash
|
||||
# packages/app
|
||||
|
||||
# From your Backstage root directory
|
||||
cd packages/app
|
||||
yarn add @backstage/plugin-fossa
|
||||
```
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-fossa",
|
||||
"version": "0.2.5",
|
||||
"version": "0.2.6",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -32,7 +32,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.7.3",
|
||||
"@backstage/core": "^0.7.6",
|
||||
"@backstage/core": "^0.7.7",
|
||||
"@backstage/errors": "^0.1.1",
|
||||
"@backstage/plugin-catalog-react": "^0.1.1",
|
||||
"@backstage/theme": "^0.2.6",
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/core": "^0.7.6",
|
||||
"@backstage/core": "^0.7.7",
|
||||
"@backstage/theme": "^0.2.6",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
# @backstage/plugin-github-actions
|
||||
|
||||
## 0.4.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 4c42ecca2: Wrap EmptyState in Card
|
||||
- c614ede9a: Updated README to have up-to-date install instructions.
|
||||
- Updated dependencies [9afcac5af]
|
||||
- Updated dependencies [e0c9ed759]
|
||||
- Updated dependencies [6eaecbd81]
|
||||
- @backstage/core@0.7.7
|
||||
|
||||
## 0.4.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -11,15 +11,15 @@ TBD
|
||||
### Generic Requirements
|
||||
|
||||
1. Provide OAuth credentials:
|
||||
1. [Create an OAuth App](https://developer.github.com/apps/building-oauth-apps/creating-an-oauth-app/) with callback URL set to `http://localhost:7000/api/auth/github`.
|
||||
2. Take Client ID and Client Secret from the newly created app's settings page and put them into `AUTH_GITHUB_CLIENT_ID` and `AUTH_GITHUB_CLIENT_SECRET` env variables.
|
||||
1. [Create an OAuth App](https://developer.github.com/apps/building-oauth-apps/creating-an-oauth-app/) with the callback URL set to `http://localhost:7000/api/auth/github`.
|
||||
2. Take the Client ID and Client Secret from the newly created app's settings page and put them into `AUTH_GITHUB_CLIENT_ID` and `AUTH_GITHUB_CLIENT_SECRET` environment variables.
|
||||
2. Annotate your component with a correct GitHub Actions repository and owner:
|
||||
|
||||
The annotation key is `github.com/project-slug`.
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
```yaml
|
||||
apiVersion: backstage.io/v1alpha1
|
||||
kind: Component
|
||||
metadata:
|
||||
@@ -38,6 +38,7 @@ TBD
|
||||
1. Install the plugin dependency in your Backstage app package:
|
||||
|
||||
```bash
|
||||
# From your Backstage root directory
|
||||
cd packages/app
|
||||
yarn add @backstage/plugin-github-actions
|
||||
```
|
||||
@@ -45,22 +46,24 @@ yarn add @backstage/plugin-github-actions
|
||||
2. Add to the app `EntityPage` component:
|
||||
|
||||
```tsx
|
||||
// packages/app/src/components/catalog/EntityPage.tsx
|
||||
import { EntityGithubActionsContent } from '@backstage/plugin-github-actions';
|
||||
// In packages/app/src/components/catalog/EntityPage.tsx
|
||||
import {
|
||||
EntityGithubActionsContent,
|
||||
isGithubActionsAvailable,
|
||||
} from '@backstage/plugin-github-actions';
|
||||
|
||||
// ...
|
||||
// You can add the tab to any number of pages, the service page is shown as an
|
||||
// example here
|
||||
const serviceEntityPage = (
|
||||
<EntityPageLayout>
|
||||
...
|
||||
<EntityLayout>
|
||||
{/* other tabs... */}
|
||||
<EntityLayout.Route path="/github-actions" title="GitHub Actions">
|
||||
<EntityGithubActionsContent />
|
||||
</EntityLayout.Route>
|
||||
...
|
||||
</EntityPageLayout>
|
||||
);
|
||||
```
|
||||
|
||||
2. Run the app with `yarn start` and the backend with `yarn --cwd packages/backend start`, navigate to `/github-actions/`.
|
||||
3. Run the app with `yarn start` and the backend with `yarn start-backend`.
|
||||
Then navigate to `/github-actions/` under any entity.
|
||||
|
||||
## Features
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-github-actions",
|
||||
"version": "0.4.3",
|
||||
"version": "0.4.4",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -34,13 +34,13 @@
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.7.5",
|
||||
"@backstage/plugin-catalog-react": "^0.1.4",
|
||||
"@backstage/core": "^0.7.6",
|
||||
"@backstage/core": "^0.7.7",
|
||||
"@backstage/integration": "^0.5.1",
|
||||
"@backstage/theme": "^0.2.6",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "4.0.0-alpha.45",
|
||||
"@octokit/rest": "^18.0.12",
|
||||
"@octokit/rest": "^18.5.3",
|
||||
"moment": "^2.27.0",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
|
||||
@@ -202,13 +202,13 @@ export const WorkflowRunDetails = ({ entity }: { entity: Entity }) => {
|
||||
<TableCell>
|
||||
<Typography noWrap>Message</Typography>
|
||||
</TableCell>
|
||||
<TableCell>{details.value?.head_commit.message}</TableCell>
|
||||
<TableCell>{details.value?.head_commit?.message}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Typography noWrap>Commit ID</Typography>
|
||||
</TableCell>
|
||||
<TableCell>{details.value?.head_commit.id}</TableCell>
|
||||
<TableCell>{details.value?.head_commit?.id}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
@@ -231,7 +231,7 @@ export const WorkflowRunDetails = ({ entity }: { entity: Entity }) => {
|
||||
<TableCell>
|
||||
<Typography noWrap>Author</Typography>
|
||||
</TableCell>
|
||||
<TableCell>{`${details.value?.head_commit.author?.name} (${details.value?.head_commit.author?.email})`}</TableCell>
|
||||
<TableCell>{`${details.value?.head_commit?.author?.name} (${details.value?.head_commit?.author?.email})`}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
# @backstage/plugin-github-deployments
|
||||
|
||||
## 0.1.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- c614ede9a: Updated README to have up-to-date install instructions.
|
||||
- Updated dependencies [9afcac5af]
|
||||
- Updated dependencies [e0c9ed759]
|
||||
- Updated dependencies [6eaecbd81]
|
||||
- @backstage/core@0.7.7
|
||||
|
||||
## 0.1.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -13,8 +13,8 @@ The GitHub Deployments Plugin displays recent deployments from GitHub.
|
||||
1. Install the GitHub Deployments Plugin.
|
||||
|
||||
```bash
|
||||
# packages/app
|
||||
|
||||
# From your Backstage root directory
|
||||
cd packages/app
|
||||
yarn add @backstage/plugin-github-deployments
|
||||
```
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-github-deployments",
|
||||
"version": "0.1.3",
|
||||
"version": "0.1.4",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -21,7 +21,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.7.6",
|
||||
"@backstage/core": "^0.7.6",
|
||||
"@backstage/core": "^0.7.7",
|
||||
"@backstage/plugin-catalog-react": "^0.1.3",
|
||||
"@backstage/theme": "^0.2.6",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/core": "^0.7.6",
|
||||
"@backstage/core": "^0.7.7",
|
||||
"@backstage/theme": "^0.2.6",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
# @backstage/plugin-graphiql
|
||||
|
||||
## 0.2.10
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- c614ede9a: Updated README to have up-to-date install instructions.
|
||||
- Updated dependencies [9afcac5af]
|
||||
- Updated dependencies [e0c9ed759]
|
||||
- Updated dependencies [6eaecbd81]
|
||||
- @backstage/core@0.7.7
|
||||
|
||||
## 0.2.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -12,6 +12,7 @@ By exposing GraphiQL as a plugin instead of a standalone app, it's possible to p
|
||||
Start out by installing the plugin in your Backstage app:
|
||||
|
||||
```bash
|
||||
# From your Backstage root directory
|
||||
cd packages/app
|
||||
yarn add @backstage/plugin-graphiql
|
||||
```
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/plugin-graphiql",
|
||||
"description": "Backstage plugin for browsing GraphQL APIs",
|
||||
"version": "0.2.9",
|
||||
"version": "0.2.10",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
@@ -31,7 +31,7 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/core": "^0.7.6",
|
||||
"@backstage/core": "^0.7.7",
|
||||
"@backstage/theme": "^0.2.6",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
# @backstage/plugin-jenkins
|
||||
|
||||
## 0.4.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- c614ede9a: Updated README to have up-to-date install instructions.
|
||||
- Updated dependencies [9afcac5af]
|
||||
- Updated dependencies [e0c9ed759]
|
||||
- Updated dependencies [6eaecbd81]
|
||||
- @backstage/core@0.7.7
|
||||
|
||||
## 0.4.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
+23
-17
@@ -11,25 +11,25 @@ Website: [https://jenkins.io/](https://jenkins.io/)
|
||||
1. If you have a standalone app (you didn't clone this repo), then do
|
||||
|
||||
```bash
|
||||
# From your Backstage root directory
|
||||
cd packages/app
|
||||
yarn add @backstage/plugin-jenkins
|
||||
```
|
||||
|
||||
2. Add the `EntityJenkinsContent` extension to the entity page in the app:
|
||||
|
||||
```tsx
|
||||
// packages/app/src/components/catalog/EntityPage.tsx
|
||||
import { EntityJenkinsContent } from '@backstage/plugin-circleci';
|
||||
// In packages/app/src/components/catalog/EntityPage.tsx
|
||||
import { EntityJenkinsContent } from '@backstage/plugin-jenkins';
|
||||
|
||||
// ...
|
||||
// You can add the tab to any number of pages, the service page is shown as an
|
||||
// example here
|
||||
const serviceEntityPage = (
|
||||
<EntityPageLayout>
|
||||
...
|
||||
<EntityLayout>
|
||||
{/* other tabs... */}
|
||||
<EntityLayout.Route path="/jenkins" title="Jenkins">
|
||||
<EntityJenkinsContent />
|
||||
</EntityLayout.Route>
|
||||
...
|
||||
</EntityPageLayout>
|
||||
);
|
||||
```
|
||||
|
||||
3. Add proxy configuration to `app-config.yaml`
|
||||
@@ -43,14 +43,18 @@ proxy:
|
||||
Authorization: Basic ${JENKINS_BASIC_AUTH_HEADER}
|
||||
```
|
||||
|
||||
4. Add an environment variable which contains the Jenkins credentials, (note: use an API token not your password). Here user is the name of the user created in Jenkins.
|
||||
4. Add an environment variable which contains the Jenkins credentials (NOTE:
|
||||
use an API token, not your password). Here `user` is the name of the user
|
||||
created in Jenkins.
|
||||
|
||||
```shell
|
||||
export JENKINS_BASIC_AUTH_HEADER=$(echo -n user:api-token | base64)
|
||||
```
|
||||
|
||||
5. Run app with `yarn start`
|
||||
6. Add the Jenkins folder annotation to your `catalog-info.yaml`, (note: currently this plugin only supports folders and Git SCM)
|
||||
5. Run the app with `yarn start`
|
||||
|
||||
6. Add the Jenkins folder annotation to your `catalog-info.yaml`, (NOTE:
|
||||
currently this plugin only supports folders and Git SCM)
|
||||
|
||||
```yaml
|
||||
apiVersion: backstage.io/v1alpha1
|
||||
@@ -68,11 +72,11 @@ spec:
|
||||
|
||||
7. Register your component
|
||||
|
||||
8. Click the component in the catalog you should now see Jenkins builds, and a last build result for your master build.
|
||||
8. Click the component in the catalog. You should now see Jenkins builds, and a
|
||||
last build result for your master build.
|
||||
|
||||
Note:
|
||||
|
||||
If you are not using environment variable then you can directly type API token in app-config.yaml
|
||||
Note: If you are not using environment variables, you can directly type the API
|
||||
token into `app-config.yaml`.
|
||||
|
||||
```yaml
|
||||
proxy:
|
||||
@@ -83,7 +87,8 @@ proxy:
|
||||
Authorization: Basic YWRtaW46MTFlYzI1NmU0Mzg1MDFjM2Y1Yzc2Yjc1MWE3ZTQ3YWY4Mw==
|
||||
```
|
||||
|
||||
YWRtaW46MTFlYzI1NmU0Mzg1MDFjM2Y1Yzc2Yjc1MWE3ZTQ3YWY4Mw== is the base64 of user and it's API token e.g. admin:11ec256e438501c3f5c76b751a7e47af83
|
||||
The string starting with `YWR...` is the base64 encoding of the user and their
|
||||
API token, e.g. `admin:11ec256e438501c3f5c76b751a7e47af83`.
|
||||
|
||||
## Features
|
||||
|
||||
@@ -94,4 +99,5 @@ YWRtaW46MTFlYzI1NmU0Mzg1MDFjM2Y1Yzc2Yjc1MWE3ZTQ3YWY4Mw== is the base64 of user a
|
||||
## Limitations
|
||||
|
||||
- Only works with organization folder projects backed by GitHub
|
||||
- No pagination support currently, limited to 50 projects - don't run this on a Jenkins with lots of builds
|
||||
- No pagination support currently, limited to 50 projects - don't run this on a
|
||||
Jenkins instance with lots of builds
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-jenkins",
|
||||
"version": "0.4.1",
|
||||
"version": "0.4.2",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -32,7 +32,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.7.3",
|
||||
"@backstage/core": "^0.7.6",
|
||||
"@backstage/core": "^0.7.7",
|
||||
"@backstage/plugin-catalog-react": "^0.1.3",
|
||||
"@backstage/theme": "^0.2.6",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# Kafka Backend
|
||||
|
||||
This is the backend part of the Kafka plugin. It responds to Kafka requests from the frontend.
|
||||
This is the backend part of the Kafka plugin. It responds to Kafka requests
|
||||
from the frontend.
|
||||
|
||||
## Configuration
|
||||
|
||||
@@ -16,7 +17,9 @@ A list of the brokers' host names and ports to connect to.
|
||||
|
||||
### `ssl` (optional)
|
||||
|
||||
Configure TLS connection to the Kafka cluster. The options are passed directly to [tls.connect] and used to create the TLS secure context. Normally these would include `key` and `cert`.
|
||||
Configure TLS connection to the Kafka cluster. The options are passed directly
|
||||
to [tls.connect] and used to create the TLS secure context. Normally these
|
||||
would include `key` and `cert`.
|
||||
|
||||
Example:
|
||||
|
||||
@@ -39,7 +42,7 @@ kafka:
|
||||
clusters:
|
||||
- name: prod
|
||||
brokers:
|
||||
- my-cluser:9092
|
||||
- my-cluster:9092
|
||||
ssl: true
|
||||
sasl:
|
||||
mechanism: plain # or 'scram-sha-256' or 'scram-sha-512'
|
||||
|
||||
+15
-23
@@ -7,7 +7,9 @@
|
||||
1. Run:
|
||||
|
||||
```bash
|
||||
yarn add @backstage/plugin-kafka @backstage/plugin-kafka-backend
|
||||
# From your Backstage root directory
|
||||
yarn --cwd packages/app add @backstage/plugin-kafka
|
||||
yarn --cwd packages/backend add @backstage/plugin-kafka-backend
|
||||
```
|
||||
|
||||
2. Add the plugin backend:
|
||||
@@ -29,41 +31,31 @@ export default async function createPlugin({
|
||||
And then add to `packages/backend/src/index.ts`:
|
||||
|
||||
```js
|
||||
// ...
|
||||
// In packages/backend/src/index.ts
|
||||
import kafka from './plugins/kafka';
|
||||
// ...
|
||||
async function main() {
|
||||
// ...
|
||||
const kafkaEnv = useHotMemoize(module, () => createEnv('kafka'));
|
||||
// ...
|
||||
|
||||
const apiRouter = Router();
|
||||
// ...
|
||||
apiRouter.use('/kafka', await kafka(kafkaEnv));
|
||||
// ...
|
||||
```
|
||||
|
||||
3. Add the plugin frontend to `packages/app/src/plugin.ts`:
|
||||
|
||||
```js
|
||||
export { plugin as Kafka } from '@backstage/plugin-kafka';
|
||||
```
|
||||
|
||||
4. Register the plugin frontend router in `packages/app/src/components/catalog/EntityPage.tsx`:
|
||||
3. Add the plugin as a tab to your service entities:
|
||||
|
||||
```jsx
|
||||
import { Router as KafkaRouter } from '@backstage/plugin-kafka';
|
||||
// In packages/app/src/components/catalog/EntityPage.tsx
|
||||
import { EntityKafkaContent } from '@backstage/plugin-kafka';
|
||||
|
||||
// Then, somewhere inside <EntityPageLayout>
|
||||
|
||||
<EntityPageLayout.Content
|
||||
path="/kafka/*"
|
||||
title="Kafka"
|
||||
element={<KafkaRouter entity={entity} />}
|
||||
/>;
|
||||
const serviceEntityPage = (
|
||||
<EntityLayout>
|
||||
{/* other tabs... */}
|
||||
<EntityLayout.Route path="/kafka" title="Kafka">
|
||||
<EntityKafkaContent />
|
||||
</EntityLayout.Route>
|
||||
```
|
||||
|
||||
5. Add broker configs for the backend in your `app-config.yaml` (see
|
||||
4. Add broker configs for the backend in your `app-config.yaml` (see
|
||||
[kafka-backend](https://github.com/backstage/backstage/blob/master/plugins/kafka-backend/README.md)
|
||||
for more options):
|
||||
|
||||
@@ -76,7 +68,7 @@ kafka:
|
||||
- localhost:9092
|
||||
```
|
||||
|
||||
6. Add `kafka.apache.org/consumer-groups` annotation to your services:
|
||||
5. Add the `kafka.apache.org/consumer-groups` annotation to your services:
|
||||
|
||||
```yaml
|
||||
apiVersion: backstage.io/v1alpha1
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.7.4",
|
||||
"@backstage/core": "^0.7.6",
|
||||
"@backstage/core": "^0.7.7",
|
||||
"@backstage/plugin-catalog-react": "^0.1.1",
|
||||
"@backstage/theme": "^0.2.6",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
|
||||
@@ -32,9 +32,9 @@
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.7.4",
|
||||
"@backstage/config": "^0.1.4",
|
||||
"@backstage/core": "^0.7.6",
|
||||
"@backstage/core": "^0.7.7",
|
||||
"@backstage/plugin-catalog-react": "^0.1.3",
|
||||
"@backstage/plugin-kubernetes-common": "^0.1.0",
|
||||
"@backstage/plugin-catalog-react": "^0.1.3",
|
||||
"@backstage/theme": "^0.2.6",
|
||||
"@kubernetes/client-node": "^0.14.0",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
# @backstage/plugin-lighthouse
|
||||
|
||||
## 0.2.15
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- c614ede9a: Updated README to have up-to-date install instructions.
|
||||
- Updated dependencies [9afcac5af]
|
||||
- Updated dependencies [e0c9ed759]
|
||||
- Updated dependencies [6eaecbd81]
|
||||
- @backstage/core@0.7.7
|
||||
|
||||
## 0.2.14
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -28,20 +28,21 @@ _It's likely you will need to [enable CORS](https://developer.mozilla.org/en-US/
|
||||
When you have an instance running that Backstage can hook into, first install the plugin into your app:
|
||||
|
||||
```sh
|
||||
$ yarn add @backstage/plugin-lighthouse
|
||||
# From your Backstage root directory
|
||||
cd packages/app
|
||||
yarn add @backstage/plugin-lighthouse
|
||||
```
|
||||
|
||||
Modify your app routes in `App.tsx` to include the `LighthousePage` component exported from the plugin, for example:
|
||||
|
||||
```tsx
|
||||
// At the top imports
|
||||
// In packages/app/src/App.tsx
|
||||
import { LighthousePage } from '@backstage/plugin-lighthouse';
|
||||
|
||||
<FlatRoutes>
|
||||
// ...
|
||||
<Route path="/lighthouse" element={<LighthousePage />} />
|
||||
// ...
|
||||
</FlatRoutes>;
|
||||
const routes = (
|
||||
<FlatRoutes>
|
||||
{/* ...other routes */}
|
||||
<Route path="/lighthouse" element={<LighthousePage />} />
|
||||
```
|
||||
|
||||
Then configure the `lighthouse-audit-service` URL in your [`app-config.yaml`](https://github.com/backstage/backstage/blob/master/app-config.yaml).
|
||||
@@ -63,55 +64,45 @@ kind: Component
|
||||
metadata:
|
||||
# ...
|
||||
annotations:
|
||||
# ...
|
||||
lighthouse.com/website-url: # A single website url e.g. https://backstage.io/
|
||||
```
|
||||
|
||||
> NOTE: The plugin only supports one website URL per component at this time.
|
||||
|
||||
Add a **Lighthouse tab** to the EntityPage:
|
||||
Add a Lighthouse tab to the entity page:
|
||||
|
||||
```tsx
|
||||
// packages/app/src/components/catalog/EntityPage.tsx
|
||||
import { EmbeddedRouter as LighthouseRouter } from '@backstage/plugin-lighthouse';
|
||||
// In packages/app/src/components/catalog/EntityPage.tsx
|
||||
import { EntityLighthouseContent } from '@backstage/plugin-lighthouse';
|
||||
|
||||
// ...
|
||||
const WebsiteEntityPage = ({ entity }: { entity: Entity }) => (
|
||||
<EntityPageLayout>
|
||||
// ...
|
||||
<EntityPageLayout.Content
|
||||
path="/lighthouse/*"
|
||||
title="Lighthouse"
|
||||
element={<LighthouseRouter entity={entity} />}
|
||||
/>
|
||||
</EntityPageLayout>
|
||||
);
|
||||
const websiteEntityPage = (
|
||||
<EntityLayout>
|
||||
{/* other tabs... */}
|
||||
<EntityLayout.Route path="/lighthouse" title="Lighthouse">
|
||||
<EntityLighthouseContent />
|
||||
</EntityLayout.Route>
|
||||
```
|
||||
|
||||
> NOTE: The embedded router renders page content without a header section allowing it to be rendered within a
|
||||
> catalog plugin page.
|
||||
> NOTE: The embedded router renders page content without a header section
|
||||
> allowing it to be rendered within a catalog plugin page.
|
||||
|
||||
Add a **Lighthouse card** to the overview tab on the EntityPage:
|
||||
|
||||
```tsx
|
||||
// packages/app/src/components/catalog/EntityPage.tsx
|
||||
// In packages/app/src/components/catalog/EntityPage.tsx
|
||||
import {
|
||||
LastLighthouseAuditCard,
|
||||
isPluginApplicableToEntity as isLighthouseAvailable,
|
||||
EntityLastLighthouseAuditCard,
|
||||
isLighthouseAvailable,
|
||||
} from '@backstage/plugin-lighthouse';
|
||||
|
||||
// ...
|
||||
|
||||
const OverviewContent = ({ entity }: { entity: Entity }) => (
|
||||
<Grid container spacing={3}>
|
||||
// ...
|
||||
{isLighthouseAvailable(entity) && (
|
||||
<Grid item sm={4}>
|
||||
<LastLighthouseAuditCard />
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
);
|
||||
const overviewContent = (
|
||||
<Grid container spacing={3} alignItems="stretch">
|
||||
{/* ...other content */}
|
||||
<EntitySwitch>
|
||||
<EntitySwitch.Case if={isLighthouseAvailable}>
|
||||
<Grid item md={6}>
|
||||
<EntityLastLighthouseAuditCard />
|
||||
</Grid>
|
||||
</EntitySwitch.Case>
|
||||
</EntitySwitch>
|
||||
```
|
||||
|
||||
Link Lighthouse
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-lighthouse",
|
||||
"version": "0.2.14",
|
||||
"version": "0.2.15",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -33,7 +33,7 @@
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.7.3",
|
||||
"@backstage/config": "^0.1.4",
|
||||
"@backstage/core": "^0.7.6",
|
||||
"@backstage/core": "^0.7.7",
|
||||
"@backstage/plugin-catalog-react": "^0.1.2",
|
||||
"@backstage/theme": "^0.2.6",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
|
||||
@@ -46,7 +46,8 @@ with the environment variable \`LAS_CORS\` set to \`true\`._
|
||||
When you have an instance running that Backstage can hook into, first install the plugin into your app:
|
||||
|
||||
\`\`\`sh
|
||||
$ yarn add @backstage/plugin-lighthouse
|
||||
cd packages/app
|
||||
yarn add @backstage/plugin-lighthouse
|
||||
\`\`\`
|
||||
|
||||
Modify your app routes in \`App.tsx\` to include the \`LighthousePage\` component exported from the plugin, for example:
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/core": "^0.7.6",
|
||||
"@backstage/core": "^0.7.7",
|
||||
"@backstage/theme": "^0.2.6",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.7.6",
|
||||
"@backstage/core": "^0.7.6",
|
||||
"@backstage/core": "^0.7.7",
|
||||
"@backstage/core-api": "^0.2.16",
|
||||
"@backstage/plugin-catalog-react": "^0.1.4",
|
||||
"@backstage/theme": "^0.2.6",
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
# @backstage/plugin-pagerduty
|
||||
|
||||
## 0.3.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- c614ede9a: Updated README to have up-to-date install instructions.
|
||||
- Updated dependencies [9afcac5af]
|
||||
- Updated dependencies [e0c9ed759]
|
||||
- Updated dependencies [6eaecbd81]
|
||||
- @backstage/core@0.7.7
|
||||
|
||||
## 0.3.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -19,6 +19,8 @@ This plugin provides:
|
||||
Install the plugin:
|
||||
|
||||
```bash
|
||||
# From your Backstage root directory
|
||||
cd packages/app
|
||||
yarn add @backstage/plugin-pagerduty
|
||||
```
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-pagerduty",
|
||||
"version": "0.3.2",
|
||||
"version": "0.3.3",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -31,7 +31,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.7.3",
|
||||
"@backstage/core": "^0.7.6",
|
||||
"@backstage/core": "^0.7.7",
|
||||
"@backstage/plugin-catalog-react": "^0.1.1",
|
||||
"@backstage/theme": "^0.2.6",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
|
||||
@@ -59,8 +59,8 @@ describe('buildMiddleware', () => {
|
||||
mockCreateProxyMiddleware.mockClear();
|
||||
});
|
||||
|
||||
it('accepts strings', async () => {
|
||||
buildMiddleware('/api/', logger, 'test', 'http://mocked');
|
||||
it('accepts strings prefixed by /', async () => {
|
||||
buildMiddleware('/proxy', logger, '/test', 'http://mocked');
|
||||
|
||||
expect(createProxyMiddleware).toHaveBeenCalledTimes(1);
|
||||
|
||||
@@ -74,13 +74,53 @@ describe('buildMiddleware', () => {
|
||||
expect(filter('', { method: 'PATCH', headers: {} })).toBe(true);
|
||||
expect(filter('', { method: 'DELETE', headers: {} })).toBe(true);
|
||||
|
||||
expect(fullConfig.pathRewrite).toEqual({ '^/api/test/': '/' });
|
||||
expect(fullConfig.pathRewrite).toEqual({ '^/proxy/test/': '/' });
|
||||
expect(fullConfig.changeOrigin).toBe(true);
|
||||
expect(fullConfig.logProvider!(logger)).toBe(logger);
|
||||
});
|
||||
|
||||
it('accepts routes not prefixed with / when path is not suffixed with /', async () => {
|
||||
buildMiddleware('/proxy', logger, 'test', 'http://mocked');
|
||||
|
||||
expect(createProxyMiddleware).toHaveBeenCalledTimes(1);
|
||||
|
||||
const [filter, fullConfig] = mockCreateProxyMiddleware.mock.calls[0] as [
|
||||
(pathname: string, req: Partial<http.IncomingMessage>) => boolean,
|
||||
ProxyMiddlewareConfig,
|
||||
];
|
||||
expect(filter('', { method: 'GET', headers: {} })).toBe(true);
|
||||
expect(filter('', { method: 'POST', headers: {} })).toBe(true);
|
||||
expect(filter('', { method: 'PUT', headers: {} })).toBe(true);
|
||||
expect(filter('', { method: 'PATCH', headers: {} })).toBe(true);
|
||||
expect(filter('', { method: 'DELETE', headers: {} })).toBe(true);
|
||||
|
||||
expect(fullConfig.pathRewrite).toEqual({ '^/proxy/test/': '/' });
|
||||
expect(fullConfig.changeOrigin).toBe(true);
|
||||
expect(fullConfig.logProvider!(logger)).toBe(logger);
|
||||
});
|
||||
|
||||
it('accepts routes prefixed with / when path is suffixed with /', async () => {
|
||||
buildMiddleware('/proxy/', logger, '/test', 'http://mocked');
|
||||
|
||||
expect(createProxyMiddleware).toHaveBeenCalledTimes(1);
|
||||
|
||||
const [filter, fullConfig] = mockCreateProxyMiddleware.mock.calls[0] as [
|
||||
(pathname: string, req: Partial<http.IncomingMessage>) => boolean,
|
||||
ProxyMiddlewareConfig,
|
||||
];
|
||||
expect(filter('', { method: 'GET', headers: {} })).toBe(true);
|
||||
expect(filter('', { method: 'POST', headers: {} })).toBe(true);
|
||||
expect(filter('', { method: 'PUT', headers: {} })).toBe(true);
|
||||
expect(filter('', { method: 'PATCH', headers: {} })).toBe(true);
|
||||
expect(filter('', { method: 'DELETE', headers: {} })).toBe(true);
|
||||
|
||||
expect(fullConfig.pathRewrite).toEqual({ '^/proxy/test/': '/' });
|
||||
expect(fullConfig.changeOrigin).toBe(true);
|
||||
expect(fullConfig.logProvider!(logger)).toBe(logger);
|
||||
});
|
||||
|
||||
it('limits allowedMethods', async () => {
|
||||
buildMiddleware('/api/', logger, 'test', {
|
||||
buildMiddleware('/proxy', logger, '/test', {
|
||||
target: 'http://mocked',
|
||||
allowedMethods: ['GET', 'DELETE'],
|
||||
});
|
||||
@@ -97,13 +137,13 @@ describe('buildMiddleware', () => {
|
||||
expect(filter('', { method: 'PATCH', headers: {} })).toBe(false);
|
||||
expect(filter('', { method: 'DELETE', headers: {} })).toBe(true);
|
||||
|
||||
expect(fullConfig.pathRewrite).toEqual({ '^/api/test/': '/' });
|
||||
expect(fullConfig.pathRewrite).toEqual({ '^/proxy/test/': '/' });
|
||||
expect(fullConfig.changeOrigin).toBe(true);
|
||||
expect(fullConfig.logProvider!(logger)).toBe(logger);
|
||||
});
|
||||
|
||||
it('permits default headers', async () => {
|
||||
buildMiddleware('/api/', logger, 'test', {
|
||||
buildMiddleware('/proxy', logger, '/test', {
|
||||
target: 'http://mocked',
|
||||
});
|
||||
|
||||
@@ -143,7 +183,7 @@ describe('buildMiddleware', () => {
|
||||
});
|
||||
|
||||
it('permits default and configured headers', async () => {
|
||||
buildMiddleware('/api/', logger, 'test', {
|
||||
buildMiddleware('/proxy', logger, '/test', {
|
||||
target: 'http://mocked',
|
||||
headers: {
|
||||
Authorization: 'my-token',
|
||||
@@ -176,7 +216,7 @@ describe('buildMiddleware', () => {
|
||||
});
|
||||
|
||||
it('permits configured headers', async () => {
|
||||
buildMiddleware('/api/', logger, 'test', {
|
||||
buildMiddleware('/proxy', logger, '/test', {
|
||||
target: 'http://mocked',
|
||||
allowedHeaders: ['authorization', 'cookie'],
|
||||
});
|
||||
@@ -208,7 +248,7 @@ describe('buildMiddleware', () => {
|
||||
});
|
||||
|
||||
it('responds default headers', async () => {
|
||||
buildMiddleware('/api/', logger, 'test', {
|
||||
buildMiddleware('/proxy', logger, '/test', {
|
||||
target: 'http://mocked',
|
||||
});
|
||||
|
||||
@@ -251,7 +291,7 @@ describe('buildMiddleware', () => {
|
||||
});
|
||||
|
||||
it('responds configured headers', async () => {
|
||||
buildMiddleware('/api/', logger, 'test', {
|
||||
buildMiddleware('/proxy', logger, '/test', {
|
||||
target: 'http://mocked',
|
||||
allowedHeaders: ['set-cookie'],
|
||||
});
|
||||
@@ -282,10 +322,10 @@ describe('buildMiddleware', () => {
|
||||
|
||||
it('rejects malformed target URLs', async () => {
|
||||
expect(() =>
|
||||
buildMiddleware('/api/', logger, 'test', 'backstage.io'),
|
||||
buildMiddleware('/proxy', logger, '/test', 'backstage.io'),
|
||||
).toThrowError(/Proxy target is not a valid URL/);
|
||||
expect(() =>
|
||||
buildMiddleware('/api/', logger, 'test', { target: 'backstage.io' }),
|
||||
buildMiddleware('/proxy', logger, '/test', { target: 'backstage.io' }),
|
||||
).toThrowError(/Proxy target is not a valid URL/);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -77,10 +77,24 @@ export function buildMiddleware(
|
||||
`Proxy target is not a valid URL: ${fullConfig.target ?? ''}`,
|
||||
);
|
||||
}
|
||||
|
||||
// Default is to do a path rewrite that strips out the proxy's path prefix
|
||||
// and the rest of the route.
|
||||
if (fullConfig.pathRewrite === undefined) {
|
||||
const routeWithSlash = route.endsWith('/') ? route : `${route}/`;
|
||||
let routeWithSlash = route.endsWith('/') ? route : `${route}/`;
|
||||
|
||||
if (!pathPrefix.endsWith('/') && !routeWithSlash.startsWith('/')) {
|
||||
// Need to insert a / between pathPrefix and routeWithSlash
|
||||
routeWithSlash = `/${routeWithSlash}`;
|
||||
} else if (pathPrefix.endsWith('/') && routeWithSlash.startsWith('/')) {
|
||||
// Never expect this to happen at this point in time as
|
||||
// pathPrefix is set using `getExternalBaseUrl` which "Returns the
|
||||
// external HTTP base backend URL for a given plugin,
|
||||
// **without a trailing slash.**". But in case this changes in future, we
|
||||
// need to drop a / on either pathPrefix or routeWithSlash
|
||||
routeWithSlash = routeWithSlash.substring(1);
|
||||
}
|
||||
|
||||
fullConfig.pathRewrite = {
|
||||
[`^${pathPrefix}${routeWithSlash}`]: '/',
|
||||
};
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
# @backstage/plugin-register-component
|
||||
|
||||
## 0.2.14
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- c614ede9a: Updated README to have up-to-date install instructions.
|
||||
- Updated dependencies [9afcac5af]
|
||||
- Updated dependencies [e0c9ed759]
|
||||
- Updated dependencies [6eaecbd81]
|
||||
- @backstage/core@0.7.7
|
||||
|
||||
## 0.2.13
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -19,6 +19,7 @@ When installed it is accessible on [localhost:3000/register-component](localhost
|
||||
1. Install plugin and its dependency `plugin-catalog`
|
||||
|
||||
```bash
|
||||
# From your Backstage root directory
|
||||
cd packages/app
|
||||
yarn add @backstage/plugin-register-component
|
||||
```
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-register-component",
|
||||
"version": "0.2.13",
|
||||
"version": "0.2.14",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -31,7 +31,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.7.5",
|
||||
"@backstage/core": "^0.7.6",
|
||||
"@backstage/core": "^0.7.7",
|
||||
"@backstage/plugin-catalog-react": "^0.1.4",
|
||||
"@backstage/theme": "^0.2.6",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
@@ -39,7 +39,7 @@
|
||||
"@material-ui/lab": "4.0.0-alpha.45",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-hook-form": "^6.6.0",
|
||||
"react-hook-form": "^6.15.4",
|
||||
"react-router": "6.0.0-beta.0",
|
||||
"react-router-dom": "6.0.0-beta.0",
|
||||
"react-use": "^15.3.3"
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
# @backstage/plugin-rollbar
|
||||
|
||||
## 0.3.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- c614ede9a: Updated README to have up-to-date install instructions.
|
||||
- Updated dependencies [9afcac5af]
|
||||
- Updated dependencies [e0c9ed759]
|
||||
- Updated dependencies [6eaecbd81]
|
||||
- @backstage/core@0.7.7
|
||||
|
||||
## 0.3.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -9,25 +9,23 @@ Website: [https://rollbar.com/](https://rollbar.com/)
|
||||
2. If you have standalone app (you didn't clone this repo), then do
|
||||
|
||||
```bash
|
||||
# From your Backstage root directory
|
||||
cd packages/app
|
||||
yarn add @backstage/plugin-rollbar
|
||||
```
|
||||
|
||||
3. Add to the app `EntityPage` component:
|
||||
|
||||
```tsx
|
||||
// packages/app/src/components/catalog/EntityPage.tsx
|
||||
// In packages/app/src/components/catalog/EntityPage.tsx
|
||||
import { EntityRollbarContent } from '@backstage/plugin-rollbar';
|
||||
|
||||
// ...
|
||||
const serviceEntityPage = (
|
||||
<EntityPageLayout>
|
||||
...
|
||||
<EntityLayout>
|
||||
{/* other tabs... */}
|
||||
<EntityLayout.Route path="/rollbar" title="Rollbar">
|
||||
<EntityRollbarContent />
|
||||
</EntityLayout.Route>
|
||||
...
|
||||
</EntityPageLayout>
|
||||
);
|
||||
```
|
||||
|
||||
4. Setup the `app-config.yaml` and account token environment variable
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-rollbar",
|
||||
"version": "0.3.3",
|
||||
"version": "0.3.4",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -32,7 +32,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.7.3",
|
||||
"@backstage/core": "^0.7.6",
|
||||
"@backstage/core": "^0.7.7",
|
||||
"@backstage/plugin-catalog-react": "^0.1.1",
|
||||
"@backstage/theme": "^0.2.6",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @backstage/plugin-scaffolder-backend
|
||||
|
||||
## 0.10.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- a1783f306: Added the `nebula-preview` preview to `Octokit` for repository visibility.
|
||||
|
||||
## 0.10.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-scaffolder-backend",
|
||||
"version": "0.10.0",
|
||||
"version": "0.10.1",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -37,7 +37,7 @@
|
||||
"@backstage/integration": "^0.5.1",
|
||||
"@gitbeaker/core": "^28.0.2",
|
||||
"@gitbeaker/node": "^28.0.2",
|
||||
"@octokit/rest": "^18.0.12",
|
||||
"@octokit/rest": "^18.5.3",
|
||||
"@types/dockerode": "^3.2.1",
|
||||
"@types/express": "^4.17.6",
|
||||
"@types/git-url-parse": "^9.0.0",
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
# @backstage/plugin-scaffolder
|
||||
|
||||
## 0.9.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- f6efa71ee: Enable starred templates on Scaffolder frontend
|
||||
- 19a4dd710: Removed unused `swr` dependency.
|
||||
- 23769512a: Support `anyOf`, `oneOf` and `allOf` schemas in the scaffolder template.
|
||||
- Updated dependencies [9afcac5af]
|
||||
- Updated dependencies [e0c9ed759]
|
||||
- Updated dependencies [6eaecbd81]
|
||||
- @backstage/core@0.7.7
|
||||
|
||||
## 0.9.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-scaffolder",
|
||||
"version": "0.9.1",
|
||||
"version": "0.9.2",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -33,7 +33,7 @@
|
||||
"@backstage/catalog-client": "^0.3.10",
|
||||
"@backstage/catalog-model": "^0.7.7",
|
||||
"@backstage/config": "^0.1.4",
|
||||
"@backstage/core": "^0.7.6",
|
||||
"@backstage/core": "^0.7.7",
|
||||
"@backstage/integration": "^0.5.1",
|
||||
"@backstage/integration-react": "^0.1.1",
|
||||
"@backstage/plugin-catalog-react": "^0.1.4",
|
||||
@@ -44,7 +44,7 @@
|
||||
"@rjsf/core": "^2.4.0",
|
||||
"@rjsf/material-ui": "^2.4.0",
|
||||
"classnames": "^2.2.6",
|
||||
"json-schema": "^0.2.5",
|
||||
"json-schema": "^0.3.0",
|
||||
"git-url-parse": "^11.4.4",
|
||||
"humanize-duration": "^3.25.1",
|
||||
"immer": "^9.0.1",
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @backstage/plugin-search-backend-node
|
||||
|
||||
## 0.1.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- e1e757569: Introduced Scheduler which is responsible for adding new tasks to a schedule together with it's interval timer as well as starting and stopping the scheduler processes.
|
||||
|
||||
## 0.1.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-search-backend-node",
|
||||
"version": "0.1.3",
|
||||
"version": "0.1.4",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
# @backstage/plugin-search
|
||||
|
||||
## 0.3.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- dcd54c7cd: Use `RouteRef` to generate path to search page.
|
||||
- Updated dependencies [9afcac5af]
|
||||
- Updated dependencies [e0c9ed759]
|
||||
- Updated dependencies [6eaecbd81]
|
||||
- @backstage/core@0.7.7
|
||||
|
||||
## 0.3.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-search",
|
||||
"version": "0.3.4",
|
||||
"version": "0.3.5",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -29,7 +29,7 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/core": "^0.7.6",
|
||||
"@backstage/core": "^0.7.7",
|
||||
"@backstage/catalog-model": "^0.7.3",
|
||||
"@backstage/plugin-catalog-react": "^0.1.2",
|
||||
"@backstage/search-common": "^0.1.1",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user