Merge branch 'master' into xcmetrics-builds-page
Signed-off-by: Niklas Granander <ngranander@spotify.com>
This commit is contained in:
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder': patch
|
||||
---
|
||||
|
||||
chore: set the lifecycle as beta logo
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
'@backstage/core-components': patch
|
||||
'@backstage/plugin-catalog': patch
|
||||
'@backstage/plugin-catalog-react': patch
|
||||
---
|
||||
|
||||
Improve accessibility of core & catalog components by adjusting them with non-breaking changes.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/create-app': patch
|
||||
---
|
||||
|
||||
fix typo in the comments of EntityPage component
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-backend-module-ldap': patch
|
||||
---
|
||||
|
||||
chore(deps): bump `@types/ldapjs` from 1.0.10 to 2.2.0
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-auth-backend': patch
|
||||
---
|
||||
|
||||
Allow to configure SAML auth `acceptedClockSkewMs`
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-api-docs': patch
|
||||
---
|
||||
|
||||
Add explicit import for `isomorphic-form-data` needed for `swagger-ui-react`
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/create-app': patch
|
||||
---
|
||||
|
||||
Update the `software-templates` to point to `main` branch instead of `master`
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-kubernetes-backend': patch
|
||||
---
|
||||
|
||||
Add configuration option to the kubernetes object types. Config option is under `kubernetes.resources`. Defaults to ['pods', 'services', 'configmaps', 'deployments', 'replicasets', 'horizontalpodautoscalers', 'ingresses']
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-kubernetes-backend': patch
|
||||
'@backstage/plugin-kubernetes-common': patch
|
||||
'@backstage/plugin-kubernetes': patch
|
||||
---
|
||||
|
||||
chore(deps): bump `@kubernetes/client-node` from 0.14.3 to 0.15.0
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-react': patch
|
||||
---
|
||||
|
||||
Fix `EntityListProvider` to not update url if unmounted
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-backend': patch
|
||||
---
|
||||
|
||||
Add the `scaffolder.defaultCommitMessage`, which defaults to `Initial commit`, so it can be customized.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-techdocs': patch
|
||||
---
|
||||
|
||||
TechDocs now uses a "safe by default" sanitization library, rather than relying on its own, hard-coded list of allowable tags and attributes.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-xcmetrics': minor
|
||||
---
|
||||
|
||||
New data in form of trend lines, status timeline and other is added to the dashboard of XCMetrics to give a better understanding of how the build system is behaving.
|
||||
@@ -39,3 +39,4 @@
|
||||
| [RedVentures](https://www.redventures.com/) | [Chris Diaz](https://github.com/codingdiaz) | Developer portal that brings everything an engineer needs to provide value into a single pane of glass. |
|
||||
| [MavTek](https://www.mavtek.com/) | [@fgascon](https://github.com/fgascon) | Developer portal focused on standardizing practices, centralizing documentation and streamlining developer practices. |
|
||||
| [QuintoAndar](https://www.quintoandar.com.br/) | [@quintoandar](https://github.com/quintoandar) | Developer portal, services catalog and centralization of service metrics. |
|
||||
| [empathy.co](https://empathy.co/) | [@guillermotti](https://github.com/guillermotti) | Developer portal for tech docs, service catalog, plugin discovery and much more. |
|
||||
|
||||
@@ -256,6 +256,8 @@ scaffolder:
|
||||
# defaultAuthor:
|
||||
# name: Scaffolder
|
||||
# email: scaffolder@backstage.io
|
||||
# Use to customize the default commit message when new components are created
|
||||
# defaultCommitMessage: 'Initial commit'
|
||||
github:
|
||||
token: ${GITHUB_TOKEN}
|
||||
visibility: public # or 'internal' or 'private'
|
||||
|
||||
@@ -35,7 +35,7 @@ If you want more control over the theme, and for example customize font sizes
|
||||
and margins, you can use the lower-level `createThemeOverrides` function
|
||||
exported by [@backstage/theme](https://www.npmjs.com/package/@backstage/theme)
|
||||
in combination with
|
||||
[createMuiTheme](https://material-ui.com/customization/theming/#createmuitheme-options-args-theme)
|
||||
[createTheme](https://material-ui.com/customization/theming/#createmuitheme-options-args-theme)
|
||||
from [@material-ui/core](https://www.npmjs.com/package/@material-ui/core). See
|
||||
the
|
||||
[@backstage/theme source](https://github.com/backstage/backstage/tree/master/packages/theme/src)
|
||||
|
||||
@@ -288,6 +288,16 @@ td {
|
||||
.tocToggler {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.docsSliderActive .toc section .navGroups {
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.docsSliderActive.docsNavContainer {
|
||||
background-color: #121212;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* content */
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
]
|
||||
},
|
||||
"resolutions": {
|
||||
"**/@graphql-codegen/cli/**/ws": "^7.4.6",
|
||||
"**/@roadiehq/**/@backstage/core": "*",
|
||||
"**/@roadiehq/**/@backstage/plugin-catalog": "*",
|
||||
"**/@roadiehq/**/@backstage/catalog-model": "*",
|
||||
|
||||
@@ -1,5 +1,46 @@
|
||||
# example-app
|
||||
|
||||
## 0.2.39
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/plugin-scaffolder@0.10.2
|
||||
- @backstage/core-components@0.2.0
|
||||
- @backstage/plugin-catalog@0.6.9
|
||||
- @backstage/plugin-catalog-react@0.4.0
|
||||
- @backstage/cli@0.7.6
|
||||
- @backstage/core-app-api@0.1.6
|
||||
- @backstage/core-plugin-api@0.1.4
|
||||
- @backstage/integration-react@0.1.5
|
||||
- @backstage/theme@0.2.9
|
||||
- @backstage/plugin-api-docs@0.6.4
|
||||
- @backstage/plugin-badges@0.2.6
|
||||
- @backstage/plugin-catalog-import@0.5.15
|
||||
- @backstage/plugin-circleci@0.2.20
|
||||
- @backstage/plugin-cloudbuild@0.2.20
|
||||
- @backstage/plugin-code-coverage@0.1.8
|
||||
- @backstage/plugin-cost-insights@0.11.2
|
||||
- @backstage/plugin-explore@0.3.11
|
||||
- @backstage/plugin-gcp-projects@0.3.1
|
||||
- @backstage/plugin-github-actions@0.4.14
|
||||
- @backstage/plugin-graphiql@0.2.13
|
||||
- @backstage/plugin-jenkins@0.5.1
|
||||
- @backstage/plugin-kafka@0.2.12
|
||||
- @backstage/plugin-kubernetes@0.4.9
|
||||
- @backstage/plugin-lighthouse@0.2.21
|
||||
- @backstage/plugin-newrelic@0.3.1
|
||||
- @backstage/plugin-org@0.3.18
|
||||
- @backstage/plugin-pagerduty@0.3.9
|
||||
- @backstage/plugin-rollbar@0.3.10
|
||||
- @backstage/plugin-search@0.4.5
|
||||
- @backstage/plugin-sentry@0.3.16
|
||||
- @backstage/plugin-shortcuts@0.1.5
|
||||
- @backstage/plugin-tech-radar@0.4.3
|
||||
- @backstage/plugin-techdocs@0.10.2
|
||||
- @backstage/plugin-todo@0.1.6
|
||||
- @backstage/plugin-user-settings@0.3.2
|
||||
|
||||
## 0.2.37
|
||||
|
||||
### Patch Changes
|
||||
|
||||
+38
-38
@@ -1,47 +1,47 @@
|
||||
{
|
||||
"name": "example-app",
|
||||
"version": "0.2.37",
|
||||
"version": "0.2.39",
|
||||
"private": true,
|
||||
"bundled": true,
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.9.0",
|
||||
"@backstage/cli": "^0.7.4",
|
||||
"@backstage/core-app-api": "^0.1.3",
|
||||
"@backstage/core-components": "^0.1.5",
|
||||
"@backstage/core-plugin-api": "^0.1.3",
|
||||
"@backstage/integration-react": "^0.1.4",
|
||||
"@backstage/plugin-api-docs": "^0.6.2",
|
||||
"@backstage/plugin-badges": "^0.2.5",
|
||||
"@backstage/plugin-catalog": "^0.6.7",
|
||||
"@backstage/plugin-catalog-import": "^0.5.13",
|
||||
"@backstage/plugin-catalog-react": "^0.3.0",
|
||||
"@backstage/plugin-circleci": "^0.2.19",
|
||||
"@backstage/plugin-cloudbuild": "^0.2.19",
|
||||
"@backstage/plugin-code-coverage": "^0.1.7",
|
||||
"@backstage/plugin-cost-insights": "^0.11.0",
|
||||
"@backstage/plugin-explore": "^0.3.10",
|
||||
"@backstage/plugin-gcp-projects": "^0.3.0",
|
||||
"@backstage/plugin-github-actions": "^0.4.13",
|
||||
"@backstage/plugin-graphiql": "^0.2.12",
|
||||
"@backstage/plugin-jenkins": "^0.5.0",
|
||||
"@backstage/plugin-kafka": "^0.2.11",
|
||||
"@backstage/plugin-kubernetes": "^0.4.8",
|
||||
"@backstage/plugin-lighthouse": "^0.2.20",
|
||||
"@backstage/plugin-newrelic": "^0.3.0",
|
||||
"@backstage/plugin-org": "^0.3.17",
|
||||
"@backstage/plugin-pagerduty": "0.3.8",
|
||||
"@backstage/plugin-rollbar": "^0.3.9",
|
||||
"@backstage/plugin-scaffolder": "^0.10.1",
|
||||
"@backstage/plugin-search": "^0.4.3",
|
||||
"@backstage/plugin-sentry": "^0.3.15",
|
||||
"@backstage/plugin-shortcuts": "^0.1.4",
|
||||
"@backstage/plugin-tech-radar": "^0.4.1",
|
||||
"@backstage/plugin-techdocs": "^0.10.0",
|
||||
"@backstage/plugin-todo": "^0.1.5",
|
||||
"@backstage/plugin-user-settings": "^0.3.0",
|
||||
"@backstage/cli": "^0.7.6",
|
||||
"@backstage/core-app-api": "^0.1.6",
|
||||
"@backstage/core-components": "^0.2.0",
|
||||
"@backstage/core-plugin-api": "^0.1.4",
|
||||
"@backstage/integration-react": "^0.1.5",
|
||||
"@backstage/plugin-api-docs": "^0.6.4",
|
||||
"@backstage/plugin-badges": "^0.2.6",
|
||||
"@backstage/plugin-catalog": "^0.6.9",
|
||||
"@backstage/plugin-catalog-import": "^0.5.15",
|
||||
"@backstage/plugin-catalog-react": "^0.4.0",
|
||||
"@backstage/plugin-circleci": "^0.2.20",
|
||||
"@backstage/plugin-cloudbuild": "^0.2.20",
|
||||
"@backstage/plugin-code-coverage": "^0.1.8",
|
||||
"@backstage/plugin-cost-insights": "^0.11.2",
|
||||
"@backstage/plugin-explore": "^0.3.11",
|
||||
"@backstage/plugin-gcp-projects": "^0.3.1",
|
||||
"@backstage/plugin-github-actions": "^0.4.14",
|
||||
"@backstage/plugin-graphiql": "^0.2.13",
|
||||
"@backstage/plugin-jenkins": "^0.5.1",
|
||||
"@backstage/plugin-kafka": "^0.2.12",
|
||||
"@backstage/plugin-kubernetes": "^0.4.9",
|
||||
"@backstage/plugin-lighthouse": "^0.2.21",
|
||||
"@backstage/plugin-newrelic": "^0.3.1",
|
||||
"@backstage/plugin-org": "^0.3.18",
|
||||
"@backstage/plugin-pagerduty": "0.3.9",
|
||||
"@backstage/plugin-rollbar": "^0.3.10",
|
||||
"@backstage/plugin-scaffolder": "^0.10.2",
|
||||
"@backstage/plugin-search": "^0.4.5",
|
||||
"@backstage/plugin-sentry": "^0.3.16",
|
||||
"@backstage/plugin-shortcuts": "^0.1.5",
|
||||
"@backstage/plugin-tech-radar": "^0.4.3",
|
||||
"@backstage/plugin-techdocs": "^0.10.2",
|
||||
"@backstage/plugin-todo": "^0.1.6",
|
||||
"@backstage/plugin-user-settings": "^0.3.2",
|
||||
"@backstage/search-common": "^0.1.2",
|
||||
"@backstage/theme": "^0.2.8",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@backstage/theme": "^0.2.9",
|
||||
"@material-ui/core": "^4.12.2",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "4.0.0-alpha.45",
|
||||
"@octokit/rest": "^18.5.3",
|
||||
@@ -60,7 +60,7 @@
|
||||
"zen-observable": "^0.8.15"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/test-utils": "^0.1.14",
|
||||
"@backstage/test-utils": "^0.1.16",
|
||||
"@testing-library/cypress": "^7.0.1",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^11.2.5",
|
||||
|
||||
@@ -40,6 +40,8 @@ import {
|
||||
isKind,
|
||||
EntityHasResourcesCard,
|
||||
EntityOrphanWarning,
|
||||
EntityProcessingErrorsPanel,
|
||||
hasCatalogProcessingErrors,
|
||||
isOrphan,
|
||||
} from '@backstage/plugin-catalog';
|
||||
import {
|
||||
@@ -231,6 +233,14 @@ const overviewContent = (
|
||||
</EntitySwitch.Case>
|
||||
</EntitySwitch>
|
||||
|
||||
<EntitySwitch>
|
||||
<EntitySwitch.Case if={hasCatalogProcessingErrors}>
|
||||
<Grid item xs={12}>
|
||||
<EntityProcessingErrorsPanel />
|
||||
</Grid>
|
||||
</EntitySwitch.Case>
|
||||
</EntitySwitch>
|
||||
|
||||
<Grid item md={8} xs={12}>
|
||||
<EntityAboutCard variant="gridItem" />
|
||||
</Grid>
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @backstage/catalog-client
|
||||
|
||||
## 0.3.18
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 11c370af2: Support filtering entities via property existence
|
||||
|
||||
## 0.3.17
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -25,6 +25,11 @@ export type AddLocationResponse = {
|
||||
entities: Entity[];
|
||||
};
|
||||
|
||||
// Warning: (ae-missing-release-tag) "CATALOG_FILTER_EXISTS" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const CATALOG_FILTER_EXISTS: unique symbol;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "CatalogApi" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
@@ -123,8 +128,8 @@ export class CatalogClient implements CatalogApi {
|
||||
// @public (undocumented)
|
||||
export type CatalogEntitiesRequest = {
|
||||
filter?:
|
||||
| Record<string, string | string[]>[]
|
||||
| Record<string, string | string[]>
|
||||
| Record<string, string | symbol | (string | symbol)[]>[]
|
||||
| Record<string, string | symbol | (string | symbol)[]>
|
||||
| undefined;
|
||||
fields?: string[] | undefined;
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/catalog-client",
|
||||
"version": "0.3.17",
|
||||
"version": "0.3.18",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -35,7 +35,7 @@
|
||||
"cross-fetch": "^3.0.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.7.5",
|
||||
"@backstage/cli": "^0.7.6",
|
||||
"@types/jest": "^26.0.7",
|
||||
"msw": "^0.29.0"
|
||||
},
|
||||
|
||||
@@ -18,7 +18,7 @@ import { Entity } from '@backstage/catalog-model';
|
||||
import { rest } from 'msw';
|
||||
import { setupServer } from 'msw/node';
|
||||
import { CatalogClient } from './CatalogClient';
|
||||
import { CatalogListResponse } from './types/api';
|
||||
import { CATALOG_FILTER_EXISTS, CatalogListResponse } from './types/api';
|
||||
import { DiscoveryApi } from './types/discovery';
|
||||
|
||||
const server = setupServer();
|
||||
@@ -83,7 +83,7 @@ describe('CatalogClient', () => {
|
||||
server.use(
|
||||
rest.get(`${mockBaseUrl}/entities`, (req, res, ctx) => {
|
||||
expect(req.url.search).toBe(
|
||||
'?filter=a=1,b=2,b=3,%C3%B6=%3D&filter=a=2',
|
||||
'?filter=a=1,b=2,b=3,%C3%B6=%3D&filter=a=2&filter=c',
|
||||
);
|
||||
return res(ctx.json([]));
|
||||
}),
|
||||
@@ -100,6 +100,9 @@ describe('CatalogClient', () => {
|
||||
{
|
||||
a: '2',
|
||||
},
|
||||
{
|
||||
c: CATALOG_FILTER_EXISTS,
|
||||
},
|
||||
],
|
||||
},
|
||||
{ token },
|
||||
@@ -113,7 +116,7 @@ describe('CatalogClient', () => {
|
||||
|
||||
server.use(
|
||||
rest.get(`${mockBaseUrl}/entities`, (req, res, ctx) => {
|
||||
expect(req.url.search).toBe('?filter=a=1,b=2,b=3,%C3%B6=%3D');
|
||||
expect(req.url.search).toBe('?filter=a=1,b=2,b=3,%C3%B6=%3D,c');
|
||||
return res(ctx.json([]));
|
||||
}),
|
||||
);
|
||||
@@ -124,6 +127,7 @@ describe('CatalogClient', () => {
|
||||
a: '1',
|
||||
b: ['2', '3'],
|
||||
ö: '=',
|
||||
c: CATALOG_FILTER_EXISTS,
|
||||
},
|
||||
},
|
||||
{ token },
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
import { ResponseError } from '@backstage/errors';
|
||||
import fetch from 'cross-fetch';
|
||||
import {
|
||||
CATALOG_FILTER_EXISTS,
|
||||
AddLocationRequest,
|
||||
AddLocationResponse,
|
||||
CatalogApi,
|
||||
@@ -69,9 +70,13 @@ export class CatalogClient implements CatalogApi {
|
||||
const filterParts: string[] = [];
|
||||
for (const [key, value] of Object.entries(filterItem)) {
|
||||
for (const v of [value].flat()) {
|
||||
filterParts.push(
|
||||
`${encodeURIComponent(key)}=${encodeURIComponent(v)}`,
|
||||
);
|
||||
if (v === CATALOG_FILTER_EXISTS) {
|
||||
filterParts.push(encodeURIComponent(key));
|
||||
} else if (typeof v === 'string') {
|
||||
filterParts.push(
|
||||
`${encodeURIComponent(key)}=${encodeURIComponent(v)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,10 +16,12 @@
|
||||
|
||||
import { Entity, EntityName, Location } from '@backstage/catalog-model';
|
||||
|
||||
export const CATALOG_FILTER_EXISTS = Symbol('CATALOG_FILTER_EXISTS');
|
||||
|
||||
export type CatalogEntitiesRequest = {
|
||||
filter?:
|
||||
| Record<string, string | string[]>[]
|
||||
| Record<string, string | string[]>
|
||||
| Record<string, string | symbol | (string | symbol)[]>[]
|
||||
| Record<string, string | symbol | (string | symbol)[]>
|
||||
| undefined;
|
||||
fields?: string[] | undefined;
|
||||
};
|
||||
|
||||
@@ -22,4 +22,5 @@ export type {
|
||||
CatalogListResponse,
|
||||
CatalogRequestOptions,
|
||||
} from './api';
|
||||
export { CATALOG_FILTER_EXISTS } from './api';
|
||||
export { ENTITY_STATUS_CATALOG_PROCESSING_TYPE } from './status';
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
# @backstage/cli
|
||||
|
||||
## 0.7.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 9d40fcb1e: - Bumping `material-ui/core` version to at least `4.12.2` as they made some breaking changes in later versions which broke `Pagination` of the `Table`.
|
||||
- Switching out `material-table` to `@material-table/core` for support for the later versions of `material-ui/core`
|
||||
- This causes a minor API change to `@backstage/core-components` as the interface for `Table` re-exports the `prop` from the underlying `Table` components.
|
||||
- `onChangeRowsPerPage` has been renamed to `onRowsPerPageChange`
|
||||
- `onChangePage` has been renamed to `onPageChange`
|
||||
- Migration guide is here: https://material-table-core.com/docs/breaking-changes
|
||||
|
||||
## 0.7.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/cli",
|
||||
"description": "CLI for developing Backstage plugins and apps",
|
||||
"version": "0.7.5",
|
||||
"version": "0.7.6",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
@@ -120,12 +120,12 @@
|
||||
"devDependencies": {
|
||||
"@backstage/backend-common": "^0.8.7",
|
||||
"@backstage/config": "^0.1.5",
|
||||
"@backstage/core-components": "^0.1.6",
|
||||
"@backstage/core-plugin-api": "^0.1.3",
|
||||
"@backstage/core-app-api": "^0.1.5",
|
||||
"@backstage/dev-utils": "^0.2.3",
|
||||
"@backstage/test-utils": "^0.1.15",
|
||||
"@backstage/theme": "^0.2.8",
|
||||
"@backstage/core-components": "^0.2.0",
|
||||
"@backstage/core-plugin-api": "^0.1.4",
|
||||
"@backstage/core-app-api": "^0.1.6",
|
||||
"@backstage/dev-utils": "^0.2.4",
|
||||
"@backstage/test-utils": "^0.1.16",
|
||||
"@backstage/theme": "^0.2.9",
|
||||
"@types/diff": "^5.0.0",
|
||||
"@types/express": "^4.17.6",
|
||||
"@types/fs-extra": "^9.0.1",
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
"@backstage/core-components": "^{{version '@backstage/core-components'}}",
|
||||
"@backstage/core-plugin-api": "^{{version '@backstage/core-plugin-api'}}",
|
||||
"@backstage/theme": "^{{version '@backstage/theme'}}",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/core": "^4.12.2",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "4.0.0-alpha.45",
|
||||
"react": "^16.13.1",
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
# @backstage/codemods
|
||||
|
||||
## 0.1.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/core-components@0.2.0
|
||||
- @backstage/core-app-api@0.1.6
|
||||
- @backstage/core-plugin-api@0.1.4
|
||||
|
||||
## 0.1.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/codemods",
|
||||
"description": "A collection of codemods for Backstage projects",
|
||||
"version": "0.1.6",
|
||||
"version": "0.1.7",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
|
||||
@@ -1,5 +1,20 @@
|
||||
# @backstage/core-app-api
|
||||
|
||||
## 0.1.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 9d40fcb1e: - Bumping `material-ui/core` version to at least `4.12.2` as they made some breaking changes in later versions which broke `Pagination` of the `Table`.
|
||||
- Switching out `material-table` to `@material-table/core` for support for the later versions of `material-ui/core`
|
||||
- This causes a minor API change to `@backstage/core-components` as the interface for `Table` re-exports the `prop` from the underlying `Table` components.
|
||||
- `onChangeRowsPerPage` has been renamed to `onRowsPerPageChange`
|
||||
- `onChangePage` has been renamed to `onPageChange`
|
||||
- Migration guide is here: https://material-table-core.com/docs/breaking-changes
|
||||
- Updated dependencies
|
||||
- @backstage/core-components@0.2.0
|
||||
- @backstage/core-plugin-api@0.1.4
|
||||
- @backstage/theme@0.2.9
|
||||
|
||||
## 0.1.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/core-app-api",
|
||||
"description": "Core app API used by Backstage apps",
|
||||
"version": "0.1.5",
|
||||
"version": "0.1.6",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
@@ -29,11 +29,11 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/core-components": "^0.1.6",
|
||||
"@backstage/core-components": "^0.2.0",
|
||||
"@backstage/config": "^0.1.3",
|
||||
"@backstage/core-plugin-api": "^0.1.3",
|
||||
"@backstage/theme": "^0.2.8",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@backstage/core-plugin-api": "^0.1.4",
|
||||
"@backstage/theme": "^0.2.9",
|
||||
"@material-ui/core": "^4.12.2",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@types/react": "^16.9",
|
||||
"@types/prop-types": "^15.7.3",
|
||||
@@ -44,8 +44,8 @@
|
||||
"zen-observable": "^0.8.15"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.7.5",
|
||||
"@backstage/test-utils": "^0.1.15",
|
||||
"@backstage/cli": "^0.7.6",
|
||||
"@backstage/test-utils": "^0.1.16",
|
||||
"@backstage/test-utils-core": "^0.1.1",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^11.2.5",
|
||||
|
||||
@@ -1,5 +1,48 @@
|
||||
# @backstage/core-components
|
||||
|
||||
## 0.2.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 9d40fcb1e: - Bumping `material-ui/core` version to at least `4.12.2` as they made some breaking changes in later versions which broke `Pagination` of the `Table`.
|
||||
- Switching out `material-table` to `@material-table/core` for support for the later versions of `material-ui/core`
|
||||
- This causes a minor API change to `@backstage/core-components` as the interface for `Table` re-exports the `prop` from the underlying `Table` components.
|
||||
- `onChangeRowsPerPage` has been renamed to `onRowsPerPageChange`
|
||||
- `onChangePage` has been renamed to `onPageChange`
|
||||
- Migration guide is here: https://material-table-core.com/docs/breaking-changes
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 19d9995b6: Improve accessibility of core & catalog components by adjusting them with non-breaking changes.
|
||||
- 224e54484: Added an `EntityProcessingErrorsPanel` component to show any errors that occurred when refreshing an entity from its source location.
|
||||
|
||||
If upgrading, this should be added to your `EntityPage` in your Backstage application:
|
||||
|
||||
```diff
|
||||
// packages/app/src/components/catalog/EntityPage.tsx
|
||||
|
||||
const overviewContent = (
|
||||
...
|
||||
<EntityOrphanWarning />
|
||||
</Grid>
|
||||
</EntitySwitch.Case>
|
||||
</EntitySwitch>
|
||||
+ <EntitySwitch>
|
||||
+ <EntitySwitch.Case if={hasCatalogProcessingErrors}>
|
||||
+ <Grid item xs={12}>
|
||||
+ <EntityProcessingErrorsPanel />
|
||||
+ </Grid>
|
||||
+ </EntitySwitch.Case>
|
||||
+ </EntitySwitch>
|
||||
|
||||
```
|
||||
|
||||
Additionally, `WarningPanel` now changes color based on the provided severity.
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/core-plugin-api@0.1.4
|
||||
- @backstage/theme@0.2.9
|
||||
|
||||
## 0.1.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -11,7 +11,7 @@ import { Breadcrumbs as Breadcrumbs_2 } from '@material-ui/core';
|
||||
import { ButtonProps } from '@material-ui/core';
|
||||
import { ButtonTypeMap } from '@material-ui/core';
|
||||
import { CardHeaderProps } from '@material-ui/core';
|
||||
import { Column } from 'material-table';
|
||||
import { Column } from '@material-table/core';
|
||||
import { CommonProps } from '@material-ui/core/OverridableComponent';
|
||||
import { ComponentClass } from 'react';
|
||||
import { ComponentProps } from 'react';
|
||||
@@ -26,7 +26,7 @@ import { IconComponent } from '@backstage/core-plugin-api';
|
||||
import { LinearProgressProps } from '@material-ui/core';
|
||||
import { LinkProps as LinkProps_2 } from '@material-ui/core';
|
||||
import { LinkProps as LinkProps_3 } from 'react-router-dom';
|
||||
import { MaterialTableProps } from 'material-table';
|
||||
import { MaterialTableProps } from '@material-table/core';
|
||||
import { NavLinkProps } from 'react-router-dom';
|
||||
import { ProfileInfoApi } from '@backstage/core-plugin-api';
|
||||
import { PropsWithChildren } from 'react';
|
||||
@@ -80,7 +80,7 @@ export const Avatar: ({
|
||||
// Warning: (ae-missing-release-tag) "Breadcrumbs" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const Breadcrumbs: ({ children, ...props }: Props_25) => JSX.Element;
|
||||
export const Breadcrumbs: ({ children, ...props }: Props_24) => JSX.Element;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "BrokenImageIcon" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
@@ -441,7 +441,7 @@ export const Content: ({
|
||||
noPadding,
|
||||
children,
|
||||
...props
|
||||
}: PropsWithChildren<Props_18>) => JSX.Element;
|
||||
}: PropsWithChildren<Props_17>) => JSX.Element;
|
||||
|
||||
// Warning: (ae-forgotten-export) The symbol "ContentHeaderProps" needs to be exported by the entry point index.d.ts
|
||||
// Warning: (ae-missing-release-tag) "ContentHeader" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
@@ -695,7 +695,7 @@ export const Header: ({
|
||||
tooltip,
|
||||
type,
|
||||
typeLink,
|
||||
}: PropsWithChildren<Props_19>) => JSX.Element;
|
||||
}: PropsWithChildren<Props_18>) => JSX.Element;
|
||||
|
||||
// Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts
|
||||
// Warning: (ae-missing-release-tag) "HeaderIconLinkRow" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
@@ -777,7 +777,7 @@ export const InfoCard: ({
|
||||
className,
|
||||
noPadding,
|
||||
titleTypographyProps,
|
||||
}: Props_20) => JSX.Element;
|
||||
}: Props_19) => JSX.Element;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "InfoCardVariants" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
@@ -1200,7 +1200,7 @@ export const OverflowTooltip: (props: Props_12) => JSX.Element;
|
||||
export const Page: ({
|
||||
themeId,
|
||||
children,
|
||||
}: PropsWithChildren<Props_21>) => JSX.Element;
|
||||
}: PropsWithChildren<Props_20>) => JSX.Element;
|
||||
|
||||
// Warning: (ae-forgotten-export) The symbol "PageWithHeaderProps" needs to be exported by the entry point index.d.ts
|
||||
// Warning: (ae-missing-release-tag) "PageWithHeader" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
@@ -1291,7 +1291,7 @@ export const Sidebar: ({
|
||||
openDelayMs,
|
||||
closeDelayMs,
|
||||
children,
|
||||
}: PropsWithChildren<Props_22>) => JSX.Element;
|
||||
}: PropsWithChildren<Props_21>) => JSX.Element;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "SIDEBAR_INTRO_LOCAL_STORAGE" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
@@ -2448,7 +2448,7 @@ export const SidebarSpacer: React_2.ComponentType<
|
||||
// Warning: (ae-missing-release-tag) "SignInPage" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const SignInPage: (props: Props_23) => JSX.Element;
|
||||
export const SignInPage: (props: Props_22) => JSX.Element;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "SignInProviderConfig" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
@@ -2592,7 +2592,7 @@ export const TabbedCard: ({
|
||||
deepLink,
|
||||
value,
|
||||
onChange,
|
||||
}: PropsWithChildren<Props_24>) => JSX.Element;
|
||||
}: PropsWithChildren<Props_23>) => JSX.Element;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "TabbedLayout" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
@@ -2715,11 +2715,17 @@ export const WarningIcon: IconComponent;
|
||||
// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen
|
||||
// Warning: (tsdoc-param-tag-with-invalid-optional-name) The @param should not include a JSDoc-style optional name; it must not be enclosed in '[ ]' brackets.
|
||||
// Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}'
|
||||
// Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts
|
||||
// Warning: (ae-forgotten-export) The symbol "WarningProps" needs to be exported by the entry point index.d.ts
|
||||
// Warning: (ae-missing-release-tag) "WarningPanel" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public
|
||||
export const WarningPanel: (props: Props_17) => JSX.Element;
|
||||
export const WarningPanel: ({
|
||||
severity,
|
||||
title,
|
||||
message,
|
||||
children,
|
||||
defaultExpanded,
|
||||
}: WarningProps) => JSX.Element;
|
||||
|
||||
// Warnings were encountered during analysis:
|
||||
//
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/core-components",
|
||||
"description": "Core components used by Backstage plugins and apps",
|
||||
"version": "0.1.6",
|
||||
"version": "0.2.0",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
@@ -30,10 +30,10 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/config": "^0.1.5",
|
||||
"@backstage/core-plugin-api": "^0.1.2",
|
||||
"@backstage/core-plugin-api": "^0.1.4",
|
||||
"@backstage/errors": "^0.1.1",
|
||||
"@backstage/theme": "^0.2.8",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@backstage/theme": "^0.2.9",
|
||||
"@material-ui/core": "^4.12.2",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "4.0.0-alpha.45",
|
||||
"@testing-library/react-hooks": "^3.4.2",
|
||||
@@ -50,7 +50,7 @@
|
||||
"dagre": "^0.8.5",
|
||||
"immer": "^9.0.1",
|
||||
"lodash": "^4.17.15",
|
||||
"material-table": "^1.69.1",
|
||||
"@material-table/core": "^3.1.0",
|
||||
"pluralize": "^8.0.0",
|
||||
"prop-types": "^15.7.2",
|
||||
"qs": "^6.9.4",
|
||||
@@ -70,9 +70,9 @@
|
||||
"zen-observable": "^0.8.15"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/core-app-api": "^0.1.5",
|
||||
"@backstage/cli": "^0.7.5",
|
||||
"@backstage/test-utils": "^0.1.15",
|
||||
"@backstage/core-app-api": "^0.1.6",
|
||||
"@backstage/cli": "^0.7.6",
|
||||
"@backstage/test-utils": "^0.1.16",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^11.2.5",
|
||||
"@testing-library/user-event": "^13.1.8",
|
||||
|
||||
@@ -100,6 +100,7 @@ export const ErrorPanel = ({
|
||||
}: PropsWithChildren<ErrorPanelProps>) => {
|
||||
return (
|
||||
<WarningPanel
|
||||
severity="error"
|
||||
title={title ?? error.message}
|
||||
defaultExpanded={defaultExpanded}
|
||||
>
|
||||
|
||||
@@ -47,14 +47,13 @@ import MTable, {
|
||||
MTableHeader,
|
||||
MTableToolbar,
|
||||
Options,
|
||||
} from 'material-table';
|
||||
} from '@material-table/core';
|
||||
import React, {
|
||||
forwardRef,
|
||||
MutableRefObject,
|
||||
ReactNode,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { CheckboxTreeProps } from '../CheckboxTree/CheckboxTree';
|
||||
@@ -298,13 +297,6 @@ export function Table<T extends object = {}>({
|
||||
const MTColumns = convertColumns(columns, theme);
|
||||
|
||||
const [search, setSearch] = useState(calculatedInitialState.search);
|
||||
const toolbarRef = useRef<any>();
|
||||
|
||||
useEffect(() => {
|
||||
if (toolbarRef.current) {
|
||||
toolbarRef.current.onSearchChange(search);
|
||||
}
|
||||
}, [search, toolbarRef]);
|
||||
|
||||
useEffect(() => {
|
||||
if (onStateChange) {
|
||||
@@ -440,7 +432,6 @@ export function Table<T extends object = {}>({
|
||||
toolbarProps => {
|
||||
return (
|
||||
<TableToolbar
|
||||
toolbarRef={toolbarRef}
|
||||
setSearch={setSearch}
|
||||
hasFilters={hasFilters}
|
||||
selectedFiltersLength={selectedFiltersLength}
|
||||
@@ -449,7 +440,7 @@ export function Table<T extends object = {}>({
|
||||
/>
|
||||
);
|
||||
},
|
||||
[toggleFilters, hasFilters, selectedFiltersLength, setSearch, toolbarRef],
|
||||
[toggleFilters, hasFilters, selectedFiltersLength, setSearch],
|
||||
);
|
||||
|
||||
const hasNoRows = typeof data !== 'function' && data.length === 0;
|
||||
|
||||
@@ -19,11 +19,19 @@ import { fireEvent, screen } from '@testing-library/react';
|
||||
import { renderInTestApp } from '@backstage/test-utils';
|
||||
import { Typography } from '@material-ui/core';
|
||||
|
||||
import { WarningPanel } from './WarningPanel';
|
||||
import { WarningPanel, WarningProps } from './WarningPanel';
|
||||
|
||||
const propsTitle = { title: 'Mock title' };
|
||||
const propsTitleMessage = { title: 'Mock title', message: 'Some more info' };
|
||||
const propsMessage = { message: 'Some more info' };
|
||||
const propsTitle: WarningProps = { title: 'Mock title' };
|
||||
const propsTitleMessage: WarningProps = {
|
||||
title: 'Mock title',
|
||||
message: 'Some more info',
|
||||
};
|
||||
const propsMessage: WarningProps = { message: 'Some more info' };
|
||||
const propsErrorMessage: WarningProps = {
|
||||
severity: 'error',
|
||||
title: 'Mock title',
|
||||
message: 'Some more info',
|
||||
};
|
||||
|
||||
describe('<WarningPanel />', () => {
|
||||
it('renders without exploding', async () => {
|
||||
@@ -64,4 +72,8 @@ describe('<WarningPanel />', () => {
|
||||
expect(screen.getByText('Some more info')).toBeInTheDocument();
|
||||
expect(screen.getByText('Java stacktrace')).toBeInTheDocument();
|
||||
});
|
||||
it('renders message using severity', async () => {
|
||||
await renderInTestApp(<WarningPanel {...propsErrorMessage} />);
|
||||
expect(screen.getByText('Error: Mock title')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { BackstageTheme } from '@backstage/theme';
|
||||
import {
|
||||
Accordion,
|
||||
@@ -22,30 +21,61 @@ import {
|
||||
Grid,
|
||||
makeStyles,
|
||||
Typography,
|
||||
darken,
|
||||
lighten,
|
||||
} from '@material-ui/core';
|
||||
import ErrorOutline from '@material-ui/icons/ErrorOutline';
|
||||
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
|
||||
import React from 'react';
|
||||
|
||||
const getWarningTextColor = (
|
||||
severity: NonNullable<WarningProps['severity']>,
|
||||
theme: BackstageTheme,
|
||||
) => {
|
||||
const getColor = theme.palette.type === 'light' ? darken : lighten;
|
||||
return getColor(theme.palette[severity].light, 0.6);
|
||||
};
|
||||
|
||||
const getWarningBackgroundColor = (
|
||||
severity: NonNullable<WarningProps['severity']>,
|
||||
theme: BackstageTheme,
|
||||
) => {
|
||||
const getBackgroundColor = theme.palette.type === 'light' ? lighten : darken;
|
||||
return getBackgroundColor(theme.palette[severity].light, 0.9);
|
||||
};
|
||||
|
||||
const useErrorOutlineStyles = makeStyles<BackstageTheme>(theme => ({
|
||||
root: {
|
||||
marginRight: theme.spacing(1),
|
||||
fill: theme.palette.warningText,
|
||||
fill: ({ severity }: WarningProps) =>
|
||||
getWarningTextColor(
|
||||
severity as NonNullable<WarningProps['severity']>,
|
||||
theme,
|
||||
),
|
||||
},
|
||||
}));
|
||||
const ErrorOutlineStyled = () => {
|
||||
const classes = useErrorOutlineStyles();
|
||||
|
||||
const ErrorOutlineStyled = ({ severity }: Pick<WarningProps, 'severity'>) => {
|
||||
const classes = useErrorOutlineStyles({ severity });
|
||||
return <ErrorOutline classes={classes} />;
|
||||
};
|
||||
const ExpandMoreIconStyled = () => {
|
||||
const classes = useErrorOutlineStyles();
|
||||
const ExpandMoreIconStyled = ({ severity }: Pick<WarningProps, 'severity'>) => {
|
||||
const classes = useErrorOutlineStyles({ severity });
|
||||
return <ExpandMoreIcon classes={classes} />;
|
||||
};
|
||||
|
||||
const useStyles = makeStyles<BackstageTheme>(theme => ({
|
||||
panel: {
|
||||
backgroundColor: theme.palette.warningBackground,
|
||||
color: theme.palette.warningText,
|
||||
backgroundColor: ({ severity }: WarningProps) =>
|
||||
getWarningBackgroundColor(
|
||||
severity as NonNullable<WarningProps['severity']>,
|
||||
theme,
|
||||
),
|
||||
color: ({ severity }: WarningProps) =>
|
||||
getWarningTextColor(
|
||||
severity as NonNullable<WarningProps['severity']>,
|
||||
theme,
|
||||
),
|
||||
verticalAlign: 'middle',
|
||||
},
|
||||
summary: {
|
||||
@@ -53,14 +83,26 @@ const useStyles = makeStyles<BackstageTheme>(theme => ({
|
||||
flexDirection: 'row',
|
||||
},
|
||||
summaryText: {
|
||||
color: theme.palette.warningText,
|
||||
color: ({ severity }: WarningProps) =>
|
||||
getWarningTextColor(
|
||||
severity as NonNullable<WarningProps['severity']>,
|
||||
theme,
|
||||
),
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
message: {
|
||||
width: '100%',
|
||||
display: 'block',
|
||||
color: theme.palette.warningText,
|
||||
backgroundColor: theme.palette.warningBackground,
|
||||
color: ({ severity }: WarningProps) =>
|
||||
getWarningTextColor(
|
||||
severity as NonNullable<WarningProps['severity']>,
|
||||
theme,
|
||||
),
|
||||
backgroundColor: ({ severity }: WarningProps) =>
|
||||
getWarningBackgroundColor(
|
||||
severity as NonNullable<WarningProps['severity']>,
|
||||
theme,
|
||||
),
|
||||
},
|
||||
details: {
|
||||
width: '100%',
|
||||
@@ -73,7 +115,7 @@ const useStyles = makeStyles<BackstageTheme>(theme => ({
|
||||
},
|
||||
}));
|
||||
|
||||
type Props = {
|
||||
export type WarningProps = {
|
||||
title?: string;
|
||||
severity?: 'warning' | 'error' | 'info';
|
||||
message?: React.ReactNode;
|
||||
@@ -89,19 +131,23 @@ const capitalize = (s: string) => {
|
||||
* WarningPanel. Show a user friendly error message to a user similar to ErrorPanel except that the warning panel
|
||||
* only shows the warning message to the user.
|
||||
*
|
||||
* @param {string} [severity=warning] Ability to change the severity of the alert. Not fully implemented. (error, warning, info)
|
||||
* @param {string} [severity=warning] Ability to change the severity of the alert.
|
||||
* @param {string} [title] A title for the warning. If not supplied, "Warning" will be used.
|
||||
* @param {Object} [message] Optional more detailed user-friendly message elaborating on the cause of the error.
|
||||
* @param {Object} [children] Objects to provide context, such as a stack trace or detailed error reporting.
|
||||
* Will be available inside an unfolded accordion.
|
||||
*/
|
||||
export const WarningPanel = (props: Props) => {
|
||||
const classes = useStyles(props);
|
||||
const { severity, title, message, children, defaultExpanded } = props;
|
||||
export const WarningPanel = ({
|
||||
severity = 'warning',
|
||||
title,
|
||||
message,
|
||||
children,
|
||||
defaultExpanded,
|
||||
}: WarningProps) => {
|
||||
const classes = useStyles({ severity });
|
||||
|
||||
// If no severity or title provided, the heading will read simply "Warning"
|
||||
const subTitle =
|
||||
(severity ? capitalize(severity) : 'Warning') + (title ? `: ${title}` : '');
|
||||
const subTitle = capitalize(severity) + (title ? `: ${title}` : '');
|
||||
|
||||
return (
|
||||
<Accordion
|
||||
@@ -110,10 +156,10 @@ export const WarningPanel = (props: Props) => {
|
||||
role="alert"
|
||||
>
|
||||
<AccordionSummary
|
||||
expandIcon={<ExpandMoreIconStyled />}
|
||||
expandIcon={<ExpandMoreIconStyled severity={severity} />}
|
||||
className={classes.summary}
|
||||
>
|
||||
<ErrorOutlineStyled />
|
||||
<ErrorOutlineStyled severity={severity} />
|
||||
<Typography className={classes.summaryText} variant="subtitle1">
|
||||
{subTitle}
|
||||
</Typography>
|
||||
|
||||
@@ -1,5 +1,18 @@
|
||||
# @backstage/core-plugin-api
|
||||
|
||||
## 0.1.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 9d40fcb1e: - Bumping `material-ui/core` version to at least `4.12.2` as they made some breaking changes in later versions which broke `Pagination` of the `Table`.
|
||||
- Switching out `material-table` to `@material-table/core` for support for the later versions of `material-ui/core`
|
||||
- This causes a minor API change to `@backstage/core-components` as the interface for `Table` re-exports the `prop` from the underlying `Table` components.
|
||||
- `onChangeRowsPerPage` has been renamed to `onRowsPerPageChange`
|
||||
- `onChangePage` has been renamed to `onPageChange`
|
||||
- Migration guide is here: https://material-table-core.com/docs/breaking-changes
|
||||
- Updated dependencies
|
||||
- @backstage/theme@0.2.9
|
||||
|
||||
## 0.1.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/core-plugin-api",
|
||||
"description": "Core API used by Backstage plugins",
|
||||
"version": "0.1.3",
|
||||
"version": "0.1.4",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
@@ -30,8 +30,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/config": "^0.1.3",
|
||||
"@backstage/theme": "^0.2.8",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@backstage/theme": "^0.2.9",
|
||||
"@material-ui/core": "^4.12.2",
|
||||
"@types/react": "^16.9",
|
||||
"history": "^5.0.0",
|
||||
"prop-types": "^15.7.2",
|
||||
@@ -41,9 +41,9 @@
|
||||
"zen-observable": "^0.8.15"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.7.2",
|
||||
"@backstage/core-app-api": "^0.1.3",
|
||||
"@backstage/test-utils": "^0.1.14",
|
||||
"@backstage/cli": "^0.7.6",
|
||||
"@backstage/core-app-api": "^0.1.6",
|
||||
"@backstage/test-utils": "^0.1.16",
|
||||
"@backstage/test-utils-core": "^0.1.1",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^11.2.5",
|
||||
|
||||
@@ -1,5 +1,41 @@
|
||||
# @backstage/create-app
|
||||
|
||||
## 0.3.33
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 9d40fcb1e: - Bumping `material-ui/core` version to at least `4.12.2` as they made some breaking changes in later versions which broke `Pagination` of the `Table`.
|
||||
- Switching out `material-table` to `@material-table/core` for support for the later versions of `material-ui/core`
|
||||
- This causes a minor API change to `@backstage/core-components` as the interface for `Table` re-exports the `prop` from the underlying `Table` components.
|
||||
- `onChangeRowsPerPage` has been renamed to `onRowsPerPageChange`
|
||||
- `onChangePage` has been renamed to `onPageChange`
|
||||
- Migration guide is here: https://material-table-core.com/docs/breaking-changes
|
||||
- d50c9e7c0: Update the `software-templates` to point to `main` branch instead of `master`
|
||||
- 224e54484: Added an `EntityProcessingErrorsPanel` component to show any errors that occurred when refreshing an entity from its source location.
|
||||
|
||||
If upgrading, this should be added to your `EntityPage` in your Backstage application:
|
||||
|
||||
```diff
|
||||
// packages/app/src/components/catalog/EntityPage.tsx
|
||||
|
||||
const overviewContent = (
|
||||
...
|
||||
<EntityOrphanWarning />
|
||||
</Grid>
|
||||
</EntitySwitch.Case>
|
||||
</EntitySwitch>
|
||||
+ <EntitySwitch>
|
||||
+ <EntitySwitch.Case if={hasCatalogProcessingErrors}>
|
||||
+ <Grid item xs={12}>
|
||||
+ <EntityProcessingErrorsPanel />
|
||||
+ </Grid>
|
||||
+ </EntitySwitch.Case>
|
||||
+ </EntitySwitch>
|
||||
|
||||
```
|
||||
|
||||
Additionally, `WarningPanel` now changes color based on the provided severity.
|
||||
|
||||
## 0.3.32
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/create-app",
|
||||
"description": "Create app package for Backstage",
|
||||
"version": "0.3.32",
|
||||
"version": "0.3.33",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
"@backstage/plugin-user-settings": "^{{version '@backstage/plugin-user-settings'}}",
|
||||
"@backstage/test-utils": "^{{version '@backstage/test-utils'}}",
|
||||
"@backstage/theme": "^{{version '@backstage/theme'}}",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/core": "^4.12.2",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"history": "^5.0.0",
|
||||
"react": "^16.13.1",
|
||||
|
||||
+21
-1
@@ -35,8 +35,12 @@ import {
|
||||
EntityLayout,
|
||||
EntityLinksCard,
|
||||
EntitySwitch,
|
||||
EntityOrphanWarning,
|
||||
EntityProcessingErrorsPanel,
|
||||
isComponentType,
|
||||
isKind,
|
||||
hasCatalogProcessingErrors,
|
||||
isOrphan,
|
||||
} from '@backstage/plugin-catalog';
|
||||
import {
|
||||
isGithubActionsAvailable,
|
||||
@@ -80,6 +84,22 @@ const cicdContent = (
|
||||
|
||||
const overviewContent = (
|
||||
<Grid container spacing={3} alignItems="stretch">
|
||||
<EntitySwitch>
|
||||
<EntitySwitch.Case if={isOrphan}>
|
||||
<Grid item xs={12}>
|
||||
<EntityOrphanWarning />
|
||||
</Grid>
|
||||
</EntitySwitch.Case>
|
||||
</EntitySwitch>
|
||||
|
||||
<EntitySwitch>
|
||||
<EntitySwitch.Case if={hasCatalogProcessingErrors}>
|
||||
<Grid item xs={12}>
|
||||
<EntityProcessingErrorsPanel />
|
||||
</Grid>
|
||||
</EntitySwitch.Case>
|
||||
</EntitySwitch>
|
||||
|
||||
<Grid item md={6}>
|
||||
<EntityAboutCard variant="gridItem" />
|
||||
</Grid>
|
||||
@@ -160,7 +180,7 @@ const websiteEntityPage = (
|
||||
/**
|
||||
* NOTE: This page is designed to work on small screens such as mobile devices.
|
||||
* This is based on Material UI Grid. If breakpoints are used, each grid item must set the `xs` prop to a column size or to `true`,
|
||||
* since this does not default. If no breakpoints are used, the items will equitably share the asvailable space.
|
||||
* since this does not default. If no breakpoints are used, the items will equitably share the available space.
|
||||
* https://material-ui.com/components/grid/#basic-grid.
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,5 +1,24 @@
|
||||
# @backstage/dev-utils
|
||||
|
||||
## 0.2.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 9d40fcb1e: - Bumping `material-ui/core` version to at least `4.12.2` as they made some breaking changes in later versions which broke `Pagination` of the `Table`.
|
||||
- Switching out `material-table` to `@material-table/core` for support for the later versions of `material-ui/core`
|
||||
- This causes a minor API change to `@backstage/core-components` as the interface for `Table` re-exports the `prop` from the underlying `Table` components.
|
||||
- `onChangeRowsPerPage` has been renamed to `onRowsPerPageChange`
|
||||
- `onChangePage` has been renamed to `onPageChange`
|
||||
- Migration guide is here: https://material-table-core.com/docs/breaking-changes
|
||||
- Updated dependencies
|
||||
- @backstage/core-components@0.2.0
|
||||
- @backstage/plugin-catalog-react@0.4.0
|
||||
- @backstage/core-app-api@0.1.6
|
||||
- @backstage/core-plugin-api@0.1.4
|
||||
- @backstage/integration-react@0.1.5
|
||||
- @backstage/test-utils@0.1.16
|
||||
- @backstage/theme@0.2.9
|
||||
|
||||
## 0.2.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/dev-utils",
|
||||
"description": "Utilities for developing Backstage plugins.",
|
||||
"version": "0.2.3",
|
||||
"version": "0.2.4",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
@@ -29,15 +29,15 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/core-app-api": "^0.1.5",
|
||||
"@backstage/core-components": "^0.1.6",
|
||||
"@backstage/core-plugin-api": "^0.1.3",
|
||||
"@backstage/core-app-api": "^0.1.6",
|
||||
"@backstage/core-components": "^0.2.0",
|
||||
"@backstage/core-plugin-api": "^0.1.4",
|
||||
"@backstage/catalog-model": "^0.9.0",
|
||||
"@backstage/integration-react": "^0.1.4",
|
||||
"@backstage/plugin-catalog-react": "^0.3.1",
|
||||
"@backstage/test-utils": "^0.1.15",
|
||||
"@backstage/theme": "^0.2.8",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@backstage/integration-react": "^0.1.5",
|
||||
"@backstage/plugin-catalog-react": "^0.4.0",
|
||||
"@backstage/test-utils": "^0.1.16",
|
||||
"@backstage/theme": "^0.2.9",
|
||||
"@material-ui/core": "^4.12.2",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^11.2.5",
|
||||
@@ -50,7 +50,7 @@
|
||||
"react-router-dom": "6.0.0-beta.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.7.5",
|
||||
"@backstage/cli": "^0.7.6",
|
||||
"@types/jest": "^26.0.7",
|
||||
"@types/node": "^14.14.32"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,20 @@
|
||||
# @backstage/integration-react
|
||||
|
||||
## 0.1.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 9d40fcb1e: - Bumping `material-ui/core` version to at least `4.12.2` as they made some breaking changes in later versions which broke `Pagination` of the `Table`.
|
||||
- Switching out `material-table` to `@material-table/core` for support for the later versions of `material-ui/core`
|
||||
- This causes a minor API change to `@backstage/core-components` as the interface for `Table` re-exports the `prop` from the underlying `Table` components.
|
||||
- `onChangeRowsPerPage` has been renamed to `onRowsPerPageChange`
|
||||
- `onChangePage` has been renamed to `onPageChange`
|
||||
- Migration guide is here: https://material-table-core.com/docs/breaking-changes
|
||||
- Updated dependencies
|
||||
- @backstage/core-components@0.2.0
|
||||
- @backstage/core-plugin-api@0.1.4
|
||||
- @backstage/theme@0.2.9
|
||||
|
||||
## 0.1.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/integration-react",
|
||||
"version": "0.1.4",
|
||||
"version": "0.1.5",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -21,11 +21,11 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/config": "^0.1.2",
|
||||
"@backstage/core-components": "^0.1.3",
|
||||
"@backstage/core-plugin-api": "^0.1.3",
|
||||
"@backstage/core-components": "^0.2.0",
|
||||
"@backstage/core-plugin-api": "^0.1.4",
|
||||
"@backstage/integration": "^0.5.6",
|
||||
"@backstage/theme": "^0.2.3",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@backstage/theme": "^0.2.9",
|
||||
"@material-ui/core": "^4.12.2",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "4.0.0-alpha.45",
|
||||
"react": "^16.13.1",
|
||||
@@ -33,9 +33,9 @@
|
||||
"react-use": "^17.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.7.2",
|
||||
"@backstage/dev-utils": "^0.2.0",
|
||||
"@backstage/test-utils": "^0.1.14",
|
||||
"@backstage/cli": "^0.7.6",
|
||||
"@backstage/dev-utils": "^0.2.4",
|
||||
"@backstage/test-utils": "^0.1.16",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^11.2.5",
|
||||
"@testing-library/user-event": "^13.1.8",
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
# @backstage/techdocs-common
|
||||
|
||||
## 0.7.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 59a5fa319: Migrated files are now printed when `techdocs-cli migrate` is run with the
|
||||
`--verbose` flag set.
|
||||
- 54356336e: TechDocs generator stage now supports `mkdocs.yaml` file, in addition to `.yml`
|
||||
depending on whichever is present at the time of generation. (Assumes the
|
||||
latest `spotify/techdocs` container, running mkdocs `v1.2.2` or greater).
|
||||
|
||||
## 0.7.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/techdocs-common",
|
||||
"description": "Common functionalities for TechDocs, to be shared between techdocs-backend plugin and techdocs-cli",
|
||||
"version": "0.7.0",
|
||||
"version": "0.7.1",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"private": false,
|
||||
@@ -60,7 +60,7 @@
|
||||
"winston": "^3.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.7.5",
|
||||
"@backstage/cli": "^0.7.6",
|
||||
"@types/fs-extra": "^9.0.5",
|
||||
"@types/git-url-parse": "^9.0.0",
|
||||
"@types/js-yaml": "^4.0.0",
|
||||
|
||||
@@ -23,6 +23,7 @@ import { RemoteProtocol } from '../prepare/types';
|
||||
import {
|
||||
addBuildTimestampMetadata,
|
||||
getGeneratorKey,
|
||||
getMkdocsYml,
|
||||
getRepoUrlFromLocationAnnotation,
|
||||
isValidRepoUrlForMkdocs,
|
||||
patchMkdocsYmlPreBuild,
|
||||
@@ -53,6 +54,9 @@ const mkdocsYmlWithValidDocDir = fs.readFileSync(
|
||||
const mkdocsYmlWithInvalidDocDir = fs.readFileSync(
|
||||
resolvePath(__filename, '../__fixtures__/mkdocs_invalid_doc_dir.yml'),
|
||||
);
|
||||
const mkdocsYmlWithInvalidDocDir2 = fs.readFileSync(
|
||||
resolvePath(__filename, '../__fixtures__/mkdocs_invalid_doc_dir2.yml'),
|
||||
);
|
||||
const mockLogger = getVoidLogger();
|
||||
const rootDir = os.platform() === 'win32' ? 'C:\\rootDir' : '/rootDir';
|
||||
|
||||
@@ -334,48 +338,68 @@ describe('helpers', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('validateMkdocsYaml', () => {
|
||||
beforeEach(() => {
|
||||
mockFs({
|
||||
'/mkdocs.yml': mkdocsYml,
|
||||
'/mkdocs_with_extensions.yml': mkdocsYmlWithExtensions,
|
||||
'/mkdocs_valid_doc_dir.yml': mkdocsYmlWithValidDocDir,
|
||||
'/mkdocs_invalid_doc_dir.yml': mkdocsYmlWithInvalidDocDir,
|
||||
});
|
||||
});
|
||||
|
||||
describe('getMkdocsYml', () => {
|
||||
afterEach(() => {
|
||||
mockFs.restore();
|
||||
});
|
||||
|
||||
const inputDir = resolvePath(__filename, '../__fixtures__/');
|
||||
|
||||
it('returns expected contents when .yml file is present', async () => {
|
||||
const key = path.join(inputDir, 'mkdocs.yml');
|
||||
mockFs({ [key]: mkdocsYml });
|
||||
const { path: mkdocsPath, content } = await getMkdocsYml(inputDir);
|
||||
|
||||
expect(mkdocsPath).toBe(key);
|
||||
expect(content).toBe(mkdocsYml.toString());
|
||||
});
|
||||
|
||||
it('returns expected contents when .yaml file is present', async () => {
|
||||
const key = path.join(inputDir, 'mkdocs.yaml');
|
||||
mockFs({ [key]: mkdocsYml });
|
||||
const { path: mkdocsPath, content } = await getMkdocsYml(inputDir);
|
||||
expect(mkdocsPath).toBe(key);
|
||||
expect(content).toBe(mkdocsYml.toString());
|
||||
});
|
||||
|
||||
it('throws when neither .yml nor .yaml file is present', async () => {
|
||||
const invalidInputDir = resolvePath(__filename);
|
||||
await expect(getMkdocsYml(invalidInputDir)).rejects.toThrowError(
|
||||
/Could not read MkDocs YAML config file mkdocs.yml or mkdocs.yaml for validation/,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('validateMkdocsYaml', () => {
|
||||
const inputDir = resolvePath(__filename, '../__fixtures__/');
|
||||
|
||||
it('should return true on when no docs_dir present', async () => {
|
||||
await expect(
|
||||
validateMkdocsYaml(inputDir, '/mkdocs.yml'),
|
||||
validateMkdocsYaml(inputDir, mkdocsYml.toString()),
|
||||
).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return true on when a valid docs_dir is present', async () => {
|
||||
await expect(
|
||||
validateMkdocsYaml(inputDir, '/mkdocs_valid_doc_dir.yml'),
|
||||
validateMkdocsYaml(inputDir, mkdocsYmlWithValidDocDir.toString()),
|
||||
).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return false on absolute doc_dir path', async () => {
|
||||
await expect(
|
||||
validateMkdocsYaml(inputDir, '/mkdocs_invalid_doc_dir.yml'),
|
||||
validateMkdocsYaml(inputDir, mkdocsYmlWithInvalidDocDir.toString()),
|
||||
).rejects.toThrow();
|
||||
});
|
||||
|
||||
it('should return false on doc_dir path that traverses directory structure backwards', async () => {
|
||||
await expect(
|
||||
validateMkdocsYaml(inputDir, '/mkdocs_invalid_doc_dir2.yml'),
|
||||
validateMkdocsYaml(inputDir, mkdocsYmlWithInvalidDocDir2.toString()),
|
||||
).rejects.toThrow();
|
||||
});
|
||||
|
||||
it('should validate files with custom yaml tags', async () => {
|
||||
await expect(
|
||||
validateMkdocsYaml(inputDir, '/mkdocs_with_extensions.yml'),
|
||||
validateMkdocsYaml(inputDir, mkdocsYmlWithExtensions.toString()),
|
||||
).resolves.toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -19,7 +19,7 @@ import { isChildPath } from '@backstage/backend-common';
|
||||
import { spawn } from 'child_process';
|
||||
import fs from 'fs-extra';
|
||||
import yaml, { DEFAULT_SCHEMA, Type } from 'js-yaml';
|
||||
import { resolve as resolvePath } from 'path';
|
||||
import path, { resolve as resolvePath } from 'path';
|
||||
import { PassThrough, Writable } from 'stream';
|
||||
import { Logger } from 'winston';
|
||||
import { ParsedLocationAnnotation } from '../../helpers';
|
||||
@@ -155,26 +155,50 @@ const MKDOCS_SCHEMA = DEFAULT_SCHEMA.extend([
|
||||
}),
|
||||
]);
|
||||
|
||||
/**
|
||||
* Finds and loads the contents of either an mkdocs.yml or mkdocs.yaml file,
|
||||
* depending on which is present (MkDocs supports both as of v1.2.2).
|
||||
*
|
||||
* @param {string} inputDir base dir to be searched for either an mkdocs.yml or
|
||||
* mkdocs.yaml file.
|
||||
*/
|
||||
export const getMkdocsYml = async (
|
||||
inputDir: string,
|
||||
): Promise<{ path: string; content: string }> => {
|
||||
let mkdocsYmlPath: string;
|
||||
let mkdocsYmlFileString: string;
|
||||
try {
|
||||
mkdocsYmlPath = path.join(inputDir, 'mkdocs.yaml');
|
||||
mkdocsYmlFileString = await fs.readFile(mkdocsYmlPath, 'utf8');
|
||||
} catch {
|
||||
try {
|
||||
mkdocsYmlPath = path.join(inputDir, 'mkdocs.yml');
|
||||
mkdocsYmlFileString = await fs.readFile(mkdocsYmlPath, 'utf8');
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`Could not read MkDocs YAML config file mkdocs.yml or mkdocs.yaml for validation: ${error.message}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
path: mkdocsYmlPath,
|
||||
content: mkdocsYmlFileString,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Validating mkdocs config file for incorrect/insecure values
|
||||
* Throws on invalid configs
|
||||
*
|
||||
* @param {string} inputDir base dir to be used as a docs_dir path validity check
|
||||
* @param {string} mkdocsYmlPath Absolute path to mkdocs.yml or equivalent of a docs site
|
||||
* @param {string} mkdocsYmlFileString The string contents of the loaded
|
||||
* mkdocs.yml or equivalent of a docs site
|
||||
*/
|
||||
export const validateMkdocsYaml = async (
|
||||
inputDir: string,
|
||||
mkdocsYmlPath: string,
|
||||
mkdocsYmlFileString: string,
|
||||
) => {
|
||||
let mkdocsYmlFileString;
|
||||
try {
|
||||
mkdocsYmlFileString = await fs.readFile(mkdocsYmlPath, 'utf8');
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`Could not read MkDocs YAML config file ${mkdocsYmlPath} before for validation: ${error.message}`,
|
||||
);
|
||||
}
|
||||
|
||||
const mkdocsYml: any = yaml.load(mkdocsYmlFileString, {
|
||||
schema: MKDOCS_SCHEMA,
|
||||
});
|
||||
|
||||
@@ -20,6 +20,7 @@ import path from 'path';
|
||||
import { Logger } from 'winston';
|
||||
import {
|
||||
addBuildTimestampMetadata,
|
||||
getMkdocsYml,
|
||||
patchMkdocsYmlPreBuild,
|
||||
runCommand,
|
||||
storeEtagMetadata,
|
||||
@@ -71,10 +72,8 @@ export class TechdocsGenerator implements GeneratorBase {
|
||||
logger: childLogger,
|
||||
logStream,
|
||||
}: GeneratorRunOptions): Promise<void> {
|
||||
// TODO: In future mkdocs.yml can be mkdocs.yaml. So, use a config variable here to find out
|
||||
// the correct file name.
|
||||
// Do some updates to mkdocs.yml before generating docs e.g. adding repo_url
|
||||
const mkdocsYmlPath = path.join(inputDir, 'mkdocs.yml');
|
||||
const { path: mkdocsYmlPath, content } = await getMkdocsYml(inputDir);
|
||||
if (parsedLocationAnnotation) {
|
||||
await patchMkdocsYmlPreBuild(
|
||||
mkdocsYmlPath,
|
||||
@@ -83,7 +82,7 @@ export class TechdocsGenerator implements GeneratorBase {
|
||||
);
|
||||
}
|
||||
|
||||
await validateMkdocsYaml(inputDir, mkdocsYmlPath);
|
||||
await validateMkdocsYaml(inputDir, content);
|
||||
|
||||
// Directories to bind on container
|
||||
const mountDirs = {
|
||||
|
||||
@@ -337,7 +337,7 @@ export class AwsS3Publish implements PublisherBase {
|
||||
}
|
||||
|
||||
try {
|
||||
this.logger.debug(`Migrating ${file}`);
|
||||
this.logger.verbose(`Migrating ${file}`);
|
||||
await this.storageClient
|
||||
.copyObject({
|
||||
Bucket: this.bucketName,
|
||||
|
||||
@@ -323,7 +323,7 @@ export class AzureBlobStoragePublish implements PublisherBase {
|
||||
|
||||
if (originalPath === newPath) return;
|
||||
try {
|
||||
this.logger.debug(`Migrating ${originalPath}`);
|
||||
this.logger.verbose(`Migrating ${originalPath}`);
|
||||
await this.renameBlob(originalPath, newPath, removeOriginal);
|
||||
} catch (e) {
|
||||
this.logger.warn(`Unable to migrate ${originalPath}: ${e.message}`);
|
||||
|
||||
@@ -196,7 +196,7 @@ export class LocalPublish implements PublisherBase {
|
||||
// Otherwise, copy or move the file.
|
||||
await new Promise<void>(resolve => {
|
||||
const migrate = removeOriginal ? fs.move : fs.copyFile;
|
||||
this.logger.debug(`Migrating ${relativeFile}`);
|
||||
this.logger.verbose(`Migrating ${relativeFile}`);
|
||||
migrate(file, newFile, err => {
|
||||
if (err) {
|
||||
this.logger.warn(
|
||||
|
||||
@@ -64,7 +64,7 @@ export class MigrateWriteStream extends Writable {
|
||||
const migrate = this.removeOriginal
|
||||
? file.move.bind(file)
|
||||
: file.copy.bind(file);
|
||||
this.logger.debug(`Migrating ${file.name}`);
|
||||
this.logger.verbose(`Migrating ${file.name}`);
|
||||
migrate(newFile)
|
||||
.catch(e =>
|
||||
this.logger.warn(`Unable to migrate ${file.name}: ${e.message}`),
|
||||
|
||||
@@ -1,5 +1,20 @@
|
||||
# @backstage/test-utils
|
||||
|
||||
## 0.1.16
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 9d40fcb1e: - Bumping `material-ui/core` version to at least `4.12.2` as they made some breaking changes in later versions which broke `Pagination` of the `Table`.
|
||||
- Switching out `material-table` to `@material-table/core` for support for the later versions of `material-ui/core`
|
||||
- This causes a minor API change to `@backstage/core-components` as the interface for `Table` re-exports the `prop` from the underlying `Table` components.
|
||||
- `onChangeRowsPerPage` has been renamed to `onRowsPerPageChange`
|
||||
- `onChangePage` has been renamed to `onPageChange`
|
||||
- Migration guide is here: https://material-table-core.com/docs/breaking-changes
|
||||
- Updated dependencies
|
||||
- @backstage/core-app-api@0.1.6
|
||||
- @backstage/core-plugin-api@0.1.4
|
||||
- @backstage/theme@0.2.9
|
||||
|
||||
## 0.1.15
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/test-utils",
|
||||
"description": "Utilities to test Backstage plugins and apps.",
|
||||
"version": "0.1.15",
|
||||
"version": "0.1.16",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
@@ -29,11 +29,11 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/core-app-api": "^0.1.5",
|
||||
"@backstage/core-plugin-api": "^0.1.3",
|
||||
"@backstage/core-app-api": "^0.1.6",
|
||||
"@backstage/core-plugin-api": "^0.1.4",
|
||||
"@backstage/test-utils-core": "^0.1.1",
|
||||
"@backstage/theme": "^0.2.8",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@backstage/theme": "^0.2.9",
|
||||
"@material-ui/core": "^4.12.2",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^11.2.5",
|
||||
"@testing-library/user-event": "^13.1.8",
|
||||
@@ -46,7 +46,7 @@
|
||||
"zen-observable": "^0.8.15"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.7.5",
|
||||
"@backstage/cli": "^0.7.6",
|
||||
"@types/jest": "^26.0.7",
|
||||
"@types/node": "^14.14.32"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
# @backstage/theme
|
||||
|
||||
## 0.2.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 9d40fcb1e: - Bumping `material-ui/core` version to at least `4.12.2` as they made some breaking changes in later versions which broke `Pagination` of the `Table`.
|
||||
- Switching out `material-table` to `@material-table/core` for support for the later versions of `material-ui/core`
|
||||
- This causes a minor API change to `@backstage/core-components` as the interface for `Table` re-exports the `prop` from the underlying `Table` components.
|
||||
- `onChangeRowsPerPage` has been renamed to `onRowsPerPageChange`
|
||||
- `onChangePage` has been renamed to `onPageChange`
|
||||
- Migration guide is here: https://material-table-core.com/docs/breaking-changes
|
||||
|
||||
## 0.2.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/theme",
|
||||
"description": "material-ui theme for use with Backstage.",
|
||||
"version": "0.2.8",
|
||||
"version": "0.2.9",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
@@ -28,10 +28,10 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@material-ui/core": "^4.11.0"
|
||||
"@material-ui/core": "^4.12.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.7.0"
|
||||
"@backstage/cli": "^0.7.6"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { createMuiTheme } from '@material-ui/core';
|
||||
import { createTheme as createMuiTheme } from '@material-ui/core';
|
||||
import { darken, lighten } from '@material-ui/core/styles/colorManipulator';
|
||||
import { Overrides } from '@material-ui/core/styles/overrides';
|
||||
|
||||
|
||||
@@ -1,5 +1,23 @@
|
||||
# @backstage/plugin-api-docs
|
||||
|
||||
## 0.6.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 9d40fcb1e: - Bumping `material-ui/core` version to at least `4.12.2` as they made some breaking changes in later versions which broke `Pagination` of the `Table`.
|
||||
- Switching out `material-table` to `@material-table/core` for support for the later versions of `material-ui/core`
|
||||
- This causes a minor API change to `@backstage/core-components` as the interface for `Table` re-exports the `prop` from the underlying `Table` components.
|
||||
- `onChangeRowsPerPage` has been renamed to `onRowsPerPageChange`
|
||||
- `onChangePage` has been renamed to `onPageChange`
|
||||
- Migration guide is here: https://material-table-core.com/docs/breaking-changes
|
||||
- bebc09fa8: Add explicit import for `isomorphic-form-data` needed for `swagger-ui-react`
|
||||
- Updated dependencies
|
||||
- @backstage/core-components@0.2.0
|
||||
- @backstage/plugin-catalog@0.6.9
|
||||
- @backstage/plugin-catalog-react@0.4.0
|
||||
- @backstage/core-plugin-api@0.1.4
|
||||
- @backstage/theme@0.2.9
|
||||
|
||||
## 0.6.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-api-docs",
|
||||
"version": "0.6.3",
|
||||
"version": "0.6.4",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -31,13 +31,13 @@
|
||||
"dependencies": {
|
||||
"@asyncapi/react-component": "^0.23.0",
|
||||
"@backstage/catalog-model": "^0.9.0",
|
||||
"@backstage/core-components": "^0.1.6",
|
||||
"@backstage/core-plugin-api": "^0.1.3",
|
||||
"@backstage/plugin-catalog": "^0.6.8",
|
||||
"@backstage/plugin-catalog-react": "^0.3.1",
|
||||
"@backstage/theme": "^0.2.8",
|
||||
"@backstage/core-components": "^0.2.0",
|
||||
"@backstage/core-plugin-api": "^0.1.4",
|
||||
"@backstage/plugin-catalog": "^0.6.9",
|
||||
"@backstage/plugin-catalog-react": "^0.4.0",
|
||||
"@backstage/theme": "^0.2.9",
|
||||
"@material-icons/font": "^1.0.2",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/core": "^4.12.2",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "4.0.0-alpha.45",
|
||||
"@types/react": "^16.9",
|
||||
@@ -52,10 +52,10 @@
|
||||
"swagger-ui-react": "^3.37.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.7.5",
|
||||
"@backstage/core-app-api": "^0.1.5",
|
||||
"@backstage/dev-utils": "^0.2.3",
|
||||
"@backstage/test-utils": "^0.1.15",
|
||||
"@backstage/cli": "^0.7.6",
|
||||
"@backstage/core-app-api": "^0.1.6",
|
||||
"@backstage/dev-utils": "^0.2.4",
|
||||
"@backstage/test-utils": "^0.1.16",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^11.2.5",
|
||||
"@testing-library/user-event": "^13.1.8",
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
# @backstage/plugin-auth-backend
|
||||
|
||||
## 0.3.19
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 4edca1bd0: Allow to configure SAML auth `acceptedClockSkewMs`
|
||||
- b68f2c83c: Added the `disableRefresh` option to the `OAuth2` config
|
||||
- Updated dependencies
|
||||
- @backstage/test-utils@0.1.16
|
||||
- @backstage/catalog-client@0.3.18
|
||||
|
||||
## 0.3.18
|
||||
|
||||
### Patch Changes
|
||||
|
||||
Vendored
+1
@@ -65,6 +65,7 @@ export interface Config {
|
||||
authorizationUrl: string;
|
||||
tokenUrl: string;
|
||||
scope?: string;
|
||||
disableRefresh?: boolean;
|
||||
};
|
||||
};
|
||||
oidc?: {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-auth-backend",
|
||||
"version": "0.3.18",
|
||||
"version": "0.3.19",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -30,11 +30,11 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-common": "^0.8.7",
|
||||
"@backstage/catalog-client": "^0.3.17",
|
||||
"@backstage/catalog-client": "^0.3.18",
|
||||
"@backstage/catalog-model": "^0.9.0",
|
||||
"@backstage/config": "^0.1.5",
|
||||
"@backstage/errors": "^0.1.1",
|
||||
"@backstage/test-utils": "^0.1.15",
|
||||
"@backstage/test-utils": "^0.1.16",
|
||||
"@types/express": "^4.17.6",
|
||||
"@types/passport": "^1.0.3",
|
||||
"compression": "^1.7.4",
|
||||
@@ -68,7 +68,7 @@
|
||||
"yn": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.7.5",
|
||||
"@backstage/cli": "^0.7.6",
|
||||
"@types/body-parser": "^1.19.0",
|
||||
"@types/cookie-parser": "^1.4.2",
|
||||
"@types/express-session": "^1.17.2",
|
||||
|
||||
@@ -177,6 +177,8 @@ export const createOAuth2Provider = (
|
||||
const authorizationUrl = envConfig.getString('authorizationUrl');
|
||||
const tokenUrl = envConfig.getString('tokenUrl');
|
||||
const scope = envConfig.getOptionalString('scope');
|
||||
const disableRefresh =
|
||||
envConfig.getOptionalBoolean('disableRefresh') ?? false;
|
||||
|
||||
const provider = new OAuth2AuthProvider({
|
||||
clientId,
|
||||
@@ -188,7 +190,7 @@ export const createOAuth2Provider = (
|
||||
});
|
||||
|
||||
return OAuthAdapter.fromConfig(globalConfig, provider, {
|
||||
disableRefresh: false,
|
||||
disableRefresh,
|
||||
providerId,
|
||||
tokenIssuer,
|
||||
});
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
# @backstage/plugin-badges
|
||||
|
||||
## 0.2.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 9d40fcb1e: - Bumping `material-ui/core` version to at least `4.12.2` as they made some breaking changes in later versions which broke `Pagination` of the `Table`.
|
||||
- Switching out `material-table` to `@material-table/core` for support for the later versions of `material-ui/core`
|
||||
- This causes a minor API change to `@backstage/core-components` as the interface for `Table` re-exports the `prop` from the underlying `Table` components.
|
||||
- `onChangeRowsPerPage` has been renamed to `onRowsPerPageChange`
|
||||
- `onChangePage` has been renamed to `onPageChange`
|
||||
- Migration guide is here: https://material-table-core.com/docs/breaking-changes
|
||||
- Updated dependencies
|
||||
- @backstage/core-components@0.2.0
|
||||
- @backstage/plugin-catalog-react@0.4.0
|
||||
- @backstage/core-plugin-api@0.1.4
|
||||
- @backstage/theme@0.2.9
|
||||
|
||||
## 0.2.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
+10
-10
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-badges",
|
||||
"version": "0.2.5",
|
||||
"version": "0.2.6",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -21,12 +21,12 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.9.0",
|
||||
"@backstage/core-components": "^0.1.6",
|
||||
"@backstage/core-plugin-api": "^0.1.3",
|
||||
"@backstage/core-components": "^0.2.0",
|
||||
"@backstage/core-plugin-api": "^0.1.4",
|
||||
"@backstage/errors": "^0.1.1",
|
||||
"@backstage/plugin-catalog-react": "^0.3.0",
|
||||
"@backstage/theme": "^0.2.8",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@backstage/plugin-catalog-react": "^0.4.0",
|
||||
"@backstage/theme": "^0.2.9",
|
||||
"@material-ui/core": "^4.12.2",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "4.0.0-alpha.45",
|
||||
"react": "^16.13.1",
|
||||
@@ -35,10 +35,10 @@
|
||||
"react-use": "^17.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.7.5",
|
||||
"@backstage/core-app-api": "^0.1.5",
|
||||
"@backstage/dev-utils": "^0.2.3",
|
||||
"@backstage/test-utils": "^0.1.15",
|
||||
"@backstage/cli": "^0.7.6",
|
||||
"@backstage/core-app-api": "^0.1.6",
|
||||
"@backstage/dev-utils": "^0.2.4",
|
||||
"@backstage/test-utils": "^0.1.16",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^11.2.5",
|
||||
"@testing-library/user-event": "^13.1.8",
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
# @backstage/plugin-bitrise
|
||||
|
||||
## 0.1.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 9d40fcb1e: - Bumping `material-ui/core` version to at least `4.12.2` as they made some breaking changes in later versions which broke `Pagination` of the `Table`.
|
||||
- Switching out `material-table` to `@material-table/core` for support for the later versions of `material-ui/core`
|
||||
- This causes a minor API change to `@backstage/core-components` as the interface for `Table` re-exports the `prop` from the underlying `Table` components.
|
||||
- `onChangeRowsPerPage` has been renamed to `onRowsPerPageChange`
|
||||
- `onChangePage` has been renamed to `onPageChange`
|
||||
- Migration guide is here: https://material-table-core.com/docs/breaking-changes
|
||||
- Updated dependencies
|
||||
- @backstage/core-components@0.2.0
|
||||
- @backstage/plugin-catalog-react@0.4.0
|
||||
- @backstage/core-plugin-api@0.1.4
|
||||
- @backstage/theme@0.2.9
|
||||
|
||||
## 0.1.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-bitrise",
|
||||
"version": "0.1.7",
|
||||
"version": "0.1.8",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -21,11 +21,11 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.9.0",
|
||||
"@backstage/core-components": "^0.1.6",
|
||||
"@backstage/core-plugin-api": "^0.1.3",
|
||||
"@backstage/plugin-catalog-react": "^0.3.0",
|
||||
"@backstage/theme": "^0.2.8",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@backstage/core-components": "^0.2.0",
|
||||
"@backstage/core-plugin-api": "^0.1.4",
|
||||
"@backstage/plugin-catalog-react": "^0.4.0",
|
||||
"@backstage/theme": "^0.2.9",
|
||||
"@material-ui/core": "^4.12.2",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "4.0.0-alpha.45",
|
||||
"cross-fetch": "^3.0.6",
|
||||
@@ -38,10 +38,10 @@
|
||||
"recharts": "^1.8.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.7.5",
|
||||
"@backstage/core-app-api": "^0.1.5",
|
||||
"@backstage/dev-utils": "^0.2.3",
|
||||
"@backstage/test-utils": "^0.1.15",
|
||||
"@backstage/cli": "^0.7.6",
|
||||
"@backstage/core-app-api": "^0.1.6",
|
||||
"@backstage/dev-utils": "^0.2.4",
|
||||
"@backstage/test-utils": "^0.1.16",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^11.2.5",
|
||||
"@testing-library/user-event": "^13.1.8",
|
||||
|
||||
+2
-2
@@ -219,10 +219,10 @@ export const BitriseBuildsTable = ({
|
||||
}}
|
||||
totalCount={builds.value?.paging?.total_item_count || 0}
|
||||
page={page}
|
||||
onChangePage={pageIndex => {
|
||||
onPageChange={pageIndex => {
|
||||
handleChangePage(pageIndex, builds.value?.paging?.next);
|
||||
}}
|
||||
onChangeRowsPerPage={handleChangeRowsPerPage}
|
||||
onRowsPerPageChange={handleChangeRowsPerPage}
|
||||
columns={columns}
|
||||
data={builds.value?.data || []}
|
||||
onSearchChange={(searchTerm: string) =>
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# @backstage/plugin-catalog-backend-module-ldap
|
||||
|
||||
## 0.2.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 2a2a2749b: chore(deps): bump `@types/ldapjs` from 1.0.10 to 2.2.0
|
||||
- Updated dependencies
|
||||
- @backstage/plugin-catalog-backend@0.13.1
|
||||
|
||||
## 0.2.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-catalog-backend-module-ldap",
|
||||
"version": "0.2.1",
|
||||
"version": "0.2.2",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -30,15 +30,15 @@
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.9.0",
|
||||
"@backstage/config": "^0.1.5",
|
||||
"@backstage/plugin-catalog-backend": "^0.13.0",
|
||||
"@backstage/plugin-catalog-backend": "^0.13.1",
|
||||
"@types/ldapjs": "^2.2.0",
|
||||
"ldapjs": "^2.2.0",
|
||||
"lodash": "^4.17.15",
|
||||
"winston": "^3.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.7.4",
|
||||
"@backstage/test-utils": "^0.1.13",
|
||||
"@backstage/cli": "^0.7.6",
|
||||
"@backstage/test-utils": "^0.1.16",
|
||||
"@types/lodash": "^4.14.151",
|
||||
"msw": "^0.29.0"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# @backstage/plugin-catalog-backend
|
||||
|
||||
## 0.13.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 11c370af2: Support filtering entities via property existence. For example you can now query with `/entities?filter=metadata.annotations.blah` to fetch all entities that has the particular property defined.
|
||||
- Updated dependencies
|
||||
- @backstage/catalog-client@0.3.18
|
||||
|
||||
## 0.13.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -636,6 +636,7 @@ export type EntitiesCatalog = {
|
||||
export type EntitiesSearchFilter = {
|
||||
key: string;
|
||||
matchValueIn?: string[];
|
||||
matchValueExists?: boolean;
|
||||
};
|
||||
|
||||
// Warning: (ae-missing-release-tag) "entity" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
@@ -1077,16 +1078,16 @@ export class UrlReaderProcessor implements CatalogProcessor {
|
||||
// src/catalog/types.d.ts:44:8 - (tsdoc-param-tag-with-invalid-name) The @param block should be followed by a valid parameter name: The identifier cannot non-word characters
|
||||
// src/catalog/types.d.ts:45:8 - (tsdoc-param-tag-with-invalid-name) The @param block should be followed by a valid parameter name: The identifier cannot non-word characters
|
||||
// src/catalog/types.d.ts:75:5 - (ae-forgotten-export) The symbol "LocationUpdateLogEvent" needs to be exported by the entry point index.d.ts
|
||||
// src/database/types.d.ts:121:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen
|
||||
// src/database/types.d.ts:127:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen
|
||||
// src/database/types.d.ts:128:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen
|
||||
// src/database/types.d.ts:142:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen
|
||||
// src/database/types.d.ts:143:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen
|
||||
// src/database/types.d.ts:144:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen
|
||||
// src/database/types.d.ts:125:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen
|
||||
// src/database/types.d.ts:131:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen
|
||||
// src/database/types.d.ts:132:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen
|
||||
// src/database/types.d.ts:146:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen
|
||||
// src/database/types.d.ts:159:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen
|
||||
// src/database/types.d.ts:160:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen
|
||||
// src/database/types.d.ts:161:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen
|
||||
// src/database/types.d.ts:147:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen
|
||||
// src/database/types.d.ts:148:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen
|
||||
// src/database/types.d.ts:150:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen
|
||||
// src/database/types.d.ts:163:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen
|
||||
// src/database/types.d.ts:164:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen
|
||||
// src/database/types.d.ts:165:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen
|
||||
// src/ingestion/processors/GithubMultiOrgReaderProcessor.d.ts:23:9 - (ae-forgotten-export) The symbol "GithubMultiOrgConfig" needs to be exported by the entry point index.d.ts
|
||||
// src/ingestion/processors/types.d.ts:7:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen
|
||||
// src/ingestion/processors/types.d.ts:8:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-catalog-backend",
|
||||
"version": "0.13.0",
|
||||
"version": "0.13.1",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -30,7 +30,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-common": "^0.8.6",
|
||||
"@backstage/catalog-client": "^0.3.16",
|
||||
"@backstage/catalog-client": "^0.3.18",
|
||||
"@backstage/catalog-model": "^0.9.0",
|
||||
"@backstage/config": "^0.1.5",
|
||||
"@backstage/errors": "^0.1.1",
|
||||
@@ -62,8 +62,8 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/backend-test-utils": "^0.1.4",
|
||||
"@backstage/cli": "^0.7.4",
|
||||
"@backstage/test-utils": "^0.1.14",
|
||||
"@backstage/cli": "^0.7.6",
|
||||
"@backstage/test-utils": "^0.1.16",
|
||||
"@types/core-js": "^2.5.4",
|
||||
"@types/git-url-parse": "^9.0.0",
|
||||
"@types/lodash": "^4.14.151",
|
||||
|
||||
@@ -491,6 +491,98 @@ describe('CommonDatabase', () => {
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it('can get all specific entities for matching existence filters', async () => {
|
||||
const entities: Entity[] = [
|
||||
{
|
||||
apiVersion: 'A',
|
||||
kind: 'K1',
|
||||
metadata: {
|
||||
name: 'N',
|
||||
annotations: {
|
||||
foo: 'bar',
|
||||
},
|
||||
},
|
||||
spec: { c: 'SOME' },
|
||||
},
|
||||
{
|
||||
apiVersion: 'a',
|
||||
kind: 'k2',
|
||||
metadata: {
|
||||
name: 'N',
|
||||
annotations: {
|
||||
foo: 'bar',
|
||||
},
|
||||
},
|
||||
spec: { c: 'Some' },
|
||||
},
|
||||
{
|
||||
apiVersion: 'a',
|
||||
kind: 'k3',
|
||||
metadata: { name: 'n' },
|
||||
spec: { c: 'somE' },
|
||||
},
|
||||
];
|
||||
|
||||
await db.transaction(async tx => {
|
||||
await db.addEntities(
|
||||
tx,
|
||||
entities.map(entity => ({ entity, relations: [] })),
|
||||
);
|
||||
});
|
||||
|
||||
const existRows = await db.transaction(async tx =>
|
||||
db.entities(tx, {
|
||||
filter: {
|
||||
anyOf: [
|
||||
{
|
||||
allOf: [
|
||||
{ key: 'metadata.annotations.foo', matchValueExists: true },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
expect(existRows.entities.length).toEqual(2);
|
||||
expect(existRows.entities).toEqual(
|
||||
expect.arrayContaining([
|
||||
{
|
||||
locationId: undefined,
|
||||
entity: expect.objectContaining({ kind: 'K1' }),
|
||||
},
|
||||
{
|
||||
locationId: undefined,
|
||||
entity: expect.objectContaining({ kind: 'k2' }),
|
||||
},
|
||||
]),
|
||||
);
|
||||
|
||||
const nonExistRows = await db.transaction(async tx =>
|
||||
db.entities(tx, {
|
||||
filter: {
|
||||
anyOf: [
|
||||
{
|
||||
allOf: [
|
||||
{ key: 'metadata.annotations.foo', matchValueExists: false },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
expect(nonExistRows.entities.length).toEqual(1);
|
||||
expect(nonExistRows.entities).toEqual(
|
||||
expect.arrayContaining([
|
||||
{
|
||||
locationId: undefined,
|
||||
entity: expect.objectContaining({ kind: 'k3' }),
|
||||
},
|
||||
]),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setRelations', () => {
|
||||
|
||||
@@ -218,7 +218,11 @@ export class CommonDatabase implements Database {
|
||||
|
||||
for (const singleFilter of request?.filter?.anyOf ?? []) {
|
||||
entitiesQuery = entitiesQuery.orWhere(function singleFilterFn() {
|
||||
for (const { key, matchValueIn } of singleFilter.allOf) {
|
||||
for (const {
|
||||
key,
|
||||
matchValueIn,
|
||||
matchValueExists,
|
||||
} of singleFilter.allOf) {
|
||||
// NOTE(freben): This used to be a set of OUTER JOIN, which may seem to
|
||||
// make a lot of sense. However, it had abysmal performance on sqlite
|
||||
// when datasets grew large, so we're using IN instead.
|
||||
@@ -226,7 +230,7 @@ export class CommonDatabase implements Database {
|
||||
.select('entity_id')
|
||||
.where(function keyFilter() {
|
||||
this.andWhere({ key: key.toLowerCase() });
|
||||
if (matchValueIn) {
|
||||
if (matchValueExists !== false && matchValueIn) {
|
||||
if (matchValueIn.length === 1) {
|
||||
this.andWhere({ value: matchValueIn[0].toLowerCase() });
|
||||
} else if (matchValueIn.length > 1) {
|
||||
@@ -238,7 +242,12 @@ export class CommonDatabase implements Database {
|
||||
}
|
||||
}
|
||||
});
|
||||
this.andWhere('id', 'in', matchQuery);
|
||||
// Explicitly evaluate matchValueExists as a boolean since it may be undefined
|
||||
this.andWhere(
|
||||
'id',
|
||||
matchValueExists === false ? 'not in' : 'in',
|
||||
matchQuery,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -118,6 +118,11 @@ export type EntitiesSearchFilter = {
|
||||
* case insensitive.
|
||||
*/
|
||||
matchValueIn?: string[];
|
||||
|
||||
/**
|
||||
* Match on existence of key.
|
||||
*/
|
||||
matchValueExists?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -78,7 +78,11 @@ export class NextEntitiesCatalog implements EntitiesCatalog {
|
||||
|
||||
for (const singleFilter of request?.filter?.anyOf ?? []) {
|
||||
entitiesQuery = entitiesQuery.orWhere(function singleFilterFn() {
|
||||
for (const { key, matchValueIn } of singleFilter.allOf) {
|
||||
for (const {
|
||||
key,
|
||||
matchValueIn,
|
||||
matchValueExists,
|
||||
} of singleFilter.allOf) {
|
||||
// NOTE(freben): This used to be a set of OUTER JOIN, which may seem to
|
||||
// make a lot of sense. However, it had abysmal performance on sqlite
|
||||
// when datasets grew large, so we're using IN instead.
|
||||
@@ -86,7 +90,7 @@ export class NextEntitiesCatalog implements EntitiesCatalog {
|
||||
.select('entity_id')
|
||||
.where(function keyFilter() {
|
||||
this.andWhere({ key: key.toLowerCase() });
|
||||
if (matchValueIn) {
|
||||
if (matchValueExists !== false && matchValueIn) {
|
||||
if (matchValueIn.length === 1) {
|
||||
this.andWhere({ value: matchValueIn[0].toLowerCase() });
|
||||
} else if (matchValueIn.length > 1) {
|
||||
@@ -98,7 +102,12 @@ export class NextEntitiesCatalog implements EntitiesCatalog {
|
||||
}
|
||||
}
|
||||
});
|
||||
this.andWhere('entity_id', 'in', matchQuery);
|
||||
// Explicitly evaluate matchValueExists as a boolean since it may be undefined
|
||||
this.andWhere(
|
||||
'entity_id',
|
||||
matchValueExists === false ? 'not in' : 'in',
|
||||
matchQuery,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -69,9 +69,11 @@ describe('parseEntityFilterParams', () => {
|
||||
describe('parseEntityFilterString', () => {
|
||||
it('works for the happy path', () => {
|
||||
expect(parseEntityFilterString('')).toBeUndefined();
|
||||
expect(parseEntityFilterString('a=1,b=2,a=3')).toEqual([
|
||||
expect(parseEntityFilterString('a=1,b=2,a=3,c,d=')).toEqual([
|
||||
{ key: 'a', matchValueIn: ['1', '3'] },
|
||||
{ key: 'b', matchValueIn: ['2'] },
|
||||
{ key: 'c', matchValueExists: true },
|
||||
{ key: 'd', matchValueIn: [''] },
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -83,17 +85,11 @@ describe('parseEntityFilterString', () => {
|
||||
});
|
||||
|
||||
it('rejects malformed strings', () => {
|
||||
expect(() => parseEntityFilterString('x=2,a=')).toThrow(
|
||||
"Invalid filter, 'a=' is not a valid statement (expected a string on the form a=b)",
|
||||
);
|
||||
expect(() => parseEntityFilterString('x=2,=a')).toThrow(
|
||||
"Invalid filter, '=a' is not a valid statement (expected a string on the form a=b)",
|
||||
"Invalid filter, '=a' is not a valid statement (expected a string on the form a=b or a= or a)",
|
||||
);
|
||||
expect(() => parseEntityFilterString('x=2,=')).toThrow(
|
||||
"Invalid filter, '=' is not a valid statement (expected a string on the form a=b)",
|
||||
);
|
||||
expect(() => parseEntityFilterString('x=2,a')).toThrow(
|
||||
"Invalid filter, 'a' is not a valid statement (expected a string on the form a=b)",
|
||||
"Invalid filter, '=' is not a valid statement (expected a string on the form a=b or a= or a)",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -61,25 +61,26 @@ export function parseEntityFilterString(
|
||||
|
||||
for (const statement of statements) {
|
||||
const equalsIndex = statement.indexOf('=');
|
||||
if (equalsIndex < 1) {
|
||||
throw new InputError(
|
||||
`Invalid filter, '${statement}' is not a valid statement (expected a string on the form a=b)`,
|
||||
);
|
||||
}
|
||||
|
||||
const key = statement.substr(0, equalsIndex).trim();
|
||||
const value = statement.substr(equalsIndex + 1).trim();
|
||||
if (!key || !value) {
|
||||
const key =
|
||||
equalsIndex === -1 ? statement : statement.substr(0, equalsIndex).trim();
|
||||
const value =
|
||||
equalsIndex === -1 ? undefined : statement.substr(equalsIndex + 1).trim();
|
||||
if (!key) {
|
||||
throw new InputError(
|
||||
`Invalid filter, '${statement}' is not a valid statement (expected a string on the form a=b)`,
|
||||
`Invalid filter, '${statement}' is not a valid statement (expected a string on the form a=b or a= or a)`,
|
||||
);
|
||||
}
|
||||
|
||||
const f =
|
||||
key in filtersByKey
|
||||
? filtersByKey[key]
|
||||
: (filtersByKey[key] = { key, matchValueIn: [] });
|
||||
f.matchValueIn!.push(value);
|
||||
key in filtersByKey ? filtersByKey[key] : (filtersByKey[key] = { key });
|
||||
|
||||
if (value === undefined) {
|
||||
f.matchValueExists = true;
|
||||
} else {
|
||||
f.matchValueIn = f.matchValueIn || [];
|
||||
f.matchValueIn.push(value);
|
||||
}
|
||||
}
|
||||
|
||||
return Object.values(filtersByKey);
|
||||
|
||||
@@ -1,5 +1,23 @@
|
||||
# @backstage/plugin-catalog-import
|
||||
|
||||
## 0.5.15
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 9d40fcb1e: - Bumping `material-ui/core` version to at least `4.12.2` as they made some breaking changes in later versions which broke `Pagination` of the `Table`.
|
||||
- Switching out `material-table` to `@material-table/core` for support for the later versions of `material-ui/core`
|
||||
- This causes a minor API change to `@backstage/core-components` as the interface for `Table` re-exports the `prop` from the underlying `Table` components.
|
||||
- `onChangeRowsPerPage` has been renamed to `onRowsPerPageChange`
|
||||
- `onChangePage` has been renamed to `onPageChange`
|
||||
- Migration guide is here: https://material-table-core.com/docs/breaking-changes
|
||||
- Updated dependencies
|
||||
- @backstage/core-components@0.2.0
|
||||
- @backstage/plugin-catalog-react@0.4.0
|
||||
- @backstage/core-plugin-api@0.1.4
|
||||
- @backstage/integration-react@0.1.5
|
||||
- @backstage/theme@0.2.9
|
||||
- @backstage/catalog-client@0.3.18
|
||||
|
||||
## 0.5.14
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-catalog-import",
|
||||
"version": "0.5.14",
|
||||
"version": "0.5.15",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -30,15 +30,15 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-client": "^0.3.17",
|
||||
"@backstage/catalog-client": "^0.3.18",
|
||||
"@backstage/catalog-model": "^0.9.0",
|
||||
"@backstage/core-components": "^0.1.6",
|
||||
"@backstage/core-plugin-api": "^0.1.3",
|
||||
"@backstage/core-components": "^0.2.0",
|
||||
"@backstage/core-plugin-api": "^0.1.4",
|
||||
"@backstage/integration": "^0.5.8",
|
||||
"@backstage/integration-react": "^0.1.4",
|
||||
"@backstage/plugin-catalog-react": "^0.3.1",
|
||||
"@backstage/theme": "^0.2.8",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@backstage/integration-react": "^0.1.5",
|
||||
"@backstage/plugin-catalog-react": "^0.4.0",
|
||||
"@backstage/theme": "^0.2.9",
|
||||
"@material-ui/core": "^4.12.2",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "4.0.0-alpha.45",
|
||||
"@octokit/rest": "^18.5.3",
|
||||
@@ -54,10 +54,10 @@
|
||||
"yaml": "^1.10.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.7.5",
|
||||
"@backstage/core-app-api": "^0.1.5",
|
||||
"@backstage/dev-utils": "^0.2.3",
|
||||
"@backstage/test-utils": "^0.1.15",
|
||||
"@backstage/cli": "^0.7.6",
|
||||
"@backstage/core-app-api": "^0.1.6",
|
||||
"@backstage/dev-utils": "^0.2.4",
|
||||
"@backstage/test-utils": "^0.1.16",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^11.2.5",
|
||||
"@testing-library/react-hooks": "^3.3.0",
|
||||
|
||||
@@ -1,5 +1,45 @@
|
||||
# @backstage/plugin-catalog-react
|
||||
|
||||
## 0.4.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- c5cb55803: Introduce the `useEntityOwnership` hook, which implements the full new ownership model.
|
||||
|
||||
This also means a breaking change to the interface of `UserListFilter`. It no longer
|
||||
accepts a user entity as input, but rather a function that checks ownership of an
|
||||
entity. This function is taken from the above mentioned hook output. So if you are
|
||||
instantiating the filter yourself, you will change from something like
|
||||
|
||||
```ts
|
||||
const { entity } = useOwnUser();
|
||||
const filter = new UserListFilter('owned', user, ...);
|
||||
```
|
||||
|
||||
to
|
||||
|
||||
```ts
|
||||
const { isOwnedEntity } = useEntityOwnership();
|
||||
const filter = new UserListFilter('owned', isOwnedEntity, ...);
|
||||
```
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 19d9995b6: Improve accessibility of core & catalog components by adjusting them with non-breaking changes.
|
||||
- 11c370af2: Export `CATALOG_FILTER_EXISTS` symbol
|
||||
- 9d40fcb1e: - Bumping `material-ui/core` version to at least `4.12.2` as they made some breaking changes in later versions which broke `Pagination` of the `Table`.
|
||||
- Switching out `material-table` to `@material-table/core` for support for the later versions of `material-ui/core`
|
||||
- This causes a minor API change to `@backstage/core-components` as the interface for `Table` re-exports the `prop` from the underlying `Table` components.
|
||||
- `onChangeRowsPerPage` has been renamed to `onRowsPerPageChange`
|
||||
- `onChangePage` has been renamed to `onPageChange`
|
||||
- Migration guide is here: https://material-table-core.com/docs/breaking-changes
|
||||
- 043a4238f: Fix `EntityListProvider` to not update url if unmounted
|
||||
- Updated dependencies
|
||||
- @backstage/core-components@0.2.0
|
||||
- @backstage/core-app-api@0.1.6
|
||||
- @backstage/core-plugin-api@0.1.4
|
||||
- @backstage/catalog-client@0.3.18
|
||||
|
||||
## 0.3.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
import { ApiRef } from '@backstage/core-plugin-api';
|
||||
import { AsyncState } from 'react-use/lib/useAsync';
|
||||
import { CATALOG_FILTER_EXISTS } from '@backstage/catalog-client';
|
||||
import { CatalogApi } from '@backstage/catalog-client';
|
||||
import { ComponentEntity } from '@backstage/catalog-model';
|
||||
import { Context } from 'react';
|
||||
@@ -22,6 +23,8 @@ import { SystemEntity } from '@backstage/catalog-model';
|
||||
import { TableColumn } from '@backstage/core-components';
|
||||
import { UserEntity } from '@backstage/catalog-model';
|
||||
|
||||
export { CATALOG_FILTER_EXISTS };
|
||||
|
||||
export { CatalogApi };
|
||||
|
||||
// Warning: (ae-missing-release-tag) "catalogApiRef" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
@@ -721,6 +724,14 @@ export function useEntityListProvider<
|
||||
EntityFilters extends DefaultEntityFilters = DefaultEntityFilters
|
||||
>(): EntityListContextProps<EntityFilters>;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "useEntityOwnership" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public
|
||||
export function useEntityOwnership(): {
|
||||
loading: boolean;
|
||||
isOwnedEntity: (entity: Entity | EntityName) => boolean;
|
||||
};
|
||||
|
||||
// Warning: (ae-forgotten-export) The symbol "EntityTypeReturn" needs to be exported by the entry point index.d.ts
|
||||
// Warning: (ae-missing-release-tag) "useEntityTypeFilter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
@@ -756,18 +767,18 @@ export function useRelatedEntities(
|
||||
export class UserListFilter implements EntityFilter {
|
||||
constructor(
|
||||
value: UserListFilterKind,
|
||||
user: UserEntity | undefined,
|
||||
isOwnedEntity: (entity: Entity) => boolean,
|
||||
isStarredEntity: (entity: Entity) => boolean,
|
||||
);
|
||||
// (undocumented)
|
||||
filterEntity(entity: Entity): boolean;
|
||||
// (undocumented)
|
||||
readonly isOwnedEntity: (entity: Entity) => boolean;
|
||||
// (undocumented)
|
||||
readonly isStarredEntity: (entity: Entity) => boolean;
|
||||
// (undocumented)
|
||||
toQueryValue(): string;
|
||||
// (undocumented)
|
||||
readonly user: UserEntity | undefined;
|
||||
// (undocumented)
|
||||
readonly value: UserListFilterKind;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-catalog-react",
|
||||
"version": "0.3.1",
|
||||
"version": "0.4.0",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -28,16 +28,17 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-client": "^0.3.17",
|
||||
"@backstage/catalog-client": "^0.3.18",
|
||||
"@backstage/catalog-model": "^0.9.0",
|
||||
"@backstage/core-app-api": "^0.1.5",
|
||||
"@backstage/core-components": "^0.1.6",
|
||||
"@backstage/core-plugin-api": "^0.1.3",
|
||||
"@backstage/core-app-api": "^0.1.6",
|
||||
"@backstage/core-components": "^0.2.0",
|
||||
"@backstage/core-plugin-api": "^0.1.4",
|
||||
"@backstage/integration": "^0.5.8",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/core": "^4.12.2",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "4.0.0-alpha.45",
|
||||
"@types/react": "^16.9",
|
||||
"jwt-decode": "^3.1.0",
|
||||
"lodash": "^4.17.15",
|
||||
"qs": "^6.9.4",
|
||||
"react": "^16.13.1",
|
||||
@@ -46,14 +47,15 @@
|
||||
"react-use": "^17.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.7.5",
|
||||
"@backstage/dev-utils": "^0.2.3",
|
||||
"@backstage/test-utils": "^0.1.15",
|
||||
"@backstage/cli": "^0.7.6",
|
||||
"@backstage/dev-utils": "^0.2.4",
|
||||
"@backstage/test-utils": "^0.1.16",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^11.2.5",
|
||||
"@testing-library/react-hooks": "^3.3.0",
|
||||
"@testing-library/user-event": "^13.1.8",
|
||||
"@types/jest": "^26.0.7",
|
||||
"@types/jwt-decode": "^3.1.0",
|
||||
"@types/node": "^14.14.32",
|
||||
"cross-fetch": "^3.0.6",
|
||||
"msw": "^0.29.0",
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { fireEvent, render } from '@testing-library/react';
|
||||
import { fireEvent, render, waitFor } from '@testing-library/react';
|
||||
import {
|
||||
Entity,
|
||||
RELATION_OWNED_BY,
|
||||
@@ -58,7 +58,8 @@ const mockCatalogApi = {
|
||||
} as Partial<CatalogApi>;
|
||||
|
||||
const mockIdentityApi = {
|
||||
getUserId: () => '',
|
||||
getUserId: () => 'testUser',
|
||||
getIdToken: async () => undefined,
|
||||
} as Partial<IdentityApi>;
|
||||
|
||||
const apis = ApiRegistry.from([
|
||||
@@ -68,6 +69,9 @@ const apis = ApiRegistry.from([
|
||||
[storageApiRef, MockStorageApi.create()],
|
||||
]);
|
||||
|
||||
const mockIsOwnedEntity = (entity: Entity) =>
|
||||
entity.metadata.name === 'component-1';
|
||||
|
||||
const mockIsStarredEntity = (entity: Entity) =>
|
||||
entity.metadata.name === 'component-3';
|
||||
|
||||
@@ -75,7 +79,9 @@ jest.mock('../../hooks', () => {
|
||||
const actual = jest.requireActual('../../hooks');
|
||||
return {
|
||||
...actual,
|
||||
useOwnUser: () => ({ value: mockUser }),
|
||||
useEntityOwnership: () => ({
|
||||
isOwnedEntity: mockIsOwnedEntity,
|
||||
}),
|
||||
useStarredEntities: () => ({
|
||||
isStarredEntity: mockIsStarredEntity,
|
||||
}),
|
||||
@@ -161,7 +167,7 @@ describe('<UserListPicker />', () => {
|
||||
).toEqual(['Owned', 'Starred', 'All']);
|
||||
});
|
||||
|
||||
it('includes counts alongside each filter', () => {
|
||||
it('includes counts alongside each filter', async () => {
|
||||
const { getAllByRole } = render(
|
||||
<ApiProvider apis={apis}>
|
||||
<MockEntityListContextProvider value={{ backendEntities }}>
|
||||
@@ -172,14 +178,16 @@ describe('<UserListPicker />', () => {
|
||||
|
||||
// Material UI renders ListItemSecondaryActions outside the
|
||||
// menuitem itself, so we pick off the next sibling.
|
||||
expect(
|
||||
getAllByRole('menuitem').map(
|
||||
({ nextSibling }) => nextSibling?.textContent,
|
||||
),
|
||||
).toEqual(['2', '1', '4']);
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
getAllByRole('menuitem').map(
|
||||
({ nextSibling }) => nextSibling?.textContent,
|
||||
),
|
||||
).toEqual(['1', '1', '4']);
|
||||
});
|
||||
});
|
||||
|
||||
it('respects other frontend filters in counts', () => {
|
||||
it('respects other frontend filters in counts', async () => {
|
||||
const { getAllByRole } = render(
|
||||
<ApiProvider apis={apis}>
|
||||
<MockEntityListContextProvider
|
||||
@@ -193,11 +201,13 @@ describe('<UserListPicker />', () => {
|
||||
</ApiProvider>,
|
||||
);
|
||||
|
||||
expect(
|
||||
getAllByRole('menuitem').map(
|
||||
({ nextSibling }) => nextSibling?.textContent,
|
||||
),
|
||||
).toEqual(['1', '0', '2']);
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
getAllByRole('menuitem').map(
|
||||
({ nextSibling }) => nextSibling?.textContent,
|
||||
),
|
||||
).toEqual(['1', '0', '2']);
|
||||
});
|
||||
});
|
||||
|
||||
it('respects the query parameter filter value', () => {
|
||||
@@ -214,7 +224,7 @@ describe('<UserListPicker />', () => {
|
||||
);
|
||||
|
||||
expect(updateFilters).toHaveBeenLastCalledWith({
|
||||
user: new UserListFilter('owned', mockUser, mockIsStarredEntity),
|
||||
user: new UserListFilter('owned', mockIsOwnedEntity, mockIsStarredEntity),
|
||||
});
|
||||
});
|
||||
|
||||
@@ -233,7 +243,11 @@ describe('<UserListPicker />', () => {
|
||||
fireEvent.click(getByText('Starred'));
|
||||
|
||||
expect(updateFilters).toHaveBeenLastCalledWith({
|
||||
user: new UserListFilter('starred', mockUser, mockIsStarredEntity),
|
||||
user: new UserListFilter(
|
||||
'starred',
|
||||
mockIsOwnedEntity,
|
||||
mockIsStarredEntity,
|
||||
),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,15 +14,11 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { Fragment, useEffect, useMemo, useState } from 'react';
|
||||
import { compact } from 'lodash';
|
||||
import { UserListFilterKind } from '../../types';
|
||||
import { UserListFilter } from '../../filters';
|
||||
import {
|
||||
useEntityListProvider,
|
||||
useOwnUser,
|
||||
useStarredEntities,
|
||||
} from '../../hooks';
|
||||
configApiRef,
|
||||
IconComponent,
|
||||
useApi,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import {
|
||||
Card,
|
||||
List,
|
||||
@@ -36,12 +32,16 @@ import {
|
||||
} from '@material-ui/core';
|
||||
import SettingsIcon from '@material-ui/icons/Settings';
|
||||
import StarIcon from '@material-ui/icons/Star';
|
||||
import { reduceEntityFilters } from '../../utils';
|
||||
import { compact } from 'lodash';
|
||||
import React, { Fragment, useEffect, useMemo, useState } from 'react';
|
||||
import { UserListFilter } from '../../filters';
|
||||
import {
|
||||
configApiRef,
|
||||
IconComponent,
|
||||
useApi,
|
||||
} from '@backstage/core-plugin-api';
|
||||
useEntityListProvider,
|
||||
useStarredEntities,
|
||||
useEntityOwnership,
|
||||
} from '../../hooks';
|
||||
import { UserListFilterKind } from '../../types';
|
||||
import { reduceEntityFilters } from '../../utils';
|
||||
|
||||
const useStyles = makeStyles<Theme>(theme => ({
|
||||
root: {
|
||||
@@ -136,20 +136,20 @@ export const UserListPicker = ({
|
||||
queryParameters,
|
||||
} = useEntityListProvider();
|
||||
|
||||
const { value: user } = useOwnUser();
|
||||
const { isStarredEntity } = useStarredEntities();
|
||||
const { isOwnedEntity } = useEntityOwnership();
|
||||
const [selectedUserFilter, setSelectedUserFilter] = useState(
|
||||
[queryParameters.user].flat()[0] ?? initialFilter,
|
||||
);
|
||||
|
||||
// Static filters; used for generating counts of potentially unselected kinds
|
||||
const ownedFilter = useMemo(
|
||||
() => new UserListFilter('owned', user, isStarredEntity),
|
||||
[user, isStarredEntity],
|
||||
() => new UserListFilter('owned', isOwnedEntity, isStarredEntity),
|
||||
[isOwnedEntity, isStarredEntity],
|
||||
);
|
||||
const starredFilter = useMemo(
|
||||
() => new UserListFilter('starred', user, isStarredEntity),
|
||||
[user, isStarredEntity],
|
||||
() => new UserListFilter('starred', isOwnedEntity, isStarredEntity),
|
||||
[isOwnedEntity, isStarredEntity],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -157,12 +157,12 @@ export const UserListPicker = ({
|
||||
user: selectedUserFilter
|
||||
? new UserListFilter(
|
||||
selectedUserFilter as UserListFilterKind,
|
||||
user,
|
||||
isOwnedEntity,
|
||||
isStarredEntity,
|
||||
)
|
||||
: undefined,
|
||||
});
|
||||
}, [selectedUserFilter, user, isStarredEntity, updateFilters]);
|
||||
}, [selectedUserFilter, isOwnedEntity, isStarredEntity, updateFilters]);
|
||||
|
||||
// To show proper counts for each section, apply all other frontend filters _except_ the user
|
||||
// filter that's controlled by this picker.
|
||||
|
||||
@@ -14,14 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
Entity,
|
||||
UserEntity,
|
||||
RELATION_OWNED_BY,
|
||||
} from '@backstage/catalog-model';
|
||||
import { EntityFilter, UserListFilterKind } from './types';
|
||||
import { getEntityRelations, isOwnerOf } from './utils';
|
||||
import { Entity, RELATION_OWNED_BY } from '@backstage/catalog-model';
|
||||
import { formatEntityRefTitle } from './components/EntityRefLink';
|
||||
import { EntityFilter, UserListFilterKind } from './types';
|
||||
import { getEntityRelations } from './utils';
|
||||
|
||||
export class EntityKindFilter implements EntityFilter {
|
||||
constructor(readonly value: string) {}
|
||||
@@ -116,14 +112,14 @@ export class EntityLifecycleFilter implements EntityFilter {
|
||||
export class UserListFilter implements EntityFilter {
|
||||
constructor(
|
||||
readonly value: UserListFilterKind,
|
||||
readonly user: UserEntity | undefined,
|
||||
readonly isOwnedEntity: (entity: Entity) => boolean,
|
||||
readonly isStarredEntity: (entity: Entity) => boolean,
|
||||
) {}
|
||||
|
||||
filterEntity(entity: Entity): boolean {
|
||||
switch (this.value) {
|
||||
case 'owned':
|
||||
return this.user !== undefined && isOwnerOf(this.user, entity);
|
||||
return this.isOwnedEntity(entity);
|
||||
case 'starred':
|
||||
return this.isStarredEntity(entity);
|
||||
default:
|
||||
|
||||
@@ -25,3 +25,4 @@ export { useEntityTypeFilter } from './useEntityTypeFilter';
|
||||
export { useOwnUser } from './useOwnUser';
|
||||
export { useRelatedEntities } from './useRelatedEntities';
|
||||
export { useStarredEntities } from './useStarredEntities';
|
||||
export { useEntityOwnership } from './useEntityOwnership';
|
||||
|
||||
@@ -20,7 +20,7 @@ import { MemoryRouter as Router } from 'react-router-dom';
|
||||
import { act, renderHook } from '@testing-library/react-hooks';
|
||||
import { MockStorageApi } from '@backstage/test-utils';
|
||||
import { CatalogApi } from '@backstage/catalog-client';
|
||||
import { Entity, UserEntity } from '@backstage/catalog-model';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import {
|
||||
EntityListProvider,
|
||||
useEntityListProvider,
|
||||
@@ -39,17 +39,6 @@ import {
|
||||
storageApiRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
|
||||
const mockUser: UserEntity = {
|
||||
apiVersion: 'backstage.io/v1beta1',
|
||||
kind: 'User',
|
||||
metadata: {
|
||||
name: 'guest',
|
||||
},
|
||||
spec: {
|
||||
memberOf: [],
|
||||
},
|
||||
};
|
||||
|
||||
const entities: Entity[] = [
|
||||
{
|
||||
apiVersion: '1',
|
||||
@@ -81,13 +70,12 @@ const mockConfigApi = {
|
||||
getOptionalString: () => '',
|
||||
} as Partial<ConfigApi>;
|
||||
const mockIdentityApi: Partial<IdentityApi> = {
|
||||
getUserId: () => 'guest@example.com',
|
||||
getUserId: () => 'guest',
|
||||
getIdToken: async () => undefined,
|
||||
};
|
||||
const mockCatalogApi: Partial<CatalogApi> = {
|
||||
getEntities: jest
|
||||
.fn()
|
||||
.mockImplementation(() => Promise.resolve({ items: entities })),
|
||||
getEntityByName: () => Promise.resolve(mockUser),
|
||||
getEntities: jest.fn().mockImplementation(async () => ({ items: entities })),
|
||||
getEntityByName: async () => undefined,
|
||||
};
|
||||
const apis = ApiRegistry.from([
|
||||
[configApiRef, mockConfigApi],
|
||||
@@ -117,7 +105,7 @@ const wrapper = ({
|
||||
);
|
||||
};
|
||||
|
||||
describe('<EntityListProvider/>', () => {
|
||||
describe('<EntityListProvider />', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
@@ -171,18 +159,26 @@ describe('<EntityListProvider/>', () => {
|
||||
const { result, waitFor } = renderHook(() => useEntityListProvider(), {
|
||||
wrapper,
|
||||
});
|
||||
await waitFor(() => !!result.current.entities.length);
|
||||
expect(result.current.entities.length).toBe(2);
|
||||
expect(mockCatalogApi.getEntities).toHaveBeenCalledTimes(1);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.entities.length).toBe(2);
|
||||
expect(mockCatalogApi.getEntities).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
act(() =>
|
||||
result.current.updateFilters({
|
||||
user: new UserListFilter('owned', mockUser, () => true),
|
||||
user: new UserListFilter(
|
||||
'owned',
|
||||
entity => entity.metadata.name === 'component-1',
|
||||
() => true,
|
||||
),
|
||||
}),
|
||||
);
|
||||
await waitFor(() => result.current.entities.length !== 2);
|
||||
expect(mockCatalogApi.getEntities).toHaveBeenCalledTimes(1);
|
||||
expect(result.current.entities.length).toBe(1);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.entities.length).toBe(1);
|
||||
expect(mockCatalogApi.getEntities).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
it('debounces multiple filter changes', async () => {
|
||||
@@ -205,7 +201,7 @@ describe('<EntityListProvider/>', () => {
|
||||
});
|
||||
|
||||
it('returns an error on catalogApi failure', async () => {
|
||||
const { result, waitForNextUpdate, waitForValueToChange } = renderHook(
|
||||
const { result, waitForValueToChange, waitFor } = renderHook(
|
||||
() => useEntityListProvider(),
|
||||
{
|
||||
wrapper,
|
||||
@@ -218,7 +214,8 @@ describe('<EntityListProvider/>', () => {
|
||||
act(() => {
|
||||
result.current.updateFilters({ kind: new EntityKindFilter('api') });
|
||||
});
|
||||
await waitForNextUpdate();
|
||||
expect(result.current.error).toBeDefined();
|
||||
await waitFor(() => {
|
||||
expect(result.current.error).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,255 @@
|
||||
/*
|
||||
* 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 { CatalogApi } from '@backstage/catalog-client';
|
||||
import {
|
||||
ComponentEntity,
|
||||
RELATION_MEMBER_OF,
|
||||
RELATION_OWNED_BY,
|
||||
UserEntity,
|
||||
} from '@backstage/catalog-model';
|
||||
import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
|
||||
import { IdentityApi, identityApiRef } from '@backstage/core-plugin-api';
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import React from 'react';
|
||||
import { catalogApiRef } from '../api';
|
||||
import {
|
||||
loadCatalogOwnerRefs,
|
||||
loadIdentityOwnerRefs,
|
||||
useEntityOwnership,
|
||||
} from './useEntityOwnership';
|
||||
|
||||
describe('useEntityOwnership', () => {
|
||||
type MockIdentityApi = jest.Mocked<
|
||||
Pick<IdentityApi, 'getUserId' | 'getIdToken'>
|
||||
>;
|
||||
type MockCatalogApi = jest.Mocked<Pick<CatalogApi, 'getEntityByName'>>;
|
||||
|
||||
const mockIdentityApi: MockIdentityApi = {
|
||||
getUserId: jest.fn(),
|
||||
getIdToken: jest.fn(),
|
||||
};
|
||||
const mockCatalogApi: MockCatalogApi = {
|
||||
getEntityByName: jest.fn(),
|
||||
};
|
||||
|
||||
const identityApi = (mockIdentityApi as unknown) as IdentityApi;
|
||||
const catalogApi = (mockCatalogApi as unknown) as CatalogApi;
|
||||
|
||||
const Wrapper = ({ children }: { children?: React.ReactNode }) => (
|
||||
<ApiProvider
|
||||
apis={ApiRegistry.with(identityApiRef, identityApi).with(
|
||||
catalogApiRef,
|
||||
catalogApi,
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</ApiProvider>
|
||||
);
|
||||
|
||||
const ownedEntity: ComponentEntity = {
|
||||
apiVersion: 'backstage.io/v1beta1',
|
||||
kind: 'Component',
|
||||
metadata: {
|
||||
name: 'component1',
|
||||
namespace: 'default',
|
||||
},
|
||||
spec: {
|
||||
/* should not be accessed */
|
||||
} as any,
|
||||
relations: [
|
||||
{
|
||||
type: RELATION_OWNED_BY,
|
||||
target: { kind: 'User', namespace: 'default', name: 'user1' },
|
||||
},
|
||||
{
|
||||
type: RELATION_OWNED_BY,
|
||||
target: { kind: 'Group', namespace: 'default', name: 'group1' },
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const user2Entity: UserEntity = {
|
||||
apiVersion: 'backstage.io/v1beta1',
|
||||
kind: 'User',
|
||||
metadata: {
|
||||
name: 'user2',
|
||||
namespace: 'default',
|
||||
},
|
||||
spec: {
|
||||
/* should not be accessed */
|
||||
} as any,
|
||||
relations: [
|
||||
{
|
||||
type: RELATION_MEMBER_OF,
|
||||
target: { kind: 'Group', namespace: 'default', name: 'group1' },
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
// these were generated on https://jwt.io, based off of its default example token
|
||||
// no ent at all
|
||||
const tokenNoEnt =
|
||||
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c';
|
||||
// "ent": []
|
||||
const tokenEmptyEnt =
|
||||
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJlbnQiOltdfQ.Khyza2whczkoC4wSCLBhBaBB9-ktIkk7gpXEgQPHhtY';
|
||||
// "ent": ["user:default/user1"]
|
||||
const tokenUserEnt =
|
||||
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJlbnQiOlsidXNlcjpkZWZhdWx0L3VzZXIxIl19.CMCxjwI4rj_TD3uUoBNgFjkZI23LwRTbQnSPBxzncoY';
|
||||
// "ent": ["user:default/user1", "group:default/group1"]
|
||||
const tokenUserAndGroupEnt =
|
||||
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJlbnQiOlsidXNlcjpkZWZhdWx0L3VzZXIxIiwiZ3JvdXA6ZGVmYXVsdC9ncm91cDEiXX0.ZZmZrogbQKx0hnForw63ETkyAhUyeoBE8Hgloi45rdg';
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
describe('loadIdentityOwnerRefs', () => {
|
||||
it('returns the user id when there is no relevant token info', async () => {
|
||||
mockIdentityApi.getUserId.mockReturnValueOnce('foo');
|
||||
mockIdentityApi.getIdToken.mockResolvedValueOnce(undefined);
|
||||
await expect(loadIdentityOwnerRefs(identityApi)).resolves.toEqual([
|
||||
'user:default/foo',
|
||||
]);
|
||||
|
||||
mockIdentityApi.getUserId.mockReturnValueOnce('ns/foo');
|
||||
mockIdentityApi.getIdToken.mockResolvedValueOnce(undefined);
|
||||
await expect(loadIdentityOwnerRefs(identityApi)).resolves.toEqual([
|
||||
'user:ns/foo',
|
||||
]);
|
||||
|
||||
mockIdentityApi.getUserId.mockReturnValueOnce('user:ns/foo');
|
||||
mockIdentityApi.getIdToken.mockResolvedValueOnce(undefined);
|
||||
await expect(loadIdentityOwnerRefs(identityApi)).resolves.toEqual([
|
||||
'user:ns/foo',
|
||||
]);
|
||||
|
||||
mockIdentityApi.getUserId.mockReturnValueOnce('foo');
|
||||
mockIdentityApi.getIdToken.mockResolvedValueOnce(tokenNoEnt);
|
||||
await expect(loadIdentityOwnerRefs(identityApi)).resolves.toEqual([
|
||||
'user:default/foo',
|
||||
]);
|
||||
|
||||
mockIdentityApi.getUserId.mockReturnValueOnce('foo');
|
||||
mockIdentityApi.getIdToken.mockResolvedValueOnce(tokenEmptyEnt);
|
||||
await expect(loadIdentityOwnerRefs(identityApi)).resolves.toEqual([
|
||||
'user:default/foo',
|
||||
]);
|
||||
});
|
||||
|
||||
it('returns both the user id and the token parts', async () => {
|
||||
mockIdentityApi.getUserId.mockReturnValueOnce('foo');
|
||||
mockIdentityApi.getIdToken.mockResolvedValueOnce(tokenUserEnt);
|
||||
await expect(loadIdentityOwnerRefs(identityApi)).resolves.toEqual([
|
||||
'user:default/foo',
|
||||
'user:default/user1',
|
||||
]);
|
||||
|
||||
mockIdentityApi.getUserId.mockReturnValueOnce('foo');
|
||||
mockIdentityApi.getIdToken.mockResolvedValueOnce(tokenUserAndGroupEnt);
|
||||
await expect(loadIdentityOwnerRefs(identityApi)).resolves.toEqual([
|
||||
'user:default/foo',
|
||||
'user:default/user1',
|
||||
'group:default/group1',
|
||||
]);
|
||||
});
|
||||
|
||||
it('gracefully ignores broken token', async () => {
|
||||
mockIdentityApi.getUserId.mockReturnValueOnce('foo');
|
||||
mockIdentityApi.getIdToken.mockResolvedValueOnce('not a jwt');
|
||||
await expect(loadIdentityOwnerRefs(identityApi)).resolves.toEqual([
|
||||
'user:default/foo',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('loadCatalogOwnerRefs', () => {
|
||||
it('loads the first user from the catalog', async () => {
|
||||
mockCatalogApi.getEntityByName.mockResolvedValueOnce(user2Entity);
|
||||
await expect(
|
||||
loadCatalogOwnerRefs(catalogApi, ['user:default/user2']),
|
||||
).resolves.toEqual(['group:default/group1']);
|
||||
expect(mockCatalogApi.getEntityByName).toBeCalledWith({
|
||||
kind: 'user',
|
||||
namespace: 'default',
|
||||
name: 'user2',
|
||||
});
|
||||
});
|
||||
|
||||
it('gracefully handles missing user', async () => {
|
||||
mockCatalogApi.getEntityByName.mockResolvedValueOnce(undefined);
|
||||
await expect(
|
||||
loadCatalogOwnerRefs(catalogApi, ['user:default/user2']),
|
||||
).resolves.toEqual([]);
|
||||
expect(mockCatalogApi.getEntityByName).toBeCalledWith({
|
||||
kind: 'user',
|
||||
namespace: 'default',
|
||||
name: 'user2',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('useEntityOwnership', () => {
|
||||
it('matches ownership via token claims', async () => {
|
||||
mockIdentityApi.getUserId.mockReturnValue('foo');
|
||||
mockIdentityApi.getIdToken.mockResolvedValue(tokenUserAndGroupEnt);
|
||||
mockCatalogApi.getEntityByName.mockResolvedValue(undefined);
|
||||
|
||||
const { result, waitForValueToChange } = renderHook(
|
||||
() => useEntityOwnership(),
|
||||
{
|
||||
wrapper: Wrapper,
|
||||
},
|
||||
);
|
||||
|
||||
expect(result.current.loading).toBe(true);
|
||||
expect(result.current.isOwnedEntity(ownedEntity)).toBe(false);
|
||||
|
||||
await waitForValueToChange(() => result.current.loading);
|
||||
|
||||
expect(result.current.loading).toBe(false);
|
||||
expect(result.current.isOwnedEntity(ownedEntity)).toBe(true);
|
||||
});
|
||||
|
||||
it('matches ownership via catalog user entity', async () => {
|
||||
mockIdentityApi.getUserId.mockReturnValue('user2');
|
||||
mockIdentityApi.getIdToken.mockResolvedValue(undefined);
|
||||
mockCatalogApi.getEntityByName.mockResolvedValue(user2Entity);
|
||||
|
||||
const { result, waitForValueToChange } = renderHook(
|
||||
() => useEntityOwnership(),
|
||||
{
|
||||
wrapper: Wrapper,
|
||||
},
|
||||
);
|
||||
|
||||
expect(result.current.loading).toBe(true);
|
||||
expect(result.current.isOwnedEntity(ownedEntity)).toBe(false);
|
||||
|
||||
await waitForValueToChange(() => result.current.loading);
|
||||
|
||||
expect(result.current.loading).toBe(false);
|
||||
expect(result.current.isOwnedEntity(ownedEntity)).toBe(true);
|
||||
|
||||
expect(mockCatalogApi.getEntityByName).toBeCalledWith({
|
||||
kind: 'user',
|
||||
namespace: 'default',
|
||||
name: 'user2',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
* 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 { CatalogApi } from '@backstage/catalog-client';
|
||||
import {
|
||||
Entity,
|
||||
EntityName,
|
||||
parseEntityRef,
|
||||
RELATION_MEMBER_OF,
|
||||
RELATION_OWNED_BY,
|
||||
stringifyEntityRef,
|
||||
} from '@backstage/catalog-model';
|
||||
import {
|
||||
IdentityApi,
|
||||
identityApiRef,
|
||||
useApi,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import jwtDecoder from 'jwt-decode';
|
||||
import { useMemo } from 'react';
|
||||
import { useAsync } from 'react-use';
|
||||
import { catalogApiRef } from '../api';
|
||||
import { getEntityRelations } from '../utils/getEntityRelations';
|
||||
|
||||
// Takes a user ID from the identity, which can be on basically any form, and
|
||||
// returns an entity ref. E.g. if the input is "foo", it returns
|
||||
// "user:default/foo" to make sure it's a full ref.
|
||||
function extendUserId(id: string): string {
|
||||
try {
|
||||
const ref = parseEntityRef(id, {
|
||||
defaultKind: 'User',
|
||||
defaultNamespace: 'default',
|
||||
});
|
||||
return stringifyEntityRef(ref);
|
||||
} catch {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
// Takes the relevant parts of the Backstage identity, and translates them into
|
||||
// a list of entity refs on string form that represent the user's ownership
|
||||
// connections.
|
||||
export async function loadIdentityOwnerRefs(
|
||||
identityApi: IdentityApi,
|
||||
): Promise<string[]> {
|
||||
const id = identityApi.getUserId();
|
||||
const token = await identityApi.getIdToken();
|
||||
const result: string[] = [];
|
||||
|
||||
if (id) {
|
||||
result.push(extendUserId(id));
|
||||
}
|
||||
|
||||
if (token) {
|
||||
try {
|
||||
const decoded = jwtDecoder(token) as any;
|
||||
if (decoded?.ent) {
|
||||
[decoded.ent]
|
||||
.flat()
|
||||
.filter(x => typeof x === 'string')
|
||||
.map(x => x.toLocaleLowerCase('en-US'))
|
||||
.forEach(x => result.push(x));
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Takes the relevant parts of the User entity corresponding to the Backstage
|
||||
// identity, and translates them into a list of entity refs on string form that
|
||||
// represent the user's ownership connections.
|
||||
export async function loadCatalogOwnerRefs(
|
||||
catalogApi: CatalogApi,
|
||||
identityOwnerRefs: string[],
|
||||
): Promise<string[]> {
|
||||
const result = new Array<string>();
|
||||
|
||||
const primaryUserRef = identityOwnerRefs.find(ref => ref.startsWith('user:'));
|
||||
if (primaryUserRef) {
|
||||
const entity = await catalogApi.getEntityByName(
|
||||
parseEntityRef(primaryUserRef),
|
||||
);
|
||||
if (entity) {
|
||||
const memberOf = getEntityRelations(entity, RELATION_MEMBER_OF, {
|
||||
kind: 'Group',
|
||||
});
|
||||
for (const group of memberOf) {
|
||||
result.push(stringifyEntityRef(group));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a function that checks whether the currently signed-in user is an
|
||||
* owner of a given entity. When the hook is initially mounted, the loading
|
||||
* flag will be true and the results returned from the function will always be
|
||||
* false.
|
||||
*/
|
||||
export function useEntityOwnership(): {
|
||||
loading: boolean;
|
||||
isOwnedEntity: (entity: Entity | EntityName) => boolean;
|
||||
} {
|
||||
const identityApi = useApi(identityApiRef);
|
||||
const catalogApi = useApi(catalogApiRef);
|
||||
|
||||
// Trigger load only on mount
|
||||
const { loading, value: refs } = useAsync(async () => {
|
||||
const identityRefs = await loadIdentityOwnerRefs(identityApi);
|
||||
const catalogRefs = await loadCatalogOwnerRefs(catalogApi, identityRefs);
|
||||
return new Set([...identityRefs, ...catalogRefs]);
|
||||
}, []);
|
||||
|
||||
const isOwnedEntity = useMemo(() => {
|
||||
const myOwnerRefs = new Set(refs ?? []);
|
||||
return (entity: Entity | EntityName) => {
|
||||
const entityOwnerRefs = ('metadata' in entity
|
||||
? getEntityRelations(entity, RELATION_OWNED_BY)
|
||||
: [entity]
|
||||
).map(stringifyEntityRef);
|
||||
for (const ref of entityOwnerRefs) {
|
||||
if (myOwnerRefs.has(ref)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
}, [refs]);
|
||||
|
||||
return useMemo(() => ({ loading, isOwnedEntity }), [loading, isOwnedEntity]);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user