diff --git a/.changeset/2826.md b/.changeset/2826.md
new file mode 100644
index 0000000000..fb2dbb6877
--- /dev/null
+++ b/.changeset/2826.md
@@ -0,0 +1,20 @@
+---
+'example-app': minor
+'@backstage/core': minor
+'@backstage/create-app': minor
+'@backstage/plugin-catalog': minor
+'@backstage/plugin-github-actions': minor
+'@backstage/plugin-jenkins': minor
+'@backstage/plugin-lighthouse': minor
+---
+
+The InfoCard variant `'height100'` is deprecated. Use variant `'gridItem'` instead.
+
+When the InfoCard is displayed as a grid item within a grid, you may want items to have the same height for all items.
+Set to the `'gridItem'` variant to display the InfoCard with full height suitable for Grid:
+`...`
+
+Changed the InfoCards in '@backstage/plugin-github-actions', '@backstage/plugin-jenkins', '@backstage/plugin-lighthouse'
+to pass an optional variant to the corresponding card of the plugin.
+
+As a result the overview content of the EntityPage shows cards with full height suitable for Grid.
diff --git a/.changeset/carpal-tunnel-driver.md b/.changeset/carpal-tunnel-driver.md
new file mode 100644
index 0000000000..2e6a814533
--- /dev/null
+++ b/.changeset/carpal-tunnel-driver.md
@@ -0,0 +1,13 @@
+---
+'@backstage/plugin-catalog-backend': minor
+---
+
+- The `CatalogProcessor` API was updated to have `preProcessEntity` and
+ `postProcessEntity` methods, instead of just one `processEntity`. This makes
+ it easier to make processors that have several stages in one, and to make
+ different processors more position independent in the list of processors.
+- The `EntityPolicy` is now given directly to the `LocationReaders`, instead of
+ being enforced inside a policy. We have decided to separate out the act of
+ validating an entity to be outside of the processing flow, to make it
+ possible to apply more liberally and to evolve it as a separate concept.
+- Because of the above, the `EntityPolicyProcessor` has been removed.
diff --git a/.changeset/funny-readers-breathe.md b/.changeset/funny-readers-breathe.md
new file mode 100644
index 0000000000..8897b24cf6
--- /dev/null
+++ b/.changeset/funny-readers-breathe.md
@@ -0,0 +1,5 @@
+---
+'@backstage/core': patch
+---
+
+fix the warning of all the core components test cases
diff --git a/microsite/data/plugins/aws-lambda.yaml b/microsite/data/plugins/aws-lambda.yaml
new file mode 100644
index 0000000000..7fa276f444
--- /dev/null
+++ b/microsite/data/plugins/aws-lambda.yaml
@@ -0,0 +1,9 @@
+---
+title: AWS Lambda
+author: roadie.io
+authorUrl: https://roadie.io
+category: Monitoring
+description: View AWS Lambda functions for your components in Backstage.
+documentation: https://roadie.io/backstage/plugins/aws-lambda
+iconUrl: https://roadie.io/static/77f62f79e27ae8565496e4df7eef8be5/45f2b/logo.png
+npmPackageName: '@roadiehq/backstage-plugin-aws-lambda'
diff --git a/microsite/static/css/custom.css b/microsite/static/css/custom.css
index 6308513276..22855a2b4d 100644
--- a/microsite/static/css/custom.css
+++ b/microsite/static/css/custom.css
@@ -15,9 +15,6 @@ table tr:nth-child(2n) {
background-color: #2b2b2b;
}
-@media only screen and (min-device-width: 360px) and (max-device-width: 736px) {
-}
-
@media only screen and (min-width: 1024px) and (max-width: 1500px) {
/* Experience */
.content-experience {
@@ -857,7 +854,6 @@ code {
position: relative;
padding-top: 75%;
width: 100%;
- /* border: 1px dotted #f00; */
}
.Block__Graphic {
diff --git a/packages/app/package.json b/packages/app/package.json
index 11c47769a8..dc452e0803 100644
--- a/packages/app/package.json
+++ b/packages/app/package.json
@@ -35,7 +35,7 @@
"@material-ui/icons": "^4.9.1",
"@octokit/rest": "^18.0.0",
"@roadiehq/backstage-plugin-github-insights": "^0.2.7",
- "@roadiehq/backstage-plugin-github-pull-requests": "^0.5.1",
+ "@roadiehq/backstage-plugin-github-pull-requests": "^0.5.2",
"@roadiehq/backstage-plugin-travis-ci": "^0.2.3",
"dayjs": "^1.9.1",
"history": "^5.0.0",
diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx
index f30e7ff34f..0a3007237e 100644
--- a/packages/app/src/components/catalog/EntityPage.tsx
+++ b/packages/app/src/components/catalog/EntityPage.tsx
@@ -105,10 +105,12 @@ const RecentCICDRunsSwitcher = ({ entity }: { entity: Entity }) => {
let content: ReactNode;
switch (true) {
case isJenkinsAvailable(entity):
- content = ;
+ content = ;
break;
case isGitHubActionsAvailable(entity):
- content = ;
+ content = (
+
+ );
break;
case isTravisCIAvailable(entity):
content = ;
@@ -127,9 +129,9 @@ const RecentCICDRunsSwitcher = ({ entity }: { entity: Entity }) => {
};
const OverviewContent = ({ entity }: { entity: Entity }) => (
-
+
-
+
{isGitHubAvailable(entity) && (
@@ -145,7 +147,7 @@ const OverviewContent = ({ entity }: { entity: Entity }) => (
)}
{isLighthouseAvailable(entity) && (
-
+
)}
{isPullRequestsAvailable(entity) && (
diff --git a/packages/catalog-model/src/entity/policies/ReservedFieldsEntityPolicy.test.ts b/packages/catalog-model/src/entity/policies/ReservedFieldsEntityPolicy.test.ts
deleted file mode 100644
index 5d4e60a1a3..0000000000
--- a/packages/catalog-model/src/entity/policies/ReservedFieldsEntityPolicy.test.ts
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright 2020 Spotify AB
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import yaml from 'yaml';
-import { ReservedFieldsEntityPolicy } from './ReservedFieldsEntityPolicy';
-
-describe('ReservedFieldsEntityPolicy', () => {
- let data: any;
- let policy: ReservedFieldsEntityPolicy;
-
- beforeEach(() => {
- data = yaml.parse(`
- apiVersion: backstage.io/v1alpha1
- kind: Component
- metadata:
- uid: e01199ab-08cc-44c2-8e19-5c29ded82521
- etag: lsndfkjsndfkjnsdfkjnsd==
- generation: 13
- name: my-component-yay
- namespace: the-namespace
- labels:
- backstage.io/custom: ValueStuff
- annotations:
- example.com/bindings: are-secret
- tags:
- - java
- - data
- spec:
- custom: stuff
- `);
- policy = new ReservedFieldsEntityPolicy();
- });
-
- it('works for the happy path', async () => {
- await expect(policy.enforce(data)).resolves.toBe(data);
- });
-
- it('rejects reserved keys in the spec root', async () => {
- data.spec.apiVersion = 'a/b';
- await expect(policy.enforce(data)).rejects.toThrow(/spec.*apiVersion/i);
- });
-
- it('rejects reserved keys in labels', async () => {
- data.metadata.labels.apiVersion = 'a';
- await expect(policy.enforce(data)).rejects.toThrow(/label.*apiVersion/i);
- });
-
- it('rejects reserved keys in annotations', async () => {
- data.metadata.annotations.apiVersion = 'a';
- await expect(policy.enforce(data)).rejects.toThrow(
- /annotation.*apiVersion/i,
- );
- });
-
- it('rejects core fields mistakenly placed in metadata', async () => {
- data.metadata.owner = 'emma';
- await expect(policy.enforce(data)).rejects.toThrow(/owner/i);
- });
-});
diff --git a/packages/catalog-model/src/entity/policies/ReservedFieldsEntityPolicy.ts b/packages/catalog-model/src/entity/policies/ReservedFieldsEntityPolicy.ts
deleted file mode 100644
index edb63cf05c..0000000000
--- a/packages/catalog-model/src/entity/policies/ReservedFieldsEntityPolicy.ts
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright 2020 Spotify AB
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import { EntityPolicy } from '../../types';
-import { Entity } from '../Entity';
-
-const DEFAULT_RESERVED_ENTITY_FIELDS: string[] = [
- 'apiVersion',
- 'kind',
- 'spec',
- 'metadata.uid',
- 'metadata.etag',
- 'metadata.generation',
- 'metadata.name',
- 'metadata.namespace',
- 'metadata.description',
- 'metadata.labels',
- 'metadata.annotations',
- 'metadata.tags',
- // The below items are known to appear in core kinds, and therefore should
- // not be appearing in metadata (which would indicate that the user made a
- // mistake in where to place them).
- 'spec.lifecycle',
- 'spec.owner',
-];
-
-/**
- * Ensures that fields are not given certain reserved names.
- */
-export class ReservedFieldsEntityPolicy implements EntityPolicy {
- private readonly reservedFields: string[];
-
- constructor(fields?: string[]) {
- this.reservedFields = [
- ...(fields ?? []),
- ...DEFAULT_RESERVED_ENTITY_FIELDS,
- ];
- }
-
- async enforce(entity: Entity): Promise {
- for (const path of this.reservedFields) {
- const [where, name] = path.includes('.')
- ? path.split('.')
- : [undefined, path];
-
- if (where !== 'metadata' && entity.metadata.hasOwnProperty(name)) {
- throw new Error(
- `The metadata may not contain the field ${name}, because it has reserved meaning`,
- );
- }
- if (where !== 'spec' && entity.spec?.hasOwnProperty(name)) {
- throw new Error(
- `The spec may not contain the field ${name}, because it has reserved meaning`,
- );
- }
- if (where !== 'labels' && entity.metadata.labels?.hasOwnProperty(name)) {
- throw new Error(
- `A label may not have the field ${name}, because it has reserved meaning`,
- );
- }
- if (
- where !== 'annotations' &&
- entity.metadata.annotations?.hasOwnProperty(name)
- ) {
- throw new Error(
- `An annotation may not have the field ${name}, because it has reserved meaning`,
- );
- }
- }
- return entity;
- }
-}
diff --git a/packages/catalog-model/src/entity/policies/index.ts b/packages/catalog-model/src/entity/policies/index.ts
index eb7c0e8378..c381d29a23 100644
--- a/packages/catalog-model/src/entity/policies/index.ts
+++ b/packages/catalog-model/src/entity/policies/index.ts
@@ -17,5 +17,4 @@
export { DefaultNamespaceEntityPolicy } from './DefaultNamespaceEntityPolicy';
export { FieldFormatEntityPolicy } from './FieldFormatEntityPolicy';
export { NoForeignRootFieldsEntityPolicy } from './NoForeignRootFieldsEntityPolicy';
-export { ReservedFieldsEntityPolicy } from './ReservedFieldsEntityPolicy';
export { SchemaValidEntityPolicy } from './SchemaValidEntityPolicy';
diff --git a/packages/cli/src/commands/remove-plugin/removePlugin.ts b/packages/cli/src/commands/remove-plugin/removePlugin.ts
index 643bd599d7..81d8322ffa 100644
--- a/packages/cli/src/commands/remove-plugin/removePlugin.ts
+++ b/packages/cli/src/commands/remove-plugin/removePlugin.ts
@@ -62,7 +62,7 @@ export const removePluginDirectory = async (destination: string) => {
export const removeSymLink = async (destination: string) => {
await Task.forItem('removing', 'symbolic link', async () => {
- const symLinkExists = fse.pathExists(destination);
+ const symLinkExists = await fse.pathExists(destination);
if (symLinkExists) {
try {
await fse.remove(destination);
@@ -189,7 +189,7 @@ export default async () => {
return chalk.red('Please enter an ID for the plugin');
} else if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(value)) {
return chalk.red(
- 'Plugin IDs must be kehbab-cased and contain only letters, digits and dashes.',
+ 'Plugin IDs must be kebab-cased and contain only letters, digits and dashes.',
);
}
return true;
diff --git a/packages/core/src/components/Button/Button.test.jsx b/packages/core/src/components/Button/Button.test.tsx
similarity index 94%
rename from packages/core/src/components/Button/Button.test.jsx
rename to packages/core/src/components/Button/Button.test.tsx
index 115835be3f..8bae5f2767 100644
--- a/packages/core/src/components/Button/Button.test.jsx
+++ b/packages/core/src/components/Button/Button.test.tsx
@@ -34,7 +34,9 @@ describe('', () => {
);
expect(() => getByText(testString)).toThrow();
- await act(async () => fireEvent.click(getByText(buttonLabel)));
+ await act(async () => {
+ fireEvent.click(getByText(buttonLabel));
+ });
expect(getByText(testString)).toBeInTheDocument();
});
});
diff --git a/packages/core/src/components/CodeSnippet/CodeSnippet.test.tsx b/packages/core/src/components/CodeSnippet/CodeSnippet.test.tsx
index 466abe6012..7d5d4de087 100644
--- a/packages/core/src/components/CodeSnippet/CodeSnippet.test.tsx
+++ b/packages/core/src/components/CodeSnippet/CodeSnippet.test.tsx
@@ -15,8 +15,9 @@
*/
import React from 'react';
-import { render, fireEvent } from '@testing-library/react';
-import { wrapInTestApp } from '@backstage/test-utils';
+import { fireEvent } from '@testing-library/react';
+import { act } from 'react-dom/test-utils';
+import { renderInTestApp } from '@backstage/test-utils';
import { CodeSnippet } from './CodeSnippet';
@@ -32,24 +33,24 @@ const minProps = {
};
describe('', () => {
- it('renders text without exploding', () => {
- const { getByText } = render(wrapInTestApp());
+ it('renders text without exploding', async () => {
+ const { getByText } = await renderInTestApp();
expect(getByText(/"Hello"/)).toBeInTheDocument();
expect(getByText(/"World"/)).toBeInTheDocument();
});
- it('renders without line numbers', () => {
- const { queryByText } = render(
- wrapInTestApp(),
+ it('renders without line numbers', async () => {
+ const { queryByText } = await renderInTestApp(
+ ,
);
expect(queryByText('1')).not.toBeInTheDocument();
expect(queryByText('2')).not.toBeInTheDocument();
expect(queryByText('3')).not.toBeInTheDocument();
});
- it('renders with line numbers', () => {
- const { getByText } = render(
- wrapInTestApp(),
+ it('renders with line numbers', async () => {
+ const { getByText } = await renderInTestApp(
+ ,
);
expect(getByText('1')).toBeInTheDocument();
expect(getByText('2')).toBeInTheDocument();
@@ -57,12 +58,17 @@ describe('', () => {
});
it('copy code using button', async () => {
+ jest.useFakeTimers();
document.execCommand = jest.fn();
- const { getByTitle } = render(
- wrapInTestApp(),
+ const { getByTitle } = await renderInTestApp(
+ ,
);
const button = getByTitle('Text copied to clipboard');
fireEvent.click(button);
+ act(() => {
+ jest.runAllTimers();
+ });
expect(document.execCommand).toHaveBeenCalled();
+ jest.useRealTimers();
});
});
diff --git a/packages/core/src/components/CopyTextButton/CopyTextButton.test.tsx b/packages/core/src/components/CopyTextButton/CopyTextButton.test.tsx
index cab992e11a..07bce97d74 100644
--- a/packages/core/src/components/CopyTextButton/CopyTextButton.test.tsx
+++ b/packages/core/src/components/CopyTextButton/CopyTextButton.test.tsx
@@ -15,8 +15,9 @@
*/
import React from 'react';
-import { render, fireEvent } from '@testing-library/react';
-import { wrapInTestApp } from '@backstage/test-utils';
+import { fireEvent } from '@testing-library/react';
+import { act } from 'react-dom/test-utils';
+import { renderInTestApp } from '@backstage/test-utils';
import { CopyTextButton } from './CopyTextButton';
import {
ApiRegistry,
@@ -55,29 +56,30 @@ const apiRegistry = ApiRegistry.from([
]);
describe('', () => {
- it('renders without exploding', () => {
- const { getByDisplayValue } = render(
- wrapInTestApp(
-
-
- ,
- ),
+ it('renders without exploding', async () => {
+ const { getByDisplayValue } = await renderInTestApp(
+
+
+ ,
);
getByDisplayValue('mockText');
});
it('displays tooltip on click', async () => {
+ jest.useFakeTimers();
document.execCommand = jest.fn();
- const rendered = render(
- wrapInTestApp(
-
-
- ,
- ),
+ const rendered = await renderInTestApp(
+
+
+ ,
);
const button = rendered.getByTitle('mockTooltip');
fireEvent.click(button);
+ act(() => {
+ jest.runAllTimers();
+ });
expect(document.execCommand).toHaveBeenCalled();
rendered.getByText('mockTooltip');
+ jest.useRealTimers();
});
});
diff --git a/packages/core/src/components/HorizontalScrollGrid/HorizontalScrollGrid.test.jsx b/packages/core/src/components/HorizontalScrollGrid/HorizontalScrollGrid.test.tsx
similarity index 53%
rename from packages/core/src/components/HorizontalScrollGrid/HorizontalScrollGrid.test.jsx
rename to packages/core/src/components/HorizontalScrollGrid/HorizontalScrollGrid.test.tsx
index 83cec85fc5..0d07d8ec84 100644
--- a/packages/core/src/components/HorizontalScrollGrid/HorizontalScrollGrid.test.jsx
+++ b/packages/core/src/components/HorizontalScrollGrid/HorizontalScrollGrid.test.tsx
@@ -15,31 +15,30 @@
*/
import React from 'react';
-import { render, fireEvent } from '@testing-library/react';
-import { renderWithEffects, wrapInTestApp } from '@backstage/test-utils';
+import { fireEvent } from '@testing-library/react';
+import { renderInTestApp } from '@backstage/test-utils';
import { HorizontalScrollGrid } from './HorizontalScrollGrid';
import { Grid } from '@material-ui/core';
describe('', () => {
beforeEach(() => {
jest.spyOn(window.performance, 'now').mockReturnValue(5);
- jest
- .spyOn(window, 'requestAnimationFrame')
- .mockImplementation(cb => cb(20));
+ jest.spyOn(window, 'requestAnimationFrame').mockImplementation(cb => {
+ cb(20);
+ return 1;
+ });
});
afterEach(() => {
jest.restoreAllMocks();
});
- it('renders without exploding', () => {
- const rendered = render(
- wrapInTestApp(
-
- item1
- item2
- ,
- ),
+ it('renders without exploding', async () => {
+ const rendered = await renderInTestApp(
+
+ item1
+ item2
+ ,
);
rendered.getByText('item1');
rendered.getByText('item2');
@@ -48,37 +47,31 @@ describe('', () => {
});
it('should show scroll buttons', async () => {
- Object.defineProperties(HTMLElement.prototype, {
- scrollLeft: {
- configurable: true,
- value: 5,
- },
- offsetWidth: {
- configurable: true,
- value: 10,
- },
- scrollWidth: {
- configurable: true,
- value: 20,
- },
- });
+ jest
+ .spyOn(HTMLElement.prototype, 'scrollLeft', 'get')
+ .mockImplementation(() => 5);
+ jest
+ .spyOn(HTMLElement.prototype, 'offsetWidth', 'get')
+ .mockImplementation(() => 10);
+ jest
+ .spyOn(HTMLElement.prototype, 'scrollWidth', 'get')
+ .mockImplementation(() => 20);
let lastScroll = 0;
- HTMLElement.prototype.scrollBy = ({ left }) => {
- lastScroll = left;
- };
+ const scrollBy = HTMLElement.prototype.scrollBy;
+ HTMLElement.prototype.scrollBy = (({ left }: ScrollToOptions): void => {
+ lastScroll = left || 0;
+ }) as any;
- const rendered = await renderWithEffects(
- wrapInTestApp(
-
-
- item1
-
-
- item2
-
- ,
- ),
+ const rendered = await renderInTestApp(
+
+
+ item1
+
+
+ item2
+
+ ,
);
rendered.getByTitle('Scroll Left');
@@ -89,9 +82,6 @@ describe('', () => {
fireEvent.click(rendered.getByTitle('Scroll Left'));
expect(lastScroll).toBeLessThan(0);
- delete HTMLElement.prototype.scrollLeft;
- delete HTMLElement.prototype.offsetWidth;
- delete HTMLElement.prototype.scrollWidth;
- delete HTMLElement.prototype.scrollBy;
+ HTMLElement.prototype.scrollBy = scrollBy;
});
});
diff --git a/packages/core/src/components/Lifecycle/Lifecycle.test.jsx b/packages/core/src/components/Lifecycle/Lifecycle.test.tsx
similarity index 74%
rename from packages/core/src/components/Lifecycle/Lifecycle.test.jsx
rename to packages/core/src/components/Lifecycle/Lifecycle.test.tsx
index ac3823d85c..b5567b2b5a 100644
--- a/packages/core/src/components/Lifecycle/Lifecycle.test.jsx
+++ b/packages/core/src/components/Lifecycle/Lifecycle.test.tsx
@@ -15,28 +15,27 @@
*/
import React from 'react';
-import { render } from '@testing-library/react';
-import { wrapInTestApp } from '@backstage/test-utils';
+import { renderInTestApp } from '@backstage/test-utils';
import { Lifecycle } from './Lifecycle';
describe('', () => {
it('renders Alpha with shorthand', async () => {
- const { getByText } = render(wrapInTestApp());
+ const { getByText } = await renderInTestApp();
expect(getByText('α')).toBeInTheDocument();
});
it('renders Alpha without shorthand', async () => {
- const { getByText } = render(wrapInTestApp());
+ const { getByText } = await renderInTestApp();
expect(getByText('Alpha')).toBeInTheDocument();
});
it('renders Beta with shorthand', async () => {
- const { getByText } = render(wrapInTestApp());
+ const { getByText } = await renderInTestApp();
expect(getByText('β')).toBeInTheDocument();
});
it('renders Beta without shorthand', async () => {
- const { getByText } = render(wrapInTestApp());
+ const { getByText } = await renderInTestApp();
expect(getByText('Beta')).toBeInTheDocument();
});
});
diff --git a/packages/core/src/components/Link/Link.test.jsx b/packages/core/src/components/Link/Link.test.tsx
similarity index 94%
rename from packages/core/src/components/Link/Link.test.jsx
rename to packages/core/src/components/Link/Link.test.tsx
index 4bb78ef6d4..23d2728bf6 100644
--- a/packages/core/src/components/Link/Link.test.jsx
+++ b/packages/core/src/components/Link/Link.test.tsx
@@ -34,7 +34,9 @@ describe('', () => {
),
);
expect(() => getByText(testString)).toThrow();
- await act(async () => fireEvent.click(getByText(linkText)));
+ await act(async () => {
+ fireEvent.click(getByText(linkText));
+ });
expect(getByText(testString)).toBeInTheDocument();
});
});
diff --git a/packages/core/src/components/ProgressBars/Gauge.test.jsx b/packages/core/src/components/ProgressBars/Gauge.test.tsx
similarity index 59%
rename from packages/core/src/components/ProgressBars/Gauge.test.jsx
rename to packages/core/src/components/ProgressBars/Gauge.test.tsx
index 709de7b0c4..00cf0fd009 100644
--- a/packages/core/src/components/ProgressBars/Gauge.test.jsx
+++ b/packages/core/src/components/ProgressBars/Gauge.test.tsx
@@ -15,34 +15,34 @@
*/
import React from 'react';
-import { render } from '@testing-library/react';
-import { wrapInTestApp } from '@backstage/test-utils';
+import { renderInTestApp } from '@backstage/test-utils';
import { Gauge, getProgressColor } from './Gauge';
+import * as theme from '@backstage/theme';
describe('', () => {
- it('renders without exploding', () => {
- const { getByText } = render(
- wrapInTestApp(),
+ it('renders without exploding', async () => {
+ const { getByText } = await renderInTestApp(
+ ,
);
getByText('10%');
});
- it('handles fractional prop', () => {
- const { getByText } = render(
- wrapInTestApp(),
+ it('handles fractional prop', async () => {
+ const { getByText } = await renderInTestApp(
+ ,
);
getByText('10%');
});
- it('handles max prop', () => {
- const { getByText } = render(
- wrapInTestApp(),
+ it('handles max prop', async () => {
+ const { getByText } = await renderInTestApp(
+ ,
);
getByText('1%');
});
- it('handles unit prop', () => {
- const { getByText } = render(
- wrapInTestApp(),
+ it('handles unit prop', async () => {
+ const { getByText } = await renderInTestApp(
+ ,
);
getByText('10m');
});
@@ -50,17 +50,20 @@ describe('', () => {
const ok = '#111';
const warning = '#222';
const error = '#333';
- const palette = { status: { ok, warning, error } };
+ const palette = {
+ ...theme.lightTheme.palette,
+ status: { ...theme.lightTheme.palette.status, ok, warning, error },
+ };
it('colors the progress correctly', () => {
- expect(getProgressColor(palette)).toBe('#ddd');
+ expect(getProgressColor(palette, 'Not a Number' as any)).toBe('#ddd');
expect(getProgressColor(palette, 10)).toBe(error);
expect(getProgressColor(palette, 50)).toBe(warning);
expect(getProgressColor(palette, 90)).toBe(ok);
});
it('colors the inverse progress correctly', () => {
- expect(getProgressColor(palette)).toBe('#ddd');
+ expect(getProgressColor(palette, 'Not a Number' as any)).toBe('#ddd');
expect(getProgressColor(palette, 10, true)).toBe(ok);
expect(getProgressColor(palette, 50, true)).toBe(warning);
expect(getProgressColor(palette, 90, true)).toBe(error);
diff --git a/packages/core/src/components/ProgressBars/GaugeCard.test.jsx b/packages/core/src/components/ProgressBars/GaugeCard.test.tsx
similarity index 65%
rename from packages/core/src/components/ProgressBars/GaugeCard.test.jsx
rename to packages/core/src/components/ProgressBars/GaugeCard.test.tsx
index 27ef0bb188..db112fa2ab 100644
--- a/packages/core/src/components/ProgressBars/GaugeCard.test.jsx
+++ b/packages/core/src/components/ProgressBars/GaugeCard.test.tsx
@@ -15,33 +15,32 @@
*/
import React from 'react';
-import { render } from '@testing-library/react';
-import { wrapInTestApp } from '@backstage/test-utils';
+import { renderInTestApp } from '@backstage/test-utils';
import { GaugeCard } from './GaugeCard';
const minProps = { title: 'Tingle upgrade', progress: 0.12 };
describe('', () => {
- it('renders without exploding', () => {
- const { getByText } = render(wrapInTestApp());
+ it('renders without exploding', async () => {
+ const { getByText } = await renderInTestApp();
expect(getByText(/Tingle.*/)).toBeInTheDocument();
});
- it('renders progress and title', () => {
- const { getByText } = render(wrapInTestApp());
+ it('renders progress and title', async () => {
+ const { getByText } = await renderInTestApp();
expect(getByText(/Tingle.*/)).toBeInTheDocument();
expect(getByText(/12%.*/)).toBeInTheDocument();
});
- it('does not render deepLink', () => {
- const { queryByText } = render(wrapInTestApp());
+ it('does not render deepLink', async () => {
+ const { queryByText } = await renderInTestApp();
expect(queryByText('View more')).not.toBeInTheDocument();
});
- it('handles invalid numbers', () => {
- const badProps = { title: 'Tingle upgrade', progress: 'hejjo' };
- const { getByText } = render(wrapInTestApp());
+ it('handles invalid numbers', async () => {
+ const badProps = { title: 'Tingle upgrade', progress: 'hejjo' } as any;
+ const { getByText } = await renderInTestApp();
expect(getByText(/N\/A.*/)).toBeInTheDocument();
});
});
diff --git a/packages/core/src/components/SimpleStepper/SimpleStepper.test.tsx b/packages/core/src/components/SimpleStepper/SimpleStepper.test.tsx
index 043084e4fb..527160c4ff 100644
--- a/packages/core/src/components/SimpleStepper/SimpleStepper.test.tsx
+++ b/packages/core/src/components/SimpleStepper/SimpleStepper.test.tsx
@@ -14,8 +14,8 @@
* limitations under the License.
*/
import React from 'react';
-import { render, fireEvent, within } from '@testing-library/react';
-import { wrapInTestApp } from '@backstage/test-utils';
+import { fireEvent, within } from '@testing-library/react';
+import { renderInTestApp } from '@backstage/test-utils';
import { SimpleStepper as Stepper } from './SimpleStepper';
import { SimpleStepperStep as Step } from './SimpleStepperStep';
@@ -24,20 +24,18 @@ const getTextInSlide = (rendered: any, index: number) =>
describe('Stepper', () => {
it('Maintains state history', async () => {
- const rendered = render(
- wrapInTestApp(
-
-
- step0
-
-
- step1
-
-
- step2
-
- ,
- ),
+ const rendered = await renderInTestApp(
+
+
+ step0
+
+
+ step1
+
+
+ step2
+
+ ,
);
fireEvent.click(getTextInSlide(rendered, 0)('Next') as Node);
@@ -51,27 +49,25 @@ describe('Stepper', () => {
});
it('Handles nextStep property', async () => {
- const rendered = render(
- wrapInTestApp(
-
-
- step0
-
- 3 }}
- data-testid="step1"
- >
- step1
-
-
- step2
-
-
- step3
-
- ,
- ),
+ const rendered = await renderInTestApp(
+
+
+ step0
+
+ 3 }}
+ data-testid="step1"
+ >
+ step1
+
+
+ step2
+
+
+ step3
+
+ ,
);
fireEvent.click(getTextInSlide(rendered, 0)('Next') as Node);
@@ -84,28 +80,26 @@ describe('Stepper', () => {
expect(rendered.getByText('step1')).toBeInTheDocument();
});
- it('Shows controls and content when going back to first step', () => {
- const rendered = render(
- wrapInTestApp(
-
-
- step0
-
- 3 }}
- data-testid="step1"
- >
- step1
-
-
- step2
-
-
- step3
-
- ,
- ),
+ it('Shows controls and content when going back to first step', async () => {
+ const rendered = await renderInTestApp(
+
+
+ step0
+
+ 3 }}
+ data-testid="step1"
+ >
+ step1
+
+
+ step2
+
+
+ step3
+
+ ,
);
fireEvent.click(getTextInSlide(rendered, 0)('Next') as Node);
@@ -123,30 +117,28 @@ describe('Stepper', () => {
expect(getTextInSlide(rendered, 0)('Next')).toBeInTheDocument();
});
- it('uses nextText if specified in all steps', () => {
- const rendered = render(
- wrapInTestApp(
+ it('uses nextText if specified in all steps', async () => {
+ const rendered = await renderInTestApp(
+
-
-
- step0
-
-
- final step
-
-
- ,
- ),
+
+ step0
+
+
+ final step
+
+
+ ,
);
expect(rendered.getByText('Step0Next')).toBeInTheDocument();
fireEvent.click(rendered.getByText('Step0Next'));
diff --git a/packages/core/src/components/Status/Status.test.jsx b/packages/core/src/components/Status/Status.test.tsx
similarity index 100%
rename from packages/core/src/components/Status/Status.test.jsx
rename to packages/core/src/components/Status/Status.test.tsx
diff --git a/packages/core/src/components/StructuredMetadataTable/StructuredMetadataTable.test.jsx b/packages/core/src/components/StructuredMetadataTable/StructuredMetadataTable.test.tsx
similarity index 69%
rename from packages/core/src/components/StructuredMetadataTable/StructuredMetadataTable.test.jsx
rename to packages/core/src/components/StructuredMetadataTable/StructuredMetadataTable.test.tsx
index 1ae810ca17..68d4262c3e 100644
--- a/packages/core/src/components/StructuredMetadataTable/StructuredMetadataTable.test.jsx
+++ b/packages/core/src/components/StructuredMetadataTable/StructuredMetadataTable.test.tsx
@@ -14,11 +14,10 @@
* limitations under the License.
*/
-import React from 'react';
import { render } from '@testing-library/react';
-
-import { StructuredMetadataTable } from './StructuredMetadataTable';
import { startCase } from 'lodash';
+import React from 'react';
+import { StructuredMetadataTable } from './StructuredMetadataTable';
describe('', () => {
it('renders without exploding', () => {
@@ -31,32 +30,34 @@ describe('', () => {
describe('Item Mappings', () => {
it('Iterates over and displays every field in the map', () => {
- const metadata = { field1: 'one', field2: 'two', field3: 'three' };
+ const metadata = {
+ field1: 'one',
+ field2: 'two',
+ field3: 'three',
+ } as const;
const { getByText } = render(
,
);
- const keys = Object.keys(metadata);
- keys.forEach(value => {
- expect(getByText(startCase(value))).toBeInTheDocument();
- expect(getByText(metadata[value])).toBeInTheDocument();
- });
+ for (const [key, value] of Object.entries(metadata)) {
+ expect(getByText(startCase(key))).toBeInTheDocument();
+ expect(getByText(value)).toBeInTheDocument();
+ }
});
- it('Supports primative value fields', () => {
- const metadata = { strField: 'my field', intField: 1 };
+ it('Supports primitive value fields', () => {
+ const metadata = { strField: 'my field', intField: 1 } as const;
const { getByText } = render(
,
);
- const keys = Object.keys(metadata);
- keys.forEach(value => {
- expect(getByText(startCase(value))).toBeInTheDocument();
- expect(getByText(metadata[value].toString())).toBeInTheDocument();
- });
+ for (const [key, value] of Object.entries(metadata)) {
+ expect(getByText(startCase(key))).toBeInTheDocument();
+ expect(getByText(value.toString())).toBeInTheDocument();
+ }
});
it('Supports array fields', () => {
- const metadata = { arrayField: ['arrVal1', 'arrVal2'] };
+ const metadata = { arrayField: ['arrVal1', 'arrVal2'] } as const;
const { getByText } = render(
,
);
@@ -79,20 +80,19 @@ describe('', () => {
});
it('Supports object elements', () => {
- const metadata = { config: { a: 1, b: 2 } };
+ const metadata = {
+ config: { a: 1, b: 2 },
+ } as const;
const { getByText } = render(
,
);
- const keys = Object.keys(metadata.config);
- keys.forEach(value => {
+ for (const [key, value] of Object.entries(metadata.config)) {
+ expect(getByText(startCase(key), { exact: false })).toBeInTheDocument();
expect(
- getByText(startCase(value), { exact: false }),
+ getByText(value.toString(), { exact: false }),
).toBeInTheDocument();
- expect(
- getByText(metadata.config[value].toString(), { exact: false }),
- ).toBeInTheDocument();
- });
+ }
});
});
});
diff --git a/packages/core/src/components/Table/Table.test.tsx b/packages/core/src/components/Table/Table.test.tsx
index 24d68fe0d1..48b90be419 100644
--- a/packages/core/src/components/Table/Table.test.tsx
+++ b/packages/core/src/components/Table/Table.test.tsx
@@ -15,8 +15,7 @@
*/
import React from 'react';
-import { render } from '@testing-library/react';
-import { wrapInTestApp } from '@backstage/test-utils';
+import { renderInTestApp } from '@backstage/test-utils';
import { Table } from './Table';
const minProps = {
@@ -43,14 +42,14 @@ const minProps = {
};
describe('', () => {
- it('renders without exploding', () => {
- const rendered = render(wrapInTestApp());
+ it('renders without exploding', async () => {
+ const rendered = await renderInTestApp();
expect(rendered.getByText('second value, second row')).toBeInTheDocument();
});
- it('renders with subtitle', () => {
- const rendered = render(
- wrapInTestApp(),
+ it('renders with subtitle', async () => {
+ const rendered = await renderInTestApp(
+ ,
);
expect(rendered.getByText('subtitle')).toBeInTheDocument();
});
diff --git a/packages/core/src/components/Tabs/Tab.test.tsx b/packages/core/src/components/Tabs/Tab.test.tsx
index 5b155ef536..df8540ff14 100644
--- a/packages/core/src/components/Tabs/Tab.test.tsx
+++ b/packages/core/src/components/Tabs/Tab.test.tsx
@@ -15,13 +15,12 @@
*/
import React from 'react';
-import { render } from '@testing-library/react';
-import { wrapInTestApp } from '@backstage/test-utils';
+import { renderInTestApp } from '@backstage/test-utils';
import { StyledTab } from './Tab';
describe('', () => {
- it('renders without exploding', () => {
- const rendered = render(wrapInTestApp());
+ it('renders without exploding', async () => {
+ const rendered = await renderInTestApp();
expect(rendered.getByText('test')).toBeInTheDocument();
});
});
diff --git a/packages/core/src/components/TrendLine/TrendLine.test.tsx b/packages/core/src/components/TrendLine/TrendLine.test.tsx
index dbd8adfccf..f3d658265b 100644
--- a/packages/core/src/components/TrendLine/TrendLine.test.tsx
+++ b/packages/core/src/components/TrendLine/TrendLine.test.tsx
@@ -17,24 +17,24 @@
/* eslint-disable jest/no-disabled-tests */
import React from 'react';
import { render } from '@testing-library/react';
-import { wrapInTestApp } from '@backstage/test-utils';
+import { renderInTestApp, wrapInTestApp } from '@backstage/test-utils';
import { TrendLine } from './TrendLine';
describe('TrendLine', () => {
describe('when no data is present', () => {
- it('renders null without throwing', () => {
- const rendered = render(
- wrapInTestApp(),
+ it('renders null without throwing', async () => {
+ const rendered = await renderInTestApp(
+ ,
);
expect(rendered.queryByTitle('sparkline')).not.toBeInTheDocument();
});
});
describe('when one datapoint is present', () => {
- it('renders as a straight line', () => {
- const rendered = render(
- wrapInTestApp(),
+ it('renders as a straight line', async () => {
+ const rendered = await renderInTestApp(
+ ,
);
expect(rendered.getByTitle('sparkline')).toBeInTheDocument();
});
diff --git a/packages/core/src/components/WarningPanel/WarningPanel.test.tsx b/packages/core/src/components/WarningPanel/WarningPanel.test.tsx
index 493b143bbf..07a25d34c8 100644
--- a/packages/core/src/components/WarningPanel/WarningPanel.test.tsx
+++ b/packages/core/src/components/WarningPanel/WarningPanel.test.tsx
@@ -15,22 +15,21 @@
*/
import React from 'react';
-import { render } from '@testing-library/react';
-import { wrapInTestApp } from '@backstage/test-utils';
+import { renderInTestApp } from '@backstage/test-utils';
import { WarningPanel } from './WarningPanel';
const minProps = { title: 'Mock title', message: 'Some more info' };
describe('', () => {
- it('renders without exploding', () => {
- const { getByText } = render(wrapInTestApp());
+ it('renders without exploding', async () => {
+ const { getByText } = await renderInTestApp();
expect(getByText('Mock title')).toBeInTheDocument();
});
- it('renders message and children', () => {
- const { getByText } = render(
- wrapInTestApp(children),
+ it('renders message and children', async () => {
+ const { getByText } = await renderInTestApp(
+ children,
);
expect(getByText('Some more info')).toBeInTheDocument();
expect(getByText('children')).toBeInTheDocument();
diff --git a/packages/core/src/layout/BottomLink/BottomLink.test.tsx b/packages/core/src/layout/BottomLink/BottomLink.test.tsx
index 9871e18ebb..92ae2ecb3b 100644
--- a/packages/core/src/layout/BottomLink/BottomLink.test.tsx
+++ b/packages/core/src/layout/BottomLink/BottomLink.test.tsx
@@ -15,8 +15,7 @@
*/
import React from 'react';
-import { render } from '@testing-library/react';
-import { wrapInTestApp } from '@backstage/test-utils';
+import { renderInTestApp } from '@backstage/test-utils';
import { BottomLink } from './BottomLink';
const minProps = {
@@ -25,8 +24,8 @@ const minProps = {
};
describe('', () => {
- it('renders without exploding', () => {
- const rendered = render(wrapInTestApp());
+ it('renders without exploding', async () => {
+ const rendered = await renderInTestApp();
expect(rendered.getByText('A deepLink title')).toBeInTheDocument();
});
});
diff --git a/packages/core/src/layout/ContentHeader/ContentHeader.test.tsx b/packages/core/src/layout/ContentHeader/ContentHeader.test.tsx
index 5db676dcc5..b28ad614bf 100644
--- a/packages/core/src/layout/ContentHeader/ContentHeader.test.tsx
+++ b/packages/core/src/layout/ContentHeader/ContentHeader.test.tsx
@@ -15,9 +15,8 @@
*/
import React from 'react';
-import { render } from '@testing-library/react';
import { ContentHeader } from './ContentHeader';
-import { wrapInTestApp } from '@backstage/test-utils';
+import { renderInTestApp } from '@backstage/test-utils';
jest.mock('react-helmet', () => {
return {
@@ -26,23 +25,23 @@ jest.mock('react-helmet', () => {
});
describe('', () => {
- it('should render with title', () => {
- const rendered = render(wrapInTestApp());
+ it('should render with title', async () => {
+ const rendered = await renderInTestApp();
rendered.getByText('Title');
});
- it('should render with titleComponent', () => {
+ it('should render with titleComponent', async () => {
const title = 'Custom title';
const titleComponent = () => {title}
;
- const rendered = render(
- wrapInTestApp(),
+ const rendered = await renderInTestApp(
+ ,
);
rendered.getByText(title);
});
- it('should render with description', () => {
- const rendered = render(
- wrapInTestApp(),
+ it('should render with description', async () => {
+ const rendered = await renderInTestApp(
+ ,
);
rendered.getByText('description');
});
diff --git a/packages/core/src/layout/ErrorPage/ErrorPage.test.tsx b/packages/core/src/layout/ErrorPage/ErrorPage.test.tsx
index c47ff8d49e..91322ee2a5 100644
--- a/packages/core/src/layout/ErrorPage/ErrorPage.test.tsx
+++ b/packages/core/src/layout/ErrorPage/ErrorPage.test.tsx
@@ -15,14 +15,13 @@
*/
import React from 'react';
-import { render } from '@testing-library/react';
import { ErrorPage } from './ErrorPage';
-import { wrapInTestApp } from '@backstage/test-utils';
+import { renderInTestApp } from '@backstage/test-utils';
describe('', () => {
- it('should render with status code, status message and go back link', () => {
- const rendered = render(
- wrapInTestApp(),
+ it('should render with status code, status message and go back link', async () => {
+ const rendered = await renderInTestApp(
+ ,
);
rendered.getByText(/page not found/i);
rendered.getByText(/404/i);
diff --git a/packages/core/src/layout/ErrorPage/MicDrop.jsx b/packages/core/src/layout/ErrorPage/MicDrop.tsx
similarity index 100%
rename from packages/core/src/layout/ErrorPage/MicDrop.jsx
rename to packages/core/src/layout/ErrorPage/MicDrop.tsx
diff --git a/packages/core/src/layout/Header/Header.test.tsx b/packages/core/src/layout/Header/Header.test.tsx
index 320592c218..51468f6ab5 100644
--- a/packages/core/src/layout/Header/Header.test.tsx
+++ b/packages/core/src/layout/Header/Header.test.tsx
@@ -15,8 +15,7 @@
*/
import React from 'react';
-import { render } from '@testing-library/react';
-import { wrapInTestApp } from '@backstage/test-utils';
+import { renderInTestApp } from '@backstage/test-utils';
import { Header } from './Header';
jest.mock('react-helmet', () => {
@@ -26,42 +25,42 @@ jest.mock('react-helmet', () => {
});
describe('', () => {
- it('should render with title', () => {
- const rendered = render(wrapInTestApp());
+ it('should render with title', async () => {
+ const rendered = await renderInTestApp();
rendered.getByText('Title');
});
- it('should set document title', () => {
- const rendered = render(wrapInTestApp());
+ it('should set document title', async () => {
+ const rendered = await renderInTestApp();
rendered.getByText('Title1');
rendered.getByText('defaultTitle: Title1 | Backstage');
});
- it('should override document title', () => {
- const rendered = render(
- wrapInTestApp(),
+ it('should override document title', async () => {
+ const rendered = await renderInTestApp(
+ ,
);
rendered.getByText('Title1');
rendered.getByText('defaultTitle: Title2 | Backstage');
});
- it('should have subtitle', () => {
- const rendered = render(
- wrapInTestApp(),
+ it('should have subtitle', async () => {
+ const rendered = await renderInTestApp(
+ ,
);
rendered.getByText('Subtitle');
});
- it('should have type rendered', () => {
- const rendered = render(
- wrapInTestApp(),
+ it('should have type rendered', async () => {
+ const rendered = await renderInTestApp(
+ ,
);
rendered.getByText('tool');
});
- it('should have breadcrumb rendered', () => {
- const rendered = render(
- wrapInTestApp(),
+ it('should have breadcrumb rendered', async () => {
+ const rendered = await renderInTestApp(
+ ,
);
rendered.getAllByText('Title');
});
diff --git a/packages/core/src/layout/HeaderActionMenu/HeaderActionMenu.test.tsx b/packages/core/src/layout/HeaderActionMenu/HeaderActionMenu.test.tsx
index 00fa4d27ac..76ede5398d 100644
--- a/packages/core/src/layout/HeaderActionMenu/HeaderActionMenu.test.tsx
+++ b/packages/core/src/layout/HeaderActionMenu/HeaderActionMenu.test.tsx
@@ -16,22 +16,24 @@
import React from 'react';
import { render, fireEvent } from '@testing-library/react';
-import { wrapInTestApp, Keyboard } from '@backstage/test-utils';
+import {
+ wrapInTestApp,
+ Keyboard,
+ renderInTestApp,
+} from '@backstage/test-utils';
import { HeaderActionMenu } from './HeaderActionMenu';
describe('', () => {
- it('renders without any items and without exploding', () => {
- render(wrapInTestApp());
+ it('renders without any items and without exploding', async () => {
+ await renderInTestApp();
});
- it('can open the menu and click menu items', () => {
+ it('can open the menu and click menu items', async () => {
const onClickFunction = jest.fn();
- const rendered = render(
- wrapInTestApp(
- ,
- ),
+ const rendered = await renderInTestApp(
+ ,
);
expect(rendered.queryByText('Some label')).not.toBeInTheDocument();
expect(onClickFunction).not.toHaveBeenCalled();
@@ -48,12 +50,10 @@ describe('', () => {
});
it('Disabled', async () => {
- const rendered = render(
- wrapInTestApp(
- ,
- ),
+ const rendered = await renderInTestApp(
+ ,
);
fireEvent.click(rendered.getByTestId('header-action-menu'));
@@ -63,22 +63,20 @@ describe('', () => {
);
});
- it('Test wrapper, and secondary label', () => {
+ it('Test wrapper, and secondary label', async () => {
const onClickFunction = jest.fn();
- const rendered = render(
- wrapInTestApp(
- (
-
- ),
- },
- ]}
- />,
- ),
+ const rendered = await renderInTestApp(
+ (
+
+ ),
+ },
+ ]}
+ />,
);
expect(onClickFunction).not.toHaveBeenCalled();
diff --git a/packages/core/src/layout/HeaderLabel/HeaderLabel.test.tsx b/packages/core/src/layout/HeaderLabel/HeaderLabel.test.tsx
index fdc6ef8c6e..acadb22525 100644
--- a/packages/core/src/layout/HeaderLabel/HeaderLabel.test.tsx
+++ b/packages/core/src/layout/HeaderLabel/HeaderLabel.test.tsx
@@ -15,38 +15,37 @@
*/
import React from 'react';
-import { render } from '@testing-library/react';
-import { wrapInTestApp } from '@backstage/test-utils';
+import { renderInTestApp } from '@backstage/test-utils';
import { HeaderLabel } from './HeaderLabel';
describe('', () => {
- it('should have a label', () => {
- const rendered = render(wrapInTestApp());
+ it('should have a label', async () => {
+ const rendered = await renderInTestApp();
expect(rendered.getByText('Label')).toBeInTheDocument();
});
- it('should say unknown', () => {
- const rendered = render(wrapInTestApp());
+ it('should say unknown', async () => {
+ const rendered = await renderInTestApp();
expect(rendered.getByText('')).toBeInTheDocument();
});
- it('should say unknown when passing null as value prop', () => {
- const rendered = render(
- wrapInTestApp(),
+ it('should say unknown when passing null as value prop', async () => {
+ const rendered = await renderInTestApp(
+ ,
);
expect(rendered.getByText('')).toBeInTheDocument();
});
- it('should have value', () => {
- const rendered = render(
- wrapInTestApp(),
+ it('should have value', async () => {
+ const rendered = await renderInTestApp(
+ ,
);
expect(rendered.getByText('Value')).toBeInTheDocument();
});
- it('should have a link', () => {
- const rendered = render(
- wrapInTestApp(),
+ it('should have a link', async () => {
+ const rendered = await renderInTestApp(
+ ,
);
const anchor = rendered.container.querySelector('a') as HTMLAnchorElement;
expect(rendered.getByText('Value')).toBeInTheDocument();
diff --git a/packages/core/src/layout/HeaderTabs/HeaderTabs.test.tsx b/packages/core/src/layout/HeaderTabs/HeaderTabs.test.tsx
index a007bf9113..81d2123049 100644
--- a/packages/core/src/layout/HeaderTabs/HeaderTabs.test.tsx
+++ b/packages/core/src/layout/HeaderTabs/HeaderTabs.test.tsx
@@ -15,8 +15,7 @@
*/
import React from 'react';
-import { render } from '@testing-library/react';
-import { wrapInTestApp } from '@backstage/test-utils';
+import { renderInTestApp } from '@backstage/test-utils';
import { HeaderTabs } from './';
const mockTabs = [
@@ -25,15 +24,15 @@ const mockTabs = [
];
describe('', () => {
- it('should render tabs', () => {
- const rendered = render(wrapInTestApp());
+ it('should render tabs', async () => {
+ const rendered = await renderInTestApp();
expect(rendered.getByText('Overview')).toBeInTheDocument();
expect(rendered.getByText('Docs')).toBeInTheDocument();
});
- it('should render correct selected tab', () => {
- const rendered = render(wrapInTestApp());
+ it('should render correct selected tab', async () => {
+ const rendered = await renderInTestApp();
expect(rendered.getByText('Docs').parentElement).toHaveAttribute(
'aria-selected',
diff --git a/packages/core/src/layout/InfoCard/InfoCard.test.tsx b/packages/core/src/layout/InfoCard/InfoCard.test.tsx
index d58fdb2ccb..f82d51fe06 100644
--- a/packages/core/src/layout/InfoCard/InfoCard.test.tsx
+++ b/packages/core/src/layout/InfoCard/InfoCard.test.tsx
@@ -15,8 +15,7 @@
*/
import React from 'react';
-import { render } from '@testing-library/react';
-import { wrapInTestApp } from '@backstage/test-utils';
+import { renderInTestApp } from '@backstage/test-utils';
import { InfoCard } from './InfoCard';
const minProps = {
@@ -28,13 +27,13 @@ const minProps = {
};
describe('', () => {
- it('renders without exploding', () => {
- const rendered = render(wrapInTestApp());
+ it('renders without exploding', async () => {
+ const rendered = await renderInTestApp();
expect(rendered.getByText('Some title')).toBeInTheDocument();
});
- it('renders a deepLink when prop is set', () => {
- const rendered = render(wrapInTestApp());
+ it('renders a deepLink when prop is set', async () => {
+ const rendered = await renderInTestApp();
expect(rendered.getByText('A deepLink title')).toBeInTheDocument();
});
});
diff --git a/packages/core/src/layout/InfoCard/InfoCard.tsx b/packages/core/src/layout/InfoCard/InfoCard.tsx
index 1a4ceb024b..d13a94375f 100644
--- a/packages/core/src/layout/InfoCard/InfoCard.tsx
+++ b/packages/core/src/layout/InfoCard/InfoCard.tsx
@@ -70,6 +70,15 @@ const VARIANT_STYLES = {
flexDirection: 'column',
height: '100%',
},
+ gridItem: {
+ display: 'flex',
+ flexDirection: 'column',
+ height: 'calc(100% - 10px)', // for pages without content header
+ marginBottom: '10px',
+ },
+ /**
+ * @deprecated This variant is replaced by 'gridItem'.
+ */
height100: {
display: 'flex',
flexDirection: 'column',
@@ -81,9 +90,15 @@ const VARIANT_STYLES = {
fullHeight: {
flex: 1,
},
+ /**
+ * @deprecated This variant is replaced by 'gridItem'.
+ */
height100: {
flex: 1,
},
+ gridItem: {
+ flex: 1,
+ },
},
};
@@ -100,9 +115,10 @@ const VARIANT_STYLES = {
* By default the InfoCard has no custom layout of its children, but is treated as a block element. A
* couple common variants are provided and can be specified via the variant property:
*
- * Display the card full height suitable for DataGrid:
+ * When the InfoCard is displayed as a grid item within a grid, you may want items to have the same height for all items.
+ * Set to the 'gridItem' variant to display the InfoCard with full height suitable for Grid:
*
- * ...
+ * ...
*/
type Props = {
title?: ReactNode;
@@ -142,17 +158,21 @@ export const InfoCard = ({
noPadding,
}: Props): JSX.Element => {
const classes = useStyles();
-
/**
* If variant is specified, we build up styles for that particular variant for both
* the Card and the CardContent (since these need to be synced)
*/
let calculatedStyle = {};
let calculatedCardStyle = {};
-
if (variant) {
const variants = variant.split(/[\s]+/g);
variants.forEach(name => {
+ if (name === 'height100') {
+ // eslint-disable-next-line no-console
+ console.warn(
+ "Variant 'height100' of InfoCard is deprecated. Use variant 'gridItem' instead.",
+ );
+ }
calculatedStyle = {
...calculatedStyle,
...VARIANT_STYLES.card[name as keyof typeof VARIANT_STYLES['card']],
diff --git a/packages/core/src/layout/TabbedCard/TabbedCard.test.tsx b/packages/core/src/layout/TabbedCard/TabbedCard.test.tsx
index ceeae8d49d..89abdfdd53 100644
--- a/packages/core/src/layout/TabbedCard/TabbedCard.test.tsx
+++ b/packages/core/src/layout/TabbedCard/TabbedCard.test.tsx
@@ -16,7 +16,7 @@
import React from 'react';
import { render, fireEvent } from '@testing-library/react';
-import { wrapInTestApp } from '@backstage/test-utils';
+import { renderInTestApp, wrapInTestApp } from '@backstage/test-utils';
import { TabbedCard, CardTab } from '.';
const minProps = {
@@ -28,38 +28,32 @@ const minProps = {
};
describe('', () => {
- it('renders without exploding', () => {
- const rendered = render(
- wrapInTestApp(
-
- Test Content
- Test Content
- ,
- ),
+ it('renders without exploding', async () => {
+ const rendered = await renderInTestApp(
+
+ Test Content
+ Test Content
+ ,
);
expect(rendered.getByText('Some title')).toBeInTheDocument();
});
- it('renders a deepLink when prop is set', () => {
- const rendered = render(
- wrapInTestApp(
-
- Test Content
- Test Content
- ,
- ),
+ it('renders a deepLink when prop is set', async () => {
+ const rendered = await renderInTestApp(
+
+ Test Content
+ Test Content
+ ,
);
expect(rendered.getByText('A deepLink title')).toBeInTheDocument();
});
- it('switches tabs when clicking', () => {
- const rendered = render(
- wrapInTestApp(
-
- Test Content 1
- Test Content 2
- ,
- ),
+ it('switches tabs when clicking', async () => {
+ const rendered = await renderInTestApp(
+
+ Test Content 1
+ Test Content 2
+ ,
);
expect(rendered.getByText('Test Content 1')).toBeInTheDocument();
diff --git a/packages/create-app/templates/default-app/packages/app/src/components/catalog/EntityPage.tsx b/packages/create-app/templates/default-app/packages/app/src/components/catalog/EntityPage.tsx
index 5480d8218a..7d65b3261a 100644
--- a/packages/create-app/templates/default-app/packages/app/src/components/catalog/EntityPage.tsx
+++ b/packages/create-app/templates/default-app/packages/app/src/components/catalog/EntityPage.tsx
@@ -53,9 +53,9 @@ const CICDSwitcher = ({ entity }: { entity: Entity }) => {
};
const OverviewContent = ({ entity }: { entity: Entity }) => (
-
+
-
+
);
@@ -77,7 +77,7 @@ const ServiceEntityPage = ({ entity }: { entity: Entity }) => (
title="API"
element={}
/>
- }
@@ -97,7 +97,7 @@ const WebsiteEntityPage = ({ entity }: { entity: Entity }) => (
title="CI/CD"
element={}
/>
- }
@@ -112,7 +112,7 @@ const DefaultEntityPage = ({ entity }: { entity: Entity }) => (
title="Overview"
element={}
/>
- }
diff --git a/plugins/catalog-backend/src/ingestion/LocationReaders.ts b/plugins/catalog-backend/src/ingestion/LocationReaders.ts
index c36b3d2b05..e23a4524b6 100644
--- a/plugins/catalog-backend/src/ingestion/LocationReaders.ts
+++ b/plugins/catalog-backend/src/ingestion/LocationReaders.ts
@@ -17,6 +17,7 @@
import { UrlReader } from '@backstage/backend-common';
import {
Entity,
+ EntityPolicy,
ENTITY_DEFAULT_NAMESPACE,
LocationSpec,
} from '@backstage/catalog-model';
@@ -44,6 +45,7 @@ type Options = {
config: Config;
processors: CatalogProcessor[];
rulesEnforcer: CatalogRulesEnforcer;
+ policy: EntityPolicy;
};
/**
@@ -74,10 +76,12 @@ export class LocationReaders implements LocationReader {
} else if (item.type === 'entity') {
if (rulesEnforcer.isAllowed(item.entity, item.location)) {
const entity = await this.handleEntity(item, emit);
- output.entities.push({
- entity,
- location: item.location,
- });
+ if (entity) {
+ output.entities.push({
+ entity,
+ location: item.location,
+ });
+ }
} else {
output.errors.push({
location: item.location,
@@ -162,25 +166,65 @@ export class LocationReaders implements LocationReader {
private async handleEntity(
item: CatalogProcessorEntityResult,
emit: CatalogProcessorEmit,
- ): Promise {
+ ): Promise {
const { processors, logger } = this.options;
let current = item.entity;
+ // Construct the name carefully, this happens before validation below
+ // so we do not want to crash here due to missing metadata or so
+ const kind = current.kind || '';
+ const namespace = !current.metadata
+ ? ''
+ : current.metadata.namespace ?? ENTITY_DEFAULT_NAMESPACE;
+ const name = !current.metadata ? '' : current.metadata.name;
+
for (const processor of processors) {
- if (processor.processEntity) {
+ if (processor.preProcessEntity) {
try {
- current = await processor.processEntity(current, item.location, emit);
+ current = await processor.preProcessEntity(
+ current,
+ item.location,
+ emit,
+ );
} catch (e) {
- // Construct the name carefully, if we got validation errors we do
- // not want to crash here due to missing metadata or so
- const namespace = !current.metadata
- ? ''
- : current.metadata.namespace ?? ENTITY_DEFAULT_NAMESPACE;
- const name = !current.metadata ? '' : current.metadata.name;
- const message = `Processor ${processor.constructor.name} threw an error while processing entity ${current.kind}:${namespace}/${name} at ${item.location.type} ${item.location.target}, ${e}`;
+ const message = `Processor ${processor.constructor.name} threw an error while preprocessing entity ${kind}:${namespace}/${name} at ${item.location.type} ${item.location.target}, ${e}`;
emit(result.generalError(item.location, message));
logger.warn(message);
+ return undefined;
+ }
+ }
+ }
+
+ try {
+ const next = await this.options.policy.enforce(current);
+ if (!next) {
+ const message = `Policy unexpectedly returned no data while analyzing entity ${kind}:${namespace}/${name} at ${item.location.type} ${item.location.target}`;
+ emit(result.generalError(item.location, message));
+ logger.warn(message);
+ return undefined;
+ }
+ current = next;
+ } catch (e) {
+ const message = `Policy check failed while analyzing entity ${kind}:${namespace}/${name} at ${item.location.type} ${item.location.target}, ${e}`;
+ emit(result.inputError(item.location, message));
+ logger.warn(message);
+ return undefined;
+ }
+
+ for (const processor of processors) {
+ if (processor.postProcessEntity) {
+ try {
+ current = await processor.postProcessEntity(
+ current,
+ item.location,
+ emit,
+ );
+ } catch (e) {
+ const message = `Processor ${processor.constructor.name} threw an error while postprocessing entity ${kind}:${namespace}/${name} at ${item.location.type} ${item.location.target}, ${e}`;
+ emit(result.generalError(item.location, message));
+ logger.warn(message);
+ return undefined;
}
}
}
diff --git a/plugins/catalog-backend/src/ingestion/processors/AnnotateLocationEntityProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/AnnotateLocationEntityProcessor.ts
index fea70b6c5e..ea8afcddc5 100644
--- a/plugins/catalog-backend/src/ingestion/processors/AnnotateLocationEntityProcessor.ts
+++ b/plugins/catalog-backend/src/ingestion/processors/AnnotateLocationEntityProcessor.ts
@@ -19,7 +19,10 @@ import lodash from 'lodash';
import { CatalogProcessor } from './types';
export class AnnotateLocationEntityProcessor implements CatalogProcessor {
- async processEntity(entity: Entity, location: LocationSpec): Promise {
+ async preProcessEntity(
+ entity: Entity,
+ location: LocationSpec,
+ ): Promise {
return lodash.merge(
{
metadata: {
diff --git a/plugins/catalog-backend/src/ingestion/processors/CodeOwnersProcessor.test.ts b/plugins/catalog-backend/src/ingestion/processors/CodeOwnersProcessor.test.ts
index e1bd5bcb46..622af9ea59 100644
--- a/plugins/catalog-backend/src/ingestion/processors/CodeOwnersProcessor.test.ts
+++ b/plugins/catalog-backend/src/ingestion/processors/CodeOwnersProcessor.test.ts
@@ -224,7 +224,7 @@ describe('CodeOwnersProcessor', () => {
spec: { owner: '@acme/foo-team' },
});
- const result = await processor.processEntity(
+ const result = await processor.preProcessEntity(
entity as any,
mockLocation(),
);
@@ -235,7 +235,7 @@ describe('CodeOwnersProcessor', () => {
it('should ignore url locations', async () => {
const { entity, processor } = setupTest();
- const result = await processor.processEntity(
+ const result = await processor.preProcessEntity(
entity as any,
mockLocation({ type: 'url' }),
);
@@ -246,7 +246,7 @@ describe('CodeOwnersProcessor', () => {
it('should ignore invalid kinds', async () => {
const { entity, processor } = setupTest({ kind: 'Group' });
- const result = await processor.processEntity(
+ const result = await processor.preProcessEntity(
entity as any,
mockLocation(),
);
@@ -257,7 +257,7 @@ describe('CodeOwnersProcessor', () => {
it('should set owner from codeowner', async () => {
const { entity, processor } = setupTest();
- const result = await processor.processEntity(
+ const result = await processor.preProcessEntity(
entity as any,
mockLocation(),
);
diff --git a/plugins/catalog-backend/src/ingestion/processors/CodeOwnersProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/CodeOwnersProcessor.ts
index 1802e01d99..bc78b6e34a 100644
--- a/plugins/catalog-backend/src/ingestion/processors/CodeOwnersProcessor.ts
+++ b/plugins/catalog-backend/src/ingestion/processors/CodeOwnersProcessor.ts
@@ -40,7 +40,10 @@ type Options = {
export class CodeOwnersProcessor implements CatalogProcessor {
constructor(private readonly options: Options) {}
- async processEntity(entity: Entity, location: LocationSpec): Promise {
+ async preProcessEntity(
+ entity: Entity,
+ location: LocationSpec,
+ ): Promise {
// Only continue if the owner is not set
if (
!entity ||
diff --git a/plugins/catalog-backend/src/ingestion/processors/EntityPolicyProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/EntityPolicyProcessor.ts
deleted file mode 100644
index 87543d91f4..0000000000
--- a/plugins/catalog-backend/src/ingestion/processors/EntityPolicyProcessor.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright 2020 Spotify AB
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import { Entity, EntityPolicy } from '@backstage/catalog-model';
-import { CatalogProcessor } from './types';
-
-export class EntityPolicyProcessor implements CatalogProcessor {
- private readonly policy: EntityPolicy;
-
- constructor(policy: EntityPolicy) {
- this.policy = policy;
- }
-
- async processEntity(entity: Entity): Promise {
- const output = await this.policy.enforce(entity);
- if (!output) {
- throw new Error(`Entity did not match any known schema`);
- }
- return output;
- }
-}
diff --git a/plugins/catalog-backend/src/ingestion/processors/LocationEntityProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/LocationEntityProcessor.ts
index 1917f58fbd..86e17f07f2 100644
--- a/plugins/catalog-backend/src/ingestion/processors/LocationEntityProcessor.ts
+++ b/plugins/catalog-backend/src/ingestion/processors/LocationEntityProcessor.ts
@@ -19,7 +19,7 @@ import * as result from './results';
import { CatalogProcessor, CatalogProcessorEmit } from './types';
export class LocationRefProcessor implements CatalogProcessor {
- async processEntity(
+ async postProcessEntity(
entity: Entity,
_location: LocationSpec,
emit: CatalogProcessorEmit,
diff --git a/plugins/catalog-backend/src/ingestion/processors/PlaceholderProcessor.test.ts b/plugins/catalog-backend/src/ingestion/processors/PlaceholderProcessor.test.ts
index 294cb10f29..44f3163a97 100644
--- a/plugins/catalog-backend/src/ingestion/processors/PlaceholderProcessor.test.ts
+++ b/plugins/catalog-backend/src/ingestion/processors/PlaceholderProcessor.test.ts
@@ -46,7 +46,7 @@ describe('PlaceholderProcessor', () => {
reader,
});
await expect(
- processor.processEntity(input, { type: 't', target: 'l' }),
+ processor.preProcessEntity(input, { type: 't', target: 'l' }),
).resolves.toBe(input);
});
@@ -62,7 +62,7 @@ describe('PlaceholderProcessor', () => {
});
await expect(
- processor.processEntity(
+ processor.preProcessEntity(
{
apiVersion: 'a',
kind: 'k',
@@ -98,7 +98,7 @@ describe('PlaceholderProcessor', () => {
});
await expect(
- processor.processEntity(
+ processor.preProcessEntity(
{
apiVersion: 'a',
kind: 'k',
@@ -122,7 +122,7 @@ describe('PlaceholderProcessor', () => {
});
await expect(
- processor.processEntity(
+ processor.preProcessEntity(
{
apiVersion: 'a',
kind: 'k',
@@ -143,7 +143,7 @@ describe('PlaceholderProcessor', () => {
});
await expect(
- processor.processEntity(
+ processor.preProcessEntity(
{
apiVersion: 'a',
kind: 'k',
@@ -177,7 +177,7 @@ describe('PlaceholderProcessor', () => {
});
await expect(
- processor.processEntity(
+ processor.preProcessEntity(
{
apiVersion: 'a',
kind: 'k',
@@ -209,7 +209,7 @@ describe('PlaceholderProcessor', () => {
});
await expect(
- processor.processEntity(
+ processor.preProcessEntity(
{
apiVersion: 'a',
kind: 'k',
@@ -241,7 +241,7 @@ describe('PlaceholderProcessor', () => {
});
await expect(
- processor.processEntity(
+ processor.preProcessEntity(
{
apiVersion: 'a',
kind: 'k',
@@ -277,7 +277,7 @@ describe('PlaceholderProcessor', () => {
});
await expect(
- processor.processEntity(
+ processor.preProcessEntity(
{
apiVersion: 'a',
kind: 'k',
@@ -316,7 +316,7 @@ describe('PlaceholderProcessor', () => {
});
await expect(
- processor.processEntity(
+ processor.preProcessEntity(
{
apiVersion: 'a',
kind: 'k',
diff --git a/plugins/catalog-backend/src/ingestion/processors/PlaceholderProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/PlaceholderProcessor.ts
index 3941a2bedc..0e23d51f61 100644
--- a/plugins/catalog-backend/src/ingestion/processors/PlaceholderProcessor.ts
+++ b/plugins/catalog-backend/src/ingestion/processors/PlaceholderProcessor.ts
@@ -45,7 +45,10 @@ type Options = {
export class PlaceholderProcessor implements CatalogProcessor {
constructor(private readonly options: Options) {}
- async processEntity(entity: Entity, location: LocationSpec): Promise {
+ async preProcessEntity(
+ entity: Entity,
+ location: LocationSpec,
+ ): Promise {
const process = async (data: any): Promise<[any, boolean]> => {
if (!data || !(data instanceof Object)) {
// Scalars can't have placeholders
diff --git a/plugins/catalog-backend/src/ingestion/processors/index.ts b/plugins/catalog-backend/src/ingestion/processors/index.ts
index f966ef3348..59a87af555 100644
--- a/plugins/catalog-backend/src/ingestion/processors/index.ts
+++ b/plugins/catalog-backend/src/ingestion/processors/index.ts
@@ -23,7 +23,6 @@ export { AnnotateLocationEntityProcessor } from './AnnotateLocationEntityProcess
export { AzureApiReaderProcessor } from './AzureApiReaderProcessor';
export { BitbucketApiReaderProcessor } from './BitbucketApiReaderProcessor';
export { CodeOwnersProcessor } from './CodeOwnersProcessor';
-export { EntityPolicyProcessor } from './EntityPolicyProcessor';
export { FileReaderProcessor } from './FileReaderProcessor';
export { GithubOrgReaderProcessor } from './GithubOrgReaderProcessor';
export { GithubReaderProcessor } from './GithubReaderProcessor';
diff --git a/plugins/catalog-backend/src/ingestion/processors/types.ts b/plugins/catalog-backend/src/ingestion/processors/types.ts
index 0e6b1491a6..c9adf07eff 100644
--- a/plugins/catalog-backend/src/ingestion/processors/types.ts
+++ b/plugins/catalog-backend/src/ingestion/processors/types.ts
@@ -46,15 +46,33 @@ export type CatalogProcessor = {
): Promise;
/**
- * Processes an emitted entity, e.g. by validating or modifying it.
+ * Pre-processes an emitted entity, after it has been emitted but before it
+ * has been validated.
*
- * @param entity The entity to process
+ * This type of processing usually involves enriching the entity with
+ * additional data, and the input entity may actually still be incomplete
+ * when the processor is invoked.
+ *
+ * @param entity The (possibly partial) entity to process
* @param location The location that the entity came from
- * @param read Reads the contents of a location
* @param emit A sink for auxiliary items resulting from the processing
* @returns The same entity or a modified version of it
*/
- processEntity?(
+ preProcessEntity?(
+ entity: Entity,
+ location: LocationSpec,
+ emit: CatalogProcessorEmit,
+ ): Promise;
+
+ /**
+ * Post-processes an emitted entity, after it has been validated.
+ *
+ * @param entity The entity to process
+ * @param location The location that the entity came from
+ * @param emit A sink for auxiliary items resulting from the processing
+ * @returns The same entity or a modified version of it
+ */
+ postProcessEntity?(
entity: Entity,
location: LocationSpec,
emit: CatalogProcessorEmit,
diff --git a/plugins/catalog-backend/src/service/CatalogBuilder.test.ts b/plugins/catalog-backend/src/service/CatalogBuilder.test.ts
index 66e7dba682..3f7c613c00 100644
--- a/plugins/catalog-backend/src/service/CatalogBuilder.test.ts
+++ b/plugins/catalog-backend/src/service/CatalogBuilder.test.ts
@@ -49,49 +49,6 @@ describe('CatalogBuilder', () => {
reader.read.mockResolvedValue(Buffer.from('junk'));
const builder = new CatalogBuilder(env)
- .replaceReaderProcessors([
- {
- async readLocation(
- location: LocationSpec,
- _optional: boolean,
- emit: CatalogProcessorEmit,
- ) {
- expect(location.type).toBe('test');
- emit(result.data(location, await reader.read('ignored')));
- return true;
- },
- },
- ])
- .replaceParserProcessors([
- {
- async parseData(
- data: Buffer,
- location: LocationSpec,
- emit: CatalogProcessorEmit,
- ) {
- expect(data.toString()).toEqual('junk');
- emit(
- result.entity(location, {
- apiVersion: 'av',
- kind: 'Component',
- metadata: { name: 'n' },
- }),
- );
- return true;
- },
- },
- ])
- .replacePreProcessors([
- {
- async processEntity(entity: Entity) {
- expect(entity.apiVersion).toBe('av');
- return {
- ...entity,
- metadata: { ...entity.metadata, namespace: 'ns' },
- };
- },
- },
- ])
.replaceEntityPolicies([
{
async enforce(entity: Entity) {
@@ -108,9 +65,51 @@ describe('CatalogBuilder', () => {
},
},
])
- .replacePostProcessors([
+ .setPlaceholderResolver('t', async ({ value }) => {
+ expect(value).toBe('tt');
+ return 'tt2';
+ })
+ .setFieldFormatValidators({
+ isValidEntityName: n => {
+ expect(n).toBe('n');
+ return true;
+ },
+ })
+ .replaceProcessors([
{
- async processEntity(entity: Entity) {
+ async readLocation(
+ location: LocationSpec,
+ _optional: boolean,
+ emit: CatalogProcessorEmit,
+ ) {
+ expect(location.type).toBe('test');
+ emit(result.data(location, await reader.read('ignored')));
+ return true;
+ },
+ async parseData(
+ data: Buffer,
+ location: LocationSpec,
+ emit: CatalogProcessorEmit,
+ ) {
+ expect(data.toString()).toEqual('junk');
+ emit(
+ result.entity(location, {
+ apiVersion: 'av',
+ kind: 'Component',
+ metadata: { name: 'n', replaced: { $t: 'tt' } },
+ }),
+ );
+ return true;
+ },
+ async preProcessEntity(entity: Entity) {
+ expect(entity.apiVersion).toBe('av');
+ return {
+ ...entity,
+ metadata: { ...entity.metadata, namespace: 'ns' },
+ };
+ },
+ async postProcessEntity(entity: Entity) {
+ expect(entity.metadata.namespace).toBe('ns');
return {
...entity,
metadata: { ...entity.metadata, post: 'p' },
@@ -124,6 +123,7 @@ describe('CatalogBuilder', () => {
type: 'test',
target: '',
});
+ expect.assertions(8);
expect(added.entities).toEqual([
{
apiVersion: 'av',
@@ -132,6 +132,7 @@ describe('CatalogBuilder', () => {
name: 'n',
namespace: 'ns',
post: 'p',
+ replaced: 'tt2',
}),
},
]);
diff --git a/plugins/catalog-backend/src/service/CatalogBuilder.ts b/plugins/catalog-backend/src/service/CatalogBuilder.ts
index aa4f97258d..e6fb540f16 100644
--- a/plugins/catalog-backend/src/service/CatalogBuilder.ts
+++ b/plugins/catalog-backend/src/service/CatalogBuilder.ts
@@ -26,7 +26,6 @@ import {
locationEntityV1alpha1Policy,
makeValidator,
NoForeignRootFieldsEntityPolicy,
- ReservedFieldsEntityPolicy,
SchemaValidEntityPolicy,
templateEntityV1alpha1Policy,
userEntityV1alpha1Policy,
@@ -48,7 +47,6 @@ import {
BitbucketApiReaderProcessor,
CatalogProcessor,
CodeOwnersProcessor,
- EntityPolicyProcessor,
FileReaderProcessor,
GithubOrgReaderProcessor,
GithubReaderProcessor,
@@ -84,36 +82,24 @@ export type CatalogEnvironment = {
*
* The touch points where you can replace or extend behavior are as follows:
*
- * - Reader processors can be added or replaced. These implement the
- * functionality of reading raw data from a location, in the form of an
- * entity definition file.
- * - Parser processors can be added or replaced. These accept the raw data as
- * read by the previous processors and parse it into raw structured data
- * (for example, from a binary buffer containing yaml text, to a JS object
- * structure).
+ * - Entity policies can be added or replaced. These are automatically run
+ * after the processors' pre-processing steps. All policies are given the
+ * chance to inspect the entity, and all of them have to pass in order for
+ * the entity to be considered valid from an overall point of view.
+ * - Entity kinds can be added or replaced. These are the second line of
+ * validation that is applied after the entity policies, which adds
+ * additional kind-specific validation (usually based on a schema). Only one
+ * of the entity kinds has to accept the entity, but if none of them do, the
+ * entity is rejected as a whole.
* - Placeholder resolvers can be replaced or added. These run on the raw
* structured data between the parsing and pre-processing steps, to replace
* dollar-prefixed entries with their actual values (like $file).
- * - Pre-processors can be added or replaced. These take the raw unvalidated
- * data from the parser processors and can enrich or extend it before
- * validation. This is the place where for example codeowners data can be
- * injected into partial entity definitions.
- * - Entity policies can be added or replaced. These are the first line of
- * validation from the output of the pre-processing step. All policies are
- * given the chance to inspect the entity, and all of them have to pass in
- * order for the entity to be considered valid from an overall point of
- * view.
* - Field format validators can be replaced. These check the format of
- * individual core fields such as metadata.name, such that they adhere to
- * certain rules.
- * - Entity kinds can be added or replaced. These are the second line of
- * validation that is applied after the entity policies, which add additional
- * kind-specific validation (usually based on a schema). Only one of the
- * entity kinds has to accept the entity, but if none of them do, the
- * entity is rejected as a whole.
- * - Post-processors can be added or replaced. These take the validated
- * entities out of the validation step and can perform additional actions on
- * them.
+ * individual core fields such as metadata.name, to ensure that they adhere
+ * to certain rules.
+ * - Processors can be added or replaced. These implement the functionality of
+ * reading, parsing and processing the entity data before it is persisted in
+ * the catalog.
*/
export class CatalogBuilder {
private readonly env: CatalogEnvironment;
@@ -121,16 +107,10 @@ export class CatalogBuilder {
private entityPoliciesReplace: boolean;
private entityKinds: EntityPolicy[];
private entityKindsReplace: boolean;
- private readerProcessors: CatalogProcessor[];
- private readerProcessorsReplace: boolean;
- private parserProcessors: CatalogProcessor[];
- private parserProcessorsReplace: boolean;
- private preProcessors: CatalogProcessor[];
- private preProcessorsReplace: boolean;
- private postProcessors: CatalogProcessor[];
- private postProcessorsReplace: boolean;
private placeholderResolvers: Record;
private fieldFormatValidators: Partial;
+ private processors: CatalogProcessor[];
+ private processorsReplace: boolean;
constructor(env: CatalogEnvironment) {
this.env = env;
@@ -138,16 +118,10 @@ export class CatalogBuilder {
this.entityPoliciesReplace = false;
this.entityKinds = [];
this.entityKindsReplace = false;
- this.readerProcessors = [];
- this.readerProcessorsReplace = false;
- this.parserProcessors = [];
- this.parserProcessorsReplace = false;
- this.preProcessors = [];
- this.preProcessorsReplace = false;
- this.postProcessors = [];
- this.postProcessorsReplace = false;
this.placeholderResolvers = {};
this.fieldFormatValidators = {};
+ this.processors = [];
+ this.processorsReplace = false;
}
/**
@@ -212,114 +186,6 @@ export class CatalogBuilder {
return this;
}
- /**
- * Adds processors that support reading of definition files. These are run
- * before the entities are parsed, pre-processed, validated and post-
- * processed.
- *
- * @param processors One or more processors
- */
- addReaderProcessor(...processors: CatalogProcessor[]): CatalogBuilder {
- this.readerProcessors.push(...processors);
- return this;
- }
-
- /**
- * Sets what processors to use for the reading of definition files. These are
- * run before the entities are parsed, pre-processed, validated and post-
- * processed.
- *
- * This function replaces the default set of processors in this stage; use
- * with care.
- *
- * @param processors One or more processors
- */
- replaceReaderProcessors(processors: CatalogProcessor[]): CatalogBuilder {
- this.readerProcessors = [...processors];
- this.readerProcessorsReplace = true;
- return this;
- }
-
- /**
- * Adds processors that run after each definition file has been read, in
- * order to parse the raw data. These are run before the entities are
- * pre-processed, validated and post-processed.
- *
- * @param processors One or more processors
- */
- addParserProcessor(...processors: CatalogProcessor[]): CatalogBuilder {
- this.parserProcessors.push(...processors);
- return this;
- }
-
- /**
- * Sets what processors to run after each definition file has been read, in
- * order to parse the raw data. These are run before the entities are
- * pre-processed, validated and post-processed.
- *
- * This function replaces the default set of processors in this stage; use
- * with care.
- *
- * @param processors One or more processors
- */
- replaceParserProcessors(processors: CatalogProcessor[]): CatalogBuilder {
- this.parserProcessors = [...processors];
- this.parserProcessorsReplace = true;
- return this;
- }
-
- /**
- * Adds processors that run after each entity has been read and parsed,
- * but before being validated and post-processed.
- *
- * @param processors One or more processors
- */
- addPreProcessor(...processors: CatalogProcessor[]): CatalogBuilder {
- this.preProcessors.push(...processors);
- return this;
- }
-
- /**
- * Sets what processors to run after each entity has been read and parsed,
- * but before being validated and post-processed.
- *
- * This function replaces the default set of processors in this stage; use
- * with care.
- *
- * @param processors One or more processors
- */
- replacePreProcessors(processors: CatalogProcessor[]): CatalogBuilder {
- this.preProcessors = [...processors];
- this.preProcessorsReplace = true;
- return this;
- }
-
- /**
- * Adds processors that run after each entity has been read, parsed,
- * run through the pre-processors, and validated.
- *
- * @param processors One or more processors
- */
- addPostProcessor(...processors: CatalogProcessor[]): CatalogBuilder {
- this.postProcessors.push(...processors);
- return this;
- }
-
- /**
- * Sets what processors to run after each entity has been read, parsed,
- * run through the pre-processors, and validated.
- *
- * This function replaces the default set of processors in this stage; use
- * with care.
- *
- * @param processors One or more processors
- */
- replacePostProcessors(processors: CatalogProcessor[]): CatalogBuilder {
- this.postProcessors = [...processors];
- this.postProcessorsReplace = true;
- return this;
- }
-
/**
* Adds, or overwrites, a handler for placeholders (e.g. $file) in entity
* definition files.
@@ -327,8 +193,12 @@ export class CatalogBuilder {
* @param key The key that identifies the placeholder, e.g. "file"
* @param resolver The resolver that gets values for this placeholder
*/
- setPlaceholderResolver(key: string, resolver: PlaceholderResolver) {
+ setPlaceholderResolver(
+ key: string,
+ resolver: PlaceholderResolver,
+ ): CatalogBuilder {
this.placeholderResolvers[key] = resolver;
+ return this;
}
/**
@@ -341,8 +211,34 @@ export class CatalogBuilder {
*
* @param validators The (subset of) validators to set
*/
- setFieldFormatValidators(validators: Partial) {
+ setFieldFormatValidators(validators: Partial): CatalogBuilder {
lodash.merge(this.fieldFormatValidators, validators);
+ return this;
+ }
+
+ /**
+ * Adds entity processors. These are responsible for reading, parsing, and
+ * processing entities before they are persisted in the catalog.
+ *
+ * @param processors One or more processors
+ */
+ addProcessor(...processors: CatalogProcessor[]): CatalogBuilder {
+ this.processors.push(...processors);
+ return this;
+ }
+
+ /**
+ * Sets what entity processors to use. These are responsible for reading,
+ * parsing, and processing entities before they are persisted in the catalog.
+ *
+ * This function replaces the default set of processors; use with care.
+ *
+ * @param processors One or more processors
+ */
+ replaceProcessors(processors: CatalogProcessor[]): CatalogBuilder {
+ this.processors = [...processors];
+ this.processorsReplace = true;
+ return this;
}
/**
@@ -355,14 +251,15 @@ export class CatalogBuilder {
}> {
const { config, database, logger } = this.env;
- const entityPolicy = this.buildEntityPolicy();
- const processors = this.buildProcessors(entityPolicy);
+ const policy = this.buildEntityPolicy();
+ const processors = this.buildProcessors();
const rulesEnforcer = CatalogRulesEnforcer.fromConfig(config);
const locationReader = new LocationReaders({
...this.env,
processors,
rulesEnforcer,
+ policy,
});
const db = await DatabaseManager.createDatabase(
@@ -396,7 +293,6 @@ export class CatalogBuilder {
new FieldFormatEntityPolicy(
makeValidator(this.fieldFormatValidators),
),
- new ReservedFieldsEntityPolicy(),
...this.entityPolicies,
];
@@ -418,106 +314,74 @@ export class CatalogBuilder {
]);
}
- private buildProcessors(entityPolicy: EntityPolicy): CatalogProcessor[] {
- const { config, reader } = this.env;
+ private buildProcessors(): CatalogProcessor[] {
+ const { config, logger, reader } = this.env;
- const placeholderResolvers = lodash.merge(
- {
- json: jsonPlaceholderResolver,
- yaml: yamlPlaceholderResolver,
- text: textPlaceholderResolver,
- },
- this.placeholderResolvers,
- );
+ const placeholderResolvers: Record = {
+ json: jsonPlaceholderResolver,
+ yaml: yamlPlaceholderResolver,
+ text: textPlaceholderResolver,
+ ...this.placeholderResolvers,
+ };
+
+ const processors = this.processorsReplace
+ ? this.processors
+ : [
+ new FileReaderProcessor(),
+ GithubOrgReaderProcessor.fromConfig(config, { logger }),
+ LdapOrgReaderProcessor.fromConfig(config, { logger }),
+ new UrlReaderProcessor({ reader, logger }),
+ new YamlProcessor(),
+ new CodeOwnersProcessor({ reader }),
+ new LocationRefProcessor(),
+ new AnnotateLocationEntityProcessor(),
+ ];
return [
StaticLocationProcessor.fromConfig(config),
- ...this.buildReaderProcessors(),
- ...this.buildParserProcessors(),
new PlaceholderProcessor({ resolvers: placeholderResolvers, reader }),
- ...this.buildPreProcessors(),
- new EntityPolicyProcessor(entityPolicy),
- ...this.buildPostProcessors(),
+ ...this.buildDeprecatedReaderProcessors(),
+ ...processors,
];
}
- private buildReaderProcessors(): CatalogProcessor[] {
- const { config, logger, reader } = this.env;
+ // TODO(Rugvip): These are added for backwards compatibility if config exists
+ // The idea is to have everyone migrate from using the old processors to
+ // the new integration config driven UrlReaders. In an upcoming release we
+ // can then completely remove support for the old processors, but still
+ // keep handling the deprecated location types for a while, but with a
+ // warning.
+ private buildDeprecatedReaderProcessors(): CatalogProcessor[] {
+ const { config, logger } = this.env;
- if (this.readerProcessorsReplace) {
- return this.readerProcessors;
- }
-
- // TODO(Rugvip): These are added for backwards compatibility if config exists
- // The idea is to have everyone migrate from using the old processors to the new
- // integration config driven UrlReaders. In an upcoming release we can then completely
- // remove support for the old processors, but still keep handling the deprecated location
- // types for a while, but with a warning.
- const oldProcessors = [];
+ const result = [];
const pc = config.getOptionalConfig('catalog.processors');
if (pc?.has('github')) {
logger.warn(
`Using deprecated configuration for catalog.processors.github, move to using integrations.github instead`,
);
- oldProcessors.push(GithubReaderProcessor.fromConfig(config, logger));
+ result.push(GithubReaderProcessor.fromConfig(config, logger));
}
if (pc?.has('gitlabApi')) {
logger.warn(
`Using deprecated configuration for catalog.processors.gitlabApi, move to using integrations.gitlab instead`,
);
- oldProcessors.push(new GitlabApiReaderProcessor(config));
- oldProcessors.push(new GitlabReaderProcessor());
+ result.push(new GitlabApiReaderProcessor(config));
+ result.push(new GitlabReaderProcessor());
}
if (pc?.has('bitbucketApi')) {
logger.warn(
`Using deprecated configuration for catalog.processors.bitbucketApi, move to using integrations.bitbucket instead`,
);
- oldProcessors.push(new BitbucketApiReaderProcessor(config));
+ result.push(new BitbucketApiReaderProcessor(config));
}
if (pc?.has('azureApi')) {
logger.warn(
`Using deprecated configuration for catalog.processors.azureApi, move to using integrations.azure instead`,
);
- oldProcessors.push(new AzureApiReaderProcessor(config));
+ result.push(new AzureApiReaderProcessor(config));
}
- return [
- new FileReaderProcessor(),
- ...oldProcessors,
- GithubOrgReaderProcessor.fromConfig(config, { logger }),
- LdapOrgReaderProcessor.fromConfig(config, { logger }),
- new UrlReaderProcessor({ reader, logger }),
- ...this.readerProcessors,
- ];
- }
-
- private buildParserProcessors(): CatalogProcessor[] {
- if (this.parserProcessorsReplace) {
- return this.parserProcessors;
- }
-
- return [new YamlProcessor(), ...this.parserProcessors];
- }
-
- private buildPreProcessors(): CatalogProcessor[] {
- const { reader } = this.env;
-
- if (this.preProcessorsReplace) {
- return this.preProcessors;
- }
-
- return [new CodeOwnersProcessor({ reader }), ...this.preProcessors];
- }
-
- private buildPostProcessors(): CatalogProcessor[] {
- if (this.postProcessorsReplace) {
- return this.postProcessors;
- }
-
- return [
- new LocationRefProcessor(),
- new AnnotateLocationEntityProcessor(),
- ...this.postProcessors,
- ];
+ return result;
}
}
diff --git a/plugins/catalog/src/components/AboutCard/AboutCard.tsx b/plugins/catalog/src/components/AboutCard/AboutCard.tsx
index 939449f719..266d5fa843 100644
--- a/plugins/catalog/src/components/AboutCard/AboutCard.tsx
+++ b/plugins/catalog/src/components/AboutCard/AboutCard.tsx
@@ -14,24 +14,23 @@
* limitations under the License.
*/
-import React from 'react';
+import { Entity, ENTITY_DEFAULT_NAMESPACE } from '@backstage/catalog-model';
import {
- Grid,
- Typography,
- makeStyles,
- Chip,
- IconButton,
Card,
CardContent,
CardHeader,
+ Chip,
Divider,
+ Grid,
+ IconButton,
+ makeStyles,
+ Typography,
} from '@material-ui/core';
-import { Entity } from '@backstage/catalog-model';
-
-import GitHubIcon from '@material-ui/icons/GitHub';
-import { IconLinkVertical } from './IconLinkVertical';
-import EditIcon from '@material-ui/icons/Edit';
import DocsIcon from '@material-ui/icons/Description';
+import EditIcon from '@material-ui/icons/Edit';
+import GitHubIcon from '@material-ui/icons/GitHub';
+import React from 'react';
+import { IconLinkVertical } from './IconLinkVertical';
const useStyles = makeStyles(theme => ({
links: {
@@ -59,6 +58,15 @@ const useStyles = makeStyles(theme => ({
description: {
wordBreak: 'break-word',
},
+ gridItemCard: {
+ display: 'flex',
+ flexDirection: 'column',
+ height: 'calc(100% - 10px)', // for pages without content header
+ marginBottom: '10px',
+ },
+ gridItemCardContent: {
+ flex: 1,
+ },
}));
const iconMap: Record = {
@@ -83,14 +91,15 @@ function getCodeLinkInfo(entity: Entity): CodeLinkInfo {
type AboutCardProps = {
entity: Entity;
+ variant?: string;
};
-export function AboutCard({ entity }: AboutCardProps) {
+export function AboutCard({ entity, variant }: AboutCardProps) {
const classes = useStyles();
const codeLink = getCodeLinkInfo(entity);
return (
-
+
}
- href={`/docs/${entity.kind}:${entity.metadata.namespace ?? ''}:${
- entity.metadata.name
- }`}
+ href={`/docs/${
+ entity.metadata.namespace || ENTITY_DEFAULT_NAMESPACE
+ }/${entity.kind}/${entity.metadata.name}`}
/>
}
/>
-
+
{
+ // Display the card full height suitable for
+ variant,
+}: Props) => {
const errorApi = useApi(errorApiRef);
const [owner, repo] = (
entity?.metadata.annotations?.[GITHUB_ACTIONS_ANNOTATION] ?? '/'
@@ -102,7 +101,7 @@ export const LatestWorkflowRunCard = ({
}, [error, errorApi]);
return (
-
+
(
-
+ variant,
+}: Props) => (
+
);
diff --git a/plugins/github-actions/src/components/Cards/RecentWorkflowRunsCard.tsx b/plugins/github-actions/src/components/Cards/RecentWorkflowRunsCard.tsx
index e5bba7fad9..46f60981a0 100644
--- a/plugins/github-actions/src/components/Cards/RecentWorkflowRunsCard.tsx
+++ b/plugins/github-actions/src/components/Cards/RecentWorkflowRunsCard.tsx
@@ -18,9 +18,9 @@ import { errorApiRef, useApi } from '@backstage/core-api';
import { GITHUB_ACTIONS_ANNOTATION } from '../useProjectName';
import { useWorkflowRuns } from '../useWorkflowRuns';
import React, { useEffect } from 'react';
-import { EmptyState, Table } from '@backstage/core';
+import { EmptyState, InfoCard, Table } from '@backstage/core';
import { WorkflowRunStatus } from '../WorkflowRunStatus';
-import { Button, Card, Link, TableContainer } from '@material-ui/core';
+import { Button, Link } from '@material-ui/core';
import { generatePath, Link as RouterLink } from 'react-router-dom';
const firstLine = (message: string): string => message.split('\n')[0];
@@ -30,6 +30,7 @@ export type Props = {
branch?: string;
dense?: boolean;
limit?: number;
+ variant?: string;
};
export const RecentWorkflowRunsCard = ({
@@ -37,6 +38,7 @@ export const RecentWorkflowRunsCard = ({
branch,
dense = false,
limit = 5,
+ variant,
}: Props) => {
const errorApi = useApi(errorApiRef);
const [owner, repo] = (
@@ -70,15 +72,19 @@ export const RecentWorkflowRunsCard = ({
}
/>
) : (
-
+
-
+
);
};
diff --git a/plugins/github-actions/src/components/WorkflowRunDetails/WorkflowRunDetails.tsx b/plugins/github-actions/src/components/WorkflowRunDetails/WorkflowRunDetails.tsx
index 7714ad6cb6..a2753589eb 100644
--- a/plugins/github-actions/src/components/WorkflowRunDetails/WorkflowRunDetails.tsx
+++ b/plugins/github-actions/src/components/WorkflowRunDetails/WorkflowRunDetails.tsx
@@ -77,8 +77,9 @@ const JobsList = ({ jobs, entity }: { jobs?: Jobs; entity: Entity }) => {
{jobs &&
jobs.total_count > 0 &&
- jobs.jobs.map((job: Job) => (
+ jobs.jobs.map(job => (
{
@@ -142,8 +143,8 @@ const JobListItem = ({
- {job.steps.map((step: Step) => (
-
+ {job.steps.map(step => (
+
))}
diff --git a/plugins/jenkins/src/components/Cards/Cards.tsx b/plugins/jenkins/src/components/Cards/Cards.tsx
index 5034effdcf..31acedc180 100644
--- a/plugins/jenkins/src/components/Cards/Cards.tsx
+++ b/plugins/jenkins/src/components/Cards/Cards.tsx
@@ -61,12 +61,18 @@ const WidgetContent = ({
);
};
-export const LatestRunCard = ({ branch = 'master' }: { branch: string }) => {
+export const LatestRunCard = ({
+ branch = 'master',
+ variant,
+}: {
+ branch: string;
+ variant?: string;
+}) => {
const { owner, repo } = useProjectSlugFromEntity();
const [{ builds, loading }] = useBuilds(owner, repo, branch);
const lastRun = builds ?? {};
return (
-
+
);
diff --git a/plugins/lighthouse/src/components/Cards/LastLighthouseAuditCard.tsx b/plugins/lighthouse/src/components/Cards/LastLighthouseAuditCard.tsx
index d3bc362877..3f7020cba5 100644
--- a/plugins/lighthouse/src/components/Cards/LastLighthouseAuditCard.tsx
+++ b/plugins/lighthouse/src/components/Cards/LastLighthouseAuditCard.tsx
@@ -88,9 +88,10 @@ const LighthouseAuditSummary: FC<{ audit: Audit; dense?: boolean }> = ({
return ;
};
-export const LastLighthouseAuditCard: FC<{ dense?: boolean }> = ({
- dense = false,
-}) => {
+export const LastLighthouseAuditCard: FC<{
+ dense?: boolean;
+ variant?: string;
+}> = ({ dense = false, variant }) => {
const { value: website, loading, error } = useWebsiteForEntity();
let content;
@@ -105,5 +106,9 @@ export const LastLighthouseAuditCard: FC<{ dense?: boolean }> = ({
);
}
- return {content};
+ return (
+
+ {content}
+
+ );
};
diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json
index fe6f54593d..97f5cd891c 100644
--- a/plugins/scaffolder/package.json
+++ b/plugins/scaffolder/package.json
@@ -28,8 +28,8 @@
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
- "@rjsf/core": "^2.1.0",
- "@rjsf/material-ui": "^2.1.0",
+ "@rjsf/core": "^2.4.0",
+ "@rjsf/material-ui": "^2.4.0",
"classnames": "^2.2.6",
"moment": "^2.26.0",
"react": "^16.13.1",
diff --git a/yarn.lock b/yarn.lock
index 9a62dba3f2..e55b479f78 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3644,15 +3644,10 @@
prop-types "^15.6.1"
react-lifecycles-compat "^3.0.4"
-"@rehooks/local-storage@^2.4.0":
+"@rjsf/core@^2.4.0":
version "2.4.0"
- resolved "https://registry.npmjs.org/@rehooks/local-storage/-/local-storage-2.4.0.tgz#fb884b2b657cad5f77aa6ab60bb3532f0e0725d2"
- integrity sha512-LoXDbEHsuIckVgBsFAv8SuU/M7memjyfWut9Zf36TQXqqCHBRFv8bweg9PymQCa1aWIMjNrZQflFdo55FDlXYg==
-
-"@rjsf/core@^2.1.0":
- version "2.3.0"
- resolved "https://registry.npmjs.org/@rjsf/core/-/core-2.3.0.tgz#334c73d2262ef1a8cda477e238067af7336c5599"
- integrity sha512-OZKYHt9tjKhzOH4CvsPiCwepuIacqI++cNmnL2fsxh1IF+uEWGlo3NLDWhhSaBbOv9jps6a5YQcLbLtjNuSwug==
+ resolved "https://registry.npmjs.org/@rjsf/core/-/core-2.4.0.tgz#c50bcff0d8178948ce08123177399d8816d51929"
+ integrity sha512-8zlydBkGldOxGXFEwNGFa1gzTxpcxaYn7ofegcu8XHJ7IKMCfpnU3ABg+H3eml1KZCX3FODmj1tHFJKuTmfynw==
dependencies:
"@babel/runtime-corejs2" "^7.8.7"
"@types/json-schema" "^7.0.4"
@@ -3666,10 +3661,10 @@
react-is "^16.9.0"
shortid "^2.2.14"
-"@rjsf/material-ui@^2.1.0":
- version "2.3.0"
- resolved "https://registry.npmjs.org/@rjsf/material-ui/-/material-ui-2.3.0.tgz#a051eb4db2ad778e39933a31ba806f415dd3ba90"
- integrity sha512-v/xZ4Xk18ZgBARcCe99IDqdO97GU0UC784gG8PhGGFDdy5Rbdy7ixTjHkGYttkr43PB7SX6ttohNhkKSW4nATQ==
+"@rjsf/material-ui@^2.4.0":
+ version "2.4.0"
+ resolved "https://registry.npmjs.org/@rjsf/material-ui/-/material-ui-2.4.0.tgz#1b5859298bf3f61137d7b05084f058a775d6fd73"
+ integrity sha512-U8F/suzg4MuV+8mK1/ufs0Y6c3O8hc1wnuD2IKoOVJvegGfz5JCafyoyGAW6iyuT1DZBMPzVWEqfiuYPmoE7pw==
"@roadiehq/backstage-plugin-github-insights@^0.2.7":
version "0.2.7"
@@ -3691,6 +3686,51 @@
react-router "^6.0.0-beta.0"
react-use "^15.3.3"
+"@roadiehq/backstage-plugin-github-pull-requests@^0.5.2":
+ version "0.5.2"
+ resolved "https://registry.npmjs.org/@roadiehq/backstage-plugin-github-pull-requests/-/backstage-plugin-github-pull-requests-0.5.2.tgz#455355fe5cdcc43c22b80a8ef3697378084680a1"
+ integrity sha512-GM+nWQZ+aehMclN70Pp35UQe3I46SQCjWiGZ6/0TfDzrueHln39L2UspUj1L/713xDDtDK8ZnTJdFn/qKx7j4A==
+ dependencies:
+ "@backstage/catalog-model" "^0.1.1-alpha.25"
+ "@backstage/core" "^0.1.1-alpha.25"
+ "@backstage/plugin-catalog" "^0.1.1-alpha.25"
+ "@backstage/theme" "^0.1.1-alpha.25"
+ "@material-ui/core" "^4.11.0"
+ "@material-ui/icons" "^4.9.1"
+ "@material-ui/lab" "^4.0.0-alpha.56"
+ "@octokit/rest" "^18.0.0"
+ "@octokit/types" "^5.0.1"
+ "@types/react-dom" "^16.9.8"
+ history "^5.0.0"
+ moment "^2.27.0"
+ react "^16.13.1"
+ react-dom "^16.13.1"
+ react-router "6.0.0-beta.0"
+ react-use "^15.3.3"
+
+"@roadiehq/backstage-plugin-travis-ci@^0.2.3":
+ version "0.2.3"
+ resolved "https://registry.npmjs.org/@roadiehq/backstage-plugin-travis-ci/-/backstage-plugin-travis-ci-0.2.3.tgz#2d1c5a7ed3eab4fdcf95243b73dee44c0cff5a57"
+ integrity sha512-Xi8vZFbcm+D4ykyU3I9hImAd22F1v6M1F/H9CJanRHqj5esJxnuCS2+Kf+w09bwb9DuM8/c68PuMlf3PMIsOwg==
+ dependencies:
+ "@backstage/catalog-model" "^0.1.1-alpha.22"
+ "@backstage/core" "^0.1.1-alpha.22"
+ "@backstage/core-api" "^0.1.1-alpha.22"
+ "@backstage/plugin-catalog" "^0.1.1-alpha.22"
+ "@backstage/theme" "^0.1.1-alpha.22"
+ "@material-ui/core" "^4.9.1"
+ "@material-ui/icons" "^4.9.1"
+ "@material-ui/lab" "4.0.0-alpha.45"
+ date-fns "^2.15.0"
+ history "^5.0.0"
+ moment "^2.27.0"
+ react "^16.13.1"
+ react-dom "^16.13.1"
+ react-lazylog "^4.5.2"
+ react-router "6.0.0-beta.0"
+ react-router-dom "6.0.0-beta.0"
+ react-use "^15.3.3"
+
"@rollup/plugin-commonjs@^13.0.0":
version "13.0.0"
resolved "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-13.0.0.tgz#8a1d684ba6848afe8b9e3d85649d4b2f6f7217ec"
@@ -17202,9 +17242,9 @@ object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.1:
integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==
object-path@^0.11.4:
- version "0.11.4"
- resolved "https://registry.npmjs.org/object-path/-/object-path-0.11.4.tgz#370ae752fbf37de3ea70a861c23bba8915691949"
- integrity sha1-NwrnUvvzfePqcKhhwju6iRVpGUk=
+ version "0.11.5"
+ resolved "https://registry.yarnpkg.com/object-path/-/object-path-0.11.5.tgz#d4e3cf19601a5140a55a16ad712019a9c50b577a"
+ integrity sha512-jgSbThcoR/s+XumvGMTMf81QVBmah+/Q7K7YduKeKVWL7N111unR2d6pZZarSk6kY/caeNxUDyxOvMWyzoU2eg==
object-visit@^1.0.0:
version "1.0.1"