Merge branch 'master' into xcmetrics-builds-page

Signed-off-by: Niklas Granander <ngranander@spotify.com>
This commit is contained in:
Niklas Granander
2021-07-30 15:50:51 +02:00
213 changed files with 3103 additions and 917 deletions
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/plugin-scaffolder': patch
---
chore: set the lifecycle as beta logo
-7
View File
@@ -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.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/create-app': patch
---
fix typo in the comments of EntityPage component
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/plugin-catalog-backend-module-ldap': patch
---
chore(deps): bump `@types/ldapjs` from 1.0.10 to 2.2.0
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/plugin-auth-backend': patch
---
Allow to configure SAML auth `acceptedClockSkewMs`
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/plugin-api-docs': patch
---
Add explicit import for `isomorphic-form-data` needed for `swagger-ui-react`
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/create-app': patch
---
Update the `software-templates` to point to `main` branch instead of `master`
+5
View File
@@ -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']
-7
View File
@@ -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
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/plugin-catalog-react': patch
---
Fix `EntityListProvider` to not update url if unmounted
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-scaffolder-backend': patch
---
Add the `scaffolder.defaultCommitMessage`, which defaults to `Initial commit`, so it can be customized.
-5
View File
@@ -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.
-5
View File
@@ -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.
+1
View File
@@ -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. |
+2
View File
@@ -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'
+1 -1
View File
@@ -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)
+10
View File
@@ -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 */
+1
View File
@@ -41,6 +41,7 @@
]
},
"resolutions": {
"**/@graphql-codegen/cli/**/ws": "^7.4.6",
"**/@roadiehq/**/@backstage/core": "*",
"**/@roadiehq/**/@backstage/plugin-catalog": "*",
"**/@roadiehq/**/@backstage/catalog-model": "*",
+41
View File
@@ -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
View File
@@ -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>
+6
View File
@@ -1,5 +1,11 @@
# @backstage/catalog-client
## 0.3.18
### Patch Changes
- 11c370af2: Support filtering entities via property existence
## 0.3.17
### Patch Changes
+7 -2
View File
@@ -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;
};
+2 -2
View File
@@ -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 },
+8 -3
View File
@@ -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)}`,
);
}
}
}
+4 -2
View File
@@ -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';
+11
View File
@@ -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
+7 -7
View File
@@ -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",
+9
View File
@@ -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 -1
View File
@@ -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",
+15
View File
@@ -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
+7 -7
View File
@@ -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",
+43
View File
@@ -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
+18 -12
View File
@@ -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:
//
+8 -8
View File
@@ -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>
+13
View File
@@ -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
+6 -6
View File
@@ -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",
+36
View File
@@ -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 -1
View File
@@ -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",
@@ -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.
*/
+19
View File
@@ -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
+10 -10
View File
@@ -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"
},
+15
View File
@@ -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
+8 -8
View File
@@ -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",
+10
View File
@@ -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
+2 -2
View File
@@ -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}`),
+15
View File
@@ -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
+6 -6
View File
@@ -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"
},
+11
View File
@@ -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
+3 -3
View File
@@ -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"
+1 -1
View File
@@ -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';
+18
View File
@@ -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
+11 -11
View File
@@ -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",
+10
View File
@@ -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
+1
View File
@@ -65,6 +65,7 @@ export interface Config {
authorizationUrl: string;
tokenUrl: string;
scope?: string;
disableRefresh?: boolean;
};
};
oidc?: {
+4 -4
View File
@@ -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,
});
+16
View File
@@ -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
View File
@@ -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",
+16
View File
@@ -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
+10 -10
View File
@@ -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",
@@ -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"
},
+8
View File
@@ -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
+10 -9
View File
@@ -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
+4 -4
View File
@@ -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);
+18
View File
@@ -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
+12 -12
View File
@@ -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",
+40
View File
@@ -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
+14 -3
View File
@@ -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;
}
+11 -9
View File
@@ -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.
+5 -9
View File
@@ -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:
+1
View File
@@ -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