({
+ id: 'y',
+});
+
+function Verifier() {
+ const holder = useApiHolder();
+ const x = holder.get(xApiRef);
+ const y = holder.get(yApiRef);
+
+ return (
+
+ {x ? (
+
+ x={x.a},{x.b}
+
+ ) : (
+ no x
+ )}
+ {y ? y={y} : no y}
+
+ );
+}
+
+describe('TestApiProvider', () => {
+ it('should provide APIs', () => {
+ render(
+
+
+ ,
+ );
+ expect(screen.getByText('x=a,3')).toBeInTheDocument();
+ expect(screen.getByText('y=y')).toBeInTheDocument();
+ });
+
+ it('should provide partial APIs', () => {
+ render(
+
+
+ ,
+ );
+ expect(screen.getByText('x=a,')).toBeInTheDocument();
+ expect(screen.getByText('no y')).toBeInTheDocument();
+ });
+
+ it('should require partial implementations to still match types', () => {
+ render(
+ // @ts-expect-error
+
+
+ ,
+ );
+ expect(screen.getByText('x=3,')).toBeInTheDocument();
+ expect(screen.getByText('no y')).toBeInTheDocument();
+ });
+
+ it('should allow empty APIs', () => {
+ render(
+
+
+ ,
+ );
+ expect(screen.getByText('no x')).toBeInTheDocument();
+ expect(screen.getByText('no y')).toBeInTheDocument();
+ });
+});
+
+describe('TestApiRegistry', () => {
+ it('should be created with APIs', () => {
+ const x = { a: 'a', b: 3 };
+ const y = 'y';
+ const registry = TestApiRegistry.from([xApiRef, x], [yApiRef, y]);
+
+ expect(registry.get(xApiRef)).toBe(x);
+ expect(registry.get(yApiRef)).toBe(y);
+ });
+
+ it('should allow partial implementations', () => {
+ const x = { a: 'a' };
+ const registry = TestApiRegistry.from([xApiRef, x]);
+
+ expect(registry.get(xApiRef)).toBe(x);
+ expect(registry.get(yApiRef)).toBeUndefined();
+ });
+
+ it('should require partial implementations to match types', () => {
+ const x = { a: 2 };
+ // @ts-expect-error
+ const registry = TestApiRegistry.from([xApiRef, x]);
+
+ expect(registry.get(xApiRef)).toBe(x);
+ expect(registry.get(yApiRef)).toBeUndefined();
+ });
+
+ it('should prefer last duplicate API that was provided', () => {
+ const x1 = { a: 'a' };
+ const x2 = { a: 's' };
+ const x3 = { a: 'd' };
+ const registry = TestApiRegistry.from(
+ [xApiRef, x1],
+ [xApiRef, x2],
+ [xApiRef, x3],
+ );
+
+ expect(registry.get(xApiRef)).toBe(x3);
+ });
+});
diff --git a/packages/test-utils/src/testUtils/TestApiProvider.tsx b/packages/test-utils/src/testUtils/TestApiProvider.tsx
new file mode 100644
index 0000000000..b1499b0cde
--- /dev/null
+++ b/packages/test-utils/src/testUtils/TestApiProvider.tsx
@@ -0,0 +1,130 @@
+/*
+ * 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 React, { ReactNode } from 'react';
+import { ApiProvider } from '@backstage/core-app-api';
+import { ApiRef, ApiHolder } from '@backstage/core-plugin-api';
+
+/** @ignore */
+type TestApiProviderPropsApiPair = TApi extends infer TImpl
+ ? readonly [ApiRef, Partial]
+ : never;
+
+/** @ignore */
+type TestApiProviderPropsApiPairs = {
+ [TIndex in keyof TApiPairs]: TestApiProviderPropsApiPair;
+};
+
+/**
+ * Properties for the {@link TestApiProvider} component.
+ *
+ * @public
+ */
+export type TestApiProviderProps = {
+ apis: readonly [...TestApiProviderPropsApiPairs];
+ children: ReactNode;
+};
+
+/**
+ * The `TestApiRegistry` is an {@link @backstage/core-plugin-api#ApiHolder} implementation
+ * that is particularly well suited for development and test environments such as
+ * unit tests, storybooks, and isolated plugin development setups.
+ *
+ * @public
+ */
+export class TestApiRegistry implements ApiHolder {
+ /**
+ * Creates a new {@link TestApiRegistry} with a list of API implementation pairs.
+ *
+ * Similar to the {@link TestApiProvider}, there is no need to provide a full
+ * implementation of each API, it's enough to implement the methods that are tested.
+ *
+ * @example
+ * ```ts
+ * const apis = TestApiRegistry.from(
+ * [configApiRef, new ConfigReader({})],
+ * [identityApiRef, { getUserId: () => 'tester' }],
+ * );
+ * ```
+ *
+ * @public
+ * @param apis - A list of pairs mapping an ApiRef to its respective implementation.
+ */
+ static from(
+ ...apis: readonly [...TestApiProviderPropsApiPairs]
+ ) {
+ return new TestApiRegistry(
+ new Map(apis.map(([api, impl]) => [api.id, impl])),
+ );
+ }
+
+ private constructor(private readonly apis: Map) {}
+
+ /**
+ * Returns an implementation of the API.
+ *
+ * @public
+ */
+ get(api: ApiRef): T | undefined {
+ return this.apis.get(api.id) as T | undefined;
+ }
+}
+
+/**
+ * The `TestApiProvider` is a Utility API context provider that is particularly
+ * well suited for development and test environments such as unit tests, storybooks,
+ * and isolated plugin development setups.
+ *
+ * It lets you provide any number of API implementations, without necessarily
+ * having to fully implement each of the APIs.
+ *
+ * A migration from `ApiRegistry` and `ApiProvider` might look like this, from:
+ *
+ * ```tsx
+ * renderInTestApp(
+ *
+ * {...}
+ *
+ * )
+ * ```
+ *
+ * To the following:
+ *
+ * ```tsx
+ * renderInTestApp(
+ *
+ * {...}
+ *
+ * )
+ * ```
+ *
+ * Note that the cast to `IdentityApi` is no longer needed as long as the mock API
+ * implements a subset of the `IdentityApi`.
+ *
+ * @public
+ **/
+export const TestApiProvider = ({
+ apis,
+ children,
+}: TestApiProviderProps) => {
+ return (
+
+ );
+};
diff --git a/packages/test-utils/src/testUtils/index.tsx b/packages/test-utils/src/testUtils/index.tsx
index 7d93d606cc..c778c5c837 100644
--- a/packages/test-utils/src/testUtils/index.tsx
+++ b/packages/test-utils/src/testUtils/index.tsx
@@ -22,3 +22,5 @@ export * from './msw';
export * from './Keyboard';
export * from './logCollector';
export * from './testingLibrary';
+export { TestApiProvider, TestApiRegistry } from './TestApiProvider';
+export type { TestApiProviderProps } from './TestApiProvider';
diff --git a/packages/theme/package.json b/packages/theme/package.json
index d9e89c969f..28a05af98d 100644
--- a/packages/theme/package.json
+++ b/packages/theme/package.json
@@ -31,7 +31,7 @@
"@material-ui/core": "^4.12.2"
},
"devDependencies": {
- "@backstage/cli": "^0.8.2"
+ "@backstage/cli": "^0.9.0"
},
"files": [
"dist"
diff --git a/packages/types/package.json b/packages/types/package.json
index 8632422a25..197d51b061 100644
--- a/packages/types/package.json
+++ b/packages/types/package.json
@@ -31,7 +31,7 @@
},
"dependencies": {},
"devDependencies": {
- "@backstage/cli": "^0.8.0",
+ "@backstage/cli": "^0.9.0",
"@types/zen-observable": "^0.8.0",
"zen-observable": "^0.8.15"
},
diff --git a/packages/version-bridge/package.json b/packages/version-bridge/package.json
index 7cc0b56a34..35a6e8f71d 100644
--- a/packages/version-bridge/package.json
+++ b/packages/version-bridge/package.json
@@ -33,7 +33,7 @@
"react": "^16.12.0"
},
"devDependencies": {
- "@backstage/cli": "^0.8.0",
+ "@backstage/cli": "^0.9.0",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/react-hooks": "^7.0.2"
diff --git a/plugins/allure/CHANGELOG.md b/plugins/allure/CHANGELOG.md
index 3de49b9b74..f769b6e133 100644
--- a/plugins/allure/CHANGELOG.md
+++ b/plugins/allure/CHANGELOG.md
@@ -1,5 +1,16 @@
# @backstage/plugin-allure
+## 0.1.7
+
+### Patch Changes
+
+- a125278b81: Refactor out the deprecated path and icon from RouteRefs
+- Updated dependencies
+ - @backstage/catalog-model@0.9.7
+ - @backstage/plugin-catalog-react@0.6.4
+ - @backstage/core-components@0.7.4
+ - @backstage/core-plugin-api@0.2.0
+
## 0.1.6
### Patch Changes
diff --git a/plugins/allure/package.json b/plugins/allure/package.json
index b157795eec..9efe6132e8 100644
--- a/plugins/allure/package.json
+++ b/plugins/allure/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-allure",
"description": "A Backstage plugin that integrates with Allure",
- "version": "0.1.6",
+ "version": "0.1.7",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -22,10 +22,10 @@
"clean": "backstage-cli clean"
},
"dependencies": {
- "@backstage/catalog-model": "^0.9.5",
- "@backstage/core-components": "^0.7.3",
- "@backstage/core-plugin-api": "^0.1.13",
- "@backstage/plugin-catalog-react": "^0.6.1",
+ "@backstage/catalog-model": "^0.9.7",
+ "@backstage/core-components": "^0.7.4",
+ "@backstage/core-plugin-api": "^0.2.0",
+ "@backstage/plugin-catalog-react": "^0.6.4",
"@backstage/theme": "^0.2.13",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
@@ -36,10 +36,10 @@
"react-use": "^17.2.4"
},
"devDependencies": {
- "@backstage/cli": "^0.8.2",
- "@backstage/core-app-api": "^0.1.20",
- "@backstage/dev-utils": "^0.2.12",
- "@backstage/test-utils": "^0.1.21",
+ "@backstage/cli": "^0.9.0",
+ "@backstage/core-app-api": "^0.1.22",
+ "@backstage/dev-utils": "^0.2.13",
+ "@backstage/test-utils": "^0.1.22",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
diff --git a/plugins/allure/src/plugin.ts b/plugins/allure/src/plugin.ts
index f5ca7beeb3..a2eaa06a38 100644
--- a/plugins/allure/src/plugin.ts
+++ b/plugins/allure/src/plugin.ts
@@ -23,7 +23,7 @@ import {
import { AllureApiClient, allureApiRef } from './api';
export const allureRouteRef = createRouteRef({
- title: 'allure-report',
+ id: 'allure',
});
export const allurePlugin = createPlugin({
diff --git a/plugins/analytics-module-ga/CHANGELOG.md b/plugins/analytics-module-ga/CHANGELOG.md
index a59bcb6351..acc5c235f9 100644
--- a/plugins/analytics-module-ga/CHANGELOG.md
+++ b/plugins/analytics-module-ga/CHANGELOG.md
@@ -1,5 +1,13 @@
# @backstage/plugin-analytics-module-ga
+## 0.1.3
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-components@0.7.4
+ - @backstage/core-plugin-api@0.2.0
+
## 0.1.2
### Patch Changes
diff --git a/plugins/analytics-module-ga/package.json b/plugins/analytics-module-ga/package.json
index 4970f50c04..e0a32960c5 100644
--- a/plugins/analytics-module-ga/package.json
+++ b/plugins/analytics-module-ga/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-analytics-module-ga",
- "version": "0.1.2",
+ "version": "0.1.3",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -22,8 +22,8 @@
},
"dependencies": {
"@backstage/config": "^0.1.5",
- "@backstage/core-components": "^0.7.3",
- "@backstage/core-plugin-api": "^0.1.13",
+ "@backstage/core-components": "^0.7.4",
+ "@backstage/core-plugin-api": "^0.2.0",
"@backstage/theme": "^0.2.13",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
@@ -34,10 +34,10 @@
"react-use": "^17.2.4"
},
"devDependencies": {
- "@backstage/cli": "^0.8.2",
- "@backstage/core-app-api": "^0.1.20",
- "@backstage/dev-utils": "^0.2.12",
- "@backstage/test-utils": "^0.1.21",
+ "@backstage/cli": "^0.9.0",
+ "@backstage/core-app-api": "^0.1.22",
+ "@backstage/dev-utils": "^0.2.13",
+ "@backstage/test-utils": "^0.1.22",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
diff --git a/plugins/api-docs/CHANGELOG.md b/plugins/api-docs/CHANGELOG.md
index 78f4b08bd7..0b0bd4890c 100644
--- a/plugins/api-docs/CHANGELOG.md
+++ b/plugins/api-docs/CHANGELOG.md
@@ -1,5 +1,17 @@
# @backstage/plugin-api-docs
+## 0.6.14
+
+### Patch Changes
+
+- a125278b81: Refactor out the deprecated path and icon from RouteRefs
+- Updated dependencies
+ - @backstage/plugin-catalog@0.7.3
+ - @backstage/catalog-model@0.9.7
+ - @backstage/plugin-catalog-react@0.6.4
+ - @backstage/core-components@0.7.4
+ - @backstage/core-plugin-api@0.2.0
+
## 0.6.13
### Patch Changes
diff --git a/plugins/api-docs/package.json b/plugins/api-docs/package.json
index 61ae26c776..a4aa15c419 100644
--- a/plugins/api-docs/package.json
+++ b/plugins/api-docs/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-api-docs",
"description": "A Backstage plugin that helps represent API entities in the frontend",
- "version": "0.6.13",
+ "version": "0.6.14",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -31,11 +31,11 @@
},
"dependencies": {
"@asyncapi/react-component": "^0.23.0",
- "@backstage/catalog-model": "^0.9.5",
- "@backstage/core-components": "^0.7.3",
- "@backstage/core-plugin-api": "^0.1.13",
- "@backstage/plugin-catalog": "^0.7.2",
- "@backstage/plugin-catalog-react": "^0.6.3",
+ "@backstage/catalog-model": "^0.9.7",
+ "@backstage/core-components": "^0.7.4",
+ "@backstage/core-plugin-api": "^0.2.0",
+ "@backstage/plugin-catalog": "^0.7.3",
+ "@backstage/plugin-catalog-react": "^0.6.4",
"@backstage/theme": "^0.2.13",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
@@ -53,10 +53,10 @@
"swagger-ui-react": "^4.0.0-rc.3"
},
"devDependencies": {
- "@backstage/cli": "^0.8.2",
- "@backstage/core-app-api": "^0.1.20",
- "@backstage/dev-utils": "^0.2.12",
- "@backstage/test-utils": "^0.1.21",
+ "@backstage/cli": "^0.9.0",
+ "@backstage/core-app-api": "^0.1.22",
+ "@backstage/dev-utils": "^0.2.13",
+ "@backstage/test-utils": "^0.1.22",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
diff --git a/plugins/api-docs/src/routes.ts b/plugins/api-docs/src/routes.ts
index ab976dbf18..b7c152889e 100644
--- a/plugins/api-docs/src/routes.ts
+++ b/plugins/api-docs/src/routes.ts
@@ -19,12 +19,8 @@ import {
createRouteRef,
} from '@backstage/core-plugin-api';
-const NoIcon = () => null;
-
export const rootRoute = createRouteRef({
- icon: NoIcon,
- path: '/api-docs',
- title: 'APIs',
+ id: 'api-docs',
});
export const createComponentRouteRef = createExternalRouteRef({
diff --git a/plugins/app-backend/CHANGELOG.md b/plugins/app-backend/CHANGELOG.md
index 0658b10ebb..a58b028049 100644
--- a/plugins/app-backend/CHANGELOG.md
+++ b/plugins/app-backend/CHANGELOG.md
@@ -1,5 +1,13 @@
# @backstage/plugin-app-backend
+## 0.3.19
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/config-loader@0.8.0
+ - @backstage/backend-common@0.9.10
+
## 0.3.18
### Patch Changes
diff --git a/plugins/app-backend/package.json b/plugins/app-backend/package.json
index d989457c79..ebb0393192 100644
--- a/plugins/app-backend/package.json
+++ b/plugins/app-backend/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-app-backend",
"description": "A Backstage backend plugin that serves the Backstage frontend app",
- "version": "0.3.18",
+ "version": "0.3.19",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -30,8 +30,8 @@
"clean": "backstage-cli clean"
},
"dependencies": {
- "@backstage/backend-common": "^0.9.8",
- "@backstage/config-loader": "^0.7.1",
+ "@backstage/backend-common": "^0.9.10",
+ "@backstage/config-loader": "^0.8.0",
"@backstage/config": "^0.1.11",
"@backstage/types": "^0.1.1",
"@types/express": "^4.17.6",
@@ -42,7 +42,7 @@
"yn": "^4.0.0"
},
"devDependencies": {
- "@backstage/cli": "^0.8.1",
+ "@backstage/cli": "^0.9.0",
"@backstage/types": "^0.1.1",
"@types/supertest": "^2.0.8",
"msw": "^0.35.0",
diff --git a/plugins/auth-backend/CHANGELOG.md b/plugins/auth-backend/CHANGELOG.md
index a11f561986..5dc0cdb861 100644
--- a/plugins/auth-backend/CHANGELOG.md
+++ b/plugins/auth-backend/CHANGELOG.md
@@ -1,5 +1,16 @@
# @backstage/plugin-auth-backend
+## 0.4.8
+
+### Patch Changes
+
+- 892c1d9202: Update OAuthAdapter to create identity.token from identity.idToken if it does not exist, and prevent overwrites to identity.toke. Update login page commonProvider to prefer .token over .idToken
+- Updated dependencies
+ - @backstage/catalog-client@0.5.2
+ - @backstage/catalog-model@0.9.7
+ - @backstage/backend-common@0.9.10
+ - @backstage/test-utils@0.1.22
+
## 0.4.7
### Patch Changes
diff --git a/plugins/auth-backend/README.md b/plugins/auth-backend/README.md
index 1fea3346a7..fe2cd05c22 100644
--- a/plugins/auth-backend/README.md
+++ b/plugins/auth-backend/README.md
@@ -34,7 +34,7 @@ Follow this link, [Create new OAuth App](https://github.com/settings/application
1. Set Application Name to `backstage-dev` or something along those lines.
1. You can set the Homepage URL to whatever you want to.
1. The Authorization Callback URL should match the redirect URI set in Backstage.
- 1. Set this to `http://localhost:7000/api/auth/github` for local development.
+ 1. Set this to `http://localhost:7007/api/auth/github` for local development.
1. Set this to `http://{APP_FQDN}:{APP_BACKEND_PORT}/api/auth/github` for non-local deployments.
```bash
@@ -58,7 +58,7 @@ Follow this link, [Add new application](https://gitlab.com/-/profile/application
1. Set Application Name to `backstage-dev` or something along those lines.
1. The Authorization Callback URL should match the redirect URI set in Backstage.
- 1. Set this to `http://localhost:7000/api/auth/gitlab/handler/frame` for local development.
+ 1. Set this to `http://localhost:7007/api/auth/gitlab/handler/frame` for local development.
1. Set this to `http://{APP_FQDN}:{APP_BACKEND_PORT}/api/auth/gitlab/handler/frame` for non-local deployments.
1. Select the following scopes from the list:
- [x] `read_user` Grants read-only access to the authenticated user's profile through the /user API endpoint, which includes username, public email, and full name. Also grants access to read-only API endpoints under /users.
@@ -91,9 +91,9 @@ export AUTH_GITLAB_CLIENT_SECRET=x
Add a new Okta application using the following URI conventions:
-Login redirect URI's: `http://localhost:7000/api/auth/okta/handler/frame`
-Logout redirect URI's: `http://localhost:7000/api/auth/okta/logout`
-Initiate login URI's: `http://localhost:7000/api/auth/okta/start`
+Login redirect URI's: `http://localhost:7007/api/auth/okta/handler/frame`
+Logout redirect URI's: `http://localhost:7007/api/auth/okta/logout`
+Initiate login URI's: `http://localhost:7007/api/auth/okta/start`
Then configure the following environment variables to be used in the `app-config.yaml` file:
@@ -122,7 +122,7 @@ Click [here](https://portal.azure.com/#blade/Microsoft_AAD_IAM/ActiveDirectoryMe
- Give the app a name. e.g. `backstage-dev`
- Select `Accounts in this organizational directory only` under supported account types.
- Enter the callback URL for your backstage backend instance:
- - For local development, this is likely `http://localhost:7000/api/auth/microsoft/handler/frame`
+ - For local development, this is likely `http://localhost:7007/api/auth/microsoft/handler/frame`
- For non-local deployments, this will be `https://{APP_FQDN}:{APP_BACKEND_PORT}/auth/microsoft/handler/frame`
- Click `Register`.
diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json
index e9ec29970b..3a9e8102eb 100644
--- a/plugins/auth-backend/package.json
+++ b/plugins/auth-backend/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-auth-backend",
"description": "A Backstage backend plugin that handles authentication",
- "version": "0.4.7",
+ "version": "0.4.8",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -30,12 +30,12 @@
"clean": "backstage-cli clean"
},
"dependencies": {
- "@backstage/backend-common": "^0.9.9",
- "@backstage/catalog-client": "^0.5.1",
- "@backstage/catalog-model": "^0.9.6",
+ "@backstage/backend-common": "^0.9.10",
+ "@backstage/catalog-client": "^0.5.2",
+ "@backstage/catalog-model": "^0.9.7",
"@backstage/config": "^0.1.11",
"@backstage/errors": "^0.1.4",
- "@backstage/test-utils": "^0.1.21",
+ "@backstage/test-utils": "^0.1.22",
"@google-cloud/firestore": "^4.15.1",
"@types/express": "^4.17.6",
"@types/passport": "^1.0.3",
@@ -73,7 +73,7 @@
"yn": "^4.0.0"
},
"devDependencies": {
- "@backstage/cli": "^0.8.2",
+ "@backstage/cli": "^0.9.0",
"@types/body-parser": "^1.19.0",
"@types/cookie-parser": "^1.4.2",
"@types/express-session": "^1.17.2",
diff --git a/plugins/auth-backend/scripts/start-saml-idp.sh b/plugins/auth-backend/scripts/start-saml-idp.sh
index b312a4b85e..e12b725191 100755
--- a/plugins/auth-backend/scripts/start-saml-idp.sh
+++ b/plugins/auth-backend/scripts/start-saml-idp.sh
@@ -18,4 +18,4 @@ fi
echo "Downloading and starting SAML-IdP"
export NPM_CONFIG_REGISTRY=https://registry.npmjs.org
-exec npx saml-idp --acsUrl "http://localhost:7000/api/auth/saml/handler/frame" --audience "http://localhost:7000" --port 7001
+exec npx saml-idp --acsUrl "http://localhost:7007/api/auth/saml/handler/frame" --audience "http://localhost:7007" --port 7001
diff --git a/plugins/auth-backend/src/lib/oauth/OAuthAdapter.test.ts b/plugins/auth-backend/src/lib/oauth/OAuthAdapter.test.ts
index dfb3a0a79a..fa61d69d80 100644
--- a/plugins/auth-backend/src/lib/oauth/OAuthAdapter.test.ts
+++ b/plugins/auth-backend/src/lib/oauth/OAuthAdapter.test.ts
@@ -176,7 +176,7 @@ describe('OAuthAdapter', () => {
const mockResponse = {
cookie: jest.fn().mockReturnThis(),
- send: jest.fn().mockReturnThis(),
+ end: jest.fn().mockReturnThis(),
status: jest.fn().mockReturnThis(),
} as unknown as express.Response;
@@ -187,6 +187,7 @@ describe('OAuthAdapter', () => {
'',
expect.objectContaining({ path: '/auth/test-provider' }),
);
+ expect(mockResponse.end).toHaveBeenCalledTimes(1);
});
it('gets new access-token when refreshing', async () => {
@@ -230,21 +231,14 @@ describe('OAuthAdapter', () => {
const mockRequest = {
header: () => 'XMLHttpRequest',
- cookies: {
- 'test-provider-refresh-token': 'token',
- },
- query: {},
} as unknown as express.Request;
- const mockResponse = {
- send: jest.fn().mockReturnThis(),
- status: jest.fn().mockReturnThis(),
- } as unknown as express.Response;
+ const mockResponse = {} as unknown as express.Response;
- await oauthProvider.refresh(mockRequest, mockResponse);
- expect(mockResponse.send).toHaveBeenCalledTimes(1);
- expect(mockResponse.send).toHaveBeenCalledWith(
- 'Refresh token not supported for provider: test-provider',
+ await expect(
+ oauthProvider.refresh(mockRequest, mockResponse),
+ ).rejects.toThrow(
+ 'Refresh token is not supported for provider test-provider',
);
});
});
diff --git a/plugins/auth-backend/src/lib/oauth/OAuthAdapter.ts b/plugins/auth-backend/src/lib/oauth/OAuthAdapter.ts
index 24b9a0f210..e1a6fdf2f9 100644
--- a/plugins/auth-backend/src/lib/oauth/OAuthAdapter.ts
+++ b/plugins/auth-backend/src/lib/oauth/OAuthAdapter.ts
@@ -22,7 +22,12 @@ import {
BackstageIdentity,
AuthProviderConfig,
} from '../../providers/types';
-import { InputError, isError, NotAllowedError } from '@backstage/errors';
+import {
+ AuthenticationError,
+ InputError,
+ isError,
+ NotAllowedError,
+} from '@backstage/errors';
import { TokenIssuer } from '../../identity/types';
import { readState, verifyNonce } from './helpers';
import { postMessageResponse, ensuresXRequestedWith } from '../flow';
@@ -166,29 +171,24 @@ export class OAuthAdapter implements AuthProviderRouteHandlers {
async logout(req: express.Request, res: express.Response): Promise {
if (!ensuresXRequestedWith(req)) {
- res.status(401).send('Invalid X-Requested-With header');
- return;
+ throw new AuthenticationError('Invalid X-Requested-With header');
}
// remove refresh token cookie if it is set
this.removeRefreshTokenCookie(res);
- res.status(200).send('logout!');
+ res.status(200).end();
}
async refresh(req: express.Request, res: express.Response): Promise {
if (!ensuresXRequestedWith(req)) {
- res.status(401).send('Invalid X-Requested-With header');
- return;
+ throw new AuthenticationError('Invalid X-Requested-With header');
}
if (!this.handlers.refresh || this.options.disableRefresh) {
- res
- .status(400)
- .send(
- `Refresh token not supported for provider: ${this.options.providerId}`,
- );
- return;
+ throw new InputError(
+ `Refresh token is not supported for provider ${this.options.providerId}`,
+ );
}
try {
@@ -197,7 +197,7 @@ export class OAuthAdapter implements AuthProviderRouteHandlers {
// throw error if refresh token is missing in the request
if (!refreshToken) {
- throw new Error('Missing session cookie');
+ throw new InputError('Missing session cookie');
}
const scope = req.query.scope?.toString() ?? '';
@@ -220,7 +220,7 @@ export class OAuthAdapter implements AuthProviderRouteHandlers {
res.status(200).json(response);
} catch (error) {
- res.status(401).send(String(error));
+ throw new AuthenticationError('Refresh failed', error);
}
}
diff --git a/plugins/auth-backend/src/lib/oauth/OAuthEnvironmentHandler.ts b/plugins/auth-backend/src/lib/oauth/OAuthEnvironmentHandler.ts
index a2f427e741..95a0547a5e 100644
--- a/plugins/auth-backend/src/lib/oauth/OAuthEnvironmentHandler.ts
+++ b/plugins/auth-backend/src/lib/oauth/OAuthEnvironmentHandler.ts
@@ -16,7 +16,7 @@
import express from 'express';
import { Config } from '@backstage/config';
-import { InputError } from '@backstage/errors';
+import { InputError, NotFoundError } from '@backstage/errors';
import { readState } from './helpers';
import { AuthProviderRouteHandlers } from '../../providers/types';
@@ -42,26 +42,26 @@ export class OAuthEnvironmentHandler implements AuthProviderRouteHandlers {
) {}
async start(req: express.Request, res: express.Response): Promise {
- const provider = this.getProviderForEnv(req, res);
- await provider?.start(req, res);
+ const provider = this.getProviderForEnv(req);
+ await provider.start(req, res);
}
async frameHandler(
req: express.Request,
res: express.Response,
): Promise {
- const provider = this.getProviderForEnv(req, res);
- await provider?.frameHandler(req, res);
+ const provider = this.getProviderForEnv(req);
+ await provider.frameHandler(req, res);
}
async refresh(req: express.Request, res: express.Response): Promise {
- const provider = this.getProviderForEnv(req, res);
- await provider?.refresh?.(req, res);
+ const provider = this.getProviderForEnv(req);
+ await provider.refresh?.(req, res);
}
async logout(req: express.Request, res: express.Response): Promise {
- const provider = this.getProviderForEnv(req, res);
- await provider?.logout?.(req, res);
+ const provider = this.getProviderForEnv(req);
+ await provider.logout?.(req, res);
}
private getRequestFromEnv(req: express.Request): string | undefined {
@@ -77,26 +77,20 @@ export class OAuthEnvironmentHandler implements AuthProviderRouteHandlers {
return env;
}
- private getProviderForEnv(
- req: express.Request,
- res: express.Response,
- ): AuthProviderRouteHandlers | undefined {
+ private getProviderForEnv(req: express.Request): AuthProviderRouteHandlers {
const env: string | undefined = this.getRequestFromEnv(req);
if (!env) {
throw new InputError(`Must specify 'env' query to select environment`);
}
- if (!this.handlers.has(env)) {
- res.status(404).send(
- `Missing configuration.
-
-
- For this flow to work you need to supply a valid configuration for the "${env}" environment of provider.`,
+ const handler = this.handlers.get(env);
+ if (!handler) {
+ throw new NotFoundError(
+ `No configuration available for the '${env}' environment of this provider.`,
);
- return undefined;
}
- return this.handlers.get(env);
+ return handler;
}
}
diff --git a/plugins/azure-devops-backend/CHANGELOG.md b/plugins/azure-devops-backend/CHANGELOG.md
index d2fe981eb0..06fc4833c4 100644
--- a/plugins/azure-devops-backend/CHANGELOG.md
+++ b/plugins/azure-devops-backend/CHANGELOG.md
@@ -1,5 +1,14 @@
# @backstage/plugin-azure-devops-backend
+## 0.2.1
+
+### Patch Changes
+
+- 2b5ccd2964: Improved Date handling for the Azure DevOps set of plugins by using strings and letting the frontend handle the conversion to DateTime
+- Updated dependencies
+ - @backstage/backend-common@0.9.10
+ - @backstage/plugin-azure-devops-common@0.1.0
+
## 0.2.0
### Minor Changes
diff --git a/plugins/azure-devops-backend/README.md b/plugins/azure-devops-backend/README.md
index 6101794cd1..79db9f5ff1 100644
--- a/plugins/azure-devops-backend/README.md
+++ b/plugins/azure-devops-backend/README.md
@@ -63,7 +63,7 @@ Here's how to get the backend up and running:
```
4. Now run `yarn start-backend` from the repo root
-5. Finally open `http://localhost:7000/api/azure-devops/health` in a browser and it should return `{"status":"ok"}`
+5. Finally open `http://localhost:7007/api/azure-devops/health` in a browser and it should return `{"status":"ok"}`
## Links
diff --git a/plugins/azure-devops-backend/package.json b/plugins/azure-devops-backend/package.json
index 901ad0b960..6d213cbce6 100644
--- a/plugins/azure-devops-backend/package.json
+++ b/plugins/azure-devops-backend/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-azure-devops-backend",
- "version": "0.2.0",
+ "version": "0.2.1",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -20,9 +20,9 @@
"clean": "backstage-cli clean"
},
"dependencies": {
- "@backstage/backend-common": "^0.9.9",
+ "@backstage/backend-common": "^0.9.10",
"@backstage/config": "^0.1.11",
- "@backstage/plugin-azure-devops-common": "^0.0.2",
+ "@backstage/plugin-azure-devops-common": "^0.1.0",
"@types/express": "^4.17.6",
"azure-devops-node-api": "^11.0.1",
"express": "^4.17.1",
@@ -31,7 +31,7 @@
"yn": "^4.0.0"
},
"devDependencies": {
- "@backstage/cli": "^0.8.2",
+ "@backstage/cli": "^0.9.0",
"@types/supertest": "^2.0.8",
"supertest": "^4.0.2",
"msw": "^0.35.0"
diff --git a/plugins/azure-devops-backend/src/run.ts b/plugins/azure-devops-backend/src/run.ts
index addfdfd6d7..53d4e4334a 100644
--- a/plugins/azure-devops-backend/src/run.ts
+++ b/plugins/azure-devops-backend/src/run.ts
@@ -18,7 +18,7 @@ import { getRootLogger } from '@backstage/backend-common';
import yn from 'yn';
import { startStandaloneServer } from './service/standaloneServer';
-const port = process.env.PLUGIN_PORT ? Number(process.env.PLUGIN_PORT) : 7000;
+const port = process.env.PLUGIN_PORT ? Number(process.env.PLUGIN_PORT) : 7007;
const enableCors = yn(process.env.PLUGIN_CORS, { default: false });
const logger = getRootLogger();
diff --git a/plugins/azure-devops-common/CHANGELOG.md b/plugins/azure-devops-common/CHANGELOG.md
index b414cd3ce6..4a834fb068 100644
--- a/plugins/azure-devops-common/CHANGELOG.md
+++ b/plugins/azure-devops-common/CHANGELOG.md
@@ -1,5 +1,11 @@
# @backstage/plugin-azure-devops-common
+## 0.1.0
+
+### Minor Changes
+
+- 2b5ccd2964: Improved Date handling for the Azure DevOps set of plugins by using strings and letting the frontend handle the conversion to DateTime
+
## 0.0.2
### Patch Changes
diff --git a/plugins/azure-devops-common/package.json b/plugins/azure-devops-common/package.json
index a1ddddcbe0..fe9df644b1 100644
--- a/plugins/azure-devops-common/package.json
+++ b/plugins/azure-devops-common/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-azure-devops-common",
- "version": "0.0.2",
+ "version": "0.1.0",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -29,7 +29,7 @@
"clean": "backstage-cli clean"
},
"devDependencies": {
- "@backstage/cli": "^0.8.2"
+ "@backstage/cli": "^0.9.0"
},
"files": [
"dist"
diff --git a/plugins/azure-devops/CHANGELOG.md b/plugins/azure-devops/CHANGELOG.md
index 2f75f64236..0bcd9bc686 100644
--- a/plugins/azure-devops/CHANGELOG.md
+++ b/plugins/azure-devops/CHANGELOG.md
@@ -1,5 +1,19 @@
# @backstage/plugin-azure-devops
+## 0.1.4
+
+### Patch Changes
+
+- a125278b81: Refactor out the deprecated path and icon from RouteRefs
+- b5eac957f2: Added entity view for Azure Repo Pull Requests
+- 2b5ccd2964: Improved Date handling for the Azure DevOps set of plugins by using strings and letting the frontend handle the conversion to DateTime
+- Updated dependencies
+ - @backstage/catalog-model@0.9.7
+ - @backstage/plugin-catalog-react@0.6.4
+ - @backstage/core-components@0.7.4
+ - @backstage/core-plugin-api@0.2.0
+ - @backstage/plugin-azure-devops-common@0.1.0
+
## 0.1.3
### Patch Changes
diff --git a/plugins/azure-devops/README.md b/plugins/azure-devops/README.md
index 47a0c4debd..7b39dae443 100644
--- a/plugins/azure-devops/README.md
+++ b/plugins/azure-devops/README.md
@@ -6,10 +6,16 @@ Website: [https://dev.azure.com/](https://dev.azure.com/)
### Azure Pipelines
-Lists the top _n_ builds for a given repository where _n_ is a configurable value
+Lists the top _n_ builds for a given Azure Repo where _n_ is a configurable value

+### Azure Repos
+
+Lists the top _n_ Active, Completed, or Abandoned Pull Requests for a given repository where _n_ is a configurable value
+
+
+
## Setup
The following sections will help you get the Azure DevOps plugin setup and running
@@ -69,14 +75,55 @@ To get the Azure Pipelines component working you'll need to do the following two
// ...
- // Set defaultLimit to the max number of builds you would like to be able to see
- // the default if not set is 10
// ...
```
+**Notes:**
+
+- The `if` prop is optional on the `EntitySwitch.Case`, you can remove it if you always want to see the tab even if the entity being viewed does not have the needed annotation
+- The `defaultLimit` property on the `EntityAzurePipelinesContent` will set the max number of Builds you would like to see, if not set this will default to 10
+
+### Azure Repos Component
+
+To get the Azure Repos component working you'll need to do the following two steps:
+
+1. First we need to add the @backstage/plugin-azure-devops package to your frontend app:
+
+ ```bash
+ # From your Backstage root directory
+ cd packages/app
+ yarn add @backstage/plugin-azure-devops
+ ```
+
+2. Second we need to add the `EntityAzurePullRequestsContent` extension to the entity page in your app:
+
+ ```tsx
+ // In packages/app/src/components/catalog/EntityPage.tsx
+ import {
+ EntityAzurePullRequestsContent,
+ isAzureDevOpsAvailable,
+ } from '@backstage/plugin-azure-devops';
+
+ // For example in the Service section
+ const serviceEntityPage = (
+
+ // ...
+
+
+
+ // ...
+
+ ```
+
+**Notes:**
+
+- You'll need to add the `EntityLayout.Route` above from step 2 to all the entity sections you want to see Pull Requests in. For example if you wanted to see Pull Requests when looking at Website entities then you would need to add this to the `websiteEntityPage` section.
+- The `if` prop is optional on the `EntityLayout.Route`, you can remove it if you always want to see the tab even if the entity being viewed does not have the needed annotation
+- The `defaultLimit` property on the `EntityAzurePullRequestsContent` will set the max number of Pull Requests you would like to see, if not set this will default to 10
+
## Limitations
- Currently multiple organizations are not supported
diff --git a/plugins/azure-devops/api-report.md b/plugins/azure-devops/api-report.md
index fbe28c179c..ab659534c1 100644
--- a/plugins/azure-devops/api-report.md
+++ b/plugins/azure-devops/api-report.md
@@ -7,17 +7,11 @@
import { BackstagePlugin } from '@backstage/core-plugin-api';
import { Entity } from '@backstage/catalog-model';
-import { RouteRef } from '@backstage/core-plugin-api';
// Warning: (ae-missing-release-tag) "azureDevOpsPlugin" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
-export const azureDevOpsPlugin: BackstagePlugin<
- {
- entityContent: RouteRef;
- },
- {}
->;
+export const azureDevOpsPlugin: BackstagePlugin<{}, {}>;
// Warning: (ae-missing-release-tag) "EntityAzurePipelinesContent" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
@@ -28,6 +22,15 @@ export const EntityAzurePipelinesContent: ({
defaultLimit?: number | undefined;
}) => JSX.Element;
+// Warning: (ae-missing-release-tag) "EntityAzurePullRequestsContent" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
+//
+// @public (undocumented)
+export const EntityAzurePullRequestsContent: ({
+ defaultLimit,
+}: {
+ defaultLimit?: number | undefined;
+}) => JSX.Element;
+
// Warning: (ae-missing-release-tag) "isAzureDevOpsAvailable" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
diff --git a/plugins/azure-devops/docs/azure-devops-builds.png b/plugins/azure-devops/docs/azure-devops-builds.png
index 4788a185ed..6ff93dc7e4 100644
Binary files a/plugins/azure-devops/docs/azure-devops-builds.png and b/plugins/azure-devops/docs/azure-devops-builds.png differ
diff --git a/plugins/azure-devops/docs/azure-devops-pull-requests.png b/plugins/azure-devops/docs/azure-devops-pull-requests.png
new file mode 100644
index 0000000000..ce1b2a4267
Binary files /dev/null and b/plugins/azure-devops/docs/azure-devops-pull-requests.png differ
diff --git a/plugins/azure-devops/package.json b/plugins/azure-devops/package.json
index 235304c9fa..9d235b0641 100644
--- a/plugins/azure-devops/package.json
+++ b/plugins/azure-devops/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-azure-devops",
- "version": "0.1.3",
+ "version": "0.1.4",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -27,16 +27,17 @@
"clean": "backstage-cli clean"
},
"dependencies": {
- "@backstage/catalog-model": "^0.9.6",
- "@backstage/core-components": "^0.7.3",
- "@backstage/core-plugin-api": "^0.1.13",
+ "@backstage/catalog-model": "^0.9.7",
+ "@backstage/core-components": "^0.7.4",
+ "@backstage/core-plugin-api": "^0.2.0",
"@backstage/errors": "^0.1.4",
- "@backstage/plugin-azure-devops-common": "^0.0.2",
- "@backstage/plugin-catalog-react": "^0.6.3",
+ "@backstage/plugin-azure-devops-common": "^0.1.0",
+ "@backstage/plugin-catalog-react": "^0.6.4",
"@backstage/theme": "^0.2.13",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.57",
+ "humanize-duration": "^3.27.0",
"luxon": "^2.0.2",
"react": "^16.13.1",
"react-dom": "^16.13.1",
@@ -44,10 +45,10 @@
"react-use": "^17.2.4"
},
"devDependencies": {
- "@backstage/cli": "^0.8.2",
- "@backstage/core-app-api": "^0.1.20",
- "@backstage/dev-utils": "^0.2.12",
- "@backstage/test-utils": "^0.1.21",
+ "@backstage/cli": "^0.9.0",
+ "@backstage/core-app-api": "^0.1.22",
+ "@backstage/dev-utils": "^0.2.13",
+ "@backstage/test-utils": "^0.1.22",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
diff --git a/plugins/azure-devops/src/api/AzureDevOpsApi.ts b/plugins/azure-devops/src/api/AzureDevOpsApi.ts
index 05377b0b7b..90d291b25d 100644
--- a/plugins/azure-devops/src/api/AzureDevOpsApi.ts
+++ b/plugins/azure-devops/src/api/AzureDevOpsApi.ts
@@ -15,6 +15,8 @@
*/
import {
+ PullRequest,
+ PullRequestOptions,
RepoBuild,
RepoBuildOptions,
} from '@backstage/plugin-azure-devops-common';
@@ -33,4 +35,10 @@ export interface AzureDevOpsApi {
repoName: string,
options?: RepoBuildOptions,
): Promise<{ items: RepoBuild[] }>;
+
+ getPullRequests(
+ projectName: string,
+ repoName: string,
+ options?: PullRequestOptions,
+ ): Promise<{ items: PullRequest[] }>;
}
diff --git a/plugins/azure-devops/src/api/AzureDevOpsClient.ts b/plugins/azure-devops/src/api/AzureDevOpsClient.ts
index 4fd4e781c4..cbcc496725 100644
--- a/plugins/azure-devops/src/api/AzureDevOpsClient.ts
+++ b/plugins/azure-devops/src/api/AzureDevOpsClient.ts
@@ -16,6 +16,8 @@
import { DiscoveryApi, IdentityApi } from '@backstage/core-plugin-api';
import {
+ PullRequest,
+ PullRequestOptions,
RepoBuild,
RepoBuildOptions,
} from '@backstage/plugin-azure-devops-common';
@@ -35,14 +37,40 @@ export class AzureDevOpsClient implements AzureDevOpsApi {
this.identityApi = options.identityApi;
}
- async getRepoBuilds(
+ public async getRepoBuilds(
projectName: string,
repoName: string,
options?: RepoBuildOptions,
): Promise<{ items: RepoBuild[] }> {
- const items = await this.get(
- `repo-builds/${projectName}/${repoName}?top=${options?.top}`,
- );
+ const queryString = new URLSearchParams();
+ if (options?.top) {
+ queryString.append('top', options.top.toString());
+ }
+ const urlSegment = `repo-builds/${encodeURIComponent(
+ projectName,
+ )}/${encodeURIComponent(repoName)}?${queryString}`;
+
+ const items = await this.get(urlSegment);
+ return { items };
+ }
+
+ public async getPullRequests(
+ projectName: string,
+ repoName: string,
+ options?: PullRequestOptions,
+ ): Promise<{ items: PullRequest[] }> {
+ const queryString = new URLSearchParams();
+ if (options?.top) {
+ queryString.append('top', options.top.toString());
+ }
+ if (options?.status) {
+ queryString.append('status', options.status.toString());
+ }
+ const urlSegment = `pull-requests/${encodeURIComponent(
+ projectName,
+ )}/${encodeURIComponent(repoName)}?${queryString}`;
+
+ const items = await this.get(urlSegment);
return { items };
}
diff --git a/plugins/azure-devops/src/components/AzurePipelinesIcon/AzurePipelinesIcon.tsx b/plugins/azure-devops/src/components/AzurePipelinesIcon/AzurePipelinesIcon.tsx
new file mode 100644
index 0000000000..bd19331f6e
--- /dev/null
+++ b/plugins/azure-devops/src/components/AzurePipelinesIcon/AzurePipelinesIcon.tsx
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2021 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { SvgIcon, SvgIconProps } from '@material-ui/core';
+
+import React from 'react';
+
+export const AzurePipelinesIcon = (props: SvgIconProps) => (
+
+
+
+
+);
diff --git a/plugins/azure-devops/src/components/AzurePipelinesIcon/index.ts b/plugins/azure-devops/src/components/AzurePipelinesIcon/index.ts
new file mode 100644
index 0000000000..4eb161f7bc
--- /dev/null
+++ b/plugins/azure-devops/src/components/AzurePipelinesIcon/index.ts
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2021 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export { AzurePipelinesIcon } from './AzurePipelinesIcon';
diff --git a/plugins/azure-devops/src/components/AzurePullRequestsIcon/AzurePullRequestsIcon.tsx b/plugins/azure-devops/src/components/AzurePullRequestsIcon/AzurePullRequestsIcon.tsx
new file mode 100644
index 0000000000..bb4cb8c081
--- /dev/null
+++ b/plugins/azure-devops/src/components/AzurePullRequestsIcon/AzurePullRequestsIcon.tsx
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2021 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { SvgIcon, SvgIconProps } from '@material-ui/core';
+
+import React from 'react';
+
+export const AzurePullRequestsIcon = (props: SvgIconProps) => (
+
+
+
+
+
+
+
+);
diff --git a/plugins/azure-devops/src/components/AzurePullRequestsIcon/index.ts b/plugins/azure-devops/src/components/AzurePullRequestsIcon/index.ts
new file mode 100644
index 0000000000..1f49119b4b
--- /dev/null
+++ b/plugins/azure-devops/src/components/AzurePullRequestsIcon/index.ts
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2021 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export { AzurePullRequestsIcon } from './AzurePullRequestsIcon';
diff --git a/plugins/azure-devops/src/components/BuildTable/BuildTable.tsx b/plugins/azure-devops/src/components/BuildTable/BuildTable.tsx
index c16c836393..463bf15d78 100644
--- a/plugins/azure-devops/src/components/BuildTable/BuildTable.tsx
+++ b/plugins/azure-devops/src/components/BuildTable/BuildTable.tsx
@@ -33,8 +33,10 @@ import {
TableColumn,
} from '@backstage/core-components';
+import { AzurePipelinesIcon } from '../AzurePipelinesIcon';
import { DateTime } from 'luxon';
import React from 'react';
+import { getDurationFromDates } from '../../utils/getDurationFromDates';
export const getBuildResultComponent = (result: number | undefined) => {
switch (result) {
@@ -144,6 +146,18 @@ const columns: TableColumn[] = [
),
},
+ {
+ title: 'Duration',
+ field: 'queueTime',
+ width: 'auto',
+ render: (row: Partial) => (
+
+
+ {getDurationFromDates(row.startTime, row.finishTime)}
+
+
+ ),
+ },
{
title: 'Age',
field: 'queueTime',
@@ -181,7 +195,13 @@ export const BuildTable = ({ items, loading, error }: BuildTableProps) => {
pageSize: 5,
showEmptyDataSourceMessage: !loading,
}}
- title={`Builds (${items ? items.length : 0})`}
+ title={
+
+
+
+ Azure Pipelines - Builds ({items ? items.length : 0})
+
+ }
data={items ?? []}
/>
);
diff --git a/plugins/azure-devops/src/components/EntityPageAzurePullRequests/EntityPageAzurePullRequests.tsx b/plugins/azure-devops/src/components/EntityPageAzurePullRequests/EntityPageAzurePullRequests.tsx
new file mode 100644
index 0000000000..3d1a3ef77a
--- /dev/null
+++ b/plugins/azure-devops/src/components/EntityPageAzurePullRequests/EntityPageAzurePullRequests.tsx
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2021 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { PullRequestTable } from '../PullRequestTable/PullRequestTable';
+import React from 'react';
+
+export const EntityPageAzurePullRequests = ({
+ defaultLimit,
+}: {
+ defaultLimit?: number;
+}) => {
+ return ;
+};
diff --git a/plugins/azure-devops/src/components/EntityPageAzurePullRequests/index.ts b/plugins/azure-devops/src/components/EntityPageAzurePullRequests/index.ts
new file mode 100644
index 0000000000..2ec6c5bf04
--- /dev/null
+++ b/plugins/azure-devops/src/components/EntityPageAzurePullRequests/index.ts
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2021 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export { EntityPageAzurePullRequests } from './EntityPageAzurePullRequests';
diff --git a/plugins/azure-devops/src/components/PullRequestStatusButtonGroup/PullRequestStatusButtonGroup.tsx b/plugins/azure-devops/src/components/PullRequestStatusButtonGroup/PullRequestStatusButtonGroup.tsx
new file mode 100644
index 0000000000..f516bfb0e5
--- /dev/null
+++ b/plugins/azure-devops/src/components/PullRequestStatusButtonGroup/PullRequestStatusButtonGroup.tsx
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2021 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Button, ButtonGroup } from '@material-ui/core';
+
+import { PullRequestStatus } from '@backstage/plugin-azure-devops-common';
+import React from 'react';
+
+export const PullRequestStatusButtonGroup = ({
+ status,
+ setStatus,
+}: {
+ status: PullRequestStatus;
+ setStatus: (pullRequestStatus: PullRequestStatus) => void;
+}) => {
+ return (
+
+
+
+
+
+ );
+};
diff --git a/plugins/azure-devops/src/components/PullRequestStatusButtonGroup/index.ts b/plugins/azure-devops/src/components/PullRequestStatusButtonGroup/index.ts
new file mode 100644
index 0000000000..10a667803a
--- /dev/null
+++ b/plugins/azure-devops/src/components/PullRequestStatusButtonGroup/index.ts
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2021 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export { PullRequestStatusButtonGroup } from './PullRequestStatusButtonGroup';
diff --git a/plugins/azure-devops/src/components/PullRequestTable/PullRequestTable.tsx b/plugins/azure-devops/src/components/PullRequestTable/PullRequestTable.tsx
new file mode 100644
index 0000000000..4365b796bc
--- /dev/null
+++ b/plugins/azure-devops/src/components/PullRequestTable/PullRequestTable.tsx
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2021 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Box, Chip } from '@material-ui/core';
+import {
+ Link,
+ ResponseErrorPanel,
+ Table,
+ TableColumn,
+} from '@backstage/core-components';
+import {
+ PullRequest,
+ PullRequestStatus,
+} from '@backstage/plugin-azure-devops-common';
+import React, { useState } from 'react';
+
+import { AzurePullRequestsIcon } from '../AzurePullRequestsIcon';
+import { DateTime } from 'luxon';
+import { PullRequestStatusButtonGroup } from '../PullRequestStatusButtonGroup';
+import { useEntity } from '@backstage/plugin-catalog-react';
+import { usePullRequests } from '../../hooks/usePullRequests';
+
+const columns: TableColumn[] = [
+ {
+ title: 'ID',
+ field: 'pullRequestId',
+ highlight: false,
+ width: 'auto',
+ },
+ {
+ title: 'Title',
+ field: 'title',
+ width: 'auto',
+ render: (row: Partial) => (
+
+ {row.title}
+ {row.isDraft && (
+
+
+
+ )}
+
+ ),
+ },
+ {
+ title: 'Source',
+ field: 'sourceRefName',
+ width: 'auto',
+ },
+ {
+ title: 'Target',
+ field: 'targetRefName',
+ width: 'auto',
+ },
+ {
+ title: 'Created By',
+ field: 'createdBy',
+ width: 'auto',
+ },
+ {
+ title: 'Created',
+ field: 'creationDate',
+ width: 'auto',
+ render: (row: Partial) =>
+ (row.creationDate
+ ? DateTime.fromISO(row.creationDate)
+ : DateTime.now()
+ ).toRelative(),
+ },
+];
+
+type PullRequestTableProps = {
+ defaultLimit?: number;
+};
+
+export const PullRequestTable = ({ defaultLimit }: PullRequestTableProps) => {
+ const [pullRequestStatusState, setPullRequestStatusState] =
+ useState(PullRequestStatus.Active);
+ const { entity } = useEntity();
+
+ const { items, loading, error } = usePullRequests(
+ entity,
+ defaultLimit,
+ pullRequestStatusState,
+ );
+
+ if (error) {
+ return (
+
+
+
+ );
+ }
+
+ return (
+
+
+
+ Azure Repos - Pull Requests ({items ? items.length : 0})
+
+
+
+
+ }
+ data={items ?? []}
+ />
+ );
+};
diff --git a/plugins/azure-devops/src/components/PullRequestTable/index.ts b/plugins/azure-devops/src/components/PullRequestTable/index.ts
new file mode 100644
index 0000000000..956e8afa2d
--- /dev/null
+++ b/plugins/azure-devops/src/components/PullRequestTable/index.ts
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2021 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export { PullRequestTable } from './PullRequestTable';
diff --git a/plugins/azure-devops/src/hooks/usePullRequests.ts b/plugins/azure-devops/src/hooks/usePullRequests.ts
new file mode 100644
index 0000000000..178bf0495f
--- /dev/null
+++ b/plugins/azure-devops/src/hooks/usePullRequests.ts
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2021 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {
+ PullRequest,
+ PullRequestOptions,
+ PullRequestStatus,
+} from '@backstage/plugin-azure-devops-common';
+
+import { AZURE_DEVOPS_DEFAULT_TOP } from '../constants';
+import { Entity } from '@backstage/catalog-model';
+import { azureDevOpsApiRef } from '../api';
+import { useApi } from '@backstage/core-plugin-api';
+import { useAsync } from 'react-use';
+import { useProjectRepoFromEntity } from './useProjectRepoFromEntity';
+
+export function usePullRequests(
+ entity: Entity,
+ defaultLimit?: number,
+ requestedStatus?: PullRequestStatus,
+): {
+ items?: PullRequest[];
+ loading: boolean;
+ error?: Error;
+} {
+ const top = defaultLimit ?? AZURE_DEVOPS_DEFAULT_TOP;
+ const status = requestedStatus ?? PullRequestStatus.Active;
+ const options: PullRequestOptions = {
+ top,
+ status,
+ };
+
+ const api = useApi(azureDevOpsApiRef);
+ const { project, repo } = useProjectRepoFromEntity(entity);
+
+ const { value, loading, error } = useAsync(() => {
+ return api.getPullRequests(project, repo, options);
+ }, [api, project, repo, top, status]);
+
+ return {
+ items: value?.items,
+ loading,
+ error,
+ };
+}
diff --git a/plugins/azure-devops/src/index.ts b/plugins/azure-devops/src/index.ts
index 71d3b654fe..4da533b534 100644
--- a/plugins/azure-devops/src/index.ts
+++ b/plugins/azure-devops/src/index.ts
@@ -16,5 +16,6 @@
export {
azureDevOpsPlugin,
EntityAzurePipelinesContent,
+ EntityAzurePullRequestsContent,
isAzureDevOpsAvailable,
} from './plugin';
diff --git a/plugins/azure-devops/src/plugin.ts b/plugins/azure-devops/src/plugin.ts
index ab6d816460..7a5f80d0d3 100644
--- a/plugins/azure-devops/src/plugin.ts
+++ b/plugins/azure-devops/src/plugin.ts
@@ -14,6 +14,10 @@
* limitations under the License.
*/
+import {
+ azurePipelinesEntityContentRouteRef,
+ azurePullRequestsEntityContentRouteRef,
+} from './routes';
import {
createApiFactory,
createPlugin,
@@ -26,7 +30,6 @@ import { AZURE_DEVOPS_ANNOTATION } from './constants';
import { AzureDevOpsClient } from './api/AzureDevOpsClient';
import { Entity } from '@backstage/catalog-model';
import { azureDevOpsApiRef } from './api/AzureDevOpsApi';
-import { azureDevOpsRouteRef } from './routes';
export const isAzureDevOpsAvailable = (entity: Entity) =>
Boolean(entity.metadata.annotations?.[AZURE_DEVOPS_ANNOTATION]);
@@ -41,9 +44,6 @@ export const azureDevOpsPlugin = createPlugin({
new AzureDevOpsClient({ discoveryApi, identityApi }),
}),
],
- routes: {
- entityContent: azureDevOpsRouteRef,
- },
});
export const EntityAzurePipelinesContent = azureDevOpsPlugin.provide(
@@ -53,6 +53,17 @@ export const EntityAzurePipelinesContent = azureDevOpsPlugin.provide(
import('./components/EntityPageAzurePipelines').then(
m => m.EntityPageAzurePipelines,
),
- mountPoint: azureDevOpsRouteRef,
+ mountPoint: azurePipelinesEntityContentRouteRef,
+ }),
+);
+
+export const EntityAzurePullRequestsContent = azureDevOpsPlugin.provide(
+ createRoutableExtension({
+ name: 'EntityAzurePullRequestsContent',
+ component: () =>
+ import('./components/EntityPageAzurePullRequests').then(
+ m => m.EntityPageAzurePullRequests,
+ ),
+ mountPoint: azurePullRequestsEntityContentRouteRef,
}),
);
diff --git a/plugins/azure-devops/src/routes.ts b/plugins/azure-devops/src/routes.ts
index 81f4b48fda..7a4adea33e 100644
--- a/plugins/azure-devops/src/routes.ts
+++ b/plugins/azure-devops/src/routes.ts
@@ -13,8 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
import { createRouteRef } from '@backstage/core-plugin-api';
-export const azureDevOpsRouteRef = createRouteRef({
- title: 'azure-devops',
+export const azurePipelinesEntityContentRouteRef = createRouteRef({
+ id: 'azure-pipelines-entity-content',
+});
+
+export const azurePullRequestsEntityContentRouteRef = createRouteRef({
+ id: 'azure-pull-requests-entity-content',
});
diff --git a/plugins/azure-devops/src/utils/getDurationFromDates.test.ts b/plugins/azure-devops/src/utils/getDurationFromDates.test.ts
new file mode 100644
index 0000000000..fac0e917d0
--- /dev/null
+++ b/plugins/azure-devops/src/utils/getDurationFromDates.test.ts
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2021 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { getDurationFromDates } from './getDurationFromDates';
+
+describe('getDurationFromDates', () => {
+ beforeAll(() => {
+ jest.useFakeTimers('modern');
+ jest.setSystemTime(new Date('2021-10-15T09:45:25.0000000Z'));
+ });
+
+ afterAll(() => {
+ jest.useRealTimers();
+ });
+
+ describe('getDurationFromDates with valid startTime and valid finishTime', () => {
+ it('should return empty result', () => {
+ const startTime = '2021-10-15T09:30:00.0000000Z';
+ const finishTime = '2021-10-15T11:00:00.0000000Z';
+
+ const result = getDurationFromDates(startTime, finishTime);
+
+ expect(result).toEqual('1h, 30m');
+ });
+ });
+
+ describe('getDurationFromDates with valid startTime and undefined finishTime', () => {
+ it('should return empty result', () => {
+ const startTime = '2021-10-15T09:30:00.0000000Z';
+
+ const result = getDurationFromDates(startTime, undefined);
+
+ expect(result).toEqual('15m, 25s');
+ });
+ });
+
+ describe('getDurationFromDates with undefined startTime and valid finishTime', () => {
+ it('should return empty result', () => {
+ const finishTime = '2021-10-15T11:00:00.0000000Z';
+
+ const result = getDurationFromDates(undefined, finishTime);
+
+ expect(result).toEqual('');
+ });
+ });
+
+ describe('getDurationFromDates with undefined startTime and undefined finishTime', () => {
+ it('should return empty result', () => {
+ const result = getDurationFromDates(undefined, undefined);
+
+ expect(result).toEqual('');
+ });
+ });
+});
diff --git a/plugins/azure-devops/src/utils/getDurationFromDates.ts b/plugins/azure-devops/src/utils/getDurationFromDates.ts
new file mode 100644
index 0000000000..789849f975
--- /dev/null
+++ b/plugins/azure-devops/src/utils/getDurationFromDates.ts
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2021 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { DateTime, Interval } from 'luxon';
+import humanizeDuration from 'humanize-duration';
+
+export const getDurationFromDates = (
+ startTime?: string,
+ finishTime?: string,
+): string => {
+ if (!startTime || (!startTime && !finishTime)) {
+ return '';
+ }
+
+ const start = DateTime.fromISO(startTime);
+ const finish = finishTime ? DateTime.fromISO(finishTime) : DateTime.now();
+
+ const formatted = Interval.fromDateTimes(start, finish)
+ .toDuration()
+ .valueOf();
+
+ const shortEnglishHumanizer = humanizeDuration.humanizer({
+ language: 'shortEn',
+ languages: {
+ shortEn: {
+ y: () => 'y',
+ mo: () => 'mo',
+ w: () => 'w',
+ d: () => 'd',
+ h: () => 'h',
+ m: () => 'm',
+ s: () => 's',
+ ms: () => 'ms',
+ },
+ },
+ });
+
+ return shortEnglishHumanizer(formatted, {
+ largest: 2,
+ round: true,
+ spacer: '',
+ });
+};
diff --git a/plugins/badges-backend/package.json b/plugins/badges-backend/package.json
index 51a181612a..2186c8d52e 100644
--- a/plugins/badges-backend/package.json
+++ b/plugins/badges-backend/package.json
@@ -31,9 +31,9 @@
"clean": "backstage-cli clean"
},
"dependencies": {
- "@backstage/backend-common": "^0.9.7",
- "@backstage/catalog-client": "^0.5.0",
- "@backstage/catalog-model": "^0.9.5",
+ "@backstage/backend-common": "^0.9.10",
+ "@backstage/catalog-client": "^0.5.2",
+ "@backstage/catalog-model": "^0.9.7",
"@backstage/config": "^0.1.10",
"@backstage/errors": "^0.1.3",
"@types/express": "^4.17.6",
@@ -46,7 +46,7 @@
"yn": "^4.0.0"
},
"devDependencies": {
- "@backstage/cli": "^0.8.0",
+ "@backstage/cli": "^0.9.0",
"@types/supertest": "^2.0.8",
"supertest": "^6.1.3"
},
diff --git a/plugins/badges-backend/src/run.ts b/plugins/badges-backend/src/run.ts
index addfdfd6d7..53d4e4334a 100644
--- a/plugins/badges-backend/src/run.ts
+++ b/plugins/badges-backend/src/run.ts
@@ -18,7 +18,7 @@ import { getRootLogger } from '@backstage/backend-common';
import yn from 'yn';
import { startStandaloneServer } from './service/standaloneServer';
-const port = process.env.PLUGIN_PORT ? Number(process.env.PLUGIN_PORT) : 7000;
+const port = process.env.PLUGIN_PORT ? Number(process.env.PLUGIN_PORT) : 7007;
const enableCors = yn(process.env.PLUGIN_CORS, { default: false });
const logger = getRootLogger();
diff --git a/plugins/badges-backend/src/service/router.test.ts b/plugins/badges-backend/src/service/router.test.ts
index 51ad1e7e11..286a2335f1 100644
--- a/plugins/badges-backend/src/service/router.test.ts
+++ b/plugins/badges-backend/src/service/router.test.ts
@@ -73,7 +73,7 @@ describe('createRouter', () => {
config = new ConfigReader({
backend: {
baseUrl: 'http://127.0.0.1',
- listen: { port: 7000 },
+ listen: { port: 7007 },
},
});
discovery = SingleHostDiscovery.fromConfig(config);
diff --git a/plugins/badges/CHANGELOG.md b/plugins/badges/CHANGELOG.md
index 3a6ed61717..44bc597d0a 100644
--- a/plugins/badges/CHANGELOG.md
+++ b/plugins/badges/CHANGELOG.md
@@ -1,5 +1,16 @@
# @backstage/plugin-badges
+## 0.2.14
+
+### Patch Changes
+
+- a125278b81: Refactor out the deprecated path and icon from RouteRefs
+- Updated dependencies
+ - @backstage/catalog-model@0.9.7
+ - @backstage/plugin-catalog-react@0.6.4
+ - @backstage/core-components@0.7.4
+ - @backstage/core-plugin-api@0.2.0
+
## 0.2.13
### Patch Changes
diff --git a/plugins/badges/package.json b/plugins/badges/package.json
index 0c792808bc..4d02d64237 100644
--- a/plugins/badges/package.json
+++ b/plugins/badges/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-badges",
"description": "A Backstage plugin that generates README badges for your entities",
- "version": "0.2.13",
+ "version": "0.2.14",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -27,11 +27,11 @@
"clean": "backstage-cli clean"
},
"dependencies": {
- "@backstage/catalog-model": "^0.9.5",
- "@backstage/core-components": "^0.7.3",
- "@backstage/core-plugin-api": "^0.1.13",
+ "@backstage/catalog-model": "^0.9.7",
+ "@backstage/core-components": "^0.7.4",
+ "@backstage/core-plugin-api": "^0.2.0",
"@backstage/errors": "^0.1.3",
- "@backstage/plugin-catalog-react": "^0.6.1",
+ "@backstage/plugin-catalog-react": "^0.6.4",
"@backstage/theme": "^0.2.13",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
@@ -42,10 +42,10 @@
"react-use": "^17.2.4"
},
"devDependencies": {
- "@backstage/cli": "^0.8.2",
- "@backstage/core-app-api": "^0.1.20",
- "@backstage/dev-utils": "^0.2.12",
- "@backstage/test-utils": "^0.1.21",
+ "@backstage/cli": "^0.9.0",
+ "@backstage/core-app-api": "^0.1.22",
+ "@backstage/dev-utils": "^0.2.13",
+ "@backstage/test-utils": "^0.1.22",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
diff --git a/plugins/badges/src/api/BadgesClient.ts b/plugins/badges/src/api/BadgesClient.ts
index d81b042ce5..432a485a6b 100644
--- a/plugins/badges/src/api/BadgesClient.ts
+++ b/plugins/badges/src/api/BadgesClient.ts
@@ -17,7 +17,6 @@
import { generatePath } from 'react-router';
import { ResponseError } from '@backstage/errors';
import { Entity, ENTITY_DEFAULT_NAMESPACE } from '@backstage/catalog-model';
-import { entityRoute } from '@backstage/plugin-catalog-react';
import { BadgesApi, BadgeSpec } from './types';
import { DiscoveryApi, IdentityApi } from '@backstage/core-plugin-api';
@@ -53,7 +52,7 @@ export class BadgesClient implements BadgesApi {
private async getEntityBadgeSpecsUrl(entity: Entity): Promise {
const routeParams = this.getEntityRouteParams(entity);
- const path = generatePath(entityRoute.path, routeParams);
+ const path = generatePath(`:kind/:namespace/:name`, routeParams);
return `${await this.discoveryApi.getBaseUrl(
'badges',
)}/entity/${path}/badge-specs`;
diff --git a/plugins/bazaar-backend/CHANGELOG.md b/plugins/bazaar-backend/CHANGELOG.md
new file mode 100644
index 0000000000..88e646b5e3
--- /dev/null
+++ b/plugins/bazaar-backend/CHANGELOG.md
@@ -0,0 +1,10 @@
+# @backstage/plugin-bazaar-backend
+
+## 0.1.2
+
+### Patch Changes
+
+- f6ba309d9e: A Bazaar project has been extended with the following fields: size, start date (optional), end date (optional) and a responsible person.
+- Updated dependencies
+ - @backstage/backend-common@0.9.10
+ - @backstage/backend-test-utils@0.1.9
diff --git a/plugins/bazaar-backend/migrations/20211020073922_add_columns.js b/plugins/bazaar-backend/migrations/20211020073922_add_columns.js
new file mode 100644
index 0000000000..d4cb87bd82
--- /dev/null
+++ b/plugins/bazaar-backend/migrations/20211020073922_add_columns.js
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2021 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+exports.up = async function up(knex) {
+ await knex.schema.alterTable('metadata', table => {
+ table
+ .text('size')
+ .defaultTo('medium')
+ .notNullable()
+ .comment('The estimated magnitude of the project');
+ table
+ .text('start_date')
+ .comment('Optional start date of the project (ISO 8601 format)');
+ table
+ .text('end_date')
+ .comment('Optional end date of the project (ISO 8601 format)');
+ table
+ .text('responsible')
+ .notNullable()
+ .comment('Contact person of the project');
+ });
+};
+
+exports.down = async function down(knex) {
+ await knex.schema.alterTable('metadata', table => {
+ table
+ .dropColumn('size')
+ .dropColumn('start_date')
+ .dropColumn('end_date')
+ .dropColumn('responsible');
+ });
+};
diff --git a/plugins/bazaar-backend/package.json b/plugins/bazaar-backend/package.json
index a9ac0153b8..a6610493e2 100644
--- a/plugins/bazaar-backend/package.json
+++ b/plugins/bazaar-backend/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-bazaar-backend",
- "version": "0.1.1",
+ "version": "0.1.2",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -20,8 +20,8 @@
"clean": "backstage-cli clean"
},
"dependencies": {
- "@backstage/backend-common": "^0.9.7",
- "@backstage/backend-test-utils": "^0.1.8",
+ "@backstage/backend-common": "^0.9.10",
+ "@backstage/backend-test-utils": "^0.1.9",
"@backstage/config": "^0.1.5",
"@types/express": "^4.17.6",
"express": "^4.17.1",
@@ -31,7 +31,7 @@
"yn": "^4.0.0"
},
"devDependencies": {
- "@backstage/cli": "^0.8.0"
+ "@backstage/cli": "^0.9.0"
},
"files": [
"dist",
diff --git a/plugins/bazaar-backend/src/run.ts b/plugins/bazaar-backend/src/run.ts
index 54d2716290..0a3ed2b7f0 100644
--- a/plugins/bazaar-backend/src/run.ts
+++ b/plugins/bazaar-backend/src/run.ts
@@ -18,7 +18,7 @@ import { getRootLogger } from '@backstage/backend-common';
import yn from 'yn';
import { startStandaloneServer } from './service/standaloneServer';
-const port = process.env.PLUGIN_PORT ? Number(process.env.PLUGIN_PORT) : 7000;
+const port = process.env.PLUGIN_PORT ? Number(process.env.PLUGIN_PORT) : 7007;
const enableCors = yn(process.env.PLUGIN_CORS, { default: false });
const logger = getRootLogger();
diff --git a/plugins/bazaar-backend/src/service/DatabaseHandler.test.ts b/plugins/bazaar-backend/src/service/DatabaseHandler.test.ts
index 7cce5ee414..3afde44d5e 100644
--- a/plugins/bazaar-backend/src/service/DatabaseHandler.test.ts
+++ b/plugins/bazaar-backend/src/service/DatabaseHandler.test.ts
@@ -18,14 +18,17 @@ import { DatabaseHandler } from './DatabaseHandler';
import { TestDatabaseId, TestDatabases } from '@backstage/backend-test-utils';
const bazaarProject: any = {
- name: 'name',
- entityRef: 'ref',
+ name: 'n1',
+ entityRef: 'ref1',
community: '',
status: 'proposed',
announcement: 'a',
membersCount: 0,
+ startDate: '2021-11-07T13:27:00.000Z',
+ endDate: null,
+ size: 'small',
+ responsible: 'r',
};
-
describe('DatabaseHandler', () => {
const databases = TestDatabases.create({
ids: ['POSTGRES_13', 'POSTGRES_9', 'SQLITE_3'],
@@ -33,18 +36,40 @@ describe('DatabaseHandler', () => {
async function createDatabaseHandler(databaseId: TestDatabaseId) {
const knex = await databases.init(databaseId);
- return await DatabaseHandler.create({ database: knex });
+ return {
+ knex,
+ dbHandler: await DatabaseHandler.create({ database: knex }),
+ };
}
it.each(databases.eachSupportedId())(
- 'should do a full sync with the locations on connect, %p',
+ 'should insert and get entity, %p',
async databaseId => {
- const db = await createDatabaseHandler(databaseId);
- await db.insertMetadata(bazaarProject);
+ const { knex, dbHandler } = await createDatabaseHandler(databaseId);
- const entities = await db.getEntities();
- expect(entities.length).toEqual(1);
- expect(entities[0].entity_ref).toEqual(bazaarProject.entityRef);
+ await knex('metadata').insert({
+ entity_ref: bazaarProject.entityRef,
+ name: bazaarProject.name,
+ announcement: bazaarProject.announcement,
+ community: bazaarProject.community,
+ status: bazaarProject.status,
+ updated_at: new Date().toISOString(),
+ start_date: bazaarProject.startDate,
+ end_date: bazaarProject.endDate,
+ size: bazaarProject.size,
+ responsible: bazaarProject.responsible,
+ });
+
+ const res = await dbHandler.getMetadata('ref1');
+
+ expect(res).toHaveLength(1);
+ expect(res[0].announcement).toEqual('a');
+ expect(res[0].community).toEqual('');
+ expect(res[0].status).toEqual('proposed');
+ expect(res[0].start_date).toEqual('2021-11-07T13:27:00.000Z');
+ expect(res[0].end_date).toEqual(null);
+ expect(res[0].size).toEqual('small');
+ expect(res[0].responsible).toEqual('r');
},
60_000,
);
diff --git a/plugins/bazaar-backend/src/service/DatabaseHandler.ts b/plugins/bazaar-backend/src/service/DatabaseHandler.ts
index d4da6e65e7..60f579af2d 100644
--- a/plugins/bazaar-backend/src/service/DatabaseHandler.ts
+++ b/plugins/bazaar-backend/src/service/DatabaseHandler.ts
@@ -43,6 +43,20 @@ export class DatabaseHandler {
this.database = options.database;
}
+ private columns = [
+ 'members.entity_ref',
+ 'metadata.entity_ref',
+ 'metadata.name',
+ 'metadata.announcement',
+ 'metadata.status',
+ 'metadata.updated_at',
+ 'metadata.community',
+ 'metadata.size',
+ 'metadata.start_date',
+ 'metadata.end_date',
+ 'metadata.responsible',
+ ];
+
async getMembers(entityRef: string) {
return await this.database
.select('*')
@@ -72,25 +86,25 @@ export class DatabaseHandler {
'coalesce(count(members.entity_ref), 0) as members_count',
);
- const columns = [
- 'members.entity_ref',
- 'metadata.entity_ref',
- 'metadata.name',
- 'metadata.announcement',
- 'metadata.status',
- 'metadata.updated_at',
- 'metadata.community',
- ];
-
return await this.database('metadata')
- .select([...columns, coalesce])
+ .select([...this.columns, coalesce])
.where({ 'metadata.entity_ref': entityRef })
- .groupBy(columns)
+ .groupBy(this.columns)
.leftJoin('members', 'metadata.entity_ref', '=', 'members.entity_ref');
}
async insertMetadata(bazaarProject: any) {
- const { name, entityRef, community, announcement, status } = bazaarProject;
+ const {
+ name,
+ entityRef,
+ community,
+ announcement,
+ status,
+ size,
+ startDate,
+ endDate,
+ responsible,
+ } = bazaarProject;
await this.database
.insert({
@@ -100,12 +114,25 @@ export class DatabaseHandler {
announcement: announcement,
status: status,
updated_at: new Date().toISOString(),
+ size,
+ start_date: startDate,
+ end_date: endDate,
+ responsible,
})
.into('metadata');
}
async updateMetadata(bazaarProject: any) {
- const { entityRef, community, announcement, status } = bazaarProject;
+ const {
+ entityRef,
+ community,
+ announcement,
+ status,
+ size,
+ startDate,
+ endDate,
+ responsible,
+ } = bazaarProject;
return await this.database('metadata')
.where({ entity_ref: entityRef })
@@ -114,6 +141,10 @@ export class DatabaseHandler {
community: community,
status: status,
updated_at: new Date().toISOString(),
+ size,
+ start_date: startDate,
+ end_date: endDate,
+ responsible,
});
}
@@ -128,18 +159,9 @@ export class DatabaseHandler {
'coalesce(count(members.entity_ref), 0) as members_count',
);
- const columns = [
- 'members.entity_ref',
- 'metadata.entity_ref',
- 'metadata.name',
- 'metadata.announcement',
- 'metadata.status',
- 'metadata.updated_at',
- 'metadata.community',
- ];
return await this.database('metadata')
- .select([...columns, coalesce])
- .groupBy(columns)
+ .select([...this.columns, coalesce])
+ .groupBy(this.columns)
.leftJoin('members', 'metadata.entity_ref', '=', 'members.entity_ref');
}
}
diff --git a/plugins/bazaar/CHANGELOG.md b/plugins/bazaar/CHANGELOG.md
index 731f62b9e9..8988bf5d62 100644
--- a/plugins/bazaar/CHANGELOG.md
+++ b/plugins/bazaar/CHANGELOG.md
@@ -1,5 +1,19 @@
# @backstage/plugin-bazaar
+## 0.1.4
+
+### Patch Changes
+
+- a125278b81: Refactor out the deprecated path and icon from RouteRefs
+- f6ba309d9e: A Bazaar project has been extended with the following fields: size, start date (optional), end date (optional) and a responsible person.
+- Updated dependencies
+ - @backstage/plugin-catalog@0.7.3
+ - @backstage/cli@0.9.0
+ - @backstage/catalog-model@0.9.7
+ - @backstage/plugin-catalog-react@0.6.4
+ - @backstage/core-components@0.7.4
+ - @backstage/core-plugin-api@0.2.0
+
## 0.1.3
### Patch Changes
diff --git a/plugins/bazaar/README.md b/plugins/bazaar/README.md
index cd26f01997..f07e751984 100644
--- a/plugins/bazaar/README.md
+++ b/plugins/bazaar/README.md
@@ -58,7 +58,7 @@ const overviewContent = (
-+
++
+
+
@@ -75,12 +75,23 @@ The latest modified Bazaar projects are displayed in the Bazaar landing page, lo
### Workflow
-To add a project to the Bazaar, you need to create a project with one of the templates in Backstage. Click the add project-button, choose the project and fill in the form. You will be asked to add an announcement for new team members. The purpose of the announcement is for you to present your ideas and what skills you are looking for. Further you need to provide the status of the project.
+To add a project to the Bazaar, you need to create a project with one of the templates in Backstage. Click the add project-button, choose the project and fill in the form.
+
+The following fields are mandatory:
+
+- announcement - present your idea and what skills you are looking for
+- status - whether or not the project has started
+- size - small, medium or large
+- responsible - main contact person of the project
+
+The other fields are:
+
+- start date
+- end date
+- community link - link where the project members can chat, e.g. Teams or Discord link
When the project is added, you will see the Bazaar information in the Bazaar card on the entity page. There you can join a project, edit or delete it.
-
-
### Database
The metadata related to the Bazaar is stored in a database. Right now there are two tables, one for storing the metadata and one for storing the members of a Bazaar project.
@@ -92,6 +103,10 @@ The metadata related to the Bazaar is stored in a database. Right now there are
- announcement - announcement of the project and its current need of skills/team member
- status - status of the project, 'proposed' or 'ongoing'
- updated_at - date when the Bazaar information was last modified (ISO 8601 format)
+- size - small, medium or large
+- start_date - date when the project is estimated to start (ISO 8601 format)
+- end_date - date when the project is estimated to end (ISO 8601 format)
+- responsible - main contact person of the project
**members**:
@@ -107,12 +122,8 @@ The metadata related to the Bazaar is stored in a database. Right now there are
- Bazaar landing page
- - Add a tab 'My page', where your personal data is displayed. For example: your projects and its latest activities, projects or tags you are following etc.
- - Make it possible to sort the project based on the number of members
-
-- Bazaar card
-
- - Make it possible to follow tags/projects
+ - Add a tab 'My page', where your personal data is displayed. For example: your projects and its latest activities etc.
+ - Make it possible to sort the project based on e.g. the number of members
- Bazaar tab on the EntityPage
diff --git a/plugins/bazaar/media/bazaar_demo.gif b/plugins/bazaar/media/bazaar_demo.gif
deleted file mode 100644
index c5383de477..0000000000
Binary files a/plugins/bazaar/media/bazaar_demo.gif and /dev/null differ
diff --git a/plugins/bazaar/media/bazaar_pr_fullscreen.png b/plugins/bazaar/media/bazaar_pr_fullscreen.png
index d0187977db..6c5cf1277b 100644
Binary files a/plugins/bazaar/media/bazaar_pr_fullscreen.png and b/plugins/bazaar/media/bazaar_pr_fullscreen.png differ
diff --git a/plugins/bazaar/package.json b/plugins/bazaar/package.json
index 4d18784d2d..fd07a3b5c7 100644
--- a/plugins/bazaar/package.json
+++ b/plugins/bazaar/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-bazaar",
- "version": "0.1.3",
+ "version": "0.1.4",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -21,16 +21,18 @@
"clean": "backstage-cli clean"
},
"dependencies": {
- "@backstage/catalog-model": "^0.9.5",
- "@backstage/cli": "^0.8.2",
- "@backstage/core-components": "^0.7.3",
- "@backstage/core-plugin-api": "^0.1.13",
- "@backstage/plugin-catalog": "^0.7.2",
- "@backstage/plugin-catalog-react": "^0.6.3",
+ "@backstage/catalog-model": "^0.9.7",
+ "@backstage/cli": "^0.9.0",
+ "@backstage/core-components": "^0.7.4",
+ "@backstage/core-plugin-api": "^0.2.0",
+ "@backstage/plugin-catalog": "^0.7.3",
+ "@backstage/plugin-catalog-react": "^0.6.4",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.57",
+ "@material-ui/pickers": "^3.3.10",
"@testing-library/jest-dom": "^5.10.1",
+ "@date-io/luxon": "1.x",
"luxon": "^2.0.2",
"react": "^16.13.1",
"react-dom": "^16.13.1",
@@ -39,8 +41,8 @@
"react-use": "^17.2.4"
},
"devDependencies": {
- "@backstage/cli": "^0.8.2",
- "@backstage/dev-utils": "^0.2.12",
+ "@backstage/cli": "^0.9.0",
+ "@backstage/dev-utils": "^0.2.13",
"@testing-library/jest-dom": "^5.10.1",
"cross-fetch": "^3.0.6"
},
diff --git a/plugins/bazaar/src/components/AddProjectDialog/AddProjectDialog.tsx b/plugins/bazaar/src/components/AddProjectDialog/AddProjectDialog.tsx
index 82d682b4df..310ffe051d 100644
--- a/plugins/bazaar/src/components/AddProjectDialog/AddProjectDialog.tsx
+++ b/plugins/bazaar/src/components/AddProjectDialog/AddProjectDialog.tsx
@@ -16,11 +16,11 @@
import React, { useState, useEffect } from 'react';
import { Entity, stringifyEntityRef } from '@backstage/catalog-model';
-import { SubmitHandler } from 'react-hook-form';
+import { UseFormReset, UseFormGetValues } from 'react-hook-form';
import { useApi } from '@backstage/core-plugin-api';
import { ProjectDialog } from '../ProjectDialog';
import { ProjectSelector } from '../ProjectSelector';
-import { BazaarProject, FormValues, Status } from '../../types';
+import { BazaarProject, FormValues, Size, Status } from '../../types';
import { bazaarApiRef } from '../../api';
type Props = {
@@ -52,6 +52,10 @@ export const AddProjectDialog = ({
community: '',
announcement: '',
status: 'proposed' as Status,
+ size: 'medium' as Size,
+ responsible: '',
+ startDate: null,
+ endDate: null,
};
const handleListItemClick = (entity: Entity) => {
@@ -63,9 +67,9 @@ export const AddProjectDialog = ({
handleClose();
};
- const handleSave: SubmitHandler = async (
- getValues: any,
- reset: any,
+ const handleSave: any = async (
+ getValues: UseFormGetValues,
+ reset: UseFormReset,
) => {
const formValues = getValues();
@@ -77,6 +81,10 @@ export const AddProjectDialog = ({
status: formValues.status,
community: formValues.community,
membersCount: 0,
+ size: formValues.size,
+ startDate: formValues.startDate ?? null,
+ endDate: formValues.endDate ?? null,
+ responsible: formValues.responsible,
} as BazaarProject);
fetchBazaarProjects();
diff --git a/plugins/bazaar/src/components/DateSelector/DateSelector.tsx b/plugins/bazaar/src/components/DateSelector/DateSelector.tsx
new file mode 100644
index 0000000000..db564a7b68
--- /dev/null
+++ b/plugins/bazaar/src/components/DateSelector/DateSelector.tsx
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2021 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import FormControl from '@material-ui/core/FormControl';
+import { Controller, Control, UseFormSetValue } from 'react-hook-form';
+import { FormValues } from '../../types';
+import {
+ KeyboardDatePicker,
+ MuiPickersUtilsProvider,
+} from '@material-ui/pickers';
+import LuxonUtils from '@date-io/luxon';
+import { IconButton } from '@material-ui/core';
+import ClearIcon from '@material-ui/icons/Clear';
+
+type Props = {
+ name: 'startDate' | 'endDate';
+ control: Control;
+ setValue: UseFormSetValue;
+};
+
+export const DateSelector = ({ name, control, setValue }: Props) => {
+ const label = `${
+ name.charAt(0).toLocaleUpperCase('en-US') + name.slice(1, name.indexOf('D'))
+ } date`;
+
+ return (
+ (
+
+
+ {
+ setValue(name, date?.toISO());
+ }}
+ InputProps={{
+ endAdornment: (
+ setValue(name, null)}>
+
+
+ ),
+ }}
+ InputAdornmentProps={{
+ position: 'start',
+ }}
+ />
+
+
+ )}
+ />
+ );
+};
diff --git a/plugins/bazaar/src/components/DateSelector/index.ts b/plugins/bazaar/src/components/DateSelector/index.ts
new file mode 100644
index 0000000000..9bdb078164
--- /dev/null
+++ b/plugins/bazaar/src/components/DateSelector/index.ts
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2021 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export { DateSelector } from './DateSelector';
diff --git a/plugins/bazaar/src/components/DoubleDateSelector/DoubleDateSelector.tsx b/plugins/bazaar/src/components/DoubleDateSelector/DoubleDateSelector.tsx
new file mode 100644
index 0000000000..b37e734453
--- /dev/null
+++ b/plugins/bazaar/src/components/DoubleDateSelector/DoubleDateSelector.tsx
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2021 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import { Control, UseFormSetValue } from 'react-hook-form';
+import { FormValues } from '../../types';
+import { Typography } from '@material-ui/core';
+import { DateSelector } from '../DateSelector/DateSelector';
+
+type Props = {
+ control: Control;
+ setValue: UseFormSetValue;
+};
+
+export const DoubleDateSelector = ({ control, setValue }: Props) => {
+ return (
+
+ );
+};
diff --git a/plugins/bazaar/src/components/DoubleDateSelector/index.ts b/plugins/bazaar/src/components/DoubleDateSelector/index.ts
new file mode 100644
index 0000000000..9162827b35
--- /dev/null
+++ b/plugins/bazaar/src/components/DoubleDateSelector/index.ts
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2021 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export { DoubleDateSelector } from './DoubleDateSelector';
diff --git a/plugins/bazaar/src/components/EditProjectDialog/EditProjectDialog.tsx b/plugins/bazaar/src/components/EditProjectDialog/EditProjectDialog.tsx
index 172bdf548d..41c9e474d4 100644
--- a/plugins/bazaar/src/components/EditProjectDialog/EditProjectDialog.tsx
+++ b/plugins/bazaar/src/components/EditProjectDialog/EditProjectDialog.tsx
@@ -18,8 +18,9 @@ import React, { useState, useEffect } from 'react';
import { Entity, stringifyEntityRef } from '@backstage/catalog-model';
import { useApi } from '@backstage/core-plugin-api';
import { ProjectDialog } from '../ProjectDialog';
-import { BazaarProject, FormValues } from '../../types';
+import { BazaarProject, FormValues, Size, Status } from '../../types';
import { bazaarApiRef } from '../../api';
+import { UseFormGetValues } from 'react-hook-form';
type Props = {
entity: Entity;
@@ -41,6 +42,10 @@ export const EditProjectDialog = ({
announcement: bazaarProject.announcement,
community: bazaarProject.community,
status: bazaarProject.status,
+ size: bazaarProject.size,
+ startDate: bazaarProject?.startDate ?? null,
+ endDate: bazaarProject?.endDate ?? null,
+ responsible: bazaarProject.responsible,
});
const bazaarApi = useApi(bazaarApiRef);
@@ -50,19 +55,27 @@ export const EditProjectDialog = ({
announcement: bazaarProject.announcement,
community: bazaarProject.community,
status: bazaarProject.status,
+ size: bazaarProject.size,
+ startDate: bazaarProject?.startDate ?? null,
+ endDate: bazaarProject?.endDate ?? null,
+ responsible: bazaarProject.responsible,
});
}, [bazaarProject]);
- const handleSave: any = async (getValues: any, _: any) => {
+ const handleSave: any = async (getValues: UseFormGetValues) => {
const formValues = getValues();
const updateResponse = await bazaarApi.updateMetadata({
name: entity.metadata.name,
entityRef: stringifyEntityRef(entity),
announcement: formValues.announcement,
- status: formValues.status,
+ status: formValues.status as Status,
community: formValues.community,
membersCount: bazaarProject.membersCount,
+ size: formValues.size as Size,
+ startDate: formValues?.startDate ?? null,
+ endDate: formValues?.endDate ?? null,
+ responsible: formValues.responsible,
});
if (updateResponse.status === 'ok') fetchBazaarProject();
diff --git a/plugins/bazaar/src/components/EntityBazaarInfoCard/EntityBazaarInfoCard.tsx b/plugins/bazaar/src/components/EntityBazaarInfoCard/EntityBazaarInfoCard.tsx
index 6bf844d592..acae8f5342 100644
--- a/plugins/bazaar/src/components/EntityBazaarInfoCard/EntityBazaarInfoCard.tsx
+++ b/plugins/bazaar/src/components/EntityBazaarInfoCard/EntityBazaarInfoCard.tsx
@@ -121,6 +121,10 @@ export const EntityBazaarInfoCard = () => {
status: metadata.status,
updatedAt: metadata.updated_at,
membersCount: metadata.members_count,
+ size: metadata.size,
+ startDate: metadata.start_date,
+ endDate: metadata.end_date,
+ responsible: metadata.responsible,
} as BazaarProject;
}
}
@@ -259,7 +263,7 @@ export const EntityBazaarInfoCard = () => {
-
+
{bazaarProject?.value?.announcement
? bazaarProject?.value?.announcement
@@ -278,17 +282,11 @@ export const EntityBazaarInfoCard = () => {
-
-
-
-
-
-
-
+
{' '}
{members?.value?.length ? (
- members.value.slice(0, 3).map((member: Member) => {
+ members.value.slice(0, 7).map((member: Member) => {
return (
{
fontSize: '8px',
float: 'left',
marginRight: '0.3rem',
+ marginBottom: '0.25rem',
}}
picture={member.picture}
/>
@@ -317,6 +316,44 @@ export const EntityBazaarInfoCard = () => {
)}
+
+
+
+
+
+
+
+
+
+
+ {bazaarProject?.value?.size}
+
+
+
+
+
+
+
+ {bazaarProject?.value?.startDate?.substring(0, 10) || ''}
+
+
+
+
+
+
+
+ {bazaarProject?.value?.endDate?.substring(0, 10) || ''}
+
+
+
+
+
+
+
+ {bazaarProject?.value?.responsible || ''}
+
+
+
diff --git a/plugins/bazaar/src/components/InputField/InputField.tsx b/plugins/bazaar/src/components/InputField/InputField.tsx
index 4d284ff82c..9932e5b1fc 100644
--- a/plugins/bazaar/src/components/InputField/InputField.tsx
+++ b/plugins/bazaar/src/components/InputField/InputField.tsx
@@ -19,13 +19,18 @@ import { Controller, Control, FieldError } from 'react-hook-form';
import { TextField } from '@material-ui/core';
import { FormValues } from '../../types';
+type Rules = {
+ required: boolean;
+ pattern?: any;
+};
+
type Props = {
- inputType: 'announcement' | 'community';
+ inputType: 'announcement' | 'community' | 'responsible';
error?: FieldError | undefined;
control: Control;
helperText?: string;
placeholder?: string;
- rules?: Object;
+ rules?: Rules | undefined;
};
export const InputField = ({
@@ -47,6 +52,7 @@ export const InputField = ({
render={({ field }) => (
;
- name: 'announcement' | 'status';
+ name: 'status' | 'size';
error?: FieldError | undefined;
};
@@ -42,14 +42,17 @@ export const InputSelector = ({ name, options, control, error }: Props) => {
render={({ field }) => (
{label}
diff --git a/plugins/graphql-backend/package.json b/plugins/graphql-backend/package.json
index 9d90dd8782..335545021e 100644
--- a/plugins/graphql-backend/package.json
+++ b/plugins/graphql-backend/package.json
@@ -31,7 +31,7 @@
"clean": "backstage-cli clean"
},
"dependencies": {
- "@backstage/backend-common": "^0.9.7",
+ "@backstage/backend-common": "^0.9.10",
"@backstage/config": "^0.1.8",
"@backstage/plugin-catalog-graphql": "^0.2.12",
"@graphql-modules/core": "^0.7.17",
@@ -47,7 +47,7 @@
"yn": "^4.0.0"
},
"devDependencies": {
- "@backstage/cli": "^0.8.0",
+ "@backstage/cli": "^0.9.0",
"@types/supertest": "^2.0.8",
"eslint-plugin-graphql": "^4.0.0",
"msw": "^0.35.0",
diff --git a/plugins/home/CHANGELOG.md b/plugins/home/CHANGELOG.md
index 06beb827a6..09e379a625 100644
--- a/plugins/home/CHANGELOG.md
+++ b/plugins/home/CHANGELOG.md
@@ -1,5 +1,14 @@
# @backstage/plugin-home
+## 0.4.6
+
+### Patch Changes
+
+- a125278b81: Refactor out the deprecated path and icon from RouteRefs
+- Updated dependencies
+ - @backstage/core-components@0.7.4
+ - @backstage/core-plugin-api@0.2.0
+
## 0.4.5
### Patch Changes
diff --git a/plugins/home/package.json b/plugins/home/package.json
index 8020d7af03..ef13ce3a00 100644
--- a/plugins/home/package.json
+++ b/plugins/home/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-home",
"description": "A Backstage plugin that helps you build a home page",
- "version": "0.4.5",
+ "version": "0.4.6",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -21,8 +21,8 @@
"clean": "backstage-cli clean"
},
"dependencies": {
- "@backstage/core-components": "^0.7.3",
- "@backstage/core-plugin-api": "^0.1.13",
+ "@backstage/core-components": "^0.7.4",
+ "@backstage/core-plugin-api": "^0.2.0",
"@backstage/theme": "^0.2.13",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
@@ -35,10 +35,10 @@
"react-use": "^17.2.4"
},
"devDependencies": {
- "@backstage/cli": "^0.8.2",
- "@backstage/core-app-api": "^0.1.20",
- "@backstage/dev-utils": "^0.2.12",
- "@backstage/test-utils": "^0.1.21",
+ "@backstage/cli": "^0.9.0",
+ "@backstage/core-app-api": "^0.1.22",
+ "@backstage/dev-utils": "^0.2.13",
+ "@backstage/test-utils": "^0.1.22",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
diff --git a/plugins/home/src/routes.ts b/plugins/home/src/routes.ts
index 2c641d8433..7b6b47215b 100644
--- a/plugins/home/src/routes.ts
+++ b/plugins/home/src/routes.ts
@@ -16,5 +16,5 @@
import { createRouteRef } from '@backstage/core-plugin-api';
export const rootRouteRef = createRouteRef({
- title: 'home',
+ id: 'home',
});
diff --git a/plugins/ilert/CHANGELOG.md b/plugins/ilert/CHANGELOG.md
index f43f2add09..1bff4b22ef 100644
--- a/plugins/ilert/CHANGELOG.md
+++ b/plugins/ilert/CHANGELOG.md
@@ -1,5 +1,16 @@
# @backstage/plugin-ilert
+## 0.1.17
+
+### Patch Changes
+
+- a125278b81: Refactor out the deprecated path and icon from RouteRefs
+- Updated dependencies
+ - @backstage/catalog-model@0.9.7
+ - @backstage/plugin-catalog-react@0.6.4
+ - @backstage/core-components@0.7.4
+ - @backstage/core-plugin-api@0.2.0
+
## 0.1.16
### Patch Changes
diff --git a/plugins/ilert/package.json b/plugins/ilert/package.json
index 69c4025181..6011dd4f46 100644
--- a/plugins/ilert/package.json
+++ b/plugins/ilert/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-ilert",
"description": "A Backstage plugin that integrates towards iLert",
- "version": "0.1.16",
+ "version": "0.1.17",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -21,11 +21,11 @@
"clean": "backstage-cli clean"
},
"dependencies": {
- "@backstage/catalog-model": "^0.9.5",
- "@backstage/core-components": "^0.7.3",
- "@backstage/core-plugin-api": "^0.1.13",
+ "@backstage/catalog-model": "^0.9.7",
+ "@backstage/core-components": "^0.7.4",
+ "@backstage/core-plugin-api": "^0.2.0",
"@backstage/errors": "^0.1.3",
- "@backstage/plugin-catalog-react": "^0.6.3",
+ "@backstage/plugin-catalog-react": "^0.6.4",
"@backstage/theme": "^0.2.13",
"@date-io/luxon": "1.x",
"@material-ui/core": "^4.12.2",
@@ -39,10 +39,10 @@
"react-use": "^17.2.4"
},
"devDependencies": {
- "@backstage/cli": "^0.8.2",
- "@backstage/core-app-api": "^0.1.20",
- "@backstage/dev-utils": "^0.2.12",
- "@backstage/test-utils": "^0.1.21",
+ "@backstage/cli": "^0.9.0",
+ "@backstage/core-app-api": "^0.1.22",
+ "@backstage/dev-utils": "^0.2.13",
+ "@backstage/test-utils": "^0.1.22",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
diff --git a/plugins/ilert/src/route-refs.tsx b/plugins/ilert/src/route-refs.tsx
index 97d11df98a..8a3d53001d 100644
--- a/plugins/ilert/src/route-refs.tsx
+++ b/plugins/ilert/src/route-refs.tsx
@@ -14,11 +14,8 @@
* limitations under the License.
*/
-import ILertIcon from './assets/ilert.icon.svg';
import { createRouteRef } from '@backstage/core-plugin-api';
export const iLertRouteRef = createRouteRef({
- icon: ILertIcon,
- path: '/ilert',
- title: 'iLert',
+ id: 'ilert',
});
diff --git a/plugins/jenkins-backend/package.json b/plugins/jenkins-backend/package.json
index 7049fc0a32..ac9efb3ffd 100644
--- a/plugins/jenkins-backend/package.json
+++ b/plugins/jenkins-backend/package.json
@@ -22,9 +22,9 @@
"clean": "backstage-cli clean"
},
"dependencies": {
- "@backstage/backend-common": "^0.9.9",
- "@backstage/catalog-client": "^0.5.1",
- "@backstage/catalog-model": "^0.9.5",
+ "@backstage/backend-common": "^0.9.10",
+ "@backstage/catalog-client": "^0.5.2",
+ "@backstage/catalog-model": "^0.9.7",
"@backstage/config": "^0.1.10",
"@types/express": "^4.17.6",
"cross-fetch": "^3.0.6",
@@ -35,7 +35,7 @@
"yn": "^4.0.0"
},
"devDependencies": {
- "@backstage/cli": "^0.8.2",
+ "@backstage/cli": "^0.9.0",
"@types/jenkins": "^0.23.1",
"@types/supertest": "^2.0.8",
"msw": "^0.35.0",
diff --git a/plugins/jenkins-backend/src/run.ts b/plugins/jenkins-backend/src/run.ts
index b96989e4b8..068a2b9d5f 100644
--- a/plugins/jenkins-backend/src/run.ts
+++ b/plugins/jenkins-backend/src/run.ts
@@ -18,7 +18,7 @@ import { getRootLogger } from '@backstage/backend-common';
import yn from 'yn';
import { startStandaloneServer } from './service/standaloneServer';
-const port = process.env.PLUGIN_PORT ? Number(process.env.PLUGIN_PORT) : 7000;
+const port = process.env.PLUGIN_PORT ? Number(process.env.PLUGIN_PORT) : 7007;
const enableCors = yn(process.env.PLUGIN_CORS, { default: false });
const logger = getRootLogger();
diff --git a/plugins/jenkins/CHANGELOG.md b/plugins/jenkins/CHANGELOG.md
index 84db4728bc..369676b195 100644
--- a/plugins/jenkins/CHANGELOG.md
+++ b/plugins/jenkins/CHANGELOG.md
@@ -1,5 +1,16 @@
# @backstage/plugin-jenkins
+## 0.5.12
+
+### Patch Changes
+
+- a125278b81: Refactor out the deprecated path and icon from RouteRefs
+- Updated dependencies
+ - @backstage/catalog-model@0.9.7
+ - @backstage/plugin-catalog-react@0.6.4
+ - @backstage/core-components@0.7.4
+ - @backstage/core-plugin-api@0.2.0
+
## 0.5.11
### Patch Changes
diff --git a/plugins/jenkins/package.json b/plugins/jenkins/package.json
index 80d8b40322..4456b15284 100644
--- a/plugins/jenkins/package.json
+++ b/plugins/jenkins/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-jenkins",
"description": "A Backstage plugin that integrates towards Jenkins",
- "version": "0.5.11",
+ "version": "0.5.12",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -32,10 +32,10 @@
"clean": "backstage-cli clean"
},
"dependencies": {
- "@backstage/catalog-model": "^0.9.5",
- "@backstage/core-components": "^0.7.3",
- "@backstage/core-plugin-api": "^0.1.13",
- "@backstage/plugin-catalog-react": "^0.6.1",
+ "@backstage/catalog-model": "^0.9.7",
+ "@backstage/core-components": "^0.7.4",
+ "@backstage/core-plugin-api": "^0.2.0",
+ "@backstage/plugin-catalog-react": "^0.6.4",
"@backstage/theme": "^0.2.13",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
@@ -48,10 +48,10 @@
"react-use": "^17.2.4"
},
"devDependencies": {
- "@backstage/cli": "^0.8.2",
- "@backstage/core-app-api": "^0.1.20",
- "@backstage/dev-utils": "^0.2.12",
- "@backstage/test-utils": "^0.1.21",
+ "@backstage/cli": "^0.9.0",
+ "@backstage/core-app-api": "^0.1.22",
+ "@backstage/dev-utils": "^0.2.13",
+ "@backstage/test-utils": "^0.1.22",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
diff --git a/plugins/jenkins/src/components/BuildsPage/lib/CITable/CITable.tsx b/plugins/jenkins/src/components/BuildsPage/lib/CITable/CITable.tsx
index ee17df8533..39187fe859 100644
--- a/plugins/jenkins/src/components/BuildsPage/lib/CITable/CITable.tsx
+++ b/plugins/jenkins/src/components/BuildsPage/lib/CITable/CITable.tsx
@@ -17,12 +17,13 @@ import React from 'react';
import { Box, IconButton, Link, Typography, Tooltip } from '@material-ui/core';
import RetryIcon from '@material-ui/icons/Replay';
import JenkinsLogo from '../../../../assets/JenkinsLogo.svg';
-import { generatePath, Link as RouterLink } from 'react-router-dom';
+import { Link as RouterLink } from 'react-router-dom';
import { JenkinsRunStatus } from '../Status';
import { useBuilds } from '../../../useBuilds';
import { buildRouteRef } from '../../../../plugin';
import { Table, TableColumn } from '@backstage/core-components';
import { Project } from '../../../../api/JenkinsApi';
+import { useRouteRef } from '@backstage/core-plugin-api';
const FailCount = ({ count }: { count: number }): JSX.Element | null => {
if (count !== 0) {
@@ -91,28 +92,33 @@ const generatedColumns: TableColumn[] = [
field: 'fullName',
highlight: true,
render: (row: Partial) => {
- if (!row.fullName || !row.lastBuild?.number) {
- return (
- <>
- {row.fullName ||
- row.fullDisplayName ||
- row.displayName ||
- 'Unknown'}
- >
- );
- }
+ const LinkWrapper = () => {
+ const routeLink = useRouteRef(buildRouteRef);
+ if (!row.fullName || !row.lastBuild?.number) {
+ return (
+ <>
+ {row.fullName ||
+ row.fullDisplayName ||
+ row.displayName ||
+ 'Unknown'}
+ >
+ );
+ }
- return (
-
- {row.fullDisplayName}
-
- );
+ return (
+
+ {row.fullDisplayName}
+
+ );
+ };
+
+ return ;
},
},
{
diff --git a/plugins/jenkins/src/components/Router.tsx b/plugins/jenkins/src/components/Router.tsx
index 35b769e9d9..0d38fe009a 100644
--- a/plugins/jenkins/src/components/Router.tsx
+++ b/plugins/jenkins/src/components/Router.tsx
@@ -19,7 +19,7 @@ import { useEntity } from '@backstage/plugin-catalog-react';
import React from 'react';
import { Route, Routes } from 'react-router';
import { JENKINS_ANNOTATION, LEGACY_JENKINS_ANNOTATION } from '../constants';
-import { buildRouteRef, rootRouteRef } from '../plugin';
+import { buildRouteRef } from '../plugin';
import { CITable } from './BuildsPage/lib/CITable';
import { DetailedViewPage } from './BuildWithStepsPage/';
@@ -41,7 +41,7 @@ export const Router = (_props: Props) => {
return (
- } />
+ } />
} />
);
diff --git a/plugins/jenkins/src/plugin.ts b/plugins/jenkins/src/plugin.ts
index d445a8cfb1..c32c9b50d1 100644
--- a/plugins/jenkins/src/plugin.ts
+++ b/plugins/jenkins/src/plugin.ts
@@ -20,20 +20,20 @@ import {
createPlugin,
createRoutableExtension,
createRouteRef,
+ createSubRouteRef,
discoveryApiRef,
identityApiRef,
} from '@backstage/core-plugin-api';
import { JenkinsClient, jenkinsApiRef } from './api';
export const rootRouteRef = createRouteRef({
- path: '',
- title: 'Jenkins',
+ id: 'jenkins',
});
-export const buildRouteRef = createRouteRef({
- path: 'build/:jobFullName/:buildNumber',
- params: ['jobFullName', 'buildNumber'],
- title: 'Jenkins build',
+export const buildRouteRef = createSubRouteRef({
+ id: 'jenkins/builds',
+ path: '/builds/:jobFullName/:buildNumber',
+ parent: rootRouteRef,
});
export const jenkinsPlugin = createPlugin({
diff --git a/plugins/kafka-backend/CHANGELOG.md b/plugins/kafka-backend/CHANGELOG.md
index 88111cd1de..482c1f75b1 100644
--- a/plugins/kafka-backend/CHANGELOG.md
+++ b/plugins/kafka-backend/CHANGELOG.md
@@ -1,5 +1,14 @@
# @backstage/plugin-kafka-backend
+## 0.2.11
+
+### Patch Changes
+
+- 9145449220: Update Kafka configuration types
+- Updated dependencies
+ - @backstage/catalog-model@0.9.7
+ - @backstage/backend-common@0.9.10
+
## 0.2.10
### Patch Changes
diff --git a/plugins/kafka-backend/package.json b/plugins/kafka-backend/package.json
index e271decc8b..dea803890f 100644
--- a/plugins/kafka-backend/package.json
+++ b/plugins/kafka-backend/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-kafka-backend",
"description": "A Backstage backend plugin that integrates towards Kafka",
- "version": "0.2.10",
+ "version": "0.2.11",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -32,8 +32,8 @@
"clean": "backstage-cli clean"
},
"dependencies": {
- "@backstage/backend-common": "^0.9.7",
- "@backstage/catalog-model": "^0.9.5",
+ "@backstage/backend-common": "^0.9.10",
+ "@backstage/catalog-model": "^0.9.7",
"@backstage/config": "^0.1.10",
"@backstage/errors": "^0.1.3",
"@types/express": "^4.17.6",
@@ -44,7 +44,7 @@
"winston": "^3.2.1"
},
"devDependencies": {
- "@backstage/cli": "^0.8.0",
+ "@backstage/cli": "^0.9.0",
"@types/jest-when": "^2.7.2",
"@types/lodash": "^4.14.151",
"jest-when": "^3.1.0",
diff --git a/plugins/kafka/CHANGELOG.md b/plugins/kafka/CHANGELOG.md
index 2fe42565da..33ec4bd95a 100644
--- a/plugins/kafka/CHANGELOG.md
+++ b/plugins/kafka/CHANGELOG.md
@@ -1,5 +1,16 @@
# @backstage/plugin-kafka
+## 0.2.21
+
+### Patch Changes
+
+- a125278b81: Refactor out the deprecated path and icon from RouteRefs
+- Updated dependencies
+ - @backstage/catalog-model@0.9.7
+ - @backstage/plugin-catalog-react@0.6.4
+ - @backstage/core-components@0.7.4
+ - @backstage/core-plugin-api@0.2.0
+
## 0.2.20
### Patch Changes
diff --git a/plugins/kafka/package.json b/plugins/kafka/package.json
index 888c79db59..a6fbe36e99 100644
--- a/plugins/kafka/package.json
+++ b/plugins/kafka/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-kafka",
"description": "A Backstage plugin that integrates towards Kafka",
- "version": "0.2.20",
+ "version": "0.2.21",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -21,10 +21,10 @@
"clean": "backstage-cli clean"
},
"dependencies": {
- "@backstage/catalog-model": "^0.9.5",
- "@backstage/core-components": "^0.7.3",
- "@backstage/core-plugin-api": "^0.1.13",
- "@backstage/plugin-catalog-react": "^0.6.3",
+ "@backstage/catalog-model": "^0.9.7",
+ "@backstage/core-components": "^0.7.4",
+ "@backstage/core-plugin-api": "^0.2.0",
+ "@backstage/plugin-catalog-react": "^0.6.4",
"@backstage/theme": "^0.2.13",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
@@ -35,10 +35,10 @@
"react-use": "^17.2.4"
},
"devDependencies": {
- "@backstage/cli": "^0.8.2",
- "@backstage/core-app-api": "^0.1.20",
- "@backstage/dev-utils": "^0.2.12",
- "@backstage/test-utils": "^0.1.21",
+ "@backstage/cli": "^0.9.0",
+ "@backstage/core-app-api": "^0.1.22",
+ "@backstage/dev-utils": "^0.2.13",
+ "@backstage/test-utils": "^0.1.22",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/react-hooks": "^7.0.2",
diff --git a/plugins/kafka/src/Router.tsx b/plugins/kafka/src/Router.tsx
index 977c71712e..c63019253b 100644
--- a/plugins/kafka/src/Router.tsx
+++ b/plugins/kafka/src/Router.tsx
@@ -18,7 +18,6 @@ import { Entity } from '@backstage/catalog-model';
import React from 'react';
import { Route, Routes } from 'react-router';
import { useEntity } from '@backstage/plugin-catalog-react';
-import { rootCatalogKafkaRouteRef } from './plugin';
import { KAFKA_CONSUMER_GROUP_ANNOTATION } from './constants';
import { KafkaTopicsForConsumer } from './components/ConsumerGroupOffsets/ConsumerGroupOffsets';
import { MissingAnnotationEmptyState } from '@backstage/core-components';
@@ -44,10 +43,7 @@ export const Router = (_props: Props) => {
return (
- }
- />
+ } />
);
};
diff --git a/plugins/kafka/src/plugin.ts b/plugins/kafka/src/plugin.ts
index 539b0a0fda..0c345cdabc 100644
--- a/plugins/kafka/src/plugin.ts
+++ b/plugins/kafka/src/plugin.ts
@@ -25,8 +25,7 @@ import {
} from '@backstage/core-plugin-api';
export const rootCatalogKafkaRouteRef = createRouteRef({
- path: '*',
- title: 'Kafka',
+ id: 'kafka',
});
export const kafkaPlugin = createPlugin({
diff --git a/plugins/kubernetes-backend/package.json b/plugins/kubernetes-backend/package.json
index be2903dff5..4edc7b238a 100644
--- a/plugins/kubernetes-backend/package.json
+++ b/plugins/kubernetes-backend/package.json
@@ -32,8 +32,8 @@
"clean": "backstage-cli clean"
},
"dependencies": {
- "@backstage/backend-common": "^0.9.7",
- "@backstage/catalog-model": "^0.9.5",
+ "@backstage/backend-common": "^0.9.10",
+ "@backstage/catalog-model": "^0.9.7",
"@backstage/config": "^0.1.10",
"@backstage/errors": "^0.1.3",
"@backstage/plugin-kubernetes-common": "^0.1.4",
@@ -55,7 +55,7 @@
"yn": "^4.0.0"
},
"devDependencies": {
- "@backstage/cli": "^0.8.0",
+ "@backstage/cli": "^0.9.0",
"@types/aws4": "^1.5.1",
"supertest": "^6.1.3",
"aws-sdk-mock": "^5.2.1",
diff --git a/plugins/kubernetes-common/package.json b/plugins/kubernetes-common/package.json
index 28dfc94c49..3c9cb5e357 100644
--- a/plugins/kubernetes-common/package.json
+++ b/plugins/kubernetes-common/package.json
@@ -35,11 +35,11 @@
"url": "https://github.com/backstage/backstage/issues"
},
"dependencies": {
- "@backstage/catalog-model": "^0.9.5",
+ "@backstage/catalog-model": "^0.9.7",
"@kubernetes/client-node": "^0.15.0"
},
"devDependencies": {
- "@backstage/cli": "^0.8.0"
+ "@backstage/cli": "^0.9.0"
},
"jest": {
"roots": [
diff --git a/plugins/kubernetes/CHANGELOG.md b/plugins/kubernetes/CHANGELOG.md
index a757041b32..0ae3a6c012 100644
--- a/plugins/kubernetes/CHANGELOG.md
+++ b/plugins/kubernetes/CHANGELOG.md
@@ -1,5 +1,16 @@
# @backstage/plugin-kubernetes
+## 0.4.20
+
+### Patch Changes
+
+- a125278b81: Refactor out the deprecated path and icon from RouteRefs
+- Updated dependencies
+ - @backstage/catalog-model@0.9.7
+ - @backstage/plugin-catalog-react@0.6.4
+ - @backstage/core-components@0.7.4
+ - @backstage/core-plugin-api@0.2.0
+
## 0.4.19
### Patch Changes
diff --git a/plugins/kubernetes/package.json b/plugins/kubernetes/package.json
index 9dbca8aff8..618f814c21 100644
--- a/plugins/kubernetes/package.json
+++ b/plugins/kubernetes/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-kubernetes",
"description": "A Backstage plugin that integrates towards Kubernetes",
- "version": "0.4.19",
+ "version": "0.4.20",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -31,11 +31,11 @@
"clean": "backstage-cli clean"
},
"dependencies": {
- "@backstage/catalog-model": "^0.9.6",
+ "@backstage/catalog-model": "^0.9.7",
"@backstage/config": "^0.1.11",
- "@backstage/core-components": "^0.7.3",
- "@backstage/core-plugin-api": "^0.1.13",
- "@backstage/plugin-catalog-react": "^0.6.3",
+ "@backstage/core-components": "^0.7.4",
+ "@backstage/core-plugin-api": "^0.2.0",
+ "@backstage/plugin-catalog-react": "^0.6.4",
"@backstage/plugin-kubernetes-common": "^0.1.5",
"@backstage/theme": "^0.2.13",
"@kubernetes/client-node": "^0.15.0",
@@ -51,10 +51,10 @@
"react-use": "^17.2.4"
},
"devDependencies": {
- "@backstage/cli": "^0.8.2",
- "@backstage/core-app-api": "^0.1.20",
- "@backstage/dev-utils": "^0.2.12",
- "@backstage/test-utils": "^0.1.21",
+ "@backstage/cli": "^0.9.0",
+ "@backstage/core-app-api": "^0.1.22",
+ "@backstage/dev-utils": "^0.2.13",
+ "@backstage/test-utils": "^0.1.22",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/react-hooks": "^7.0.2",
diff --git a/plugins/kubernetes/src/Router.tsx b/plugins/kubernetes/src/Router.tsx
index f98d3ca45e..873bf128e4 100644
--- a/plugins/kubernetes/src/Router.tsx
+++ b/plugins/kubernetes/src/Router.tsx
@@ -18,7 +18,6 @@ import React from 'react';
import { Entity } from '@backstage/catalog-model';
import { useEntity } from '@backstage/plugin-catalog-react';
import { Route, Routes } from 'react-router-dom';
-import { rootCatalogKubernetesRouteRef } from './plugin';
import { KubernetesContent } from './components/KubernetesContent';
import { Button } from '@material-ui/core';
import { MissingAnnotationEmptyState } from '@backstage/core-components';
@@ -53,10 +52,7 @@ export const Router = (_props: Props) => {
) {
return (
- }
- />
+ } />
);
}
diff --git a/plugins/kubernetes/src/plugin.ts b/plugins/kubernetes/src/plugin.ts
index 3f2d8c6a87..a1f2f57621 100644
--- a/plugins/kubernetes/src/plugin.ts
+++ b/plugins/kubernetes/src/plugin.ts
@@ -28,8 +28,7 @@ import {
} from '@backstage/core-plugin-api';
export const rootCatalogKubernetesRouteRef = createRouteRef({
- path: '*',
- title: 'Kubernetes',
+ id: 'kubernetes',
});
export const kubernetesPlugin = createPlugin({
diff --git a/plugins/lighthouse/CHANGELOG.md b/plugins/lighthouse/CHANGELOG.md
index 4ff76d4f1b..48ef9a178d 100644
--- a/plugins/lighthouse/CHANGELOG.md
+++ b/plugins/lighthouse/CHANGELOG.md
@@ -1,5 +1,16 @@
# @backstage/plugin-lighthouse
+## 0.2.30
+
+### Patch Changes
+
+- a125278b81: Refactor out the deprecated path and icon from RouteRefs
+- Updated dependencies
+ - @backstage/catalog-model@0.9.7
+ - @backstage/plugin-catalog-react@0.6.4
+ - @backstage/core-components@0.7.4
+ - @backstage/core-plugin-api@0.2.0
+
## 0.2.29
### Patch Changes
diff --git a/plugins/lighthouse/package.json b/plugins/lighthouse/package.json
index f0dca639fc..e53f6ee261 100644
--- a/plugins/lighthouse/package.json
+++ b/plugins/lighthouse/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-lighthouse",
"description": "A Backstage plugin that integrates towards Lighthouse",
- "version": "0.2.29",
+ "version": "0.2.30",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -32,11 +32,11 @@
"start": "backstage-cli plugin:serve"
},
"dependencies": {
- "@backstage/catalog-model": "^0.9.5",
+ "@backstage/catalog-model": "^0.9.7",
"@backstage/config": "^0.1.10",
- "@backstage/core-components": "^0.7.3",
- "@backstage/core-plugin-api": "^0.1.13",
- "@backstage/plugin-catalog-react": "^0.6.1",
+ "@backstage/core-components": "^0.7.4",
+ "@backstage/core-plugin-api": "^0.2.0",
+ "@backstage/plugin-catalog-react": "^0.6.4",
"@backstage/theme": "^0.2.13",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
@@ -47,10 +47,10 @@
"react-use": "^17.2.4"
},
"devDependencies": {
- "@backstage/cli": "^0.8.2",
- "@backstage/core-app-api": "^0.1.20",
- "@backstage/dev-utils": "^0.2.12",
- "@backstage/test-utils": "^0.1.21",
+ "@backstage/cli": "^0.9.0",
+ "@backstage/core-app-api": "^0.1.22",
+ "@backstage/dev-utils": "^0.2.13",
+ "@backstage/test-utils": "^0.1.22",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/react-hooks": "^7.0.2",
diff --git a/plugins/lighthouse/src/plugin.ts b/plugins/lighthouse/src/plugin.ts
index 25cbeb3f31..1c9bc295f8 100644
--- a/plugins/lighthouse/src/plugin.ts
+++ b/plugins/lighthouse/src/plugin.ts
@@ -25,22 +25,19 @@ import {
} from '@backstage/core-plugin-api';
export const rootRouteRef = createRouteRef({
- path: '',
- title: 'Lighthouse',
+ id: 'lighthouse',
});
export const viewAuditRouteRef = createRouteRef({
- path: 'audit/:id',
- title: 'View Lighthouse Audit',
+ id: 'lighthouse:audit',
});
export const createAuditRouteRef = createRouteRef({
- path: 'create-audit',
- title: 'Create Lighthouse Audit',
+ id: 'lighthouse:create-audit',
});
export const entityContentRouteRef = createRouteRef({
- title: 'Lighthouse Entity Content',
+ id: 'lighthouse:entity-content',
});
export const lighthousePlugin = createPlugin({
diff --git a/plugins/newrelic/CHANGELOG.md b/plugins/newrelic/CHANGELOG.md
index 7ffd4b06d1..89b77e9866 100644
--- a/plugins/newrelic/CHANGELOG.md
+++ b/plugins/newrelic/CHANGELOG.md
@@ -1,5 +1,14 @@
# @backstage/plugin-newrelic
+## 0.3.9
+
+### Patch Changes
+
+- a125278b81: Refactor out the deprecated path and icon from RouteRefs
+- Updated dependencies
+ - @backstage/core-components@0.7.4
+ - @backstage/core-plugin-api@0.2.0
+
## 0.3.8
### Patch Changes
diff --git a/plugins/newrelic/package.json b/plugins/newrelic/package.json
index 271daa566e..16029f1783 100644
--- a/plugins/newrelic/package.json
+++ b/plugins/newrelic/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-newrelic",
"description": "A Backstage plugin that integrates towards New Relic",
- "version": "0.3.8",
+ "version": "0.3.9",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -32,8 +32,8 @@
"clean": "backstage-cli clean"
},
"dependencies": {
- "@backstage/core-components": "^0.7.3",
- "@backstage/core-plugin-api": "^0.1.13",
+ "@backstage/core-components": "^0.7.4",
+ "@backstage/core-plugin-api": "^0.2.0",
"@backstage/theme": "^0.2.13",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
@@ -43,10 +43,10 @@
"react-use": "^17.2.4"
},
"devDependencies": {
- "@backstage/cli": "^0.8.2",
- "@backstage/core-app-api": "^0.1.20",
- "@backstage/dev-utils": "^0.2.12",
- "@backstage/test-utils": "^0.1.21",
+ "@backstage/cli": "^0.9.0",
+ "@backstage/core-app-api": "^0.1.22",
+ "@backstage/dev-utils": "^0.2.13",
+ "@backstage/test-utils": "^0.1.22",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
diff --git a/plugins/newrelic/src/plugin.ts b/plugins/newrelic/src/plugin.ts
index cb2505e36e..6cf77a785f 100644
--- a/plugins/newrelic/src/plugin.ts
+++ b/plugins/newrelic/src/plugin.ts
@@ -24,8 +24,7 @@ import {
} from '@backstage/core-plugin-api';
export const rootRouteRef = createRouteRef({
- path: '/newrelic',
- title: 'newrelic',
+ id: 'newrelic',
});
export const newRelicPlugin = createPlugin({
diff --git a/plugins/org/CHANGELOG.md b/plugins/org/CHANGELOG.md
index 6e1cc7064f..f46095f46a 100644
--- a/plugins/org/CHANGELOG.md
+++ b/plugins/org/CHANGELOG.md
@@ -1,5 +1,15 @@
# @backstage/plugin-org
+## 0.3.28
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/catalog-model@0.9.7
+ - @backstage/plugin-catalog-react@0.6.4
+ - @backstage/core-components@0.7.4
+ - @backstage/core-plugin-api@0.2.0
+
## 0.3.27
### Patch Changes
diff --git a/plugins/org/package.json b/plugins/org/package.json
index 5348f67523..db6cfe4d0b 100644
--- a/plugins/org/package.json
+++ b/plugins/org/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-org",
"description": "A Backstage plugin that helps you create entity pages for your organization",
- "version": "0.3.27",
+ "version": "0.3.28",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -21,10 +21,10 @@
"clean": "backstage-cli clean"
},
"dependencies": {
- "@backstage/catalog-model": "^0.9.5",
- "@backstage/core-components": "^0.7.3",
- "@backstage/core-plugin-api": "^0.1.13",
- "@backstage/plugin-catalog-react": "^0.6.1",
+ "@backstage/catalog-model": "^0.9.7",
+ "@backstage/core-components": "^0.7.4",
+ "@backstage/core-plugin-api": "^0.2.0",
+ "@backstage/plugin-catalog-react": "^0.6.4",
"@backstage/theme": "^0.2.13",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
@@ -37,10 +37,10 @@
"react-use": "^17.2.4"
},
"devDependencies": {
- "@backstage/cli": "^0.8.2",
- "@backstage/core-app-api": "^0.1.20",
- "@backstage/dev-utils": "^0.2.12",
- "@backstage/test-utils": "^0.1.21",
+ "@backstage/cli": "^0.9.0",
+ "@backstage/core-app-api": "^0.1.22",
+ "@backstage/dev-utils": "^0.2.13",
+ "@backstage/test-utils": "^0.1.22",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
diff --git a/plugins/pagerduty/CHANGELOG.md b/plugins/pagerduty/CHANGELOG.md
index 11ae548db8..bb74598f2a 100644
--- a/plugins/pagerduty/CHANGELOG.md
+++ b/plugins/pagerduty/CHANGELOG.md
@@ -1,5 +1,16 @@
# @backstage/plugin-pagerduty
+## 0.3.18
+
+### Patch Changes
+
+- a125278b81: Refactor out the deprecated path and icon from RouteRefs
+- Updated dependencies
+ - @backstage/catalog-model@0.9.7
+ - @backstage/plugin-catalog-react@0.6.4
+ - @backstage/core-components@0.7.4
+ - @backstage/core-plugin-api@0.2.0
+
## 0.3.17
### Patch Changes
diff --git a/plugins/pagerduty/package.json b/plugins/pagerduty/package.json
index 7b42a15bc6..7981b2c104 100644
--- a/plugins/pagerduty/package.json
+++ b/plugins/pagerduty/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-pagerduty",
"description": "A Backstage plugin that integrates towards PagerDuty",
- "version": "0.3.17",
+ "version": "0.3.18",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -31,10 +31,10 @@
"clean": "backstage-cli clean"
},
"dependencies": {
- "@backstage/catalog-model": "^0.9.5",
- "@backstage/core-components": "^0.7.3",
- "@backstage/core-plugin-api": "^0.1.13",
- "@backstage/plugin-catalog-react": "^0.6.1",
+ "@backstage/catalog-model": "^0.9.7",
+ "@backstage/core-components": "^0.7.4",
+ "@backstage/core-plugin-api": "^0.2.0",
+ "@backstage/plugin-catalog-react": "^0.6.4",
"@backstage/theme": "^0.2.13",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
@@ -48,10 +48,10 @@
"react-use": "^17.2.4"
},
"devDependencies": {
- "@backstage/cli": "^0.8.2",
- "@backstage/core-app-api": "^0.1.20",
- "@backstage/dev-utils": "^0.2.12",
- "@backstage/test-utils": "^0.1.21",
+ "@backstage/cli": "^0.9.0",
+ "@backstage/core-app-api": "^0.1.22",
+ "@backstage/dev-utils": "^0.2.13",
+ "@backstage/test-utils": "^0.1.22",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
diff --git a/plugins/pagerduty/src/plugin.ts b/plugins/pagerduty/src/plugin.ts
index fb1e573f0c..d1c4e6d35b 100644
--- a/plugins/pagerduty/src/plugin.ts
+++ b/plugins/pagerduty/src/plugin.ts
@@ -24,8 +24,7 @@ import {
} from '@backstage/core-plugin-api';
export const rootRouteRef = createRouteRef({
- path: '/pagerduty',
- title: 'pagerduty',
+ id: 'pagerduty',
});
export const pagerDutyPlugin = createPlugin({
diff --git a/plugins/permission-common/.eslintrc.js b/plugins/permission-common/.eslintrc.js
new file mode 100644
index 0000000000..16a033dbc6
--- /dev/null
+++ b/plugins/permission-common/.eslintrc.js
@@ -0,0 +1,3 @@
+module.exports = {
+ extends: [require.resolve('@backstage/cli/config/eslint.backend')],
+};
diff --git a/plugins/permission-common/README.md b/plugins/permission-common/README.md
new file mode 100644
index 0000000000..7ba998bed5
--- /dev/null
+++ b/plugins/permission-common/README.md
@@ -0,0 +1,7 @@
+# @backstage/plugin-permission-common
+
+> NOTE: THIS PACKAGE IS EXPERIMENTAL, HERE BE DRAGONS
+
+Isomorphic types and client for Backstage permissions and authorization. For
+more information, see the [authorization
+PRFC](https://github.com/backstage/backstage/pull/7761).
diff --git a/plugins/permission-common/api-report.md b/plugins/permission-common/api-report.md
new file mode 100644
index 0000000000..043b27554c
--- /dev/null
+++ b/plugins/permission-common/api-report.md
@@ -0,0 +1,97 @@
+## API Report File for "@backstage/plugin-permission-common"
+
+> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
+
+```ts
+import { Config } from '@backstage/config';
+
+// @public
+export type AuthorizeRequest = {
+ permission: Permission;
+ resourceRef?: string;
+};
+
+// @public
+export type AuthorizeRequestOptions = {
+ token?: string;
+};
+
+// @public
+export type AuthorizeResponse =
+ | {
+ result: AuthorizeResult.ALLOW | AuthorizeResult.DENY;
+ }
+ | {
+ result: AuthorizeResult.CONDITIONAL;
+ conditions: PermissionCriteria;
+ };
+
+// @public
+export enum AuthorizeResult {
+ ALLOW = 'ALLOW',
+ CONDITIONAL = 'CONDITIONAL',
+ DENY = 'DENY',
+}
+
+// @public
+export type DiscoveryApi = {
+ getBaseUrl(pluginId: string): Promise;
+};
+
+// @public
+export type Identified = T & {
+ id: string;
+};
+
+// @public
+export function isCreatePermission(permission: Permission): boolean;
+
+// @public
+export function isDeletePermission(permission: Permission): boolean;
+
+// @public
+export function isReadPermission(permission: Permission): boolean;
+
+// @public
+export function isUpdatePermission(permission: Permission): boolean;
+
+// @public
+export type Permission = {
+ name: string;
+ attributes: PermissionAttributes;
+ resourceType?: string;
+};
+
+// @public
+export type PermissionAttributes = {
+ action?: 'create' | 'read' | 'update' | 'delete';
+};
+
+// @public
+export class PermissionClient {
+ constructor(options: { discoveryApi: DiscoveryApi; configApi: Config });
+ authorize(
+ requests: AuthorizeRequest[],
+ options?: AuthorizeRequestOptions,
+ ): Promise;
+}
+
+// @public
+export type PermissionCondition = {
+ rule: string;
+ params: TParams;
+};
+
+// @public
+export type PermissionCriteria =
+ | {
+ allOf: PermissionCriteria[];
+ }
+ | {
+ anyOf: PermissionCriteria[];
+ }
+ | {
+ not: PermissionCriteria;
+ }
+ | TQuery;
+```
diff --git a/plugins/permission-common/config.d.ts b/plugins/permission-common/config.d.ts
new file mode 100644
index 0000000000..2378fa4a71
--- /dev/null
+++ b/plugins/permission-common/config.d.ts
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2021 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export interface Config {
+ /** Configuration options for Backstage permissions and authorization */
+ permission?: {
+ /**
+ * Whether authorization is enabled in Backstage. Defaults to false, which means authorization
+ * requests will be automatically allowed without invoking the authorization policy.
+ * @visibility frontend
+ */
+ enabled?: boolean;
+ };
+}
diff --git a/plugins/permission-common/package.json b/plugins/permission-common/package.json
new file mode 100644
index 0000000000..9e1d60f782
--- /dev/null
+++ b/plugins/permission-common/package.json
@@ -0,0 +1,52 @@
+{
+ "name": "@backstage/plugin-permission-common",
+ "description": "Isomorphic types and client for Backstage permissions and authorization",
+ "version": "0.1.0",
+ "main": "src/index.ts",
+ "types": "src/index.ts",
+ "publishConfig": {
+ "access": "public",
+ "main": "dist/index.cjs.js",
+ "module": "dist/index.esm.js",
+ "types": "dist/index.d.ts"
+ },
+ "homepage": "https://backstage.io",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/backstage/backstage",
+ "directory": "plugins/permission-common"
+ },
+ "keywords": [
+ "backstage",
+ "permissions"
+ ],
+ "license": "Apache-2.0",
+ "files": [
+ "dist",
+ "config.d.ts"
+ ],
+ "configSchema": "config.d.ts",
+ "scripts": {
+ "build": "backstage-cli build",
+ "lint": "backstage-cli lint",
+ "test": "backstage-cli test",
+ "prepack": "backstage-cli prepack",
+ "postpack": "backstage-cli postpack",
+ "clean": "backstage-cli clean"
+ },
+ "bugs": {
+ "url": "https://github.com/backstage/backstage/issues"
+ },
+ "dependencies": {
+ "@backstage/config": "^0.1.11",
+ "@backstage/errors": "^0.1.2",
+ "cross-fetch": "^3.0.6",
+ "uuid": "^8.0.0",
+ "zod": "^3.11.6"
+ },
+ "devDependencies": {
+ "@backstage/cli": "^0.9.0",
+ "@types/jest": "^26.0.7",
+ "msw": "^0.35.0"
+ }
+}
diff --git a/plugins/permission-common/src/PermissionClient.test.ts b/plugins/permission-common/src/PermissionClient.test.ts
new file mode 100644
index 0000000000..b2e66986a1
--- /dev/null
+++ b/plugins/permission-common/src/PermissionClient.test.ts
@@ -0,0 +1,193 @@
+/*
+ * Copyright 2021 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { RestContext, rest } from 'msw';
+import { setupServer } from 'msw/node';
+import { ConfigReader } from '@backstage/config';
+import { PermissionClient } from './PermissionClient';
+import { AuthorizeRequest, AuthorizeResult, Identified } from './types/api';
+import { DiscoveryApi } from './types/discovery';
+import { Permission } from './types/permission';
+
+const server = setupServer();
+const token = 'fake-token';
+
+const mockBaseUrl = 'http://backstage:9191/i-am-a-mock-base';
+const discoveryApi: DiscoveryApi = {
+ async getBaseUrl() {
+ return mockBaseUrl;
+ },
+};
+const client: PermissionClient = new PermissionClient({
+ discoveryApi,
+ configApi: new ConfigReader({ permission: { enabled: true } }),
+});
+
+const mockPermission: Permission = {
+ name: 'test.permission',
+ attributes: {},
+ resourceType: 'test-resource',
+};
+
+const mockAuthorizeRequest = {
+ permission: mockPermission,
+ resourceRef: 'foo',
+};
+
+describe('PermissionClient', () => {
+ beforeAll(() => server.listen({ onUnhandledRequest: 'error' }));
+ afterAll(() => server.close());
+ afterEach(() => server.resetHandlers());
+
+ describe('authorize', () => {
+ const mockAuthorizeHandler = jest.fn((req, res, { json }: RestContext) => {
+ const responses = req.body.map((a: Identified) => ({
+ id: a.id,
+ result: AuthorizeResult.ALLOW,
+ }));
+
+ return res(json(responses));
+ });
+
+ beforeEach(() => {
+ server.use(rest.post(`${mockBaseUrl}/authorize`, mockAuthorizeHandler));
+ });
+
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('should fetch entities from correct endpoint', async () => {
+ await client.authorize([mockAuthorizeRequest]);
+ expect(mockAuthorizeHandler).toHaveBeenCalled();
+ });
+
+ it('should include a request body', async () => {
+ await client.authorize([mockAuthorizeRequest]);
+
+ const request = mockAuthorizeHandler.mock.calls[0][0];
+ expect(request.body[0]).toEqual(
+ expect.objectContaining({
+ permission: mockPermission,
+ resourceRef: 'foo',
+ }),
+ );
+ });
+
+ it('should return the response from the fetch request', async () => {
+ const response = await client.authorize([mockAuthorizeRequest]);
+ expect(response[0]).toEqual(
+ expect.objectContaining({ result: AuthorizeResult.ALLOW }),
+ );
+ });
+
+ it('should not include authorization headers if no token is supplied', async () => {
+ await client.authorize([mockAuthorizeRequest]);
+
+ const request = mockAuthorizeHandler.mock.calls[0][0];
+ expect(request.headers.has('authorization')).toEqual(false);
+ });
+
+ it('should include correctly-constructed authorization header if token is supplied', async () => {
+ await client.authorize([mockAuthorizeRequest], { token });
+
+ const request = mockAuthorizeHandler.mock.calls[0][0];
+ expect(request.headers.get('authorization')).toEqual('Bearer fake-token');
+ });
+
+ it('should forward response errors', async () => {
+ mockAuthorizeHandler.mockImplementationOnce(
+ (_req, res, { status }: RestContext) => {
+ return res(status(401));
+ },
+ );
+ await expect(
+ client.authorize([mockAuthorizeRequest], { token }),
+ ).rejects.toThrowError(/request failed with 401/i);
+ });
+
+ it('should reject responses with missing ids', async () => {
+ mockAuthorizeHandler.mockImplementationOnce(
+ (_req, res, { json }: RestContext) => {
+ return res(json([{ id: 'wrong-id', result: AuthorizeResult.ALLOW }]));
+ },
+ );
+ await expect(
+ client.authorize([mockAuthorizeRequest], { token }),
+ ).rejects.toThrowError(/Unexpected authorization response/i);
+ });
+
+ it('should reject invalid responses', async () => {
+ mockAuthorizeHandler.mockImplementationOnce(
+ (req, res, { json }: RestContext) => {
+ const responses = req.body.map((a: Identified) => ({
+ id: a.id,
+ outcome: AuthorizeResult.ALLOW,
+ }));
+
+ return res(json(responses));
+ },
+ );
+ await expect(
+ client.authorize([mockAuthorizeRequest], { token }),
+ ).rejects.toThrowError(/invalid input/i);
+ });
+
+ it('should allow all when permission.enabled is false', async () => {
+ mockAuthorizeHandler.mockImplementationOnce(
+ (req, res, { json }: RestContext) => {
+ const responses = req.body.map((a: Identified) => ({
+ id: a.id,
+ outcome: AuthorizeResult.DENY,
+ }));
+
+ return res(json(responses));
+ },
+ );
+ const disabled = new PermissionClient({
+ discoveryApi,
+ configApi: new ConfigReader({ permission: { enabled: false } }),
+ });
+ const response = await disabled.authorize([mockAuthorizeRequest]);
+ expect(response[0]).toEqual(
+ expect.objectContaining({ result: AuthorizeResult.ALLOW }),
+ );
+ expect(mockAuthorizeHandler).not.toBeCalled();
+ });
+
+ it('should allow all when permission.enabled is not configured', async () => {
+ mockAuthorizeHandler.mockImplementationOnce(
+ (req, res, { json }: RestContext) => {
+ const responses = req.body.map((a: Identified) => ({
+ id: a.id,
+ outcome: AuthorizeResult.DENY,
+ }));
+
+ return res(json(responses));
+ },
+ );
+ const disabled = new PermissionClient({
+ discoveryApi,
+ configApi: new ConfigReader({}),
+ });
+ const response = await disabled.authorize([mockAuthorizeRequest]);
+ expect(response[0]).toEqual(
+ expect.objectContaining({ result: AuthorizeResult.ALLOW }),
+ );
+ expect(mockAuthorizeHandler).not.toBeCalled();
+ });
+ });
+});
diff --git a/plugins/permission-common/src/PermissionClient.ts b/plugins/permission-common/src/PermissionClient.ts
new file mode 100644
index 0000000000..f98d40a3c4
--- /dev/null
+++ b/plugins/permission-common/src/PermissionClient.ts
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2021 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Config } from '@backstage/config';
+import { ResponseError } from '@backstage/errors';
+import fetch from 'cross-fetch';
+import * as uuid from 'uuid';
+import { z } from 'zod';
+import {
+ AuthorizeResult,
+ AuthorizeRequest,
+ AuthorizeResponse,
+ Identified,
+ PermissionCriteria,
+ PermissionCondition,
+} from './types/api';
+import { DiscoveryApi } from './types/discovery';
+
+const permissionCriteriaSchema: z.ZodSchema<
+ PermissionCriteria
+> = z.lazy(() =>
+ z
+ .object({
+ rule: z.string(),
+ params: z.array(z.unknown()),
+ })
+ .or(z.object({ anyOf: z.array(permissionCriteriaSchema) }))
+ .or(z.object({ allOf: z.array(permissionCriteriaSchema) }))
+ .or(z.object({ not: permissionCriteriaSchema })),
+);
+
+const responseSchema = z.array(
+ z
+ .object({
+ id: z.string(),
+ result: z
+ .literal(AuthorizeResult.ALLOW)
+ .or(z.literal(AuthorizeResult.DENY)),
+ })
+ .or(
+ z.object({
+ id: z.string(),
+ result: z.literal(AuthorizeResult.CONDITIONAL),
+ conditions: permissionCriteriaSchema,
+ }),
+ ),
+);
+
+/**
+ * Options for authorization requests; currently only an optional auth token.
+ * @public
+ */
+export type AuthorizeRequestOptions = {
+ token?: string;
+};
+
+/**
+ * An isomorphic client for requesting authorization for Backstage permissions.
+ * @public
+ */
+export class PermissionClient {
+ private readonly enabled: boolean;
+ private readonly discoveryApi: DiscoveryApi;
+
+ constructor(options: { discoveryApi: DiscoveryApi; configApi: Config }) {
+ this.discoveryApi = options.discoveryApi;
+ this.enabled =
+ options.configApi.getOptionalBoolean('permission.enabled') ?? false;
+ }
+
+ /**
+ * Request authorization from the permission-backend for the given set of permissions.
+ *
+ * Authorization requests check that a given Backstage user can perform a protected operation,
+ * potentially for a specific resource (such as a catalog entity). The Backstage identity token
+ * should be included in the `options` if available.
+ *
+ * Permissions can be imported from plugins exposing them, such as `catalogEntityReadPermission`.
+ *
+ * The response will be either ALLOW or DENY when either the permission has no resourceType, or a
+ * resourceRef is provided in the request. For permissions with a resourceType, CONDITIONAL may be
+ * returned if no resourceRef is provided in the request. Conditional responses are intended only
+ * for backends which have access to the data source for permissioned resources, so that filters
+ * can be applied when loading collections of resources.
+ * @public
+ */
+ async authorize(
+ requests: AuthorizeRequest[],
+ options?: AuthorizeRequestOptions,
+ ): Promise {
+ // TODO(permissions): it would be great to provide some kind of typing guarantee that
+ // conditional responses will only ever be returned for requests containing a resourceType
+ // but no resourceRef. That way clients who aren't prepared to handle filtering according
+ // to conditions can be guaranteed that they won't unexpectedly get a CONDITIONAL response.
+
+ if (!this.enabled) {
+ return requests.map(_ => ({ result: AuthorizeResult.ALLOW }));
+ }
+
+ const identifiedRequests: Identified[] = requests.map(
+ request => ({
+ id: uuid.v4(),
+ ...request,
+ }),
+ );
+
+ const permissionApi = await this.discoveryApi.getBaseUrl('permission');
+ const response = await fetch(`${permissionApi}/authorize`, {
+ method: 'POST',
+ body: JSON.stringify(identifiedRequests),
+ headers: {
+ ...this.getAuthorizationHeader(options?.token),
+ 'content-type': 'application/json',
+ },
+ });
+ if (!response.ok) {
+ throw await ResponseError.fromResponse(response);
+ }
+
+ const identifiedResponses = await response.json();
+ this.assertValidResponses(identifiedRequests, identifiedResponses);
+
+ const responsesById = identifiedResponses.reduce((acc, r) => {
+ acc[r.id] = r;
+ return acc;
+ }, {} as Record>);
+
+ return identifiedRequests.map(request => responsesById[request.id]);
+ }
+
+ private getAuthorizationHeader(token?: string): Record {
+ return token ? { Authorization: `Bearer ${token}` } : {};
+ }
+
+ private assertValidResponses(
+ requests: Identified[],
+ json: any,
+ ): asserts json is Identified[] {
+ const authorizedResponses = responseSchema.parse(json);
+ const responseIds = authorizedResponses.map(r => r.id);
+ const hasAllRequestIds = requests.every(r => responseIds.includes(r.id));
+ if (!hasAllRequestIds) {
+ throw new Error(
+ 'Unexpected authorization response from permission-backend',
+ );
+ }
+ }
+}
diff --git a/plugins/permission-common/src/index.ts b/plugins/permission-common/src/index.ts
new file mode 100644
index 0000000000..7c3f6dd613
--- /dev/null
+++ b/plugins/permission-common/src/index.ts
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2021 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Isomorphic types and client for Backstage permissions and authorization
+ *
+ * @packageDocumentation
+ */
+export * from './types';
+export * from './permissions';
+export * from './PermissionClient';
diff --git a/plugins/permission-common/src/permissions/index.ts b/plugins/permission-common/src/permissions/index.ts
new file mode 100644
index 0000000000..736d898539
--- /dev/null
+++ b/plugins/permission-common/src/permissions/index.ts
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2021 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export * from './util';
diff --git a/plugins/permission-common/src/permissions/util.ts b/plugins/permission-common/src/permissions/util.ts
new file mode 100644
index 0000000000..188609f4ae
--- /dev/null
+++ b/plugins/permission-common/src/permissions/util.ts
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2021 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Permission } from '../types';
+
+/**
+ * Check if a given permission is related to a create action.
+ * @public
+ */
+export function isCreatePermission(permission: Permission) {
+ return permission.attributes.action === 'create';
+}
+
+/**
+ * Check if a given permission is related to a read action.
+ * @public
+ */
+export function isReadPermission(permission: Permission) {
+ return permission.attributes.action === 'read';
+}
+
+/**
+ * Check if a given permission is related to an update action.
+ * @public
+ */
+export function isUpdatePermission(permission: Permission) {
+ return permission.attributes.action === 'update';
+}
+
+/**
+ * Check if a given permission is related to a delete action.
+ * @public
+ */
+export function isDeletePermission(permission: Permission) {
+ return permission.attributes.action === 'delete';
+}
diff --git a/plugins/permission-common/src/types/api.ts b/plugins/permission-common/src/types/api.ts
new file mode 100644
index 0000000000..7f4cd0730b
--- /dev/null
+++ b/plugins/permission-common/src/types/api.ts
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2021 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Permission } from './permission';
+
+/**
+ * A request with a UUID identifier, so that batched responses can be matched up with the original
+ * requests.
+ * @public
+ */
+export type Identified = T & { id: string };
+
+/**
+ * The result of an authorization request.
+ * @public
+ */
+export enum AuthorizeResult {
+ /**
+ * The authorization request is denied.
+ */
+ DENY = 'DENY',
+ /**
+ * The authorization request is allowed.
+ */
+ ALLOW = 'ALLOW',
+ /**
+ * The authorization request is allowed if the provided conditions are met.
+ */
+ CONDITIONAL = 'CONDITIONAL',
+}
+
+/**
+ * An authorization request for {@link PermissionClient#authorize}.
+ * @public
+ */
+export type AuthorizeRequest = {
+ permission: Permission;
+ resourceRef?: string;
+};
+
+/**
+ * A condition returned with a CONDITIONAL authorization response.
+ *
+ * Conditions are a reference to a rule defined by a plugin, and parameters to apply the rule. For
+ * example, a rule might be `isOwner` from the catalog-backend, and params may be a list of entity
+ * claims from a identity token.
+ * @public
+ */
+export type PermissionCondition = {
+ rule: string;
+ params: TParams;
+};
+
+/**
+ * Composes several {@link PermissionCondition}s as criteria with a nested AND/OR structure.
+ * @public
+ */
+export type PermissionCriteria =
+ | { allOf: PermissionCriteria[] }
+ | { anyOf: PermissionCriteria[] }
+ | { not: PermissionCriteria }
+ | TQuery;
+
+/**
+ * An authorization response from {@link PermissionClient#authorize}.
+ * @public
+ */
+export type AuthorizeResponse =
+ | { result: AuthorizeResult.ALLOW | AuthorizeResult.DENY }
+ | {
+ result: AuthorizeResult.CONDITIONAL;
+ conditions: PermissionCriteria;
+ };
diff --git a/plugins/permission-common/src/types/discovery.ts b/plugins/permission-common/src/types/discovery.ts
new file mode 100644
index 0000000000..19ee5ed19c
--- /dev/null
+++ b/plugins/permission-common/src/types/discovery.ts
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2021 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * This is a copy of the core DiscoveryApi, to avoid importing core.
+ *
+ * @public
+ */
+export type DiscoveryApi = {
+ getBaseUrl(pluginId: string): Promise;
+};
diff --git a/plugins/permission-common/src/types/index.ts b/plugins/permission-common/src/types/index.ts
new file mode 100644
index 0000000000..e21fa19e8b
--- /dev/null
+++ b/plugins/permission-common/src/types/index.ts
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2021 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export { AuthorizeResult } from './api';
+export type {
+ AuthorizeRequest,
+ AuthorizeResponse,
+ Identified,
+ PermissionCondition,
+ PermissionCriteria,
+} from './api';
+export type { DiscoveryApi } from './discovery';
+export type { PermissionAttributes, Permission } from './permission';
diff --git a/plugins/permission-common/src/types/permission.ts b/plugins/permission-common/src/types/permission.ts
new file mode 100644
index 0000000000..181b36065f
--- /dev/null
+++ b/plugins/permission-common/src/types/permission.ts
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2021 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * The attributes related to a given permission; these should be generic and widely applicable to
+ * all permissions in the system.
+ * @public
+ */
+export type PermissionAttributes = {
+ action?: 'create' | 'read' | 'update' | 'delete';
+};
+
+/**
+ * A permission that can be checked through authorization.
+ *
+ * Permissions are the "what" part of authorization, the action to be performed. This may be reading
+ * an entity from the catalog, executing a software template, or any other action a plugin author
+ * may wish to protect.
+ *
+ * To evaluate authorization, a permission is paired with a Backstage identity (the "who") and
+ * evaluated using an authorization policy.
+ * @public
+ */
+export type Permission = {
+ name: string;
+ attributes: PermissionAttributes;
+ resourceType?: string;
+};
diff --git a/plugins/proxy-backend/package.json b/plugins/proxy-backend/package.json
index c43ea8b31a..05499d88e8 100644
--- a/plugins/proxy-backend/package.json
+++ b/plugins/proxy-backend/package.json
@@ -29,7 +29,7 @@
"clean": "backstage-cli clean"
},
"dependencies": {
- "@backstage/backend-common": "^0.9.7",
+ "@backstage/backend-common": "^0.9.10",
"@backstage/config": "^0.1.8",
"@types/express": "^4.17.6",
"express": "^4.17.1",
@@ -43,7 +43,7 @@
"yup": "^0.32.9"
},
"devDependencies": {
- "@backstage/cli": "^0.8.0",
+ "@backstage/cli": "^0.9.0",
"@types/http-proxy-middleware": "^0.19.3",
"@types/supertest": "^2.0.8",
"@types/uuid": "^8.0.0",
diff --git a/plugins/proxy-backend/src/run.ts b/plugins/proxy-backend/src/run.ts
index 54d2716290..0a3ed2b7f0 100644
--- a/plugins/proxy-backend/src/run.ts
+++ b/plugins/proxy-backend/src/run.ts
@@ -18,7 +18,7 @@ import { getRootLogger } from '@backstage/backend-common';
import yn from 'yn';
import { startStandaloneServer } from './service/standaloneServer';
-const port = process.env.PLUGIN_PORT ? Number(process.env.PLUGIN_PORT) : 7000;
+const port = process.env.PLUGIN_PORT ? Number(process.env.PLUGIN_PORT) : 7007;
const enableCors = yn(process.env.PLUGIN_CORS, { default: false });
const logger = getRootLogger();
diff --git a/plugins/proxy-backend/src/service/router.test.ts b/plugins/proxy-backend/src/service/router.test.ts
index df999dbe2a..ddb524d3aa 100644
--- a/plugins/proxy-backend/src/service/router.test.ts
+++ b/plugins/proxy-backend/src/service/router.test.ts
@@ -34,9 +34,9 @@ describe('createRouter', () => {
const logger = getVoidLogger();
const config = new ConfigReader({
backend: {
- baseUrl: 'https://example.com:7000',
+ baseUrl: 'https://example.com:7007',
listen: {
- port: 7000,
+ port: 7007,
},
},
});
diff --git a/plugins/rollbar-backend/package.json b/plugins/rollbar-backend/package.json
index 1d7be09b85..be8b6e65e5 100644
--- a/plugins/rollbar-backend/package.json
+++ b/plugins/rollbar-backend/package.json
@@ -31,7 +31,7 @@
"clean": "backstage-cli clean"
},
"dependencies": {
- "@backstage/backend-common": "^0.9.7",
+ "@backstage/backend-common": "^0.9.10",
"@backstage/config": "^0.1.10",
"@types/express": "^4.17.6",
"axios": "^0.21.1",
@@ -48,7 +48,7 @@
"yn": "^4.0.0"
},
"devDependencies": {
- "@backstage/cli": "^0.8.0",
+ "@backstage/cli": "^0.9.0",
"@types/supertest": "^2.0.8",
"supertest": "^6.1.3"
},
diff --git a/plugins/rollbar-backend/src/run.ts b/plugins/rollbar-backend/src/run.ts
index 54d2716290..0a3ed2b7f0 100644
--- a/plugins/rollbar-backend/src/run.ts
+++ b/plugins/rollbar-backend/src/run.ts
@@ -18,7 +18,7 @@ import { getRootLogger } from '@backstage/backend-common';
import yn from 'yn';
import { startStandaloneServer } from './service/standaloneServer';
-const port = process.env.PLUGIN_PORT ? Number(process.env.PLUGIN_PORT) : 7000;
+const port = process.env.PLUGIN_PORT ? Number(process.env.PLUGIN_PORT) : 7007;
const enableCors = yn(process.env.PLUGIN_CORS, { default: false });
const logger = getRootLogger();
diff --git a/plugins/rollbar/CHANGELOG.md b/plugins/rollbar/CHANGELOG.md
index 4f7f3c9a16..aee0a74525 100644
--- a/plugins/rollbar/CHANGELOG.md
+++ b/plugins/rollbar/CHANGELOG.md
@@ -1,5 +1,16 @@
# @backstage/plugin-rollbar
+## 0.3.19
+
+### Patch Changes
+
+- a125278b81: Refactor out the deprecated path and icon from RouteRefs
+- Updated dependencies
+ - @backstage/catalog-model@0.9.7
+ - @backstage/plugin-catalog-react@0.6.4
+ - @backstage/core-components@0.7.4
+ - @backstage/core-plugin-api@0.2.0
+
## 0.3.18
### Patch Changes
diff --git a/plugins/rollbar/package.json b/plugins/rollbar/package.json
index 2e2da0a284..ee3fc9f5a6 100644
--- a/plugins/rollbar/package.json
+++ b/plugins/rollbar/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-rollbar",
"description": "A Backstage plugin that integrates towards Rollbar",
- "version": "0.3.18",
+ "version": "0.3.19",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -32,10 +32,10 @@
"clean": "backstage-cli clean"
},
"dependencies": {
- "@backstage/catalog-model": "^0.9.5",
- "@backstage/core-components": "^0.7.3",
- "@backstage/core-plugin-api": "^0.1.13",
- "@backstage/plugin-catalog-react": "^0.6.1",
+ "@backstage/catalog-model": "^0.9.7",
+ "@backstage/core-components": "^0.7.4",
+ "@backstage/core-plugin-api": "^0.2.0",
+ "@backstage/plugin-catalog-react": "^0.6.4",
"@backstage/theme": "^0.2.13",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
@@ -49,10 +49,10 @@
"react-use": "^17.2.4"
},
"devDependencies": {
- "@backstage/cli": "^0.8.2",
- "@backstage/core-app-api": "^0.1.20",
- "@backstage/dev-utils": "^0.2.12",
- "@backstage/test-utils": "^0.1.21",
+ "@backstage/cli": "^0.9.0",
+ "@backstage/core-app-api": "^0.1.22",
+ "@backstage/dev-utils": "^0.2.13",
+ "@backstage/test-utils": "^0.1.22",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/react-hooks": "^7.0.2",
diff --git a/plugins/rollbar/src/components/Router.tsx b/plugins/rollbar/src/components/Router.tsx
index 845da22a20..032bf0166e 100644
--- a/plugins/rollbar/src/components/Router.tsx
+++ b/plugins/rollbar/src/components/Router.tsx
@@ -19,7 +19,6 @@ import { useEntity } from '@backstage/plugin-catalog-react';
import React from 'react';
import { Route, Routes } from 'react-router';
import { ROLLBAR_ANNOTATION } from '../constants';
-import { rootRouteRef } from '../plugin';
import { EntityPageRollbar } from './EntityPageRollbar/EntityPageRollbar';
import { MissingAnnotationEmptyState } from '@backstage/core-components';
@@ -40,10 +39,7 @@ export const Router = (_props: Props) => {
return (
- }
- />
+ } />
);
};
diff --git a/plugins/rollbar/src/plugin.ts b/plugins/rollbar/src/plugin.ts
index b0de6510f3..3ca456058f 100644
--- a/plugins/rollbar/src/plugin.ts
+++ b/plugins/rollbar/src/plugin.ts
@@ -26,8 +26,7 @@ import {
} from '@backstage/core-plugin-api';
export const rootRouteRef = createRouteRef({
- path: '',
- title: 'Rollbar',
+ id: 'rollbar',
});
export const rollbarPlugin = createPlugin({
diff --git a/plugins/scaffolder-backend-module-cookiecutter/package.json b/plugins/scaffolder-backend-module-cookiecutter/package.json
index 36a6310aa2..90258d3c28 100644
--- a/plugins/scaffolder-backend-module-cookiecutter/package.json
+++ b/plugins/scaffolder-backend-module-cookiecutter/package.json
@@ -20,10 +20,10 @@
"clean": "backstage-cli clean"
},
"dependencies": {
- "@backstage/backend-common": "^0.9.9",
+ "@backstage/backend-common": "^0.9.10",
"@backstage/errors": "^0.1.4",
"@backstage/integration": "^0.6.9",
- "@backstage/plugin-scaffolder-backend": "^0.15.12",
+ "@backstage/plugin-scaffolder-backend": "^0.15.13",
"@backstage/config": "^0.1.11",
"@backstage/types": "^0.1.1",
"command-exists": "^1.2.9",
@@ -33,7 +33,7 @@
"yn": "^4.0.0"
},
"devDependencies": {
- "@backstage/cli": "^0.8.2",
+ "@backstage/cli": "^0.9.0",
"@types/fs-extra": "^9.0.1",
"@types/mock-fs": "^4.13.0",
"@types/jest": "^26.0.7",
diff --git a/plugins/scaffolder-backend-module-rails/package.json b/plugins/scaffolder-backend-module-rails/package.json
index f4ace02eca..37b077568e 100644
--- a/plugins/scaffolder-backend-module-rails/package.json
+++ b/plugins/scaffolder-backend-module-rails/package.json
@@ -21,8 +21,8 @@
"clean": "backstage-cli clean"
},
"dependencies": {
- "@backstage/backend-common": "^0.9.9",
- "@backstage/plugin-scaffolder-backend": "^0.15.12",
+ "@backstage/backend-common": "^0.9.10",
+ "@backstage/plugin-scaffolder-backend": "^0.15.13",
"@backstage/config": "^0.1.11",
"@backstage/errors": "^0.1.4",
"@backstage/integration": "^0.6.9",
@@ -31,7 +31,7 @@
"fs-extra": "^9.0.0"
},
"devDependencies": {
- "@backstage/cli": "^0.8.2",
+ "@backstage/cli": "^0.9.0",
"@types/jest": "^26.0.7",
"@types/node": "^14.14.32",
"@types/command-exists": "^1.2.0",
diff --git a/plugins/scaffolder-backend/CHANGELOG.md b/plugins/scaffolder-backend/CHANGELOG.md
index de033eabbd..f71849a1d2 100644
--- a/plugins/scaffolder-backend/CHANGELOG.md
+++ b/plugins/scaffolder-backend/CHANGELOG.md
@@ -1,5 +1,17 @@
# @backstage/plugin-scaffolder-backend
+## 0.15.13
+
+### Patch Changes
+
+- 26eb174ce8: Skip empty file names when scaffolding with nunjucks
+- ecdcbd08ee: Expose template metadata to custom action handler in Scaffolder.
+- Updated dependencies
+ - @backstage/catalog-client@0.5.2
+ - @backstage/catalog-model@0.9.7
+ - @backstage/backend-common@0.9.10
+ - @backstage/plugin-catalog-backend@0.17.4
+
## 0.15.12
### Patch Changes
diff --git a/plugins/scaffolder-backend/api-report.md b/plugins/scaffolder-backend/api-report.md
index 3f9b77a3a3..4b27b938c9 100644
--- a/plugins/scaffolder-backend/api-report.md
+++ b/plugins/scaffolder-backend/api-report.md
@@ -41,6 +41,7 @@ export type ActionContext = {
input: Input;
output(name: string, value: JsonValue): void;
createTemporaryDirectory(): Promise;
+ metadata?: TemplateMetadata;
};
// Warning: (ae-missing-release-tag) "CatalogEntityClient" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
@@ -432,6 +433,8 @@ export interface TaskSpecV1beta2 {
// (undocumented)
baseUrl?: string;
// (undocumented)
+ metadata?: TemplateMetadata;
+ // (undocumented)
output: {
[name: string]: string;
};
@@ -454,6 +457,8 @@ export interface TaskSpecV1beta3 {
// (undocumented)
baseUrl?: string;
// (undocumented)
+ metadata?: TemplateMetadata;
+ // (undocumented)
output: {
[name: string]: JsonValue;
};
@@ -558,4 +563,9 @@ export class TemplateActionRegistry {
action: TemplateAction,
): void;
}
+
+// @public
+export type TemplateMetadata = {
+ name: string;
+};
```
diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json
index e5d13801ba..374cfeffd6 100644
--- a/plugins/scaffolder-backend/package.json
+++ b/plugins/scaffolder-backend/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-scaffolder-backend",
"description": "The Backstage backend plugin that helps you create new things",
- "version": "0.15.12",
+ "version": "0.15.13",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -30,13 +30,13 @@
"clean": "backstage-cli clean"
},
"dependencies": {
- "@backstage/backend-common": "^0.9.9",
- "@backstage/catalog-client": "^0.5.1",
- "@backstage/catalog-model": "^0.9.6",
+ "@backstage/backend-common": "^0.9.10",
+ "@backstage/catalog-client": "^0.5.2",
+ "@backstage/catalog-model": "^0.9.7",
"@backstage/config": "^0.1.11",
"@backstage/errors": "^0.1.4",
"@backstage/integration": "^0.6.9",
- "@backstage/plugin-catalog-backend": "^0.17.3",
+ "@backstage/plugin-catalog-backend": "^0.17.4",
"@backstage/plugin-scaffolder-common": "^0.1.1",
"@backstage/plugin-scaffolder-backend-module-cookiecutter": "^0.1.4",
"@backstage/types": "^0.1.1",
@@ -71,8 +71,8 @@
"yaml": "^1.10.0"
},
"devDependencies": {
- "@backstage/cli": "^0.8.2",
- "@backstage/test-utils": "^0.1.21",
+ "@backstage/cli": "^0.9.0",
+ "@backstage/test-utils": "^0.1.22",
"@types/command-exists": "^1.2.0",
"@types/fs-extra": "^9.0.1",
"@types/git-url-parse": "^9.0.0",
diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/types.ts b/plugins/scaffolder-backend/src/scaffolder/actions/types.ts
index ca3459d29c..52149be724 100644
--- a/plugins/scaffolder-backend/src/scaffolder/actions/types.ts
+++ b/plugins/scaffolder-backend/src/scaffolder/actions/types.ts
@@ -18,6 +18,7 @@ import { Logger } from 'winston';
import { Writable } from 'stream';
import { JsonValue, JsonObject } from '@backstage/types';
import { Schema } from 'jsonschema';
+import { TemplateMetadata } from '../tasks/types';
type PartialJsonObject = Partial;
type PartialJsonValue = PartialJsonObject | JsonValue | undefined;
@@ -44,6 +45,8 @@ export type ActionContext = {
* Creates a temporary directory for use by the action, which is then cleaned up automatically.
*/
createTemporaryDirectory(): Promise;
+
+ metadata?: TemplateMetadata;
};
export type TemplateAction = {
diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/DefaultWorkflowRunner.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/DefaultWorkflowRunner.test.ts
index d07ff29fae..1f7877791c 100644
--- a/plugins/scaffolder-backend/src/scaffolder/tasks/DefaultWorkflowRunner.test.ts
+++ b/plugins/scaffolder-backend/src/scaffolder/tasks/DefaultWorkflowRunner.test.ts
@@ -146,6 +146,30 @@ describe('DefaultWorkflowRunner', () => {
expect(fakeActionHandler).toHaveBeenCalledTimes(1);
});
+
+ it('should pass metadata through', async () => {
+ const templateName = 'template name';
+ const task = createMockTaskWithSpec({
+ apiVersion: 'scaffolder.backstage.io/v1beta3',
+ parameters: {},
+ output: {},
+ steps: [
+ {
+ id: 'test',
+ name: 'name',
+ action: 'jest-validated-action',
+ input: { foo: 1 },
+ },
+ ],
+ metadata: { name: templateName },
+ });
+
+ await runner.execute(task);
+
+ expect(fakeActionHandler.mock.calls[0][0].metadata).toEqual({
+ name: templateName,
+ });
+ });
});
describe('conditionals', () => {
diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/HandlebarsWorkflowRunner.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/HandlebarsWorkflowRunner.ts
index 77d0a7be09..5582b53023 100644
--- a/plugins/scaffolder-backend/src/scaffolder/tasks/HandlebarsWorkflowRunner.ts
+++ b/plugins/scaffolder-backend/src/scaffolder/tasks/HandlebarsWorkflowRunner.ts
@@ -225,6 +225,12 @@ export class HandlebarsWorkflowRunner implements WorkflowRunner {
input: JSON.stringify(input, null, 2),
});
+ if (!task.spec.metadata) {
+ console.warn(
+ 'DEPRECATION NOTICE: metadata is undefined. metadata will be required in the future.',
+ );
+ }
+
await action.handler({
baseUrl: task.spec.baseUrl,
logger: taskLogger,
@@ -242,6 +248,7 @@ export class HandlebarsWorkflowRunner implements WorkflowRunner {
output(name: string, value: JsonValue) {
stepOutputs[name] = value;
},
+ metadata: task.spec.metadata,
});
// Remove all temporary directories that were created when executing the action
diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/LegacyWorkflowRunner.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/LegacyWorkflowRunner.test.ts
index 91e772dc4e..9b05c43831 100644
--- a/plugins/scaffolder-backend/src/scaffolder/tasks/LegacyWorkflowRunner.test.ts
+++ b/plugins/scaffolder-backend/src/scaffolder/tasks/LegacyWorkflowRunner.test.ts
@@ -28,6 +28,7 @@ describe('LegacyWorkflowRunner', () => {
let runner: HandlebarsWorkflowRunner;
const logger = getVoidLogger();
let actionRegistry = new TemplateActionRegistry();
+ let fakeActionHandler: jest.Mock;
const integrations = ScmIntegrations.fromConfig(
new ConfigReader({
@@ -59,6 +60,11 @@ describe('LegacyWorkflowRunner', () => {
ctx.output('badOutput', false);
},
});
+ fakeActionHandler = jest.fn();
+ actionRegistry.register({
+ id: 'test-metadata-action',
+ handler: fakeActionHandler,
+ });
runner = new HandlebarsWorkflowRunner({
actionRegistry,
@@ -87,6 +93,30 @@ describe('LegacyWorkflowRunner', () => {
);
});
+ it('should pass metadata through', async () => {
+ const templateName = 'template name';
+ const task = createMockTaskWithSpec({
+ apiVersion: 'backstage.io/v1beta2',
+ steps: [
+ {
+ id: 'test',
+ name: 'name',
+ action: 'test-metadata-action',
+ input: { foo: 1 },
+ },
+ ],
+ output: {},
+ values: {},
+ metadata: { name: templateName },
+ });
+
+ await runner.execute(task);
+
+ expect(fakeActionHandler.mock.calls[0][0].metadata).toEqual({
+ name: templateName,
+ });
+ });
+
describe('templating', () => {
it('should template the output', async () => {
const task = createMockTaskWithSpec({
diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts
index 587a93e1f2..c66d967103 100644
--- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts
+++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts
@@ -228,6 +228,12 @@ export class NunjucksWorkflowRunner implements WorkflowRunner {
const tmpDirs = new Array();
const stepOutput: { [outputName: string]: JsonValue } = {};
+ if (!task.spec.metadata) {
+ console.warn(
+ 'DEPRECATION NOTICE: metadata is undefined. metadata will be required in the future.',
+ );
+ }
+
await action.handler({
baseUrl: task.spec.baseUrl,
input,
@@ -244,6 +250,7 @@ export class NunjucksWorkflowRunner implements WorkflowRunner {
output(name: string, value: JsonValue) {
stepOutput[name] = value;
},
+ metadata: task.spec.metadata,
});
// Remove all temporary directories that were created when executing the action
diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/index.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/index.ts
index 510c862dae..f563a08df6 100644
--- a/plugins/scaffolder-backend/src/scaffolder/tasks/index.ts
+++ b/plugins/scaffolder-backend/src/scaffolder/tasks/index.ts
@@ -34,4 +34,5 @@ export type {
TaskContext,
TaskStore,
DispatchResult,
+ TemplateMetadata,
} from './types';
diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts
index 6cfd914bd2..b492f8ad2c 100644
--- a/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts
+++ b/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts
@@ -69,6 +69,15 @@ export type SerializedTaskEvent = {
createdAt: string;
};
+/**
+ * TemplateMetadata
+ *
+ * @public
+ */
+export type TemplateMetadata = {
+ name: string;
+};
+
/**
* TaskSpecV1beta2
*
@@ -86,6 +95,7 @@ export interface TaskSpecV1beta2 {
if?: string | boolean;
}>;
output: { [name: string]: string };
+ metadata?: TemplateMetadata;
}
export interface TaskStep {
@@ -107,6 +117,7 @@ export interface TaskSpecV1beta3 {
parameters: JsonObject;
steps: TaskStep[];
output: { [name: string]: JsonValue };
+ metadata?: TemplateMetadata;
}
/**
diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts
index 2ae30593f2..b8f2968287 100644
--- a/plugins/scaffolder-backend/src/service/router.ts
+++ b/plugins/scaffolder-backend/src/service/router.ts
@@ -209,6 +209,7 @@ export async function createRouter(
name: step.name ?? step.action,
})),
output: template.spec.output ?? {},
+ metadata: { name: template.metadata?.name },
}
: {
apiVersion: template.apiVersion,
@@ -220,6 +221,7 @@ export async function createRouter(
name: step.name ?? step.action,
})),
output: template.spec.output ?? {},
+ metadata: { name: template.metadata?.name },
};
} else {
throw new InputError(
diff --git a/plugins/scaffolder-common/package.json b/plugins/scaffolder-common/package.json
index 006d8ad7aa..05d5ae3031 100644
--- a/plugins/scaffolder-common/package.json
+++ b/plugins/scaffolder-common/package.json
@@ -36,10 +36,10 @@
"url": "https://github.com/backstage/backstage/issues"
},
"dependencies": {
- "@backstage/catalog-model": "^0.9.6",
+ "@backstage/catalog-model": "^0.9.7",
"@backstage/types": "^0.1.1"
},
"devDependencies": {
- "@backstage/cli": "^0.8.1"
+ "@backstage/cli": "^0.9.0"
}
}
diff --git a/plugins/scaffolder/CHANGELOG.md b/plugins/scaffolder/CHANGELOG.md
index dd5653240a..8c43d8d010 100644
--- a/plugins/scaffolder/CHANGELOG.md
+++ b/plugins/scaffolder/CHANGELOG.md
@@ -1,5 +1,19 @@
# @backstage/plugin-scaffolder
+## 0.11.11
+
+### Patch Changes
+
+- 8809b6c0dd: Update the json-schema dependency version.
+- a125278b81: Refactor out the deprecated path and icon from RouteRefs
+- Updated dependencies
+ - @backstage/catalog-client@0.5.2
+ - @backstage/catalog-model@0.9.7
+ - @backstage/plugin-catalog-react@0.6.4
+ - @backstage/core-components@0.7.4
+ - @backstage/core-plugin-api@0.2.0
+ - @backstage/integration-react@0.1.14
+
## 0.11.10
### Patch Changes
diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json
index 594a8e6423..ce185403bb 100644
--- a/plugins/scaffolder/package.json
+++ b/plugins/scaffolder/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-scaffolder",
"description": "The Backstage plugin that helps you create new things",
- "version": "0.11.10",
+ "version": "0.11.11",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -31,22 +31,22 @@
"clean": "backstage-cli clean"
},
"dependencies": {
- "@backstage/catalog-client": "^0.5.1",
- "@backstage/catalog-model": "^0.9.6",
+ "@backstage/catalog-client": "^0.5.2",
+ "@backstage/catalog-model": "^0.9.7",
"@backstage/config": "^0.1.11",
- "@backstage/core-components": "^0.7.3",
- "@backstage/core-plugin-api": "^0.1.13",
+ "@backstage/core-components": "^0.7.4",
+ "@backstage/core-plugin-api": "^0.2.0",
"@backstage/errors": "^0.1.4",
"@backstage/integration": "^0.6.9",
- "@backstage/integration-react": "^0.1.13",
- "@backstage/plugin-catalog-react": "^0.6.3",
+ "@backstage/integration-react": "^0.1.14",
+ "@backstage/plugin-catalog-react": "^0.6.4",
"@backstage/theme": "^0.2.13",
"@backstage/types": "^0.1.1",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.57",
- "@rjsf/core": "^3.0.0",
- "@rjsf/material-ui": "^3.0.0",
+ "@rjsf/core": "^3.2.1",
+ "@rjsf/material-ui": "^3.2.1",
"@types/react": "*",
"classnames": "^2.2.6",
"git-url-parse": "^11.6.0",
@@ -66,10 +66,10 @@
"zen-observable": "^0.8.15"
},
"devDependencies": {
- "@backstage/cli": "^0.8.2",
- "@backstage/core-app-api": "^0.1.20",
- "@backstage/dev-utils": "^0.2.12",
- "@backstage/test-utils": "^0.1.21",
+ "@backstage/cli": "^0.9.0",
+ "@backstage/core-app-api": "^0.1.22",
+ "@backstage/dev-utils": "^0.2.13",
+ "@backstage/test-utils": "^0.1.22",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/react-hooks": "^7.0.2",
diff --git a/plugins/scaffolder/src/api.ts b/plugins/scaffolder/src/api.ts
index a16ab59053..34e16708ea 100644
--- a/plugins/scaffolder/src/api.ts
+++ b/plugins/scaffolder/src/api.ts
@@ -187,7 +187,7 @@ export class ScaffolderClient implements ScaffolderApi {
});
if (!response.ok) {
- throw ResponseError.fromResponse(response);
+ throw await ResponseError.fromResponse(response);
}
return await response.json();
@@ -302,7 +302,7 @@ export class ScaffolderClient implements ScaffolderApi {
});
if (!response.ok) {
- throw ResponseError.fromResponse(response);
+ throw await ResponseError.fromResponse(response);
}
return await response.json();
diff --git a/plugins/scaffolder/src/routes.ts b/plugins/scaffolder/src/routes.ts
index 9b4861fc1e..df5868c9ea 100644
--- a/plugins/scaffolder/src/routes.ts
+++ b/plugins/scaffolder/src/routes.ts
@@ -24,5 +24,5 @@ export const registerComponentRouteRef = createExternalRouteRef({
});
export const rootRouteRef = createRouteRef({
- title: 'Create new entity',
+ id: 'scaffolder',
});
diff --git a/plugins/search-backend-module-elasticsearch/package.json b/plugins/search-backend-module-elasticsearch/package.json
index df0a783396..5ebc7850a9 100644
--- a/plugins/search-backend-module-elasticsearch/package.json
+++ b/plugins/search-backend-module-elasticsearch/package.json
@@ -30,8 +30,8 @@
"winston": "^3.2.1"
},
"devDependencies": {
- "@backstage/backend-common": "^0.9.9",
- "@backstage/cli": "^0.8.2",
+ "@backstage/backend-common": "^0.9.10",
+ "@backstage/cli": "^0.9.0",
"@elastic/elasticsearch-mock": "^0.3.0"
},
"files": [
diff --git a/plugins/search-backend-module-pg/package.json b/plugins/search-backend-module-pg/package.json
index a31d908ac4..74e9698ea4 100644
--- a/plugins/search-backend-module-pg/package.json
+++ b/plugins/search-backend-module-pg/package.json
@@ -20,15 +20,15 @@
"clean": "backstage-cli clean"
},
"dependencies": {
- "@backstage/backend-common": "^0.9.7",
+ "@backstage/backend-common": "^0.9.10",
"@backstage/search-common": "^0.2.0",
"@backstage/plugin-search-backend-node": "^0.4.2",
"lodash": "^4.17.21",
"knex": "^0.95.1"
},
"devDependencies": {
- "@backstage/backend-test-utils": "^0.1.8",
- "@backstage/cli": "^0.8.0"
+ "@backstage/backend-test-utils": "^0.1.9",
+ "@backstage/cli": "^0.9.0"
},
"files": [
"dist",
diff --git a/plugins/search-backend-node/package.json b/plugins/search-backend-node/package.json
index 4514d07756..369c52a4f6 100644
--- a/plugins/search-backend-node/package.json
+++ b/plugins/search-backend-node/package.json
@@ -26,8 +26,8 @@
"@types/lunr": "^2.3.3"
},
"devDependencies": {
- "@backstage/backend-common": "^0.9.8",
- "@backstage/cli": "^0.8.1"
+ "@backstage/backend-common": "^0.9.10",
+ "@backstage/cli": "^0.9.0"
},
"files": [
"dist"
diff --git a/plugins/search-backend/package.json b/plugins/search-backend/package.json
index 18d4484451..2df51e1bb4 100644
--- a/plugins/search-backend/package.json
+++ b/plugins/search-backend/package.json
@@ -20,7 +20,7 @@
"clean": "backstage-cli clean"
},
"dependencies": {
- "@backstage/backend-common": "^0.9.7",
+ "@backstage/backend-common": "^0.9.10",
"@backstage/search-common": "^0.2.0",
"@backstage/plugin-search-backend-node": "^0.4.2",
"@types/express": "^4.17.6",
@@ -30,7 +30,7 @@
"yn": "^4.0.0"
},
"devDependencies": {
- "@backstage/cli": "^0.8.0",
+ "@backstage/cli": "^0.9.0",
"@types/supertest": "^2.0.8",
"supertest": "^6.1.3"
},
diff --git a/plugins/search-backend/src/run.ts b/plugins/search-backend/src/run.ts
index addfdfd6d7..53d4e4334a 100644
--- a/plugins/search-backend/src/run.ts
+++ b/plugins/search-backend/src/run.ts
@@ -18,7 +18,7 @@ import { getRootLogger } from '@backstage/backend-common';
import yn from 'yn';
import { startStandaloneServer } from './service/standaloneServer';
-const port = process.env.PLUGIN_PORT ? Number(process.env.PLUGIN_PORT) : 7000;
+const port = process.env.PLUGIN_PORT ? Number(process.env.PLUGIN_PORT) : 7007;
const enableCors = yn(process.env.PLUGIN_CORS, { default: false });
const logger = getRootLogger();
diff --git a/plugins/search/CHANGELOG.md b/plugins/search/CHANGELOG.md
index 808c847914..ef3736b1df 100644
--- a/plugins/search/CHANGELOG.md
+++ b/plugins/search/CHANGELOG.md
@@ -1,5 +1,17 @@
# @backstage/plugin-search
+## 0.4.18
+
+### Patch Changes
+
+- a125278b81: Refactor out the deprecated path and icon from RouteRefs
+- 704b267e1c: Smaller UX improvements to search components such as optional autoFocus prop to SearchBar components, decreased debounce value and closing modal on link click of SearchModal, terminology updates of SearchResultPager.
+- Updated dependencies
+ - @backstage/catalog-model@0.9.7
+ - @backstage/plugin-catalog-react@0.6.4
+ - @backstage/core-components@0.7.4
+ - @backstage/core-plugin-api@0.2.0
+
## 0.4.17
### Patch Changes
diff --git a/plugins/search/api-report.md b/plugins/search/api-report.md
index 0e0ad01fca..c211c48e53 100644
--- a/plugins/search/api-report.md
+++ b/plugins/search/api-report.md
@@ -80,6 +80,7 @@ export const searchApiRef: ApiRef;
//
// @public (undocumented)
export const SearchBar: ({
+ autoFocus,
className,
debounceTime,
placeholder,
@@ -89,10 +90,12 @@ export const SearchBar: ({
//
// @public @deprecated (undocumented)
export const SearchBarNext: ({
+ autoFocus,
className,
debounceTime,
placeholder,
}: {
+ autoFocus?: boolean | undefined;
className?: string | undefined;
debounceTime?: number | undefined;
placeholder?: string | undefined;
diff --git a/plugins/search/package.json b/plugins/search/package.json
index 9d25ec723d..aaddf130bf 100644
--- a/plugins/search/package.json
+++ b/plugins/search/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-search",
"description": "The Backstage plugin that provides your backstage app with search",
- "version": "0.4.17",
+ "version": "0.4.18",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -30,12 +30,12 @@
"clean": "backstage-cli clean"
},
"dependencies": {
- "@backstage/catalog-model": "^0.9.6",
+ "@backstage/catalog-model": "^0.9.7",
"@backstage/config": "^0.1.11",
- "@backstage/core-components": "^0.7.3",
- "@backstage/core-plugin-api": "^0.1.13",
+ "@backstage/core-components": "^0.7.4",
+ "@backstage/core-plugin-api": "^0.2.0",
"@backstage/errors": "^0.1.4",
- "@backstage/plugin-catalog-react": "^0.6.3",
+ "@backstage/plugin-catalog-react": "^0.6.4",
"@backstage/search-common": "^0.2.1",
"@backstage/theme": "^0.2.13",
"@backstage/types": "^0.1.1",
@@ -51,10 +51,10 @@
"react-use": "^17.2.4"
},
"devDependencies": {
- "@backstage/cli": "^0.8.2",
- "@backstage/core-app-api": "^0.1.20",
- "@backstage/dev-utils": "^0.2.12",
- "@backstage/test-utils": "^0.1.21",
+ "@backstage/cli": "^0.9.0",
+ "@backstage/core-app-api": "^0.1.22",
+ "@backstage/dev-utils": "^0.2.13",
+ "@backstage/test-utils": "^0.1.22",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/react-hooks": "^7.0.2",
diff --git a/plugins/search/src/components/SearchBar/SearchBar.stories.tsx b/plugins/search/src/components/SearchBar/SearchBar.stories.tsx
index 2c990e512c..72b8429026 100644
--- a/plugins/search/src/components/SearchBar/SearchBar.stories.tsx
+++ b/plugins/search/src/components/SearchBar/SearchBar.stories.tsx
@@ -62,3 +62,22 @@ export const CustomPlaceholder = () => {
);
};
+
+export const Focused = () => {
+ return (
+
+ {/* @ts-ignore (defaultValue requires more than what is used here) */}
+
+
+
+
+ {/* decision up to adopter, read https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/master/docs/rules/no-autofocus.md#no-autofocus */}
+ {/* eslint-disable-next-line jsx-a11y/no-autofocus */}
+
+
+
+
+
+
+ );
+};
diff --git a/plugins/search/src/components/SearchBar/SearchBar.tsx b/plugins/search/src/components/SearchBar/SearchBar.tsx
index 53802899eb..4ab680d8fa 100644
--- a/plugins/search/src/components/SearchBar/SearchBar.tsx
+++ b/plugins/search/src/components/SearchBar/SearchBar.tsx
@@ -30,9 +30,11 @@ type PresenterProps = {
onSubmit?: () => void;
className?: string;
placeholder?: string;
+ autoFocus?: boolean;
};
export const SearchBarBase = ({
+ autoFocus,
value,
onChange,
onSubmit,
@@ -60,6 +62,9 @@ export const SearchBarBase = ({
return (
) =>
+ wrapInTestApp(
+ <>
+
+ >,
+ { mountedRoutes: { '/search': rootRouteRef } },
+ ),
+ ],
+};
+
+const mockSearchApi = {
+ query: () =>
+ Promise.resolve({
+ results: [
+ {
+ type: 'custom-result-item',
+ document: {
+ location: 'search/search-result-1',
+ title: 'Search Result 1',
+ text: 'some text from the search result',
+ },
+ },
+ {
+ type: 'no-custom-result-item',
+ document: {
+ location: 'search/search-result-2',
+ title: 'Search Result 2',
+ text: 'some text from the search result',
+ },
+ },
+ {
+ type: 'no-custom-result-item',
+ document: {
+ location: 'search/search-result-3',
+ title: 'Search Result 3',
+ text: 'some text from the search result',
+ },
+ },
+ ],
+ }),
+};
+
+const apiRegistry = () => ApiRegistry.from([[searchApiRef, mockSearchApi]]);
+
+export const Default = () => {
+ const [open, setOpen] = useState(false);
+ const toggleModal = (): void => setOpen(prevState => !prevState);
+
+ return (
+
+
+
+
+ );
+};
diff --git a/plugins/search/src/components/SearchModal/SearchModal.tsx b/plugins/search/src/components/SearchModal/SearchModal.tsx
index feabbc55af..75d4b07a3c 100644
--- a/plugins/search/src/components/SearchModal/SearchModal.tsx
+++ b/plugins/search/src/components/SearchModal/SearchModal.tsx
@@ -68,7 +68,7 @@ export const Modal = ({ open = true, toggleModal }: SearchModalProps) => {
setValue(prevValue => (prevValue !== term ? term : prevValue));
}, [term]);
- useDebounce(() => setTerm(value), 1000, [value]);
+ useDebounce(() => setTerm(value), 500, [value]);
const handleQuery = (newValue: string) => {
setValue(newValue);
@@ -114,7 +114,10 @@ export const Modal = ({ open = true, toggleModal }: SearchModalProps) => {
alignItems="center"
>
-
+
View Full Results
diff --git a/plugins/search/src/components/SearchResultPager/SearchResultPager.tsx b/plugins/search/src/components/SearchResultPager/SearchResultPager.tsx
index f1850772bb..12f07530ef 100644
--- a/plugins/search/src/components/SearchResultPager/SearchResultPager.tsx
+++ b/plugins/search/src/components/SearchResultPager/SearchResultPager.tsx
@@ -25,7 +25,7 @@ const useStyles = makeStyles(theme => ({
display: 'flex',
justifyContent: 'space-between',
gap: theme.spacing(2),
- margin: theme.spacing(4),
+ margin: theme.spacing(2, 0),
},
}));
@@ -44,9 +44,8 @@ export const SearchResultPager = () => {
disabled={!fetchPreviousPage}
onClick={fetchPreviousPage}
startIcon={}
- size="small"
>
- Back
+ Previous
diff --git a/plugins/search/src/plugin.ts b/plugins/search/src/plugin.ts
index b8c016fb36..9dda2a84bb 100644
--- a/plugins/search/src/plugin.ts
+++ b/plugins/search/src/plugin.ts
@@ -26,13 +26,11 @@ import {
} from '@backstage/core-plugin-api';
export const rootRouteRef = createRouteRef({
- path: '/search',
- title: 'search',
+ id: 'search',
});
export const rootNextRouteRef = createRouteRef({
- path: '/search-next',
- title: 'search',
+ id: 'search:next',
});
export const searchPlugin = createPlugin({
diff --git a/plugins/sentry/CHANGELOG.md b/plugins/sentry/CHANGELOG.md
index b0bcaad261..cad08289ed 100644
--- a/plugins/sentry/CHANGELOG.md
+++ b/plugins/sentry/CHANGELOG.md
@@ -1,5 +1,16 @@
# @backstage/plugin-sentry
+## 0.3.29
+
+### Patch Changes
+
+- a125278b81: Refactor out the deprecated path and icon from RouteRefs
+- Updated dependencies
+ - @backstage/catalog-model@0.9.7
+ - @backstage/plugin-catalog-react@0.6.4
+ - @backstage/core-components@0.7.4
+ - @backstage/core-plugin-api@0.2.0
+
## 0.3.28
### Patch Changes
diff --git a/plugins/sentry/package.json b/plugins/sentry/package.json
index ee2421bf7f..a04043c789 100644
--- a/plugins/sentry/package.json
+++ b/plugins/sentry/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-sentry",
"description": "A Backstage plugin that integrates towards Sentry",
- "version": "0.3.28",
+ "version": "0.3.29",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -32,10 +32,10 @@
"clean": "backstage-cli clean"
},
"dependencies": {
- "@backstage/catalog-model": "^0.9.6",
- "@backstage/core-components": "^0.7.3",
- "@backstage/core-plugin-api": "^0.1.13",
- "@backstage/plugin-catalog-react": "^0.6.3",
+ "@backstage/catalog-model": "^0.9.7",
+ "@backstage/core-components": "^0.7.4",
+ "@backstage/core-plugin-api": "^0.2.0",
+ "@backstage/plugin-catalog-react": "^0.6.4",
"@backstage/theme": "^0.2.13",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
@@ -48,10 +48,10 @@
"react-use": "^17.2.4"
},
"devDependencies": {
- "@backstage/cli": "^0.8.2",
- "@backstage/core-app-api": "^0.1.20",
- "@backstage/dev-utils": "^0.2.12",
- "@backstage/test-utils": "^0.1.21",
+ "@backstage/cli": "^0.9.0",
+ "@backstage/core-app-api": "^0.1.22",
+ "@backstage/dev-utils": "^0.2.13",
+ "@backstage/test-utils": "^0.1.22",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
diff --git a/plugins/sentry/src/plugin.ts b/plugins/sentry/src/plugin.ts
index 454c1f02a6..981745c8fe 100644
--- a/plugins/sentry/src/plugin.ts
+++ b/plugins/sentry/src/plugin.ts
@@ -25,8 +25,7 @@ import {
} from '@backstage/core-plugin-api';
export const rootRouteRef = createRouteRef({
- path: '/sentry',
- title: 'Sentry',
+ id: 'sentry',
});
export const sentryPlugin = createPlugin({
diff --git a/plugins/shortcuts/CHANGELOG.md b/plugins/shortcuts/CHANGELOG.md
index 41581cc5b4..fdd9f44231 100644
--- a/plugins/shortcuts/CHANGELOG.md
+++ b/plugins/shortcuts/CHANGELOG.md
@@ -1,5 +1,14 @@
# @backstage/plugin-shortcuts
+## 0.1.14
+
+### Patch Changes
+
+- f93e56ae3a: Add tooltip to display shortcut title on hover. This should improve the readability of shortcuts with long names, which are cutoff by ellipsis.
+- Updated dependencies
+ - @backstage/core-components@0.7.4
+ - @backstage/core-plugin-api@0.2.0
+
## 0.1.13
### Patch Changes
diff --git a/plugins/shortcuts/package.json b/plugins/shortcuts/package.json
index e73e047bc1..f0139d5728 100644
--- a/plugins/shortcuts/package.json
+++ b/plugins/shortcuts/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-shortcuts",
"description": "A Backstage plugin that provides a shortcuts feature to the sidebar",
- "version": "0.1.13",
+ "version": "0.1.14",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -21,8 +21,8 @@
"clean": "backstage-cli clean"
},
"dependencies": {
- "@backstage/core-components": "^0.7.3",
- "@backstage/core-plugin-api": "^0.1.13",
+ "@backstage/core-components": "^0.7.4",
+ "@backstage/core-plugin-api": "^0.2.0",
"@backstage/theme": "^0.2.13",
"@backstage/types": "^0.1.1",
"@material-ui/core": "^4.12.2",
@@ -38,10 +38,10 @@
"zen-observable": "^0.8.15"
},
"devDependencies": {
- "@backstage/cli": "^0.8.2",
- "@backstage/core-app-api": "^0.1.20",
- "@backstage/dev-utils": "^0.2.12",
- "@backstage/test-utils": "^0.1.21",
+ "@backstage/cli": "^0.9.0",
+ "@backstage/core-app-api": "^0.1.22",
+ "@backstage/dev-utils": "^0.2.13",
+ "@backstage/test-utils": "^0.1.22",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
diff --git a/plugins/shortcuts/src/ShortcutItem.tsx b/plugins/shortcuts/src/ShortcutItem.tsx
index d2ac716737..8ba1a96358 100644
--- a/plugins/shortcuts/src/ShortcutItem.tsx
+++ b/plugins/shortcuts/src/ShortcutItem.tsx
@@ -15,7 +15,9 @@
*/
import React from 'react';
-import { IconButton, makeStyles } from '@material-ui/core';
+import { makeStyles } from '@material-ui/core/styles';
+import IconButton from '@material-ui/core/IconButton';
+import Tooltip from '@material-ui/core/Tooltip';
import EditIcon from '@material-ui/icons/Edit';
import { ShortcutIcon } from './ShortcutIcon';
import { EditShortcut } from './EditShortcut';
@@ -76,21 +78,23 @@ export const ShortcutItem = ({ shortcut, api }: Props) => {
return (
<>
- }
- >
-
+ }
>
-
-
-
+
+
+
+
+
{
- const builder = new DefaultTechInsightsBuilder({
+ const builder = buildTechInsightsContext({
logger,
config,
database,
diff --git a/plugins/tech-insights-backend/api-report.md b/plugins/tech-insights-backend/api-report.md
index 826789dd1d..ac2d1d1170 100644
--- a/plugins/tech-insights-backend/api-report.md
+++ b/plugins/tech-insights-backend/api-report.md
@@ -36,6 +36,12 @@ export function createRouter<
CheckResultType extends CheckResult,
>(options: RouterOptions): Promise;
+// @public
+export const entityMetadataFactRetriever: FactRetriever;
+
+// @public
+export const entityOwnershipFactRetriever: FactRetriever;
+
// @public
export type PersistenceContext = {
techInsightsStore: TechInsightsStore;
@@ -52,6 +58,9 @@ export interface RouterOptions<
persistenceContext: PersistenceContext;
}
+// @public
+export const techdocsFactRetriever: FactRetriever;
+
// @public (undocumented)
export type TechInsightsContext<
CheckType extends TechInsightCheck,
diff --git a/plugins/tech-insights-backend/package.json b/plugins/tech-insights-backend/package.json
index 678bec25c4..5bc616db71 100644
--- a/plugins/tech-insights-backend/package.json
+++ b/plugins/tech-insights-backend/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-tech-insights-backend",
- "version": "0.1.0",
+ "version": "0.1.1",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -31,9 +31,9 @@
"clean": "backstage-cli clean"
},
"dependencies": {
- "@backstage/backend-common": "^0.9.0",
- "@backstage/catalog-client": "^0.3.16",
- "@backstage/catalog-model": "^0.9.0",
+ "@backstage/backend-common": "^0.9.10",
+ "@backstage/catalog-client": "^0.5.2",
+ "@backstage/catalog-model": "^0.9.7",
"@backstage/config": "^0.1.8",
"@backstage/errors": "^0.1.1",
"@backstage/plugin-tech-insights-common": "^0.1.0",
@@ -52,8 +52,8 @@
"yn": "^4.0.0"
},
"devDependencies": {
- "@backstage/backend-test-utils": "^0.1.8",
- "@backstage/cli": "^0.8.0",
+ "@backstage/backend-test-utils": "^0.1.9",
+ "@backstage/cli": "^0.9.0",
"@types/supertest": "^2.0.8",
"@types/node-cron": "^3.0.0",
"@types/semver": "^7.3.8",
diff --git a/plugins/tech-insights-backend/src/index.ts b/plugins/tech-insights-backend/src/index.ts
index 5e8e690e8d..763c565108 100644
--- a/plugins/tech-insights-backend/src/index.ts
+++ b/plugins/tech-insights-backend/src/index.ts
@@ -25,3 +25,4 @@ export type {
export type { PersistenceContext } from './service/persistence/persistenceContext';
export { createFactRetrieverRegistration } from './service/fact/createFactRetriever';
+export * from './service/fact/factRetrievers';
diff --git a/plugins/tech-insights-backend/src/service/fact/FactRetrieverEngine.test.ts b/plugins/tech-insights-backend/src/service/fact/FactRetrieverEngine.test.ts
index 4dc002465b..8d1f7180a3 100644
--- a/plugins/tech-insights-backend/src/service/fact/FactRetrieverEngine.test.ts
+++ b/plugins/tech-insights-backend/src/service/fact/FactRetrieverEngine.test.ts
@@ -44,7 +44,7 @@ const testFactRetriever: FactRetriever = {
description: '',
},
},
- handler: async () => {
+ handler: jest.fn(async () => {
return [
{
entity: {
@@ -57,7 +57,7 @@ const testFactRetriever: FactRetriever = {
},
},
];
- },
+ }),
};
const cadence = '1 * * * *';
describe('FactRetrieverEngine', () => {
@@ -145,5 +145,8 @@ describe('FactRetrieverEngine', () => {
const job: any = engine.getJob('test-factretriever');
job.triggerScheduledJobNow();
expect(job.cadence!!).toEqual(cadence);
+ expect(testFactRetriever.handler).toHaveBeenCalledWith(
+ expect.objectContaining({ entityFilter: testFactRetriever.entityFilter }),
+ );
});
});
diff --git a/plugins/tech-insights-backend/src/service/fact/FactRetrieverEngine.ts b/plugins/tech-insights-backend/src/service/fact/FactRetrieverEngine.ts
index aa2207cca8..11fd0d29b8 100644
--- a/plugins/tech-insights-backend/src/service/fact/FactRetrieverEngine.ts
+++ b/plugins/tech-insights-backend/src/service/fact/FactRetrieverEngine.ts
@@ -108,7 +108,10 @@ export class FactRetrieverEngine {
this.logger.info(
`Retrieving facts for fact retriever ${factRetriever.id}`,
);
- const facts = await factRetriever.handler(this.factRetrieverContext);
+ const facts = await factRetriever.handler({
+ ...this.factRetrieverContext,
+ entityFilter: factRetriever.entityFilter,
+ });
if (this.logger.isDebugEnabled()) {
this.logger.debug(
`Retrieved ${facts.length} facts for fact retriever ${
diff --git a/plugins/tech-insights-backend/src/service/fact/factRetrievers/entityMetadataFactRetriever.test.ts b/plugins/tech-insights-backend/src/service/fact/factRetrievers/entityMetadataFactRetriever.test.ts
new file mode 100644
index 0000000000..b99f6a1c02
--- /dev/null
+++ b/plugins/tech-insights-backend/src/service/fact/factRetrievers/entityMetadataFactRetriever.test.ts
@@ -0,0 +1,181 @@
+/*
+ * Copyright 2021 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Entity, RELATION_OWNED_BY } from '@backstage/catalog-model';
+import {
+ PluginEndpointDiscovery,
+ getVoidLogger,
+} from '@backstage/backend-common';
+import { ConfigReader } from '@backstage/config';
+import { CatalogListResponse } from '@backstage/catalog-client';
+import { entityMetadataFactRetriever } from './entityMetadataFactRetriever';
+
+const getEntitiesMock = jest.fn();
+jest.mock('@backstage/catalog-client', () => {
+ return {
+ CatalogClient: jest
+ .fn()
+ .mockImplementation(() => ({ getEntities: getEntitiesMock })),
+ };
+});
+const discovery: jest.Mocked = {
+ getBaseUrl: jest.fn(),
+ getExternalBaseUrl: jest.fn(),
+};
+
+const defaultEntityListResponse: CatalogListResponse = {
+ items: [
+ {
+ apiVersion: 'backstage.io/v1beta1',
+ metadata: {
+ name: 'service-with-owner',
+ description: 'service with an owner',
+ tags: ['a-tag'],
+ annotations: {
+ 'backstage.io/techdocs-ref': 'dir:.',
+ },
+ },
+ kind: 'Component',
+ spec: {
+ type: 'service',
+ lifecycle: 'test',
+ owner: 'team-a',
+ },
+ relations: [
+ {
+ type: RELATION_OWNED_BY,
+ target: { name: 'team-a', kind: 'group', namespace: 'default' },
+ },
+ ],
+ },
+ {
+ apiVersion: 'backstage.io/v1beta1',
+ metadata: {
+ name: 'service-with-incomplete-data',
+ description: '',
+ tags: [],
+ },
+ kind: 'Component',
+ spec: {
+ type: 'service',
+ lifecycle: 'test',
+ owner: '',
+ },
+ },
+ {
+ apiVersion: 'backstage.io/v1beta1',
+ metadata: {
+ name: 'service-with-user-owner',
+ },
+ kind: 'Component',
+ spec: {
+ type: 'service',
+ lifecycle: 'test',
+ owner: 'user:my-user',
+ },
+ },
+ {
+ apiVersion: 'backstage.io/v1beta1',
+ metadata: {
+ name: 'user-a',
+ },
+ kind: 'User',
+ spec: {
+ memberOf: 'group-a',
+ },
+ },
+ ],
+};
+
+const handlerContext = {
+ discovery,
+ logger: getVoidLogger(),
+ config: ConfigReader.fromConfigs([]),
+};
+
+const entityFactRetriever = entityMetadataFactRetriever;
+
+describe('entityMetadataFactRetriever', () => {
+ beforeEach(() => {
+ getEntitiesMock.mockResolvedValue(defaultEntityListResponse);
+ });
+ afterEach(() => {
+ getEntitiesMock.mockClear();
+ });
+
+ describe('hasDescription', () => {
+ describe('where the entity has a description', () => {
+ it('returns true for hasDescription', async () => {
+ const facts = await entityFactRetriever.handler(handlerContext);
+ expect(
+ facts.find(it => it.entity.name === 'service-with-owner'),
+ ).toMatchObject({
+ facts: {
+ hasDescription: true,
+ },
+ });
+ });
+ });
+ describe('where the entity does not have a description', () => {
+ it('returns false for hasDescription', async () => {
+ const facts = await entityFactRetriever.handler(handlerContext);
+ expect(facts.find(it => it.entity.name === 'user-a')).toMatchObject({
+ facts: {
+ hasDescription: false,
+ },
+ });
+ });
+ });
+ describe('where the entity has an empty value for description', () => {
+ it('returns false for hasDescription', async () => {
+ const facts = await entityFactRetriever.handler(handlerContext);
+ expect(
+ facts.find(it => it.entity.name === 'service-with-incomplete-data'),
+ ).toMatchObject({
+ facts: {
+ hasDescription: false,
+ },
+ });
+ });
+ });
+ });
+ describe('hasTags', () => {
+ describe('where the entity has tags', () => {
+ it('returns true for hasTags', async () => {
+ const facts = await entityFactRetriever.handler(handlerContext);
+ expect(
+ facts.find(it => it.entity.name === 'service-with-owner'),
+ ).toMatchObject({
+ facts: {
+ hasTags: true,
+ },
+ });
+ });
+ });
+ describe('where the entity has no tags', () => {
+ it('returns false for hasTags', async () => {
+ const facts = await entityFactRetriever.handler(handlerContext);
+ expect(
+ facts.find(it => it.entity.name === 'service-with-incomplete-data'),
+ ).toMatchObject({
+ facts: {
+ hasTags: false,
+ },
+ });
+ });
+ });
+ });
+});
diff --git a/plugins/tech-insights-backend/src/service/fact/factRetrievers/entityMetadataFactRetriever.ts b/plugins/tech-insights-backend/src/service/fact/factRetrievers/entityMetadataFactRetriever.ts
new file mode 100644
index 0000000000..535aede115
--- /dev/null
+++ b/plugins/tech-insights-backend/src/service/fact/factRetrievers/entityMetadataFactRetriever.ts
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2021 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {
+ FactRetriever,
+ FactRetrieverContext,
+} from '@backstage/plugin-tech-insights-node';
+import { CatalogClient } from '@backstage/catalog-client';
+import { Entity } from '@backstage/catalog-model';
+import isEmpty from 'lodash/isEmpty';
+
+/**
+ * Generates facts which indicate the completeness of entity metadata.
+ *
+ * @public
+ */
+export const entityMetadataFactRetriever: FactRetriever = {
+ id: 'entityMetadataFactRetriever',
+ version: '0.0.1',
+ schema: {
+ hasTitle: {
+ type: 'boolean',
+ description: 'The entity has a title in metadata',
+ },
+ hasDescription: {
+ type: 'boolean',
+ description: 'The entity has a description in metadata',
+ },
+ hasTags: {
+ type: 'boolean',
+ description: 'The entity has tags in metadata',
+ },
+ },
+ handler: async ({ discovery, entityFilter }: FactRetrieverContext) => {
+ const catalogClient = new CatalogClient({
+ discoveryApi: discovery,
+ });
+ const entities = await catalogClient.getEntities({ filter: entityFilter });
+
+ return entities.items.map((entity: Entity) => {
+ return {
+ entity: {
+ namespace: entity.metadata.namespace!!,
+ kind: entity.kind,
+ name: entity.metadata.name,
+ },
+ facts: {
+ hasTitle: Boolean(entity.metadata?.title),
+ hasDescription: Boolean(entity.metadata?.description),
+ hasTags: !isEmpty(entity.metadata?.tags),
+ },
+ };
+ });
+ },
+};
diff --git a/plugins/tech-insights-backend/src/service/fact/factRetrievers/entityOwnershipFactRetriever.test.ts b/plugins/tech-insights-backend/src/service/fact/factRetrievers/entityOwnershipFactRetriever.test.ts
new file mode 100644
index 0000000000..ea0801f17c
--- /dev/null
+++ b/plugins/tech-insights-backend/src/service/fact/factRetrievers/entityOwnershipFactRetriever.test.ts
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2021 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { entityOwnershipFactRetriever } from './entityOwnershipFactRetriever';
+import { Entity, RELATION_OWNED_BY } from '@backstage/catalog-model';
+import {
+ PluginEndpointDiscovery,
+ getVoidLogger,
+} from '@backstage/backend-common';
+import { ConfigReader } from '@backstage/config';
+import { CatalogListResponse } from '@backstage/catalog-client';
+
+const getEntitiesMock = jest.fn();
+jest.mock('@backstage/catalog-client', () => {
+ return {
+ CatalogClient: jest
+ .fn()
+ .mockImplementation(() => ({ getEntities: getEntitiesMock })),
+ };
+});
+const discovery: jest.Mocked = {
+ getBaseUrl: jest.fn(),
+ getExternalBaseUrl: jest.fn(),
+};
+
+const defaultEntityListResponse: CatalogListResponse = {
+ items: [
+ {
+ apiVersion: 'backstage.io/v1beta1',
+ metadata: {
+ name: 'service-with-owner',
+ description: 'service with an owner',
+ tags: ['a-tag'],
+ annotations: {
+ 'backstage.io/techdocs-ref': 'dir:.',
+ },
+ },
+ kind: 'Component',
+ spec: {
+ type: 'service',
+ lifecycle: 'test',
+ owner: 'team-a',
+ },
+ relations: [
+ {
+ type: RELATION_OWNED_BY,
+ target: { name: 'team-a', kind: 'group', namespace: 'default' },
+ },
+ ],
+ },
+ {
+ apiVersion: 'backstage.io/v1beta1',
+ metadata: {
+ name: 'service-with-incomplete-data',
+ description: '',
+ tags: [],
+ },
+ kind: 'Component',
+ spec: {
+ type: 'service',
+ lifecycle: 'test',
+ owner: '',
+ },
+ },
+ {
+ apiVersion: 'backstage.io/v1beta1',
+ metadata: {
+ name: 'service-with-user-owner',
+ },
+ kind: 'Component',
+ spec: {
+ type: 'service',
+ lifecycle: 'test',
+ owner: 'user:my-user',
+ },
+ },
+ {
+ apiVersion: 'backstage.io/v1beta1',
+ metadata: {
+ name: 'user-a',
+ },
+ kind: 'User',
+ spec: {
+ memberOf: 'group-a',
+ },
+ },
+ ],
+};
+
+const handlerContext = {
+ discovery,
+ logger: getVoidLogger(),
+ config: ConfigReader.fromConfigs([]),
+};
+
+const entityFactRetriever = entityOwnershipFactRetriever;
+
+describe('entityFactRetriever', () => {
+ beforeEach(() => {
+ getEntitiesMock.mockResolvedValue(defaultEntityListResponse);
+ });
+ afterEach(() => {
+ getEntitiesMock.mockClear();
+ });
+
+ describe('hasOwner', () => {
+ describe('where the entity has an owner in the spec', () => {
+ it('returns true for hasOwner', async () => {
+ const facts = await entityFactRetriever.handler(handlerContext);
+ expect(
+ facts.find(it => it.entity.name === 'service-with-owner'),
+ ).toMatchObject({
+ facts: {
+ hasOwner: true,
+ },
+ });
+ });
+ });
+ describe('where the entity has an empty value for owner in the spec', () => {
+ it('returns false for hasOwner', async () => {
+ const facts = await entityFactRetriever.handler(handlerContext);
+ expect(
+ facts.find(it => it.entity.name === 'service-with-incomplete-data'),
+ ).toMatchObject({
+ facts: {
+ hasOwner: false,
+ },
+ });
+ });
+ });
+
+ describe('where the entity doesn not have an owner in the spec', () => {
+ it('returns false for hasOwner', async () => {
+ const facts = await entityFactRetriever.handler(handlerContext);
+ expect(facts.find(it => it.entity.name === 'user-a')).toMatchObject({
+ facts: {
+ hasOwner: false,
+ },
+ });
+ });
+ });
+ });
+ describe('hasGroupOwner', () => {
+ describe('where the entity has an group as owner in the spec', () => {
+ it('returns true for hasGroupOwner', async () => {
+ const facts = await entityFactRetriever.handler(handlerContext);
+ expect(
+ facts.find(it => it.entity.name === 'service-with-owner'),
+ ).toMatchObject({
+ facts: {
+ hasGroupOwner: true,
+ },
+ });
+ });
+ });
+ describe('where the entity has a user as owner in the spec', () => {
+ it('returns false for hasGroupOwner', async () => {
+ const facts = await entityFactRetriever.handler(handlerContext);
+ expect(
+ facts.find(it => it.entity.name === 'service-with-user-owner'),
+ ).toMatchObject({
+ facts: {
+ hasGroupOwner: false,
+ },
+ });
+ });
+ });
+ describe('where the entity has an empty value for owner in the spec', () => {
+ it('returns false for hasGroupOwner', async () => {
+ const facts = await entityFactRetriever.handler(handlerContext);
+ expect(
+ facts.find(it => it.entity.name === 'service-with-incomplete-data'),
+ ).toMatchObject({
+ facts: {
+ hasGroupOwner: false,
+ },
+ });
+ });
+ });
+ });
+});
diff --git a/plugins/tech-insights-backend/src/service/fact/factRetrievers/entityOwnershipFactRetriever.ts b/plugins/tech-insights-backend/src/service/fact/factRetrievers/entityOwnershipFactRetriever.ts
new file mode 100644
index 0000000000..8f61f182ad
--- /dev/null
+++ b/plugins/tech-insights-backend/src/service/fact/factRetrievers/entityOwnershipFactRetriever.ts
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2021 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {
+ FactRetriever,
+ FactRetrieverContext,
+} from '@backstage/plugin-tech-insights-node';
+import { CatalogClient } from '@backstage/catalog-client';
+import { Entity } from '@backstage/catalog-model';
+
+/**
+ * Generates facts which indicate the quality of data in the spec.owner field.
+ *
+ * @public
+ */
+export const entityOwnershipFactRetriever: FactRetriever = {
+ id: 'entityOwnershipFactRetriever',
+ version: '0.0.1',
+ entityFilter: [
+ { kind: ['component', 'domain', 'system', 'api', 'resource', 'template'] },
+ ],
+ schema: {
+ hasOwner: {
+ type: 'boolean',
+ description: 'The spec.owner field is set',
+ },
+ hasGroupOwner: {
+ type: 'boolean',
+ description: 'The spec.owner field is set and refers to a group',
+ },
+ },
+ handler: async ({ discovery, entityFilter }: FactRetrieverContext) => {
+ const catalogClient = new CatalogClient({
+ discoveryApi: discovery,
+ });
+ const entities = await catalogClient.getEntities({ filter: entityFilter });
+
+ return entities.items.map((entity: Entity) => {
+ return {
+ entity: {
+ namespace: entity.metadata.namespace!!,
+ kind: entity.kind,
+ name: entity.metadata.name,
+ },
+ facts: {
+ hasOwner: Boolean(entity.spec?.owner),
+ hasGroupOwner: Boolean(
+ entity.spec?.owner &&
+ !(entity.spec?.owner as string).startsWith('user:'),
+ ),
+ },
+ };
+ });
+ },
+};
diff --git a/plugins/tech-insights-backend/src/service/fact/factRetrievers/index.ts b/plugins/tech-insights-backend/src/service/fact/factRetrievers/index.ts
new file mode 100644
index 0000000000..587db8c4b9
--- /dev/null
+++ b/plugins/tech-insights-backend/src/service/fact/factRetrievers/index.ts
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2021 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+export * from './entityOwnershipFactRetriever';
+export * from './entityMetadataFactRetriever';
+export * from './techdocsFactRetriever';
diff --git a/plugins/tech-insights-backend/src/service/fact/factRetrievers/techdocsFactRetriever.test.ts b/plugins/tech-insights-backend/src/service/fact/factRetrievers/techdocsFactRetriever.test.ts
new file mode 100644
index 0000000000..6ed9b12523
--- /dev/null
+++ b/plugins/tech-insights-backend/src/service/fact/factRetrievers/techdocsFactRetriever.test.ts
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2021 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { techdocsFactRetriever } from './techdocsFactRetriever';
+import { Entity, RELATION_OWNED_BY } from '@backstage/catalog-model';
+import {
+ PluginEndpointDiscovery,
+ getVoidLogger,
+} from '@backstage/backend-common';
+import { ConfigReader } from '@backstage/config';
+import { CatalogListResponse } from '@backstage/catalog-client';
+
+const getEntitiesMock = jest.fn();
+jest.mock('@backstage/catalog-client', () => {
+ return {
+ CatalogClient: jest
+ .fn()
+ .mockImplementation(() => ({ getEntities: getEntitiesMock })),
+ };
+});
+const discovery: jest.Mocked = {
+ getBaseUrl: jest.fn(),
+ getExternalBaseUrl: jest.fn(),
+};
+
+const defaultEntityListResponse: CatalogListResponse = {
+ items: [
+ {
+ apiVersion: 'backstage.io/v1beta1',
+ metadata: {
+ name: 'service-with-owner',
+ description: 'service with an owner',
+ tags: ['a-tag'],
+ annotations: {
+ 'backstage.io/techdocs-ref': 'dir:.',
+ },
+ },
+ kind: 'Component',
+ spec: {
+ type: 'service',
+ lifecycle: 'test',
+ owner: 'team-a',
+ },
+ relations: [
+ {
+ type: RELATION_OWNED_BY,
+ target: { name: 'team-a', kind: 'group', namespace: 'default' },
+ },
+ ],
+ },
+ {
+ apiVersion: 'backstage.io/v1beta1',
+ metadata: {
+ name: 'service-with-incomplete-data',
+ description: '',
+ tags: [],
+ },
+ kind: 'Component',
+ spec: {
+ type: 'service',
+ lifecycle: 'test',
+ owner: '',
+ },
+ },
+ {
+ apiVersion: 'backstage.io/v1beta1',
+ metadata: {
+ name: 'service-with-user-owner',
+ },
+ kind: 'Component',
+ spec: {
+ type: 'service',
+ lifecycle: 'test',
+ owner: 'user:my-user',
+ },
+ },
+ {
+ apiVersion: 'backstage.io/v1beta1',
+ metadata: {
+ name: 'user-a',
+ },
+ kind: 'User',
+ spec: {
+ memberOf: 'group-a',
+ },
+ },
+ ],
+};
+
+const handlerContext = {
+ discovery,
+ logger: getVoidLogger(),
+ config: ConfigReader.fromConfigs([]),
+};
+
+const entityFactRetriever = techdocsFactRetriever;
+
+describe('techdocsFactRetriever', () => {
+ beforeEach(() => {
+ getEntitiesMock.mockResolvedValue(defaultEntityListResponse);
+ });
+ afterEach(() => {
+ getEntitiesMock.mockClear();
+ });
+
+ describe('hasAnnotationBackstageIoTechdocsRef', () => {
+ describe('where the retriever is configured to check for the techdocs annotation', () => {
+ describe('where the entity has the techdocs annotation', () => {
+ it('returns true for hasAnnotationBackstageIoTechdocsRef', async () => {
+ const facts = await entityFactRetriever.handler(handlerContext);
+ expect(
+ facts.find(it => it.entity.name === 'service-with-owner'),
+ ).toMatchObject({
+ facts: {
+ hasAnnotationBackstageIoTechdocsRef: true,
+ },
+ });
+ });
+ });
+ describe('where the entity is missing the techdocs annotation', () => {
+ it('returns false for hasAnnotationBackstageIoTechdocsRef', async () => {
+ const facts = await entityFactRetriever.handler(handlerContext);
+ expect(
+ facts.find(it => it.entity.name === 'service-with-incomplete-data'),
+ ).toMatchObject({
+ facts: {
+ hasAnnotationBackstageIoTechdocsRef: false,
+ },
+ });
+ });
+ });
+ });
+ });
+});
diff --git a/plugins/tech-insights-backend/src/service/fact/factRetrievers/techdocsFactRetriever.ts b/plugins/tech-insights-backend/src/service/fact/factRetrievers/techdocsFactRetriever.ts
new file mode 100644
index 0000000000..c1c3ba5d20
--- /dev/null
+++ b/plugins/tech-insights-backend/src/service/fact/factRetrievers/techdocsFactRetriever.ts
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2021 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {
+ FactRetriever,
+ FactRetrieverContext,
+} from '@backstage/plugin-tech-insights-node';
+import { CatalogClient } from '@backstage/catalog-client';
+import { Entity } from '@backstage/catalog-model';
+import { entityHasAnnotation, generateAnnotationFactName } from './utils';
+
+const techdocsAnnotation = 'backstage.io/techdocs-ref';
+const techdocsAnnotationFactName =
+ generateAnnotationFactName(techdocsAnnotation);
+
+/**
+ * Generates facts related to the completeness of techdocs configuration for entities.
+ *
+ * @public
+ */
+export const techdocsFactRetriever: FactRetriever = {
+ id: 'techdocsFactRetriever',
+ version: '0.0.1',
+ schema: {
+ [techdocsAnnotationFactName]: {
+ type: 'boolean',
+ description: 'The entity has a title in metadata',
+ },
+ },
+ handler: async ({ discovery, entityFilter }: FactRetrieverContext) => {
+ const catalogClient = new CatalogClient({
+ discoveryApi: discovery,
+ });
+ const entities = await catalogClient.getEntities({ filter: entityFilter });
+
+ return entities.items.map((entity: Entity) => {
+ return {
+ entity: {
+ namespace: entity.metadata.namespace!!,
+ kind: entity.kind,
+ name: entity.metadata.name,
+ },
+ facts: {
+ [techdocsAnnotationFactName]: entityHasAnnotation(
+ entity,
+ techdocsAnnotation,
+ ),
+ },
+ };
+ });
+ },
+};
diff --git a/plugins/tech-insights-backend/src/service/fact/factRetrievers/utils.ts b/plugins/tech-insights-backend/src/service/fact/factRetrievers/utils.ts
new file mode 100644
index 0000000000..7967502a63
--- /dev/null
+++ b/plugins/tech-insights-backend/src/service/fact/factRetrievers/utils.ts
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2021 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import camelCase from 'lodash/camelCase';
+import { Entity } from '@backstage/catalog-model';
+import { get } from 'lodash';
+
+export const generateAnnotationFactName = (annotation: string) =>
+ camelCase(`hasAnnotation-${annotation}`);
+
+export const entityHasAnnotation = (entity: Entity, annotation: string) =>
+ Boolean(get(entity, ['metadata', 'annotations', annotation]));
diff --git a/plugins/tech-insights-common/package.json b/plugins/tech-insights-common/package.json
index 5e02c9e4b0..26d8922329 100644
--- a/plugins/tech-insights-common/package.json
+++ b/plugins/tech-insights-common/package.json
@@ -34,7 +34,7 @@
"luxon": "^2.0.2"
},
"devDependencies": {
- "@backstage/cli": "^0.8.0"
+ "@backstage/cli": "^0.9.0"
},
"files": [
"dist"
diff --git a/plugins/tech-insights-node/api-report.md b/plugins/tech-insights-node/api-report.md
index caf4b4cf0e..fc137da4bc 100644
--- a/plugins/tech-insights-node/api-report.md
+++ b/plugins/tech-insights-node/api-report.md
@@ -53,6 +53,9 @@ export type FactRetrieverContext = {
config: Config;
discovery: PluginEndpointDiscovery;
logger: Logger_2;
+ entityFilter?:
+ | Record[]
+ | Record;
};
// @public
diff --git a/plugins/tech-insights-node/package.json b/plugins/tech-insights-node/package.json
index 4a7cf3589b..3c2ba5da08 100644
--- a/plugins/tech-insights-node/package.json
+++ b/plugins/tech-insights-node/package.json
@@ -30,7 +30,7 @@
"clean": "backstage-cli clean"
},
"dependencies": {
- "@backstage/backend-common": "^0.9.0",
+ "@backstage/backend-common": "^0.9.10",
"@backstage/config": "^0.1.8",
"@backstage/plugin-tech-insights-common": "^0.1.0 ",
"@types/luxon": "^2.0.5",
@@ -38,7 +38,7 @@
"winston": "^3.2.1"
},
"devDependencies": {
- "@backstage/cli": "^0.8.0"
+ "@backstage/cli": "^0.9.0"
},
"files": [
"dist"
diff --git a/plugins/tech-insights-node/src/facts.ts b/plugins/tech-insights-node/src/facts.ts
index a52a91f2f4..4a3157d391 100644
--- a/plugins/tech-insights-node/src/facts.ts
+++ b/plugins/tech-insights-node/src/facts.ts
@@ -136,6 +136,9 @@ export type FactRetrieverContext = {
config: Config;
discovery: PluginEndpointDiscovery;
logger: Logger;
+ entityFilter?:
+ | Record[]
+ | Record;
};
/**
diff --git a/plugins/tech-radar/CHANGELOG.md b/plugins/tech-radar/CHANGELOG.md
index 8e7b5875a9..cef7885007 100644
--- a/plugins/tech-radar/CHANGELOG.md
+++ b/plugins/tech-radar/CHANGELOG.md
@@ -1,5 +1,14 @@
# @backstage/plugin-tech-radar
+## 0.4.12
+
+### Patch Changes
+
+- a125278b81: Refactor out the deprecated path and icon from RouteRefs
+- Updated dependencies
+ - @backstage/core-components@0.7.4
+ - @backstage/core-plugin-api@0.2.0
+
## 0.4.11
### Patch Changes
diff --git a/plugins/tech-radar/package.json b/plugins/tech-radar/package.json
index 839ad1b399..99fce10345 100644
--- a/plugins/tech-radar/package.json
+++ b/plugins/tech-radar/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-tech-radar",
"description": "A Backstage plugin that lets you display a Tech Radar for your organization",
- "version": "0.4.11",
+ "version": "0.4.12",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -31,8 +31,8 @@
"start": "backstage-cli plugin:serve"
},
"dependencies": {
- "@backstage/core-components": "^0.7.3",
- "@backstage/core-plugin-api": "^0.1.13",
+ "@backstage/core-components": "^0.7.4",
+ "@backstage/core-plugin-api": "^0.2.0",
"@backstage/theme": "^0.2.13",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
@@ -45,10 +45,10 @@
"react-use": "^17.2.4"
},
"devDependencies": {
- "@backstage/cli": "^0.8.2",
- "@backstage/core-app-api": "^0.1.20",
- "@backstage/dev-utils": "^0.2.12",
- "@backstage/test-utils": "^0.1.21",
+ "@backstage/cli": "^0.9.0",
+ "@backstage/core-app-api": "^0.1.22",
+ "@backstage/dev-utils": "^0.2.13",
+ "@backstage/test-utils": "^0.1.22",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
diff --git a/plugins/tech-radar/src/plugin.ts b/plugins/tech-radar/src/plugin.ts
index bd30d6a984..495aa47879 100644
--- a/plugins/tech-radar/src/plugin.ts
+++ b/plugins/tech-radar/src/plugin.ts
@@ -25,7 +25,7 @@ import {
} from '@backstage/core-plugin-api';
const rootRouteRef = createRouteRef({
- title: 'Tech Radar',
+ id: 'tech-radar',
});
/**
diff --git a/plugins/techdocs-backend/CHANGELOG.md b/plugins/techdocs-backend/CHANGELOG.md
index 7331ea0147..ad7a8af85e 100644
--- a/plugins/techdocs-backend/CHANGELOG.md
+++ b/plugins/techdocs-backend/CHANGELOG.md
@@ -1,5 +1,17 @@
# @backstage/plugin-techdocs-backend
+## 0.10.8
+
+### Patch Changes
+
+- e21e3c6102: Bumping minimum requirements for `dockerode` and `testcontainers`
+- 9e64a7ac1e: Allow amazon web services s3 buckets to pass an server side encryption configuration so they can publish to encrypted buckets
+- Updated dependencies
+ - @backstage/catalog-client@0.5.2
+ - @backstage/catalog-model@0.9.7
+ - @backstage/backend-common@0.9.10
+ - @backstage/techdocs-common@0.10.7
+
## 0.10.7
### Patch Changes
diff --git a/plugins/techdocs-backend/config.d.ts b/plugins/techdocs-backend/config.d.ts
index da3cd34830..9e563137bd 100644
--- a/plugins/techdocs-backend/config.d.ts
+++ b/plugins/techdocs-backend/config.d.ts
@@ -227,14 +227,14 @@ export interface Config {
};
/**
- * @example http://localhost:7000/api/techdocs
+ * @example http://localhost:7007/api/techdocs
* @visibility frontend
* @deprecated
*/
requestUrl?: string;
/**
- * @example http://localhost:7000/api/techdocs/static/docs
+ * @example http://localhost:7007/api/techdocs/static/docs
* @deprecated
*/
storageUrl?: string;
diff --git a/plugins/techdocs-backend/package.json b/plugins/techdocs-backend/package.json
index c71833948b..332884c694 100644
--- a/plugins/techdocs-backend/package.json
+++ b/plugins/techdocs-backend/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-techdocs-backend",
"description": "The Backstage backend plugin that renders technical documentation for your components",
- "version": "0.10.7",
+ "version": "0.10.8",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -31,17 +31,17 @@
"clean": "backstage-cli clean"
},
"dependencies": {
- "@backstage/backend-common": "^0.9.8",
- "@backstage/catalog-client": "^0.5.0",
- "@backstage/catalog-model": "^0.9.6",
+ "@backstage/backend-common": "^0.9.10",
+ "@backstage/catalog-client": "^0.5.2",
+ "@backstage/catalog-model": "^0.9.7",
"@backstage/config": "^0.1.11",
"@backstage/errors": "^0.1.4",
"@backstage/integration": "^0.6.9",
"@backstage/search-common": "^0.2.1",
- "@backstage/techdocs-common": "^0.10.5",
+ "@backstage/techdocs-common": "^0.10.7",
"@types/express": "^4.17.6",
"cross-fetch": "^3.0.6",
- "dockerode": "^3.2.1",
+ "dockerode": "^3.3.1",
"express": "^4.17.1",
"express-promise-router": "^4.1.0",
"fs-extra": "9.1.0",
@@ -51,9 +51,9 @@
"winston": "^3.2.1"
},
"devDependencies": {
- "@backstage/cli": "^0.8.1",
- "@backstage/test-utils": "^0.1.20",
- "@types/dockerode": "^3.2.1",
+ "@backstage/cli": "^0.9.0",
+ "@backstage/test-utils": "^0.1.22",
+ "@types/dockerode": "^3.3.0",
"msw": "^0.35.0",
"supertest": "^6.1.3"
},
diff --git a/plugins/techdocs/CHANGELOG.md b/plugins/techdocs/CHANGELOG.md
index be100b9dea..6b8a8f0ff9 100644
--- a/plugins/techdocs/CHANGELOG.md
+++ b/plugins/techdocs/CHANGELOG.md
@@ -1,5 +1,22 @@
# @backstage/plugin-techdocs
+## 0.12.6
+
+### Patch Changes
+
+- a125278b81: Refactor out the deprecated path and icon from RouteRefs
+- c1858c4cf9: Fixed entity triplet case handling for certain locales.
+- f7703981a9: Use a better checkbox rendering in a task list.
+- e266687580: Updates reader component used to display techdocs documentation. A previous change made this component not usable out of a page which don't have entityRef in url parameters. Reader component EntityRef parameter is now used instead of url parameters. Techdocs documentation component can now be used in our custom pages.
+- Updated dependencies
+ - @backstage/plugin-catalog@0.7.3
+ - @backstage/catalog-model@0.9.7
+ - @backstage/plugin-catalog-react@0.6.4
+ - @backstage/plugin-search@0.4.18
+ - @backstage/core-components@0.7.4
+ - @backstage/core-plugin-api@0.2.0
+ - @backstage/integration-react@0.1.14
+
## 0.12.5
### Patch Changes
diff --git a/plugins/techdocs/config.d.ts b/plugins/techdocs/config.d.ts
index d6bf21b10b..45fd524eff 100644
--- a/plugins/techdocs/config.d.ts
+++ b/plugins/techdocs/config.d.ts
@@ -34,7 +34,7 @@ export interface Config {
legacyUseCaseSensitiveTripletPaths?: boolean;
/**
- * @example http://localhost:7000/api/techdocs
+ * @example http://localhost:7007/api/techdocs
* @visibility frontend
* @deprecated
*/
diff --git a/plugins/techdocs/package.json b/plugins/techdocs/package.json
index 79841a0621..663c83118e 100644
--- a/plugins/techdocs/package.json
+++ b/plugins/techdocs/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-techdocs",
"description": "The Backstage plugin that renders technical documentation for your components",
- "version": "0.12.5",
+ "version": "0.12.6",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -32,16 +32,16 @@
"clean": "backstage-cli clean"
},
"dependencies": {
- "@backstage/catalog-model": "^0.9.6",
+ "@backstage/catalog-model": "^0.9.7",
"@backstage/config": "^0.1.11",
- "@backstage/core-components": "^0.7.3",
- "@backstage/core-plugin-api": "^0.1.13",
+ "@backstage/core-components": "^0.7.4",
+ "@backstage/core-plugin-api": "^0.2.0",
"@backstage/errors": "^0.1.4",
"@backstage/integration": "^0.6.9",
- "@backstage/integration-react": "^0.1.13",
- "@backstage/plugin-catalog": "^0.7.2",
- "@backstage/plugin-catalog-react": "^0.6.3",
- "@backstage/plugin-search": "^0.4.17",
+ "@backstage/integration-react": "^0.1.14",
+ "@backstage/plugin-catalog": "^0.7.3",
+ "@backstage/plugin-catalog-react": "^0.6.4",
+ "@backstage/plugin-search": "^0.4.18",
"@backstage/theme": "^0.2.13",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
@@ -61,10 +61,10 @@
"react-use": "^17.2.4"
},
"devDependencies": {
- "@backstage/cli": "^0.8.2",
- "@backstage/core-app-api": "^0.1.20",
- "@backstage/dev-utils": "^0.2.12",
- "@backstage/test-utils": "^0.1.21",
+ "@backstage/cli": "^0.9.0",
+ "@backstage/core-app-api": "^0.1.22",
+ "@backstage/dev-utils": "^0.2.13",
+ "@backstage/test-utils": "^0.1.22",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/react-hooks": "^7.0.2",
diff --git a/plugins/techdocs/src/reader/components/Reader.tsx b/plugins/techdocs/src/reader/components/Reader.tsx
index c0b0c99b59..90a3aa3489 100644
--- a/plugins/techdocs/src/reader/components/Reader.tsx
+++ b/plugins/techdocs/src/reader/components/Reader.tsx
@@ -76,8 +76,12 @@ const TechDocsReaderContext = createContext(
{} as TechDocsReaderValue,
);
-const TechDocsReaderProvider = ({ children }: PropsWithChildren<{}>) => {
- const { namespace = '', kind = '', name = '', '*': path } = useParams();
+const TechDocsReaderProvider = ({
+ children,
+ entityRef,
+}: PropsWithChildren<{ entityRef: EntityName }>) => {
+ const { '*': path } = useParams();
+ const { kind, namespace, name } = entityRef;
const value = useReaderState(kind, namespace, name, path);
return (
@@ -96,10 +100,10 @@ const TechDocsReaderProvider = ({ children }: PropsWithChildren<{}>) => {
* @internal
*/
export const withTechDocsReaderProvider =
- (Component: ComponentType) =>
+ (Component: ComponentType, entityRef: EntityName) =>
(props: T) =>
(
-
+
);
@@ -128,12 +132,12 @@ export const useTechDocsReader = () => useContext(TechDocsReaderContext);
* todo: Make public or stop exporting (see others: "altReaderExperiments")
* @internal
*/
-export const useTechDocsReaderDom = (): Element | null => {
+export const useTechDocsReaderDom = (entityRef: EntityName): Element | null => {
const navigate = useNavigate();
const theme = useTheme();
const techdocsStorageApi = useApi(techdocsStorageApiRef);
const scmIntegrationsApi = useApi(scmIntegrationsApiRef);
- const { namespace = '', kind = '', name = '' } = useParams();
+ const { namespace = '', kind = '', name = '' } = entityRef;
const { state, path, content: rawPage } = useTechDocsReader();
const [sidebars, setSidebars] = useState();
@@ -412,7 +416,7 @@ const TheReader = ({
withSearch = true,
}: Props) => {
const classes = useStyles();
- const dom = useTechDocsReaderDom();
+ const dom = useTechDocsReaderDom(entityRef);
const shadowDomRef = useRef(null);
const onReadyRef = useRef<() => void>(onReady);
@@ -452,7 +456,7 @@ export const Reader = ({
onReady = () => {},
withSearch = true,
}: Props) => (
-
+
createEnv('todo'));
+ // ...
+ apiRouter.use('/todo', await todo(todoEnv));
+```
+
## Scanned Files
The included `TodoReaderService` and `TodoScmReader` works by reading source code of to the entity that is being viewed. The location source code is determined by the value of the [`backstage.io/source-location`
diff --git a/plugins/todo-backend/package.json b/plugins/todo-backend/package.json
index c3f0e469aa..be1dc658f1 100644
--- a/plugins/todo-backend/package.json
+++ b/plugins/todo-backend/package.json
@@ -25,9 +25,9 @@
"clean": "backstage-cli clean"
},
"dependencies": {
- "@backstage/backend-common": "^0.9.7",
- "@backstage/catalog-client": "^0.5.0",
- "@backstage/catalog-model": "^0.9.5",
+ "@backstage/backend-common": "^0.9.10",
+ "@backstage/catalog-client": "^0.5.2",
+ "@backstage/catalog-model": "^0.9.7",
"@backstage/config": "^0.1.10",
"@backstage/errors": "^0.1.3",
"@backstage/integration": "^0.6.7",
@@ -40,7 +40,7 @@
"yn": "^4.0.0"
},
"devDependencies": {
- "@backstage/cli": "^0.8.0",
+ "@backstage/cli": "^0.9.0",
"@types/supertest": "^2.0.8",
"msw": "^0.35.0",
"supertest": "^6.1.3"
diff --git a/plugins/todo/CHANGELOG.md b/plugins/todo/CHANGELOG.md
index 99c33c425e..1a6a352e73 100644
--- a/plugins/todo/CHANGELOG.md
+++ b/plugins/todo/CHANGELOG.md
@@ -1,5 +1,16 @@
# @backstage/plugin-todo
+## 0.1.15
+
+### Patch Changes
+
+- a125278b81: Refactor out the deprecated path and icon from RouteRefs
+- Updated dependencies
+ - @backstage/catalog-model@0.9.7
+ - @backstage/plugin-catalog-react@0.6.4
+ - @backstage/core-components@0.7.4
+ - @backstage/core-plugin-api@0.2.0
+
## 0.1.14
### Patch Changes
diff --git a/plugins/todo/README.md b/plugins/todo/README.md
index 4165582b65..ad0ad4fc1d 100644
--- a/plugins/todo/README.md
+++ b/plugins/todo/README.md
@@ -2,6 +2,77 @@
This plugin lists `// TODO` comments in source code. It currently exports a single component extension for use on entity pages.
+## Setup
+
+1. Run:
+
+```bash
+# From your Backstage root directory
+yarn --cwd packages/app add @backstage/plugin-todo
+yarn --cwd packages/backend add @backstage/plugin-todo-backend
+```
+
+2. Add the plugin backend:
+
+In a new file named `todo.ts` under `backend/src/plugins`:
+
+```js
+import { Router } from 'express';
+import { CatalogClient } from '@backstage/catalog-client';
+import {
+ createRouter,
+ TodoReaderService,
+ TodoScmReader,
+} from '@backstage/plugin-todo-backend';
+import { PluginEnvironment } from '../types';
+
+export default async function createPlugin({
+ logger,
+ reader,
+ config,
+ discovery,
+}: PluginEnvironment): Promise {
+ const todoReader = TodoScmReader.fromConfig(config, {
+ logger,
+ reader,
+ });
+ const catalogClient = new CatalogClient({ discoveryApi: discovery });
+ const todoService = new TodoReaderService({
+ todoReader,
+ catalogClient,
+ });
+
+ return await createRouter({ todoService });
+}
+```
+
+And then add to `packages/backend/src/index.ts`:
+
+```js
+// In packages/backend/src/index.ts
+import todo from './plugins/todo';
+// ...
+async function main() {
+ // ...
+ const todoEnv = useHotMemoize(module, () => createEnv('todo'));
+ // ...
+ apiRouter.use('/todo', await todo(todoEnv));
+```
+
+3. Add the plugin as a tab to your service entities:
+
+```jsx
+// In packages/app/src/components/catalog/EntityPage.tsx
+import { EntityTodoContent } from '@backstage/plugin-todo';
+
+const serviceEntityPage = (
+
+ {/* other tabs... */}
+
+
+
+```
+
## Format
The default parser uses [Leasot](https://github.com/pgilad/leasot), which supports a wide range of languages. By default it supports the `TODO` and `FIXME` tags, along with `@` prefix and author reference through with either a `()` suffix or trailing `/`. For more information on how to configure the parser, see `@backstage/plugin-todo-backend`.
diff --git a/plugins/todo/package.json b/plugins/todo/package.json
index 5e32d84497..22a8e6b1e2 100644
--- a/plugins/todo/package.json
+++ b/plugins/todo/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-todo",
"description": "A Backstage plugin that lets you browse TODO comments in your source code",
- "version": "0.1.14",
+ "version": "0.1.15",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -27,11 +27,11 @@
"clean": "backstage-cli clean"
},
"dependencies": {
- "@backstage/catalog-model": "^0.9.5",
- "@backstage/core-components": "^0.7.3",
- "@backstage/core-plugin-api": "^0.1.13",
+ "@backstage/catalog-model": "^0.9.7",
+ "@backstage/core-components": "^0.7.4",
+ "@backstage/core-plugin-api": "^0.2.0",
"@backstage/errors": "^0.1.3",
- "@backstage/plugin-catalog-react": "^0.6.1",
+ "@backstage/plugin-catalog-react": "^0.6.4",
"@backstage/theme": "^0.2.13",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
@@ -41,10 +41,10 @@
"react-use": "^17.2.4"
},
"devDependencies": {
- "@backstage/cli": "^0.8.2",
- "@backstage/core-app-api": "^0.1.20",
- "@backstage/dev-utils": "^0.2.12",
- "@backstage/test-utils": "^0.1.21",
+ "@backstage/cli": "^0.9.0",
+ "@backstage/core-app-api": "^0.1.22",
+ "@backstage/dev-utils": "^0.2.13",
+ "@backstage/test-utils": "^0.1.22",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
diff --git a/plugins/todo/src/routes.ts b/plugins/todo/src/routes.ts
index 8ab3fb1b94..01d8d9f354 100644
--- a/plugins/todo/src/routes.ts
+++ b/plugins/todo/src/routes.ts
@@ -16,5 +16,5 @@
import { createRouteRef } from '@backstage/core-plugin-api';
export const rootRouteRef = createRouteRef({
- title: 'todo',
+ id: 'todo',
});
diff --git a/plugins/user-settings/CHANGELOG.md b/plugins/user-settings/CHANGELOG.md
index ae3bc1c72d..a309afb497 100644
--- a/plugins/user-settings/CHANGELOG.md
+++ b/plugins/user-settings/CHANGELOG.md
@@ -1,5 +1,15 @@
# @backstage/plugin-user-settings
+## 0.3.11
+
+### Patch Changes
+
+- a125278b81: Refactor out the deprecated path and icon from RouteRefs
+- 274a4fc633: Add Props Icon for Sidebar Item SidebarSearchField and Settings
+- Updated dependencies
+ - @backstage/core-components@0.7.4
+ - @backstage/core-plugin-api@0.2.0
+
## 0.3.10
### Patch Changes
diff --git a/plugins/user-settings/package.json b/plugins/user-settings/package.json
index dc63957cb9..def12ac1d2 100644
--- a/plugins/user-settings/package.json
+++ b/plugins/user-settings/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-user-settings",
"description": "A Backstage plugin that provides a settings page",
- "version": "0.3.10",
+ "version": "0.3.11",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -31,8 +31,8 @@
"clean": "backstage-cli clean"
},
"dependencies": {
- "@backstage/core-components": "^0.7.3",
- "@backstage/core-plugin-api": "^0.1.13",
+ "@backstage/core-components": "^0.7.4",
+ "@backstage/core-plugin-api": "^0.2.0",
"@backstage/theme": "^0.2.13",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
@@ -43,10 +43,10 @@
"react-use": "^17.2.4"
},
"devDependencies": {
- "@backstage/cli": "^0.8.2",
- "@backstage/core-app-api": "^0.1.20",
- "@backstage/dev-utils": "^0.2.12",
- "@backstage/test-utils": "^0.1.21",
+ "@backstage/cli": "^0.9.0",
+ "@backstage/core-app-api": "^0.1.22",
+ "@backstage/dev-utils": "^0.2.13",
+ "@backstage/test-utils": "^0.1.22",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
diff --git a/plugins/user-settings/src/components/Settings.tsx b/plugins/user-settings/src/components/Settings.tsx
index 43a00ee9cc..e9b3ae7d0a 100644
--- a/plugins/user-settings/src/components/Settings.tsx
+++ b/plugins/user-settings/src/components/Settings.tsx
@@ -18,14 +18,14 @@ import React from 'react';
import SettingsIcon from '@material-ui/icons/Settings';
import { settingsRouteRef } from '../plugin';
import { SidebarItem } from '@backstage/core-components';
-import { IconComponent } from '@backstage/core-plugin-api';
+import { useRouteRef, IconComponent } from '@backstage/core-plugin-api';
type SettingsProps = {
icon?: IconComponent;
};
export const Settings = (props: SettingsProps) => {
+ const routePath = useRouteRef(settingsRouteRef);
const Icon = props.icon ? props.icon : SettingsIcon;
-
- return ;
+ return ;
};
diff --git a/plugins/user-settings/src/plugin.ts b/plugins/user-settings/src/plugin.ts
index 54f6faceed..01793dc418 100644
--- a/plugins/user-settings/src/plugin.ts
+++ b/plugins/user-settings/src/plugin.ts
@@ -21,8 +21,7 @@ import {
} from '@backstage/core-plugin-api';
export const settingsRouteRef = createRouteRef({
- path: '/settings',
- title: 'Settings',
+ id: 'user-settings',
});
export const userSettingsPlugin = createPlugin({
diff --git a/plugins/xcmetrics/CHANGELOG.md b/plugins/xcmetrics/CHANGELOG.md
index a7d2d480b7..4ae5c49c32 100644
--- a/plugins/xcmetrics/CHANGELOG.md
+++ b/plugins/xcmetrics/CHANGELOG.md
@@ -1,5 +1,14 @@
# @backstage/plugin-xcmetrics
+## 0.2.10
+
+### Patch Changes
+
+- a125278b81: Refactor out the deprecated path and icon from RouteRefs
+- Updated dependencies
+ - @backstage/core-components@0.7.4
+ - @backstage/core-plugin-api@0.2.0
+
## 0.2.9
### Patch Changes
diff --git a/plugins/xcmetrics/package.json b/plugins/xcmetrics/package.json
index bbab66e455..f29f763666 100644
--- a/plugins/xcmetrics/package.json
+++ b/plugins/xcmetrics/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-xcmetrics",
"description": "A Backstage plugin that shows XCode build metrics for your components",
- "version": "0.2.9",
+ "version": "0.2.10",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -21,8 +21,8 @@
"clean": "backstage-cli clean"
},
"dependencies": {
- "@backstage/core-components": "^0.7.3",
- "@backstage/core-plugin-api": "^0.1.13",
+ "@backstage/core-components": "^0.7.4",
+ "@backstage/core-plugin-api": "^0.2.0",
"@backstage/errors": "^0.1.3",
"@backstage/theme": "^0.2.13",
"@material-ui/core": "^4.12.2",
@@ -36,10 +36,10 @@
"recharts": "^1.8.5"
},
"devDependencies": {
- "@backstage/cli": "^0.8.2",
- "@backstage/core-app-api": "^0.1.20",
- "@backstage/dev-utils": "^0.2.12",
- "@backstage/test-utils": "^0.1.21",
+ "@backstage/cli": "^0.9.0",
+ "@backstage/core-app-api": "^0.1.22",
+ "@backstage/dev-utils": "^0.2.13",
+ "@backstage/test-utils": "^0.1.22",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
diff --git a/plugins/xcmetrics/src/components/XcmetricsLayout/XcmetricsLayout.tsx b/plugins/xcmetrics/src/components/XcmetricsLayout/XcmetricsLayout.tsx
index 53545890a4..a21918e322 100644
--- a/plugins/xcmetrics/src/components/XcmetricsLayout/XcmetricsLayout.tsx
+++ b/plugins/xcmetrics/src/components/XcmetricsLayout/XcmetricsLayout.tsx
@@ -22,24 +22,23 @@ import {
TabbedLayout,
} from '@backstage/core-components';
import { Overview } from '../Overview';
-import { buildsRouteRef, rootRouteRef } from '../../routes';
-import { RouteRef, SubRouteRef } from '@backstage/core-plugin-api';
+import { buildsRouteRef } from '../../routes';
import { BuildsPage } from '../BuildsPage';
export interface TabConfig {
- routeRef: RouteRef | SubRouteRef;
+ path: string;
title: string;
component: ReactChild;
}
const TABS: TabConfig[] = [
{
- routeRef: rootRouteRef,
+ path: '/',
title: 'Overview',
component: ,
},
{
- routeRef: buildsRouteRef,
+ path: buildsRouteRef.path,
title: 'Builds',
component: ,
},
@@ -53,11 +52,7 @@ export const XcmetricsLayout = () => (
{TABS.map(tab => (
-
+
{tab.component}
))}
diff --git a/plugins/xcmetrics/src/routes.ts b/plugins/xcmetrics/src/routes.ts
index 212ecbae48..81934c860f 100644
--- a/plugins/xcmetrics/src/routes.ts
+++ b/plugins/xcmetrics/src/routes.ts
@@ -16,7 +16,7 @@
import { createRouteRef, createSubRouteRef } from '@backstage/core-plugin-api';
export const rootRouteRef = createRouteRef({
- title: 'XCMetrics',
+ id: 'xcmetrics',
});
export const buildsRouteRef = createSubRouteRef({
diff --git a/scripts/api-extractor.ts b/scripts/api-extractor.ts
index c8f35508fa..22eae267ee 100644
--- a/scripts/api-extractor.ts
+++ b/scripts/api-extractor.ts
@@ -30,8 +30,15 @@ import {
ExtractorConfig,
CompilerState,
ExtractorLogLevel,
+ ExtractorMessage,
} from '@microsoft/api-extractor';
-import { DocNode, IDocNodeContainerParameters } from '@microsoft/tsdoc';
+import { Program } from 'typescript';
+import {
+ DocNode,
+ IDocNodeContainerParameters,
+ TSDocTagSyntaxKind,
+} from '@microsoft/tsdoc';
+import { TSDocConfigFile } from '@microsoft/tsdoc-config';
import { ApiPackage, ApiModel } from '@microsoft/api-extractor-model';
import {
IMarkdownDocumenterOptions,
@@ -42,6 +49,7 @@ import { DocTableRow } from '@microsoft/api-documenter/lib/nodes/DocTableRow';
import { DocHeading } from '@microsoft/api-documenter/lib/nodes/DocHeading';
import { CustomMarkdownEmitter } from '@microsoft/api-documenter/lib/markdown/CustomMarkdownEmitter';
import { IMarkdownEmitterContext } from '@microsoft/api-documenter/lib/markdown/MarkdownEmitter';
+import { AstDeclaration } from '@microsoft/api-extractor/lib/analyzer/AstDeclaration';
const tmpDir = resolvePath(__dirname, '../node_modules/.cache/api-extractor');
@@ -79,11 +87,96 @@ const {
ApiReportGenerator,
} = require('@microsoft/api-extractor/lib/generators/ApiReportGenerator');
+function patchFileMessageFetcher(
+ router: any,
+ transform: (messages: ExtractorMessage[], ast?: AstDeclaration) => void,
+) {
+ const {
+ fetchAssociatedMessagesForReviewFile,
+ fetchUnassociatedMessagesForReviewFile,
+ } = router;
+
+ router.fetchAssociatedMessagesForReviewFile =
+ function patchedFetchAssociatedMessagesForReviewFile(ast) {
+ const messages = fetchAssociatedMessagesForReviewFile.call(this, ast);
+ return transform(messages, ast);
+ };
+ router.fetchUnassociatedMessagesForReviewFile =
+ function patchedFetchUnassociatedMessagesForReviewFile() {
+ const messages = fetchUnassociatedMessagesForReviewFile.call(this);
+ return transform(messages);
+ };
+}
+
const originalGenerateReviewFileContent =
ApiReportGenerator.generateReviewFileContent;
ApiReportGenerator.generateReviewFileContent =
- function decoratedGenerateReviewFileContent(...args) {
- const content = originalGenerateReviewFileContent.apply(this, args);
+ function decoratedGenerateReviewFileContent(collector, ...moreArgs) {
+ const program = collector.program as Program;
+
+ // The purpose of this override is to allow the @ignore tag to be used to ignore warnings
+ // of the form "Warning: (ae-forgotten-export) The symbol "FooBar" needs to be exported by the entry point index.d.ts"
+ patchFileMessageFetcher(
+ collector.messageRouter,
+ (messages: ExtractorMessage[]) => {
+ return messages.filter(message => {
+ if (message.messageId !== 'ae-forgotten-export') {
+ return true;
+ }
+
+ // Symbol name has to be extracted from the message :(
+ // There's frequently no AST for these exports because type literals
+ // aren't traversed by the generator.
+ const symbolMatch = message.text.match(/The symbol "([^"]+)"/);
+ if (!symbolMatch) {
+ throw new Error(
+ `Failed to extract symbol name from message "${message.text}"`,
+ );
+ }
+ const [, symbolName] = symbolMatch;
+
+ const sourceFile = program.getSourceFile(message.sourceFilePath);
+ if (!sourceFile) {
+ throw new Error(
+ `Failed to find source file in program at path "${message.sourceFilePath}"`,
+ );
+ }
+
+ // NOTE: we limit the @internal functionality to only apply to types that are declared
+ // in the same module as where they're being referenced from. This limitation makes
+ // the implementation here simpler but could be revisited if needed.
+
+ // The local name of the symbol within the file, rather than the exported name
+ const localName = (sourceFile as any).identifiers?.get(symbolName);
+ if (!localName) {
+ return true;
+ }
+ // The local AST node of the export that we're missing
+ const local = (sourceFile as any).locals?.get(localName);
+ if (!local) {
+ return true;
+ }
+
+ // If any of the TSDoc comments contain a @ignore tag, we ignore this message
+ const isIgnored = local.declarations.some(declaration => {
+ const tags = [declaration.jsDoc]
+ .flat()
+ .filter(Boolean)
+ .flatMap((tagNode: any) => tagNode.tags);
+
+ return tags.some(tag => tag?.tagName.text === 'ignore');
+ });
+
+ return !isIgnored;
+ });
+ },
+ );
+
+ const content = originalGenerateReviewFileContent.call(
+ this,
+ collector,
+ ...moreArgs,
+ );
return prettier.format(content, {
...require('@spotify/prettier-config'),
parser: 'markdown',
@@ -99,6 +192,7 @@ const SKIPPED_PACKAGES = [
join('packages', 'codemods'),
join('packages', 'create-app'),
join('packages', 'e2e-test'),
+ join('packages', 'embedded-techdocs-app'),
join('packages', 'storybook'),
join('packages', 'techdocs-cli'),
];
@@ -134,6 +228,18 @@ async function findPackageDirs() {
return packageDirs;
}
+async function getTsDocConfig() {
+ const tsdocConfigFile = await TSDocConfigFile.loadFile(
+ require.resolve('@microsoft/api-extractor/extends/tsdoc-base.json'),
+ );
+ tsdocConfigFile.addTagDefinition({
+ tagName: '@ignore',
+ syntaxKind: TSDocTagSyntaxKind.ModifierTag,
+ });
+ tsdocConfigFile.setSupportForTag('@ignore', true);
+ return tsdocConfigFile;
+}
+
function logApiReportInstructions() {
console.log('');
console.log(
@@ -236,6 +342,7 @@ async function runApiExtraction({
},
configObjectFullPath: projectFolder,
packageJsonFullPath: resolvePath(projectFolder, 'package.json'),
+ tsdocConfigFile: await getTsDocConfig(),
});
// The `packageFolder` needs to point to the location within `dist-types` in order for relative
diff --git a/scripts/check-if-release.js b/scripts/check-if-release.js
index 4475ed6207..b61b0e2eb7 100755
--- a/scripts/check-if-release.js
+++ b/scripts/check-if-release.js
@@ -96,7 +96,9 @@ async function main() {
const newVersions = packageVersions.filter(
({ oldVersion, newVersion }) =>
- oldVersion !== newVersion && newVersion !== '',
+ oldVersion !== newVersion &&
+ oldVersion !== '' &&
+ newVersion !== '',
);
if (newVersions.length === 0) {
diff --git a/scripts/list-deprecations.js b/scripts/list-deprecations.js
new file mode 100755
index 0000000000..920b01c7b5
--- /dev/null
+++ b/scripts/list-deprecations.js
@@ -0,0 +1,229 @@
+#!/usr/bin/env node
+/*
+ * Copyright 2021 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* eslint-disable import/no-extraneous-dependencies */
+
+const _ = require('lodash');
+const fs = require('fs-extra');
+const globby = require('globby');
+const { resolve: resolvePath, relative: relativePath } = require('path');
+const { execFile: execFileCb } = require('child_process');
+const { promisify } = require('util');
+
+const execFile = promisify(execFileCb);
+
+const WORKER_COUNT = 16;
+
+const deprecatedPattern = /@deprecated|DEPRECATION/;
+
+class ReleaseProvider {
+ cache = new Map();
+
+ constructor(rootPath) {
+ this.rootPath = rootPath;
+ }
+
+ async lookup(commitSha, packageDir) {
+ const key = commitSha + packageDir;
+
+ if (this.cache.has(key)) {
+ return this.cache.get(key);
+ }
+
+ const pkgJsonPath = relativePath(
+ this.rootPath,
+ resolvePath(this.rootPath, packageDir, 'package.json'),
+ );
+ const releasePromise = Promise.resolve().then(async () => {
+ // Find all tags that contain the commit
+ const { stdout: tagOutput } = await execFile(
+ 'git',
+ ['tag', '--contains', commitSha],
+ { shell: true },
+ );
+
+ // Filter out just the releases
+ const releases = tagOutput
+ .split('\n')
+ .filter(l => l.startsWith('release-'));
+
+ // Then find the earliest release that affected our package
+ for (const release of releases) {
+ let oldVersion;
+ let newVersion;
+
+ try {
+ const { stdout: content } = await execFile(
+ 'git',
+ ['show', `${release}^:${pkgJsonPath}`],
+ { shell: true },
+ );
+ oldVersion = JSON.parse(content).version;
+ } catch {
+ /* */
+ }
+ try {
+ const { stdout: content } = await execFile(
+ 'git',
+ ['show', `${release}:${pkgJsonPath}`],
+ { shell: true },
+ );
+ newVersion = JSON.parse(content).version;
+ } catch {
+ /* */
+ }
+
+ if (oldVersion !== newVersion) {
+ return release;
+ }
+ }
+ return undefined;
+ });
+
+ this.cache.set(key, releasePromise);
+ return releasePromise;
+ }
+}
+
+async function main() {
+ let packageDirQueue = process.argv.slice(2);
+
+ const rootPath = resolvePath(__dirname, '..');
+
+ if (packageDirQueue.length === 0) {
+ packageDirQueue = await Promise.all([
+ fs.readdir(resolvePath(rootPath, 'packages')),
+ fs.readdir(resolvePath(rootPath, 'plugins')),
+ ]).then(([packages, plugins]) => [
+ ...packages.map(dir => `packages/${dir}`),
+ ...plugins.map(dir => `plugins/${dir}`),
+ ]);
+ }
+
+ const fileQueue = [];
+ const deprecationQueue = [];
+ const releaseProvider = new ReleaseProvider(rootPath);
+ const deprecations = [];
+
+ await Promise.all(
+ Array(WORKER_COUNT)
+ .fill()
+ .map(async () => {
+ // Find all files we want to scan
+ while (packageDirQueue.length) {
+ const packageDir = packageDirQueue.pop();
+ const srcDir = resolvePath(rootPath, packageDir, 'src');
+
+ if (await fs.pathExists(srcDir)) {
+ const files = await globby(['**/*.{js,jsx,ts,tsx}'], {
+ cwd: srcDir,
+ });
+ fileQueue.push(
+ ...files.map(file => ({
+ packageDir,
+ file: resolvePath(srcDir, file),
+ })),
+ );
+ }
+ }
+
+ // Parse files and search for deprecations
+ while (fileQueue.length) {
+ const { packageDir, file } = fileQueue.pop();
+ const content = await fs.readFile(file, 'utf8');
+ if (!deprecatedPattern.test(content)) {
+ continue;
+ }
+
+ const lines = content.split('\n');
+ for (const [index, line] of lines.entries()) {
+ if (deprecatedPattern.test(line)) {
+ deprecationQueue.push({
+ packageDir,
+ file,
+ lineNumber: index + 1,
+ lineContent: line,
+ });
+ }
+ }
+ }
+
+ // Lookup git information for each deprecation
+ while (deprecationQueue.length) {
+ const deprecation = deprecationQueue.pop();
+
+ const { file, packageDir, lineNumber: n, lineContent } = deprecation;
+ const { stdout: blameOutput } = await execFile(
+ 'git',
+ ['blame', '--porcelain', `-L ${n},${n}`, file],
+ { shell: true },
+ );
+
+ const blameInfo = Object.fromEntries(
+ blameOutput
+ .split('\n')
+ .slice(1, -2)
+ .map(line => {
+ const [key] = line.split(' ', 1);
+ return [key, line.slice(key.length + 1)];
+ }),
+ );
+ const [commit] = blameOutput.split(' ', 1);
+ const { author, ['author-time']: authorTime } = blameInfo;
+
+ const release = await releaseProvider.lookup(commit, packageDir);
+
+ deprecations.push({
+ file: relativePath(rootPath, file),
+ release,
+ commit,
+ author,
+ authorTime: new Date(authorTime * 1000),
+ lineContent,
+ lineNumber: n,
+ });
+ }
+ }),
+ );
+
+ const maxAuthor = Math.max(...deprecations.map(d => d.author.length)) + 1;
+
+ // Group and sort by release
+ const sortedByRelease = _.sortBy(
+ Object.entries(_.groupBy(deprecations, 'release')),
+ ([release]) => release,
+ );
+
+ for (const [release, ds] of sortedByRelease) {
+ console.log(`\n### ${release === 'undefined' ? 'Not released' : release}`);
+ for (const d of _.sortBy(ds, 'authorTime', 'file', 'lineNumber')) {
+ console.log(
+ [
+ d.commit.slice(0, 8),
+ d.authorTime.toLocaleDateString().padEnd('00/00/0000'.length + 1),
+ d.author.padEnd(maxAuthor + 1),
+ `${d.file}:${d.lineNumber}`,
+ ].join(' '),
+ );
+ }
+ }
+}
+
+main().catch(err => {
+ console.error(err.stack);
+ process.exit(1);
+});
diff --git a/scripts/migrate-location-types.js b/scripts/migrate-location-types.js
index d6ac5c6a2a..168a5e825c 100755
--- a/scripts/migrate-location-types.js
+++ b/scripts/migrate-location-types.js
@@ -20,7 +20,7 @@
// catalog API endpoint and execute the script. It will delete and add
// back the locations with the correct type one by one.
-const BASE_URL = 'http://localhost:7000/api/catalog'; // Change me
+const BASE_URL = 'http://localhost:7007/api/catalog'; // Change me
const deprecatedTypes = [
'github',
@@ -81,9 +81,9 @@ async function request(method, url, body) {
return;
}
try {
- const body = Buffer.concat(chunks).toString('utf8').trim();
- if (body) {
- resolve(JSON.parse(body));
+ const responseBody = Buffer.concat(chunks).toString('utf8').trim();
+ if (responseBody) {
+ resolve(JSON.parse(responseBody));
} else {
resolve();
}
diff --git a/scripts/techdocs-cli.js b/scripts/techdocs-cli.js
new file mode 100644
index 0000000000..7cc07f15fa
--- /dev/null
+++ b/scripts/techdocs-cli.js
@@ -0,0 +1,25 @@
+#!/usr/bin/env node
+/*
+ * Copyright 2021 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+const { execSync } = require('child_process');
+
+const args = process.argv.slice(2);
+
+execSync(`yarn -s workspace @techdocs/cli build`, { stdio: 'inherit' });
+execSync(`yarn workspace @techdocs/cli link`, { stdio: 'ignore' });
+execSync(`techdocs-cli ${args.join(' ')}`, { stdio: 'inherit' });
+execSync(`yarn workspace @techdocs/cli unlink`, { stdio: 'ignore' });
diff --git a/yarn.lock b/yarn.lock
index adf51ccb32..07a5febcb8 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2172,7 +2172,7 @@
"@babel/helper-validator-identifier" "^7.14.9"
to-fast-properties "^2.0.0"
-"@backstage/catalog-client@^0.3.16", "@backstage/catalog-client@^0.3.18":
+"@backstage/catalog-client@^0.3.18":
version "0.3.19"
resolved "https://registry.npmjs.org/@backstage/catalog-client/-/catalog-client-0.3.19.tgz#109b0fde1cc2d9a306635f3775fc8f6174172b14"
integrity sha512-0ydcC/1ISNgR8SggOUnMNNHtsRwrKN5GX+AXgTVdZk4qpz1MqG1+fzrNld9V7KVvXdsbGDZAl/b5a7/FJEpCqg==
@@ -2360,6 +2360,24 @@
remark-gfm "^1.0.0"
zen-observable "^0.8.15"
+"@backstage/core-plugin-api@^0.1.10", "@backstage/core-plugin-api@^0.1.3", "@backstage/core-plugin-api@^0.1.4", "@backstage/core-plugin-api@^0.1.6", "@backstage/core-plugin-api@^0.1.8":
+ version "0.1.13"
+ resolved "https://registry.npmjs.org/@backstage/core-plugin-api/-/core-plugin-api-0.1.13.tgz#583c0fe3440450c304f13dcc85eac3bd80f357d7"
+ integrity sha512-BWEqleTXR7m7nsCyt6cEnc+Gx/VT4vLdNr9fc1kXfzSAA7cUT4VKjlVmm5kdyih3BSQ4+0k7Pm/NIf0jfa7rlg==
+ dependencies:
+ "@backstage/config" "^0.1.11"
+ "@backstage/theme" "^0.2.13"
+ "@backstage/types" "^0.1.1"
+ "@backstage/version-bridge" "^0.1.0"
+ "@material-ui/core" "^4.12.2"
+ "@types/react" "*"
+ history "^5.0.0"
+ prop-types "^15.7.2"
+ react "^16.12.0"
+ react-router-dom "6.0.0-beta.0"
+ react-use "^17.2.4"
+ zen-observable "^0.8.15"
+
"@backstage/plugin-catalog-react@^0.4.0":
version "0.4.6"
resolved "https://registry.npmjs.org/@backstage/plugin-catalog-react/-/plugin-catalog-react-0.4.6.tgz#f33f3cd734f110d3c547f7eae4e25d14806ec796"
@@ -5054,19 +5072,19 @@
resolved "https://registry.npmjs.org/@octokit/webhooks-methods/-/webhooks-methods-2.0.0.tgz#1108b9ea661ca6c81e4a8bfa63a09eb27d5bc2db"
integrity sha512-35cfQ4YWlnZnmZKmIxlGPUPLtbkF8lr/A/1Sk1eC0ddLMwQN06dOuLc+dI3YLQS+T+MoNt3DIQ0NynwgKPilig==
-"@octokit/webhooks-types@4.12.0":
- version "4.12.0"
- resolved "https://registry.npmjs.org/@octokit/webhooks-types/-/webhooks-types-4.12.0.tgz#add8b98d60ab42e965c4ba31040097f810e222de"
- integrity sha512-G0k7CoS9bK+OI7kPHgqi1KqK4WhrjDQSjy0wJI+0OTx/xvbHUIZDeqatY60ceeRINP/1ExEk6kTARboP0xavEw==
+"@octokit/webhooks-types@4.15.0":
+ version "4.15.0"
+ resolved "https://registry.npmjs.org/@octokit/webhooks-types/-/webhooks-types-4.15.0.tgz#1158cba6578237d60957a37963a4a05654f5668b"
+ integrity sha512-s9LgKsUzq/JH3PWDjaD/m1DIlC/QWgBWbmXVqjdxJXJQBA67KZrLWjStVlYPf0mWlVZ1MOKphDyHiOGCbs0+Kg==
"@octokit/webhooks@^9.14.1":
- version "9.17.0"
- resolved "https://registry.npmjs.org/@octokit/webhooks/-/webhooks-9.17.0.tgz#81140b2e127157aa9817d085cd8758545e4a72e4"
- integrity sha512-/+9WSLuDuoqNWnMY4w6ePioILBqsUOiUt0ygQzugYzd112WB+yPIjmUQjAbNXImDsAa1myLpBICAMQDZlULyAA==
+ version "9.18.0"
+ resolved "https://registry.npmjs.org/@octokit/webhooks/-/webhooks-9.18.0.tgz#19cc70e1ef281e33d830ea23e8011d25d8051f7f"
+ integrity sha512-N2hP7vCouKk9UWZxvqgWTPbp34i6g9Om/jk+TZeZ5Z+VsKjXvGtONlEd9H8DM1yOeEC+ARDpfhraX6UsK5tesQ==
dependencies:
"@octokit/request-error" "^2.0.2"
"@octokit/webhooks-methods" "^2.0.0"
- "@octokit/webhooks-types" "4.12.0"
+ "@octokit/webhooks-types" "4.15.0"
aggregate-error "^3.1.0"
"@open-draft/until@^1.0.3":
@@ -5189,25 +5207,25 @@
prop-types "^15.6.1"
react-lifecycles-compat "^3.0.4"
-"@rjsf/core@^3.0.0":
- version "3.2.0"
- resolved "https://registry.npmjs.org/@rjsf/core/-/core-3.2.0.tgz#189ff7fb132cb223463792d75fb1e04e9dd5d9f9"
- integrity sha512-Cgonq9bBWlpE0yeeIv7rAIDuJvO1wAvp9G7tT/UvhODAyb4tDb1q8J/8ubWuRRiEyzYUkuxN0W8uPIzQsJgtTQ==
+"@rjsf/core@^3.2.1":
+ version "3.2.1"
+ resolved "https://registry.npmjs.org/@rjsf/core/-/core-3.2.1.tgz#8a7b24c9a6f01f0ecb093fdfc777172c12b1b009"
+ integrity sha512-dk8ihvxFbcuIwU7G+HiJbFgwyIvaumPt5g5zfnuC26mwTUPlaDGFXKK2yITp8tJ3+hcwS5zEXtAN9wUkfuM4jA==
dependencies:
"@types/json-schema" "^7.0.7"
ajv "^6.7.0"
core-js-pure "^3.6.5"
json-schema-merge-allof "^0.6.0"
- jsonpointer "^4.0.1"
+ jsonpointer "^5.0.0"
lodash "^4.17.15"
nanoid "^3.1.23"
prop-types "^15.7.2"
react-is "^16.9.0"
-"@rjsf/material-ui@^3.0.0":
- version "3.1.0"
- resolved "https://registry.npmjs.org/@rjsf/material-ui/-/material-ui-3.1.0.tgz#ed15fc75de1594f87cd8336b7f2ebc492a432d8b"
- integrity sha512-kWz37spT5SOXkb8Axq4g4BzQjXRylQr6B7eFAg1NPhSK7KrJYwSMSsFJ9Ze2vEBxwEiR0Z0n/4puaamGuGFRBw==
+"@rjsf/material-ui@^3.2.1":
+ version "3.2.1"
+ resolved "https://registry.npmjs.org/@rjsf/material-ui/-/material-ui-3.2.1.tgz#84fbf322485aee3a84101e189161f0687779ec8d"
+ integrity sha512-8UiDeDbjCImFSfOegGu13otQ7OdP9FOYpcLjeouppnhs+MPeIEAtYS+jCcBKmi3reyTagC15/KVSRhde1wS1vg==
"@roadiehq/backstage-plugin-buildkite@^1.0.8":
version "1.0.8"
@@ -6763,6 +6781,13 @@
dependencies:
"@types/glob" "*"
+"@types/archiver@^5.1.1":
+ version "5.1.1"
+ resolved "https://registry.npmjs.org/@types/archiver/-/archiver-5.1.1.tgz#d6d7610de4386b293abd5c1cb1875e0a4f4e1c30"
+ integrity sha512-heuaCk0YH5m274NOLSi66H1zX6GtZoMsdE6TYFcpFFjBjg0FoU4i4/M/a/kNlgNg26Xk3g364mNOYe1JaiEPOQ==
+ dependencies:
+ "@types/glob" "*"
+
"@types/argparse@1.0.38":
version "1.0.38"
resolved "https://registry.npmjs.org/@types/argparse/-/argparse-1.0.38.tgz#a81fd8606d481f873a3800c6ebae4f1d768a56a9"
@@ -6859,6 +6884,14 @@
dependencies:
classnames "*"
+"@types/clean-css@*":
+ version "4.2.5"
+ resolved "https://registry.npmjs.org/@types/clean-css/-/clean-css-4.2.5.tgz#69ce62cc13557c90ca40460133f672dc52ceaf89"
+ integrity sha512-NEzjkGGpbs9S9fgC4abuBvTpVwE3i+Acu9BBod3PUyjDVZcNsGx61b8r2PphR61QGPnn0JHVs5ey6/I4eTrkxw==
+ dependencies:
+ "@types/node" "*"
+ source-map "^0.6.0"
+
"@types/codemirror@^0.0.108":
version "0.0.108"
resolved "https://registry.npmjs.org/@types/codemirror/-/codemirror-0.0.108.tgz#e640422b666bf49251b384c390cdeb2362585bde"
@@ -6890,6 +6923,13 @@
resolved "https://registry.npmjs.org/@types/command-exists/-/command-exists-1.2.0.tgz#d97e0ed10097090e4ab0367ed425b0312fad86f3"
integrity sha512-ugsxEJfsCuqMLSuCD4PIJkp5Uk2z6TCMRCgYVuhRo5cYQY3+1xXTQkSlPtkpGHuvWMjS2KTeVQXxkXRACMbM6A==
+"@types/commander@^2.12.2":
+ version "2.12.2"
+ resolved "https://registry.npmjs.org/@types/commander/-/commander-2.12.2.tgz#183041a23842d4281478fa5d23c5ca78e6fd08ae"
+ integrity sha512-0QEFiR8ljcHp9bAbWxecjVRuAMr16ivPiGOw6KFQBVrVd0RQIcM3xKdRisH2EDWgVWujiYtHwhSkSUoAAGzH7Q==
+ dependencies:
+ commander "*"
+
"@types/compression@^1.7.0":
version "1.7.0"
resolved "https://registry.npmjs.org/@types/compression/-/compression-1.7.0.tgz#8dc2a56604873cf0dd4e746d9ae4d31ae77b2390"
@@ -7040,19 +7080,19 @@
"@types/ms" "*"
"@types/diff@^5.0.0":
- version "5.0.0"
- resolved "https://registry.npmjs.org/@types/diff/-/diff-5.0.0.tgz#eb71e94feae62548282c4889308a3dfb57e36020"
- integrity sha512-jrm2K65CokCCX4NmowtA+MfXyuprZC13jbRuwprs6/04z/EcFg/MCwYdsHn+zgV4CQBiATiI7AEq7y1sZCtWKA==
+ version "5.0.1"
+ resolved "https://registry.npmjs.org/@types/diff/-/diff-5.0.1.tgz#9c9b9a331d4e41ccccff553f5d7ef964c6cf4042"
+ integrity sha512-XIpxU6Qdvp1ZE6Kr3yrkv1qgUab0fyf4mHYvW8N3Bx3PCsbN6or1q9/q72cv5jIFWolaGH08U9XyYoLLIykyKQ==
"@types/docker-modem@*":
- version "3.0.1"
- resolved "https://registry.npmjs.org/@types/docker-modem/-/docker-modem-3.0.1.tgz#2a29c914dd64c4c9213510d2ce75fa85232934df"
- integrity sha512-ZUXPF0WNnvs7AxoQRijt+DW2jsXnWCk8ac28tTYzTpBNnOEIAw83A+pYkCXjTFdJHMTc+wUmwNr71Zy2TRjlWg==
+ version "3.0.2"
+ resolved "https://registry.npmjs.org/@types/docker-modem/-/docker-modem-3.0.2.tgz#c49c902e17364fc724e050db5c1d2b298c6379d4"
+ integrity sha512-qC7prjoEYR2QEe6SmCVfB1x3rfcQtUr1n4x89+3e0wSTMQ/KYCyf+/RAA9n2tllkkNc6//JMUZePdFRiGIWfaQ==
dependencies:
"@types/node" "*"
"@types/ssh2" "*"
-"@types/dockerode@^3.2.1":
+"@types/dockerode@^3.2.5", "@types/dockerode@^3.3.0":
version "3.3.0"
resolved "https://registry.npmjs.org/@types/dockerode/-/dockerode-3.3.0.tgz#d6afcc1de31e211ee900da3a48ef4f1a496cf05a"
integrity sha512-3Mc0b2gnypJB8Gwmr+8UVPkwjpf4kg1gVxw8lAI4Y/EzpK50LixU1wBSPN9D+xqiw2Ubb02JO8oM0xpwzvi2mg==
@@ -7146,6 +7186,13 @@
dependencies:
"@types/node" "*"
+"@types/fs-extra@^9.0.6":
+ version "9.0.13"
+ resolved "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz#7594fbae04fe7f1918ce8b3d213f74ff44ac1f45"
+ integrity sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==
+ dependencies:
+ "@types/node" "*"
+
"@types/git-url-parse@^9.0.0":
version "9.0.0"
resolved "https://registry.npmjs.org/@types/git-url-parse/-/git-url-parse-9.0.0.tgz#aac1315a44fa4ed5a52c3820f6c3c2fb79cbd12d"
@@ -7203,6 +7250,24 @@
resolved "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-5.1.0.tgz#551a4589b6ee2cc9c1dff08056128aec29b94880"
integrity sha512-iYCgjm1dGPRuo12+BStjd1HiVQqhlRhWDOQigNxn023HcjnhsiFz9pc6CzJj4HwDCSQca9bxTL4PxJDbkdm3PA==
+"@types/html-minifier@*":
+ version "4.0.1"
+ resolved "https://registry.npmjs.org/@types/html-minifier/-/html-minifier-4.0.1.tgz#9486ffc144f8d7b8f75b07939c500ac3d73617a0"
+ integrity sha512-6u58FWQbWP45bsxVeMJo0yk2LEsjjZsCwn0JDe/i5Edk3L+b9TR5eZ2FGaMCrLdtGYpME5AGxUqv8o+3hWKogw==
+ dependencies:
+ "@types/clean-css" "*"
+ "@types/relateurl" "*"
+ "@types/uglify-js" "*"
+
+"@types/html-webpack-plugin@*":
+ version "3.2.6"
+ resolved "https://registry.npmjs.org/@types/html-webpack-plugin/-/html-webpack-plugin-3.2.6.tgz#07951aaf0fa260dbf626f9644f1d13106d537625"
+ integrity sha512-U8uJSvlf9lwrKG6sKFnMhqY4qJw2QXad+PHlX9sqEXVUMilVt96aVvFde73tzsdXD+QH9JS6kEytuGO2JcYZog==
+ dependencies:
+ "@types/html-minifier" "*"
+ "@types/tapable" "^1"
+ "@types/webpack" "^4"
+
"@types/http-assert@*":
version "1.5.1"
resolved "https://registry.npmjs.org/@types/http-assert/-/http-assert-1.5.1.tgz#d775e93630c2469c2f980fc27e3143240335db3b"
@@ -7731,6 +7796,17 @@
dependencies:
"@types/react" "*"
+"@types/react-dev-utils@^9.0.4":
+ version "9.0.8"
+ resolved "https://registry.npmjs.org/@types/react-dev-utils/-/react-dev-utils-9.0.8.tgz#7e4d63d1e1c71cd236c9055bc0c0dbaa3772bcf9"
+ integrity sha512-H/R8BvtCf9BUWPLL9a2agUWWBOKQQPkBIe5osdrgGaDJHZggQRiNN3emH16rQkzm5zi6TVuslOFrYrfMx+QTjw==
+ dependencies:
+ "@types/eslint" "*"
+ "@types/express" "*"
+ "@types/html-webpack-plugin" "*"
+ "@types/webpack" "^4"
+ "@types/webpack-dev-server" "^3"
+
"@types/react-dom@*", "@types/react-dom@>=16.9.0":
version "17.0.9"
resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.9.tgz#441a981da9d7be117042e1a6fd3dac4b30f55add"
@@ -7841,6 +7917,11 @@
resolved "https://registry.npmjs.org/@types/regression/-/regression-2.0.2.tgz#a1ad747fbcc6726643a8eb2c42bb804bbf34ce02"
integrity sha512-i7KOGl6xdkfpq5+p2ooC+/XFIRUMkYymZ29SD8p+Ko9lesKGUsh6860ey3YM7Y+ZG7kEDGcjzyLO3sOhozqEeA==
+"@types/relateurl@*":
+ version "0.2.29"
+ resolved "https://registry.npmjs.org/@types/relateurl/-/relateurl-0.2.29.tgz#68ccecec3d4ffdafb9c577fe764f912afc050fe6"
+ integrity sha512-QSvevZ+IRww2ldtfv1QskYsqVVVwCKQf1XbwtcyyoRvLIQzfyPhj/C+3+PKzSDRdiyejaiLgnq//XTkleorpLg==
+
"@types/request@^2.47.1":
version "2.48.5"
resolved "https://registry.npmjs.org/@types/request/-/request-2.48.5.tgz#019b8536b402069f6d11bee1b2c03e7f232937a0"
@@ -7902,6 +7983,13 @@
resolved "https://registry.npmjs.org/@types/semver/-/semver-7.3.8.tgz#508a27995498d7586dcecd77c25e289bfaf90c59"
integrity sha512-D/2EJvAlCEtYFEYmmlGwbGXuK886HzyCc3nZX/tkFTQdEU8jZDAgiv08P162yB17y4ZXZoq7yFAnW4GDBb9Now==
+"@types/serve-handler@^6.1.0":
+ version "6.1.1"
+ resolved "https://registry.npmjs.org/@types/serve-handler/-/serve-handler-6.1.1.tgz#629dc9a62b201ab79a216e1e46e162aa4c8d1455"
+ integrity sha512-bIwSmD+OV8w0t2e7EWsuQYlGoS1o5aEdVktgkXaa43Zm0qVWi21xaSRb3DQA1UXD+DJ5bRq1Rgu14ZczB+CjIQ==
+ dependencies:
+ "@types/node" "*"
+
"@types/serve-static@*":
version "1.13.9"
resolved "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.9.tgz#aacf28a85a05ee29a11fb7c3ead935ac56f33e4e"
@@ -7947,10 +8035,10 @@
"@types/node" "*"
"@types/ssh2-streams" "*"
-"@types/ssh2@^0.5.45":
- version "0.5.46"
- resolved "https://registry.npmjs.org/@types/ssh2/-/ssh2-0.5.46.tgz#e12341a242aea0e98ac2dec89e039bf421fd3584"
- integrity sha512-1pC8FHrMPYdkLoUOwTYYifnSEPzAFZRsp3JFC/vokQ+dRrVI+hDBwz0SNmQ3pL6h39OSZlPs0uCG7wKJkftnaA==
+"@types/ssh2@^0.5.48":
+ version "0.5.48"
+ resolved "https://registry.npmjs.org/@types/ssh2/-/ssh2-0.5.48.tgz#0d9e8654a76eaaf4cfeaeb88d74c4489cfcf7aea"
+ integrity sha512-cmQu0gp/6RtDXe1r2xXGgi0V0TeCdueDSRMEvBX8cTRT/sSREkUpgCYZLyh+iI8Ql+VNV8Az9toQoYa/IdgHbQ==
dependencies:
"@types/node" "*"
"@types/ssh2-streams" "*"
@@ -8096,9 +8184,9 @@
integrity sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==
"@types/unzipper@^0.10.3":
- version "0.10.3"
- resolved "https://registry.npmjs.org/@types/unzipper/-/unzipper-0.10.3.tgz#9eea872fb1fa460da76f253878b6275af588f464"
- integrity sha512-01mQdTLp3/KuBVDhP82FNBf+enzVOjJ9dGsCWa5z8fcYAFVgA9bqIQ2NmsgNFzN/DhD0PUQj4n5p7k6I9mq80g==
+ version "0.10.4"
+ resolved "https://registry.npmjs.org/@types/unzipper/-/unzipper-0.10.4.tgz#db5be3e1f7d37fdfae290024ffe4f46bdcfa47f2"
+ integrity sha512-mryXpAwwQadmfjKWoR7NXnELZVlU90xTON1v3Pq2AcOmuAPFkPh09E0X8fpbx2zofoR5zmOIxGqmWOhD0qXE7g==
dependencies:
"@types/node" "*"
@@ -8115,6 +8203,17 @@
"@types/expect" "^1.20.4"
"@types/node" "*"
+"@types/webpack-dev-server@^3":
+ version "3.11.6"
+ resolved "https://registry.npmjs.org/@types/webpack-dev-server/-/webpack-dev-server-3.11.6.tgz#d8888cfd2f0630203e13d3ed7833a4d11b8a34dc"
+ integrity sha512-XCph0RiiqFGetukCTC3KVnY1jwLcZ84illFRMbyFzCcWl90B/76ew0tSqF46oBhnLC4obNDG7dMO0JfTN0MgMQ==
+ dependencies:
+ "@types/connect-history-api-fallback" "*"
+ "@types/express" "*"
+ "@types/serve-static" "*"
+ "@types/webpack" "^4"
+ http-proxy-middleware "^1.0.0"
+
"@types/webpack-dev-server@^3.11.5":
version "3.11.5"
resolved "https://registry.npmjs.org/@types/webpack-dev-server/-/webpack-dev-server-3.11.5.tgz#f4a254a3dd0667c8ee4af90d42afdb4ad1d607f3"
@@ -8131,6 +8230,11 @@
resolved "https://registry.npmjs.org/@types/webpack-env/-/webpack-env-1.16.0.tgz#8c0a9435dfa7b3b1be76562f3070efb3f92637b4"
integrity sha512-Fx+NpfOO0CpeYX2g9bkvX8O5qh9wrU1sOF4g8sft4Mu7z+qfe387YlyY8w8daDyDsKY5vUxM0yxkAYnbkRbZEw==
+"@types/webpack-env@^1.15.3":
+ version "1.16.3"
+ resolved "https://registry.npmjs.org/@types/webpack-env/-/webpack-env-1.16.3.tgz#b776327a73e561b71e7881d0cd6d34a1424db86a"
+ integrity sha512-9gtOPPkfyNoEqCQgx4qJKkuNm/x0R2hKR7fdl7zvTJyHnIisuE/LfvXOsYWL0o3qq6uiBnKZNNNzi3l0y/X+xw==
+
"@types/webpack-sources@*":
version "0.1.6"
resolved "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-0.1.6.tgz#3d21dfc2ec0ad0c77758e79362426a9ba7d7cbcb"
@@ -8949,7 +9053,7 @@ any-observable@^0.3.0:
resolved "https://registry.npmjs.org/any-observable/-/any-observable-0.3.0.tgz#af933475e5806a67d0d7df090dd5e8bef65d119b"
integrity sha512-/FQM1EDkTsf63Ub2C6O7GuYFDsSXUwsaZDurV0np41ocwq0jthUAYCmhBX9f+KwlaCgIuWyr/4WlUQUBfKfZog==
-any-promise@^1.0.0, any-promise@^1.1.0:
+any-promise@^1.0.0:
version "1.3.0"
resolved "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f"
integrity sha1-q8av7tzqUugJzcA3au0845Y10X8=
@@ -9388,7 +9492,7 @@ asn1.js@^4.0.0:
inherits "^2.0.1"
minimalistic-assert "^1.0.0"
-asn1@^0.2.4, asn1@~0.2.0, asn1@~0.2.3:
+asn1@^0.2.4, asn1@~0.2.3:
version "0.2.4"
resolved "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136"
integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==
@@ -10285,15 +10389,15 @@ browserslist@4.14.2:
node-releases "^1.1.61"
browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.14.5, browserslist@^4.16.6:
- version "4.16.6"
- resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.16.6.tgz#d7901277a5a88e554ed305b183ec9b0c08f66fa2"
- integrity sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ==
+ version "4.18.1"
+ resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.18.1.tgz#60d3920f25b6860eb917c6c7b185576f4d8b017f"
+ integrity sha512-8ScCzdpPwR2wQh8IT82CA2VgDwjHyqMovPBZSNH54+tm4Jk2pCuv90gmAdH6J84OCRWi0b4gMe6O6XPXuJnjgQ==
dependencies:
- caniuse-lite "^1.0.30001219"
- colorette "^1.2.2"
- electron-to-chromium "^1.3.723"
+ caniuse-lite "^1.0.30001280"
+ electron-to-chromium "^1.3.896"
escalade "^3.1.1"
- node-releases "^1.1.71"
+ node-releases "^2.0.1"
+ picocolors "^1.0.0"
bser@2.1.1:
version "2.1.1"
@@ -10668,10 +10772,10 @@ caniuse-api@^3.0.0:
lodash.memoize "^4.1.2"
lodash.uniq "^4.5.0"
-caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001125, caniuse-lite@^1.0.30001219:
- version "1.0.30001230"
- resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001230.tgz#8135c57459854b2240b57a4a6786044bdc5a9f71"
- integrity sha512-5yBd5nWCBS+jWKTcHOzXwo5xzcj4ePE/yjtkZyUV1BTUmrBaA9MRGC+e7mxnqXSA90CmCA8L3eKLaSUkt099IQ==
+caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001125, caniuse-lite@^1.0.30001280:
+ version "1.0.30001282"
+ resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001282.tgz#38c781ee0a90ccfe1fe7fefd00e43f5ffdcb96fd"
+ integrity sha512-YhF/hG6nqBEllymSIjLtR2iWDDnChvhnVJqp+vloyt2tEHFG1yBR+ac2B/rOw0qOK0m0lEXU2dv4E/sMk5P9Kg==
canvas@^2.6.1:
version "2.8.0"
@@ -11328,6 +11432,11 @@ command-exists@^1.2.9:
resolved "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz#c50725af3808c8ab0260fd60b01fbfa25b954f69"
integrity sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==
+commander@*:
+ version "8.3.0"
+ resolved "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66"
+ integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==
+
commander@7.1.0:
version "7.1.0"
resolved "https://registry.npmjs.org/commander/-/commander-7.1.0.tgz#f2eaecf131f10e36e07d894698226e36ae0eb5ff"
@@ -11546,6 +11655,11 @@ contains-path@^0.1.0:
resolved "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a"
integrity sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=
+content-disposition@0.5.2:
+ version "0.5.2"
+ resolved "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4"
+ integrity sha1-DPaLud318r55YcOoUXjLhdunjLQ=
+
content-disposition@0.5.3:
version "0.5.3"
resolved "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd"
@@ -11795,6 +11909,13 @@ cp-file@^7.0.0:
nested-error-stacks "^2.0.0"
p-event "^4.1.0"
+cpu-features@0.0.2:
+ version "0.0.2"
+ resolved "https://registry.npmjs.org/cpu-features/-/cpu-features-0.0.2.tgz#9f636156f1155fd04bdbaa028bb3c2fbef3cea7a"
+ integrity sha512-/2yieBqvMcRj8McNzkycjW2v3OIUOibBfd2dLEJ0nWts8NobAxwiyw9phVNS6oDL8x8tz9F7uNVFEVpJncQpeA==
+ dependencies:
+ nan "^2.14.1"
+
cpy@^8.1.1:
version "8.1.1"
resolved "https://registry.npmjs.org/cpy/-/cpy-8.1.1.tgz#066ed4c6eaeed9577df96dae4db9438c1a90df62"
@@ -12989,27 +13110,27 @@ dns-txt@^2.0.2:
dependencies:
buffer-indexof "^1.0.0"
-docker-compose@^0.23.10:
- version "0.23.12"
- resolved "https://registry.npmjs.org/docker-compose/-/docker-compose-0.23.12.tgz#fa883b98be08f6926143d06bf9e522ef7ed3210c"
- integrity sha512-KFbSMqQBuHjTGZGmYDOCO0L4SaML3BsWTId5oSUyaBa22vALuFHNv+UdDWs3HcMylHWKsxCbLB7hnM/nCosWZw==
+docker-compose@^0.23.13:
+ version "0.23.13"
+ resolved "https://registry.npmjs.org/docker-compose/-/docker-compose-0.23.13.tgz#77d37bd05b6a966345f631e6d05e961c79514f06"
+ integrity sha512-/9fYC4g3AO+qsqxIZhmbVnFvJJPcYEV2yJbAPPXH+6AytU3urIY8lUAXOlvY8sl4u25pdKu1JrOfAmWC7lJDJg==
dependencies:
yaml "^1.10.2"
docker-modem@^3.0.0:
- version "3.0.0"
- resolved "https://registry.npmjs.org/docker-modem/-/docker-modem-3.0.0.tgz#cb912ad8daed42f858269fb3be6944df281ec12d"
- integrity sha512-WwFajJ8I5geZ/dDZ5FDMDA6TBkWa76xWwGIGw8uzUjNUGCN0to83wJ8Oi1AxrJTC0JBn+7fvIxUctnawtlwXeg==
+ version "3.0.3"
+ resolved "https://registry.npmjs.org/docker-modem/-/docker-modem-3.0.3.tgz#ac4bb1f32f81ac2e7120c5e99a068fab2458a32f"
+ integrity sha512-Tgkn2a+yiNP9FoZgMa/D9Wk+D2Db///0KOyKSYZRJa8w4+DzKyzQMkczKSdR/adQ0x46BOpeNkoyEOKjPhCzjw==
dependencies:
debug "^4.1.1"
readable-stream "^3.5.0"
split-ca "^1.0.1"
- ssh2 "^0.8.7"
+ ssh2 "^1.4.0"
-dockerode@^3.2.1:
- version "3.3.0"
- resolved "https://registry.npmjs.org/dockerode/-/dockerode-3.3.0.tgz#bedaf48ef9fa9124275a54a9881a92374c51008e"
- integrity sha512-St08lfOjpYCOXEM8XA0VLu3B3hRjtddODphNW5GFoA0AS3JHgoPQKOz0Qmdzg3P+hUPxhb02g1o1Cu1G+U3lRg==
+dockerode@^3.3.1:
+ version "3.3.1"
+ resolved "https://registry.npmjs.org/dockerode/-/dockerode-3.3.1.tgz#74f66e239e092e7910e2beae6322d35c44b08cdc"
+ integrity sha512-AS2mr8Lp122aa5n6d99HkuTNdRV1wkkhHwBdcnY6V0+28D3DSYwhxAk85/mM9XwD3RMliTxyr63iuvn5ZblFYQ==
dependencies:
docker-modem "^3.0.0"
tar-fs "~2.0.1"
@@ -13316,10 +13437,10 @@ elastic-builder@^2.16.0:
lodash.isstring "^4.0.1"
lodash.omit "^4.5.0"
-electron-to-chromium@^1.3.564, electron-to-chromium@^1.3.723:
- version "1.3.739"
- resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.739.tgz#f07756aa92cabd5a6eec6f491525a64fe62f98b9"
- integrity sha512-+LPJVRsN7hGZ9EIUUiWCpO7l4E3qBYHNadazlucBfsXBbccDFNKUBAgzE68FnkWGJPwD/AfKhSzL+G+Iqb8A4A==
+electron-to-chromium@^1.3.564, electron-to-chromium@^1.3.896:
+ version "1.3.900"
+ resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.900.tgz#5be2c5818a2a012c511b4b43e87b6ab7a296d4f5"
+ integrity sha512-SuXbQD8D4EjsaBaJJxySHbC+zq8JrFfxtb4GIr4E9n1BcROyMcRrJCYQNpJ9N+Wjf5mFp7Wp0OHykd14JNEzzQ==
elegant-spinner@^1.0.1:
version "1.0.1"
@@ -14403,6 +14524,13 @@ fast-text-encoding@^1.0.0, fast-text-encoding@^1.0.3:
resolved "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.3.tgz#ec02ac8e01ab8a319af182dae2681213cfe9ce53"
integrity sha512-dtm4QZH9nZtcDt8qJiOH9fcQd1NAgi+K1O2DbE6GG1PPCK/BWfOH3idCTRQ4ImXRUOyopDEgDEnVEE7Y/2Wrig==
+fast-url-parser@1.1.3:
+ version "1.1.3"
+ resolved "https://registry.npmjs.org/fast-url-parser/-/fast-url-parser-1.1.3.tgz#f4af3ea9f34d8a271cf58ad2b3759f431f0b318d"
+ integrity sha1-9K8+qfNNiicc9YrSs3WfQx8LMY0=
+ dependencies:
+ punycode "^1.3.2"
+
fastest-stable-stringify@^2.0.2:
version "2.0.2"
resolved "https://registry.npmjs.org/fastest-stable-stringify/-/fastest-stable-stringify-2.0.2.tgz#3757a6774f6ec8de40c4e86ec28ea02417214c76"
@@ -14610,6 +14738,15 @@ find-my-way@^2.2.2:
safe-regex2 "^2.0.0"
semver-store "^0.3.0"
+find-process@^1.4.5:
+ version "1.4.5"
+ resolved "https://registry.npmjs.org/find-process/-/find-process-1.4.5.tgz#6a0e4c87a32ca927c05cbed7b9078d62ffaac1a4"
+ integrity sha512-v11rJYYISUWn+s8qZzgGnBvlzRKf3bOtlGFM8H0kw56lGQtOmLuLCzuclA5kehA2j7S5sioOWdI4woT3jDavAw==
+ dependencies:
+ chalk "^4.0.0"
+ commander "^5.1.0"
+ debug "^4.1.1"
+
find-root@^1.1.0:
version "1.1.0"
resolved "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4"
@@ -15325,6 +15462,18 @@ glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, gl
once "^1.3.0"
path-is-absolute "^1.0.0"
+glob@^7.2.0:
+ version "7.2.0"
+ resolved "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023"
+ integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==
+ dependencies:
+ fs.realpath "^1.0.0"
+ inflight "^1.0.4"
+ inherits "2"
+ minimatch "^3.0.4"
+ once "^1.3.0"
+ path-is-absolute "^1.0.0"
+
global-dirs@^2.0.1:
version "2.0.1"
resolved "https://registry.npmjs.org/global-dirs/-/global-dirs-2.0.1.tgz#acdf3bb6685bcd55cb35e8a052266569e9469201"
@@ -16117,11 +16266,6 @@ hsla-regex@^1.0.0:
resolved "https://registry.npmjs.org/hsla-regex/-/hsla-regex-1.0.0.tgz#c1ce7a3168c8c6614033a4b5f7877f3b225f9c38"
integrity sha1-wc56MWjIxmFAM6S194d/OyJfnDg=
-html-comment-regex@^1.1.0:
- version "1.1.2"
- resolved "https://registry.npmjs.org/html-comment-regex/-/html-comment-regex-1.1.2.tgz#97d4688aeb5c81886a364faa0cad1dda14d433a7"
- integrity sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ==
-
html-encoding-sniffer@^2.0.1:
version "2.0.1"
resolved "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz#42a6dc4fd33f00281176e8b23759ca4e4fa185f3"
@@ -17335,13 +17479,6 @@ is-subdir@^1.1.1:
dependencies:
better-path-resolve "1.0.0"
-is-svg@^3.0.0:
- version "3.0.0"
- resolved "https://registry.npmjs.org/is-svg/-/is-svg-3.0.0.tgz#9321dbd29c212e5ca99c4fa9794c714bcafa2f75"
- integrity sha512-gi4iHK53LR2ujhLVVj+37Ykh9GLqYHX6JOVXbLAucaG/Cqw9xwdFOjDM2qeifLs1sF1npXXFvDu0r5HNgCMrzQ==
- dependencies:
- html-comment-regex "^1.1.0"
-
is-symbol@^1.0.2, is-symbol@^1.0.3:
version "1.0.3"
resolved "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937"
@@ -18410,10 +18547,10 @@ jsonpath-plus@^5.0.7:
resolved "https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-5.1.0.tgz#2fc4b2e461950626c98525425a3a3518b85af6c3"
integrity sha512-890w2Pjtj0iswAxalRlt2kHthi6HKrXEfZcn+ZNZptv7F3rUGIeDuZo+C+h4vXBHLEsVjJrHeCm35nYeZLzSBQ==
-jsonpointer@^4.0.1:
- version "4.1.0"
- resolved "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.1.0.tgz#501fb89986a2389765ba09e6053299ceb4f2c2cc"
- integrity sha512-CXcRvMyTlnR53xMcKnuMzfCA5i/nfblTnnr74CZb6C4vG39eu6w51t7nKmU5MfLfbTgGItliNyjO/ciNPDqClg==
+jsonpointer@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.0.tgz#f802669a524ec4805fa7389eadbc9921d5dc8072"
+ integrity sha512-PNYZIdMjVIvVgDSYKTT63Y+KZ6IZvGRNNWcxwD+GNnUz1MKPfv30J8ueCjdwcN0nDx2SlshgyB7Oy0epAzVRRg==
jsonschema@^1.2.6:
version "1.4.0"
@@ -20381,6 +20518,18 @@ mime-db@1.49.0:
resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.48.0.tgz#e35b31045dd7eada3aaad537ed88a33afbef2d1d"
integrity sha512-FM3QwxV+TnZYQ2aRqhlKBMHxk10lTbMt3bBkMAp54ddrNeVSfcQYOOKuGuy3Ddrm38I04If834fOUSq1yzslJQ==
+mime-db@~1.33.0:
+ version "1.33.0"
+ resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz#a3492050a5cb9b63450541e39d9788d2272783db"
+ integrity sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==
+
+mime-types@2.1.18:
+ version "2.1.18"
+ resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz#6f323f60a83d11146f831ff11fd66e2fe5503bb8"
+ integrity sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==
+ dependencies:
+ mime-db "~1.33.0"
+
mime-types@^2.0.8, mime-types@^2.1.12, mime-types@^2.1.27, mime-types@^2.1.31, mime-types@~2.1.17, mime-types@~2.1.19, mime-types@~2.1.24:
version "2.1.32"
resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.32.tgz#1d00e89e7de7fe02008db61001d9e02852670fd5"
@@ -20594,10 +20743,10 @@ mixin-object@^2.0.1:
for-in "^0.1.3"
is-extendable "^0.1.1"
-mixme@^0.3.1:
- version "0.3.5"
- resolved "https://registry.npmjs.org/mixme/-/mixme-0.3.5.tgz#304652cdaf24a3df0487205e61ac6162c6906ddd"
- integrity sha512-SyV9uPETRig5ZmYev0ANfiGeB+g6N2EnqqEfBbCGmmJ6MgZ3E4qv5aPbnHVdZ60KAHHXV+T3sXopdrnIXQdmjQ==
+mixme@^0.5.1:
+ version "0.5.4"
+ resolved "https://registry.npmjs.org/mixme/-/mixme-0.5.4.tgz#8cb3bd0cd32a513c161bf1ca99d143f0bcf2eff3"
+ integrity sha512-3KYa4m4Vlqx98GPdOHghxSdNtTvcP8E0kkaJ5Dlh+h2DRzF7zpuVVcA8B0QpKd11YJeP9QQ7ASkKzOeu195Wzw==
mkdirp-classic@^0.5.2:
version "0.5.2"
@@ -20811,6 +20960,11 @@ nan@^2.12.1, nan@^2.14.0:
resolved "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz#d7be34dfa3105b91494c3147089315eff8874b01"
integrity sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==
+nan@^2.14.1, nan@^2.15.0:
+ version "2.15.0"
+ resolved "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz#3f34a473ff18e15c1b5626b62903b5ad6e665fee"
+ integrity sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==
+
nano-css@^5.1.0, nano-css@^5.3.1:
version "5.3.1"
resolved "https://registry.npmjs.org/nano-css/-/nano-css-5.3.1.tgz#b709383e07ad3be61f64edffacb9d98250b87a1f"
@@ -21115,11 +21269,16 @@ node-pre-gyp@^0.11.0:
semver "^5.3.0"
tar "^4"
-node-releases@^1.1.61, node-releases@^1.1.71:
+node-releases@^1.1.61:
version "1.1.72"
resolved "https://registry.npmjs.org/node-releases/-/node-releases-1.1.72.tgz#14802ab6b1039a79a0c7d662b610a5bbd76eacbe"
integrity sha512-LLUo+PpH3dU6XizX3iVoubUNheF/owjXCZZ5yACDxNnPtgFuludV1ZL3ayK1kVep42Rmm0+R9/Y60NQbZ2bifw==
+node-releases@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.1.tgz#3d1d395f204f1f2f29a54358b9fb678765ad2fc5"
+ integrity sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==
+
nodemon@^2.0.2:
version "2.0.12"
resolved "https://registry.npmjs.org/nodemon/-/nodemon-2.0.12.tgz#5dae4e162b617b91f1873b3bfea215dd71e144d5"
@@ -22276,7 +22435,7 @@ path-is-absolute@^1.0.0:
resolved "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
-path-is-inside@^1.0.2:
+path-is-inside@1.0.2, path-is-inside@^1.0.2:
version "1.0.2"
resolved "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53"
integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=
@@ -22313,6 +22472,11 @@ path-to-regexp@0.1.7:
resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=
+path-to-regexp@2.2.1:
+ version "2.2.1"
+ resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-2.2.1.tgz#90b617025a16381a879bc82a38d4e8bdeb2bcf45"
+ integrity sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ==
+
path-to-regexp@^1.7.0:
version "1.8.0"
resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a"
@@ -22490,12 +22654,12 @@ pgtools@^0.3.0:
pg-connection-string "^0.1.3"
yargs "^5.0.0"
-picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2:
- version "2.2.2"
- resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad"
- integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==
+picocolors@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
+ integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
-picomatch@^2.2.3:
+picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2, picomatch@^2.2.3:
version "2.3.0"
resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972"
integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==
@@ -23005,11 +23169,10 @@ postcss-selector-parser@^6.0.0, postcss-selector-parser@^6.0.2, postcss-selector
util-deprecate "^1.0.2"
postcss-svgo@^4.0.2:
- version "4.0.2"
- resolved "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-4.0.2.tgz#17b997bc711b333bab143aaed3b8d3d6e3d38258"
- integrity sha512-C6wyjo3VwFm0QgBy+Fu7gCYOkCmgmClghO+pjcxvrcBKtiKt0uCF+hvbMO1fyv5BMImRK90SMb+dwUnfbGd+jw==
+ version "4.0.3"
+ resolved "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-4.0.3.tgz#343a2cdbac9505d416243d496f724f38894c941e"
+ integrity sha512-NoRbrcMWTtUghzuKSoIm6XV+sJdvZ7GZSc3wdBN0W19FTtp2ko8NqLsgoh/m9CzNhU3KLPvQmjIwtaNFkaFTvw==
dependencies:
- is-svg "^3.0.0"
postcss "^7.0.0"
postcss-value-parser "^3.0.0"
svgo "^1.0.0"
@@ -23477,7 +23640,7 @@ punycode@1.3.2:
resolved "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d"
integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=
-punycode@^1.2.4:
+punycode@^1.2.4, punycode@^1.3.2:
version "1.4.1"
resolved "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
integrity sha1-wNWmOycYgArY4esPpSachN1BhF4=
@@ -23626,6 +23789,11 @@ randomfill@^1.0.3:
randombytes "^2.0.5"
safe-buffer "^5.1.0"
+range-parser@1.2.0:
+ version "1.2.0"
+ resolved "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e"
+ integrity sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=
+
range-parser@^1.2.1, range-parser@~1.2.1:
version "1.2.1"
resolved "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
@@ -25447,10 +25615,12 @@ serialize-error@^8.0.1, serialize-error@^8.1.0:
dependencies:
type-fest "^0.20.2"
-serialize-javascript@^2.1.2:
- version "2.1.2"
- resolved "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-2.1.2.tgz#ecec53b0e0317bdc95ef76ab7074b7384785fa61"
- integrity sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ==
+serialize-javascript@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz#b525e1238489a5ecfc42afacc3fe99e666f4b1aa"
+ integrity sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==
+ dependencies:
+ randombytes "^2.1.0"
serialize-javascript@^5.0.1:
version "5.0.1"
@@ -25477,6 +25647,20 @@ serve-favicon@^2.5.0:
parseurl "~1.3.2"
safe-buffer "5.1.1"
+serve-handler@^6.1.3:
+ version "6.1.3"
+ resolved "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.3.tgz#1bf8c5ae138712af55c758477533b9117f6435e8"
+ integrity sha512-FosMqFBNrLyeiIDvP1zgO6YoTzFYHxLDEIavhlmQ+knB2Z7l1t+kGLHkZIDN7UVWqQAmKI3D20A6F6jo3nDd4w==
+ dependencies:
+ bytes "3.0.0"
+ content-disposition "0.5.2"
+ fast-url-parser "1.1.3"
+ mime-types "2.1.18"
+ minimatch "3.0.4"
+ path-is-inside "1.0.2"
+ path-to-regexp "2.2.1"
+ range-parser "1.2.0"
+
serve-index@^1.9.1:
version "1.9.1"
resolved "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239"
@@ -26062,29 +26246,24 @@ sqlstring@^2.3.2:
resolved "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.2.tgz#cdae7169389a1375b18e885f2e60b3e460809514"
integrity sha512-vF4ZbYdKS8OnoJAWBmMxCQDkiEBkGQYU7UZPtL8flbDRSNkhaXvRJ279ZtI6M+zDaQovVU4tuRgzK5fVhvFAhg==
-ssh-remote-port-forward@^1.0.3:
- version "1.0.3"
- resolved "https://registry.npmjs.org/ssh-remote-port-forward/-/ssh-remote-port-forward-1.0.3.tgz#074cebed6d54a42ce4659d6aa24987e433ff5fef"
- integrity sha512-PJ6qGFmB6n0iMCQIp+qzx8qVUE5cgacopvEh63t3NJjEQHOaza/JT9zywmmVlaol/eGtCmpvBXx2A03ih1Y+xg==
+ssh-remote-port-forward@^1.0.4:
+ version "1.0.4"
+ resolved "https://registry.npmjs.org/ssh-remote-port-forward/-/ssh-remote-port-forward-1.0.4.tgz#72b0c5df8ec27ca300c75805cc6b266dee07e298"
+ integrity sha512-x0LV1eVDwjf1gmG7TTnfqIzf+3VPRz7vrNIjX6oYLbeCrf/PeVY6hkT68Mg+q02qXxQhrLjB0jfgvhevoCRmLQ==
dependencies:
- "@types/ssh2" "^0.5.45"
- ssh2 "^0.8.9"
+ "@types/ssh2" "^0.5.48"
+ ssh2 "^1.4.0"
-ssh2-streams@~0.4.10:
- version "0.4.10"
- resolved "https://registry.npmjs.org/ssh2-streams/-/ssh2-streams-0.4.10.tgz#48ef7e8a0e39d8f2921c30521d56dacb31d23a34"
- integrity sha512-8pnlMjvnIZJvmTzUIIA5nT4jr2ZWNNVHwyXfMGdRJbug9TpI3kd99ffglgfSWqujVv/0gxwMsDn9j9RVst8yhQ==
+ssh2@^1.4.0:
+ version "1.5.0"
+ resolved "https://registry.npmjs.org/ssh2/-/ssh2-1.5.0.tgz#4dc559ba98a1cbb420e8d42998dfe35d0eda92bc"
+ integrity sha512-iUmRkhH9KGeszQwDW7YyyqjsMTf4z+0o48Cp4xOwlY5LjtbIAvyd3fwnsoUZW/hXmTCRA3yt7S/Jb9uVjErVlA==
dependencies:
- asn1 "~0.2.0"
+ asn1 "^0.2.4"
bcrypt-pbkdf "^1.0.2"
- streamsearch "~0.1.2"
-
-ssh2@^0.8.7, ssh2@^0.8.9:
- version "0.8.9"
- resolved "https://registry.npmjs.org/ssh2/-/ssh2-0.8.9.tgz#54da3a6c4ba3daf0d8477a538a481326091815f3"
- integrity sha512-GmoNPxWDMkVpMFa9LVVzQZHF6EW3WKmBwL+4/GeILf2hFmix5Isxm7Amamo8o7bHiU0tC+wXsGcUXOxp8ChPaw==
- dependencies:
- ssh2-streams "~0.4.10"
+ optionalDependencies:
+ cpu-features "0.0.2"
+ nan "^2.15.0"
sshpk@^1.7.0:
version "1.16.1"
@@ -26278,21 +26457,14 @@ stream-shift@^1.0.0:
resolved "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d"
integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==
-stream-to-array@^2.3.0:
- version "2.3.0"
- resolved "https://registry.npmjs.org/stream-to-array/-/stream-to-array-2.3.0.tgz#bbf6b39f5f43ec30bc71babcb37557acecf34353"
- integrity sha1-u/azn19D7DC8cbq8s3VXrOzzQ1M=
- dependencies:
- any-promise "^1.1.0"
-
stream-transform@^2.0.1:
- version "2.0.2"
- resolved "https://registry.npmjs.org/stream-transform/-/stream-transform-2.0.2.tgz#3cb7a14c802eb39bc40caaab0535e584f3a65caf"
- integrity sha512-J+D5jWPF/1oX+r9ZaZvEXFbu7znjxSkbNAHJ9L44bt/tCVuOEWZlDqU9qJk7N2xBU1S+K2DPpSKeR/MucmCA1Q==
+ version "2.1.3"
+ resolved "https://registry.npmjs.org/stream-transform/-/stream-transform-2.1.3.tgz#a1c3ecd72ddbf500aa8d342b0b9df38f5aa598e3"
+ integrity sha512-9GHUiM5hMiCi6Y03jD2ARC1ettBXkQBoQAe7nJsPknnI0ow10aXjTnew8QtYQmLjzn974BnmWEAJgCY6ZP1DeQ==
dependencies:
- mixme "^0.3.1"
+ mixme "^0.5.1"
-streamsearch@0.1.2, streamsearch@~0.1.2:
+streamsearch@0.1.2:
version "0.1.2"
resolved "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a"
integrity sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=
@@ -27026,15 +27198,15 @@ terser-webpack-plugin@*, terser-webpack-plugin@^5.1.3:
terser "^5.7.2"
terser-webpack-plugin@^1.4.3:
- version "1.4.3"
- resolved "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.3.tgz#5ecaf2dbdc5fb99745fd06791f46fc9ddb1c9a7c"
- integrity sha512-QMxecFz/gHQwteWwSo5nTc6UaICqN1bMedC5sMtUc7y3Ha3Q8y6ZO0iCR8pq4RJC8Hjf0FEPEHZqcMB/+DFCrA==
+ version "1.4.5"
+ resolved "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz#a217aefaea330e734ffacb6120ec1fa312d6040b"
+ integrity sha512-04Rfe496lN8EYruwi6oPQkG0vo8C+HT49X687FZnpPF0qMAIHONI6HEXYPKDOE8e5HjXTyKfqRd/agHtH0kOtw==
dependencies:
cacache "^12.0.2"
find-cache-dir "^2.1.0"
is-wsl "^1.1.0"
schema-utils "^1.0.0"
- serialize-javascript "^2.1.2"
+ serialize-javascript "^4.0.0"
source-map "^0.6.1"
terser "^4.1.2"
webpack-sources "^1.4.0"
@@ -27091,23 +27263,22 @@ test-exclude@^6.0.0:
glob "^7.1.4"
minimatch "^3.0.4"
-testcontainers@^7.10.0:
- version "7.11.1"
- resolved "https://registry.npmjs.org/testcontainers/-/testcontainers-7.11.1.tgz#b3810badf79433ba02f210683eec1a1c488c107d"
- integrity sha512-lfZeys5bLkADjOaoXfQy0V0+G8sGKr8ESANz7MhSVBwC+OTTxkP3+FVwP48bW4mwRcQ4Hojwbfw10OYT80QZmQ==
+testcontainers@^7.23.0:
+ version "7.23.0"
+ resolved "https://registry.npmjs.org/testcontainers/-/testcontainers-7.23.0.tgz#7d73a5e219a970fb75ff6a23a28dace8b7f3f232"
+ integrity sha512-90H1iijeIjOLp7WVNYKTNkM1sd+dlW5019ns45hSPcOET43WebEZQVJl8/Ag9vwSZD2mjomMum9a/EXk/st4sQ==
dependencies:
- "@types/archiver" "^5.1.0"
- "@types/dockerode" "^3.2.1"
+ "@types/archiver" "^5.1.1"
+ "@types/dockerode" "^3.2.5"
archiver "^5.3.0"
byline "^5.0.0"
- debug "^4.3.1"
- docker-compose "^0.23.10"
- dockerode "^3.2.1"
+ debug "^4.3.2"
+ docker-compose "^0.23.13"
+ dockerode "^3.3.1"
get-port "^5.1.1"
- glob "^7.1.7"
+ glob "^7.2.0"
slash "^3.0.0"
- ssh-remote-port-forward "^1.0.3"
- stream-to-array "^2.3.0"
+ ssh-remote-port-forward "^1.0.4"
tar-fs "^2.1.1"
text-encoding-utf-8@^1.0.1:
@@ -29484,7 +29655,7 @@ zip-stream@^4.1.0:
compress-commons "^4.1.0"
readable-stream "^3.6.0"
-zod@^3.9.5:
+zod@^3.11.6, zod@^3.9.5:
version "3.11.6"
resolved "https://registry.npmjs.org/zod/-/zod-3.11.6.tgz#e43a5e0c213ae2e02aefe7cb2b1a6fa3d7f1f483"
integrity sha512-daZ80A81I3/9lIydI44motWe6n59kRBfNzTuS2bfzVh1nAXi667TOTWWtatxyG+fwgNUiagSj/CWZwRRbevJIg==