{
+ return defaultIcons;
+ },
+
+ // TODO: Grab these from new API once it exists
+ getComponents(): AppComponents {
+ return defaultComponents;
+ },
+ };
+ }, [appTreeApi]);
+
+ return (
+
+ {props.children}
+
+ );
+}
+
+export function BackwardsCompatProvider(props: { children: ReactNode }) {
+ return {props.children};
+}
diff --git a/packages/core-compat-api/src/compatWrapper/ForwardsCompatProvider.tsx b/packages/core-compat-api/src/compatWrapper/ForwardsCompatProvider.tsx
new file mode 100644
index 0000000000..5544862aee
--- /dev/null
+++ b/packages/core-compat-api/src/compatWrapper/ForwardsCompatProvider.tsx
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2023 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import { ReactNode } from 'react';
+
+export function ForwardsCompatProvider(props: { children: ReactNode }) {
+ // TODO(Rugvip): Implement
+ return <>{props.children}>;
+}
diff --git a/packages/core-compat-api/src/compatWrapper/compatWrapper.test.tsx b/packages/core-compat-api/src/compatWrapper/compatWrapper.test.tsx
new file mode 100644
index 0000000000..b2ad7f3d0c
--- /dev/null
+++ b/packages/core-compat-api/src/compatWrapper/compatWrapper.test.tsx
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2023 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import {
+ coreExtensionData,
+ createExtension,
+} from '@backstage/frontend-plugin-api';
+import { createExtensionTester } from '@backstage/frontend-test-utils';
+import { screen } from '@testing-library/react';
+import { compatWrapper } from './compatWrapper';
+import { useApp } from '@backstage/core-plugin-api';
+
+describe('BackwardsCompatProvider', () => {
+ it('should convert the app context', () => {
+ // TODO(Rugvip): Replace with the new renderInTestApp once it's available, and have some plugins
+ createExtensionTester(
+ createExtension({
+ attachTo: { id: 'ignored', input: 'ignored' },
+ output: {
+ element: coreExtensionData.reactElement,
+ },
+ factory() {
+ function Component() {
+ const app = useApp();
+ return (
+
+ plugins:
+ {app
+ .getPlugins()
+ .map(p => p.getId())
+ .join(', ')}
+ {'\n'}
+ components: {Object.keys(app.getComponents()).join(', ')}
+ {'\n'}
+ icons: {Object.keys(app.getSystemIcons()).join(', ')}
+
+ );
+ }
+
+ return {
+ element: compatWrapper(),
+ };
+ },
+ }),
+ ).render();
+
+ expect(screen.getByTestId('ctx').textContent).toMatchInlineSnapshot(`
+ "plugins:
+ components: Progress, Router, NotFoundErrorPage, BootErrorPage, ErrorBoundaryFallback
+ icons: brokenImage, catalog, scaffolder, techdocs, search, chat, dashboard, docs, email, github, group, help, kind:api, kind:component, kind:domain, kind:group, kind:location, kind:system, kind:user, kind:resource, kind:template, user, warning"
+ `);
+ });
+});
diff --git a/packages/core-compat-api/src/compatWrapper/compatWrapper.tsx b/packages/core-compat-api/src/compatWrapper/compatWrapper.tsx
new file mode 100644
index 0000000000..cd7378a384
--- /dev/null
+++ b/packages/core-compat-api/src/compatWrapper/compatWrapper.tsx
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2023 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import { useVersionedContext } from '@backstage/version-bridge';
+import { ReactNode } from 'react';
+import { BackwardsCompatProvider } from './BackwardsCompatProvider';
+import { ForwardsCompatProvider } from './ForwardsCompatProvider';
+
+function BidirectionalCompatProvider(props: { children: ReactNode }) {
+ const isInNewApp = !useVersionedContext<{ 1: unknown }>('app-context');
+
+ if (isInNewApp) {
+ return ;
+ }
+
+ return ;
+}
+
+/**
+ * Wraps a React element in a bidirectional compatibility provider, allow APIs
+ * from `@backstage/core-plugin-api` to be used in an app from `@backstage/frontend-app-api`,
+ * and APIs from `@backstage/frontend-plugin-api` to be used in an app from `@backstage/core-app-api`.
+ *
+ * @public
+ */
+export function compatWrapper(element: ReactNode) {
+ return {element};
+}
diff --git a/packages/core-compat-api/src/compatWrapper/index.ts b/packages/core-compat-api/src/compatWrapper/index.ts
new file mode 100644
index 0000000000..42a8ff6dd4
--- /dev/null
+++ b/packages/core-compat-api/src/compatWrapper/index.ts
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2023 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export { compatWrapper } from './compatWrapper';
diff --git a/packages/core-compat-api/src/convertLegacyApp.test.tsx b/packages/core-compat-api/src/convertLegacyApp.test.tsx
index 0f1f6145c9..9fae0128b0 100644
--- a/packages/core-compat-api/src/convertLegacyApp.test.tsx
+++ b/packages/core-compat-api/src/convertLegacyApp.test.tsx
@@ -59,13 +59,13 @@ describe('convertLegacyApp', () => {
id: 'score-card',
extensions: [
{
- id: 'plugin.score-card.page',
- attachTo: { id: 'core.routes', input: 'routes' },
+ id: 'page:score-card',
+ attachTo: { id: 'core/routes', input: 'routes' },
disabled: false,
defaultConfig: { path: 'score-board' },
},
{
- id: 'apis.plugin.scoringdata.service',
+ id: 'api:plugin.scoringdata.service',
attachTo: { id: 'core', input: 'apis' },
disabled: false,
},
@@ -75,13 +75,13 @@ describe('convertLegacyApp', () => {
id: 'stackstorm',
extensions: [
{
- id: 'plugin.stackstorm.page',
- attachTo: { id: 'core.routes', input: 'routes' },
+ id: 'page:stackstorm',
+ attachTo: { id: 'core/routes', input: 'routes' },
disabled: false,
defaultConfig: { path: 'stackstorm' },
},
{
- id: 'apis.plugin.stackstorm.service',
+ id: 'api:plugin.stackstorm.service',
attachTo: { id: 'core', input: 'apis' },
disabled: false,
},
@@ -91,19 +91,19 @@ describe('convertLegacyApp', () => {
id: 'puppetDb',
extensions: [
{
- id: 'plugin.puppetDb.page',
- attachTo: { id: 'core.routes', input: 'routes' },
+ id: 'page:puppetDb',
+ attachTo: { id: 'core/routes', input: 'routes' },
disabled: false,
defaultConfig: { path: 'puppetdb' },
},
{
- id: 'plugin.puppetDb.page2',
- attachTo: { id: 'core.routes', input: 'routes' },
+ id: 'page:puppetDb/1',
+ attachTo: { id: 'core/routes', input: 'routes' },
disabled: false,
defaultConfig: { path: 'puppetdb' },
},
{
- id: 'apis.plugin.puppetdb.service',
+ id: 'api:plugin.puppetdb.service',
attachTo: { id: 'core', input: 'apis' },
disabled: false,
},
@@ -113,13 +113,13 @@ describe('convertLegacyApp', () => {
id: undefined,
extensions: [
{
- id: 'core.layout',
+ id: 'core/layout',
attachTo: { id: 'core', input: 'root' },
disabled: false,
},
{
- id: 'core.nav',
- attachTo: { id: 'core.layout', input: 'nav' },
+ id: 'core/nav',
+ attachTo: { id: 'core/layout', input: 'nav' },
disabled: true,
},
],
diff --git a/packages/core-compat-api/src/convertLegacyApp.ts b/packages/core-compat-api/src/convertLegacyApp.ts
index 93521b3955..9fe07cc783 100644
--- a/packages/core-compat-api/src/convertLegacyApp.ts
+++ b/packages/core-compat-api/src/convertLegacyApp.ts
@@ -100,7 +100,8 @@ export function convertLegacyApp(
const [routesEl] = routesEls;
const CoreLayoutOverride = createExtension({
- id: 'core.layout',
+ namespace: 'core',
+ name: 'layout',
attachTo: { id: 'core', input: 'root' },
inputs: {
content: createExtensionInput(
@@ -116,13 +117,18 @@ export function convertLegacyApp(
factory({ inputs }) {
// Clone the root element, this replaces the FlatRoutes declared in the app with out content input
return {
- element: React.cloneElement(rootEl, undefined, inputs.content.element),
+ element: React.cloneElement(
+ rootEl,
+ undefined,
+ inputs.content.output.element,
+ ),
};
},
});
const CoreNavOverride = createExtension({
- id: 'core.nav',
- attachTo: { id: 'core.layout', input: 'nav' },
+ namespace: 'core',
+ name: 'nav',
+ attachTo: { id: 'core/layout', input: 'nav' },
output: {},
factory: () => ({}),
disabled: true,
diff --git a/packages/core-compat-api/src/convertLegacyRouteRef.ts b/packages/core-compat-api/src/convertLegacyRouteRef.ts
index 2495f3719c..c5ee603722 100644
--- a/packages/core-compat-api/src/convertLegacyRouteRef.ts
+++ b/packages/core-compat-api/src/convertLegacyRouteRef.ts
@@ -91,6 +91,7 @@ export function convertLegacyRouteRef(
if (type === 'absolute') {
const legacyRef = ref as LegacyRouteRef;
+ const legacyRefStr = String(legacyRef);
const newRef = toInternalRouteRef(
createRouteRef<{ [key in string]: string }>({
params: legacyRef.params as string[],
@@ -104,18 +105,19 @@ export function convertLegacyRouteRef(
return newRef.getParams();
},
getDescription() {
- return newRef.getDescription();
+ return legacyRefStr;
},
setId(id: string) {
newRef.setId(id);
},
toString() {
- return newRef.toString();
+ return legacyRefStr;
},
});
}
if (type === 'sub') {
const legacyRef = ref as LegacySubRouteRef;
+ const legacyRefStr = String(legacyRef);
const newRef = toInternalSubRouteRef(
createSubRouteRef({
path: legacyRef.path,
@@ -133,15 +135,16 @@ export function convertLegacyRouteRef(
return newRef.getParent();
},
getDescription() {
- return newRef.getDescription();
+ return legacyRefStr;
},
toString() {
- return newRef.toString();
+ return legacyRefStr;
},
});
}
if (type === 'external') {
const legacyRef = ref as LegacyExternalRouteRef;
+ const legacyRefStr = String(legacyRef);
const newRef = toInternalExternalRouteRef(
createExternalRouteRef<{ [key in string]: string }>({
params: legacyRef.params as string[],
@@ -157,13 +160,13 @@ export function convertLegacyRouteRef(
return newRef.getParams();
},
getDescription() {
- return newRef.getDescription();
+ return legacyRefStr;
},
setId(id: string) {
newRef.setId(id);
},
toString() {
- return newRef.toString();
+ return legacyRefStr;
},
});
}
diff --git a/packages/core-compat-api/src/index.ts b/packages/core-compat-api/src/index.ts
index 421ced7054..cb62654684 100644
--- a/packages/core-compat-api/src/index.ts
+++ b/packages/core-compat-api/src/index.ts
@@ -13,6 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+export * from './compatWrapper';
+
export { collectLegacyRoutes } from './collectLegacyRoutes';
export { convertLegacyApp } from './convertLegacyApp';
export { convertLegacyRouteRef } from './convertLegacyRouteRef';
diff --git a/packages/core-components/CHANGELOG.md b/packages/core-components/CHANGELOG.md
index 8105c3c832..4702443de1 100644
--- a/packages/core-components/CHANGELOG.md
+++ b/packages/core-components/CHANGELOG.md
@@ -1,5 +1,45 @@
# @backstage/core-components
+## 0.13.9-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.5.0-next.1
+ - @backstage/config@1.1.1
+ - @backstage/core-plugin-api@1.8.1-next.1
+ - @backstage/errors@1.2.3
+ - @backstage/version-bridge@1.0.7
+
+## 0.13.9-next.1
+
+### Patch Changes
+
+- e8f2acef80: Added a new `/testUtils` sub-path that initially exports a `mockBreakpoint` helper.
+- 07dfdf3702: Updated dependency `linkifyjs` to `4.1.3`.
+- a518c5a25b: Updated dependency `@react-hookz/web` to `^23.0.0`.
+- f291757e70: Update `linkify-react` to version `4.1.3`
+- Updated dependencies
+ - @backstage/core-plugin-api@1.8.1-next.1
+ - @backstage/config@1.1.1
+ - @backstage/errors@1.2.3
+ - @backstage/theme@0.5.0-next.0
+ - @backstage/version-bridge@1.0.7
+
+## 0.13.9-next.0
+
+### Patch Changes
+
+- 381ed86d5e: Add missing export for IconLinkVertical
+- 5c8a3e3960: Minor improvements to `Table` component.
+- 4d9e3b39e4: Register component overrides in the global `OverrideComponentNameToClassKeys` provided by `@backstage/theme`. This will in turn will provide component style override types for `createUnifiedTheme`.
+- Updated dependencies
+ - @backstage/core-plugin-api@1.8.1-next.0
+ - @backstage/theme@0.5.0-next.0
+ - @backstage/config@1.1.1
+ - @backstage/errors@1.2.3
+ - @backstage/version-bridge@1.0.7
+
## 0.13.8
### Patch Changes
diff --git a/packages/core-components/api-report-testUtils.md b/packages/core-components/api-report-testUtils.md
new file mode 100644
index 0000000000..93a6bb2a07
--- /dev/null
+++ b/packages/core-components/api-report-testUtils.md
@@ -0,0 +1,10 @@
+## API Report File for "@backstage/core-components"
+
+> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
+
+```ts
+// @public
+export function mockBreakpoint(options: { matches: boolean }): void;
+
+// (No @packageDocumentation comment for this package)
+```
diff --git a/packages/core-components/api-report.md b/packages/core-components/api-report.md
index d03d51f4fd..cca7017089 100644
--- a/packages/core-components/api-report.md
+++ b/packages/core-components/api-report.md
@@ -8,7 +8,6 @@
import { ApiRef } from '@backstage/core-plugin-api';
import { BackstageIdentityApi } from '@backstage/core-plugin-api';
import { BackstagePalette } from '@backstage/theme';
-import { BackstageTheme } from '@backstage/theme';
import { BackstageUserIdentity } from '@backstage/core-plugin-api';
import { BottomNavigationActionProps } from '@material-ui/core/BottomNavigationAction';
import { ButtonProps as ButtonProps_2 } from '@material-ui/core/Button';
@@ -541,6 +540,17 @@ export type HorizontalScrollGridClassKey =
| 'buttonLeft'
| 'buttonRight';
+// @public (undocumented)
+export function IconLinkVertical({
+ color,
+ disabled,
+ href,
+ icon,
+ label,
+ onClick,
+ title,
+}: IconLinkVerticalProps): React_2.JSX.Element;
+
// @public (undocumented)
export type IconLinkVerticalClassKey =
| 'link'
diff --git a/packages/core-components/package.json b/packages/core-components/package.json
index 7a031a09f6..e657e50b9d 100644
--- a/packages/core-components/package.json
+++ b/packages/core-components/package.json
@@ -1,11 +1,9 @@
{
"name": "@backstage/core-components",
"description": "Core components used by Backstage plugins and apps",
- "version": "0.13.8",
+ "version": "0.13.9-next.2",
"publishConfig": {
- "access": "public",
- "main": "dist/index.esm.js",
- "types": "dist/index.d.ts"
+ "access": "public"
},
"backstage": {
"role": "web-library"
@@ -22,6 +20,21 @@
"license": "Apache-2.0",
"main": "src/index.ts",
"types": "src/index.ts",
+ "exports": {
+ ".": "./src/index.ts",
+ "./testUtils": "./src/testUtils.ts",
+ "./package.json": "./package.json"
+ },
+ "typesVersions": {
+ "*": {
+ "testUtils": [
+ "src/testUtils.ts"
+ ],
+ "package.json": [
+ "package.json"
+ ]
+ }
+ },
"sideEffects": false,
"scripts": {
"build": "backstage-cli package build",
@@ -43,7 +56,7 @@
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.61",
- "@react-hookz/web": "^20.0.0",
+ "@react-hookz/web": "^23.0.0",
"@types/react": "^16.13.1 || ^17.0.0",
"@types/react-sparklines": "^1.7.0",
"@types/react-text-truncate": "^0.14.0",
@@ -55,8 +68,8 @@
"dagre": "^0.8.5",
"history": "^5.0.0",
"immer": "^9.0.1",
- "linkify-react": "4.1.2",
- "linkifyjs": "4.1.2",
+ "linkify-react": "4.1.3",
+ "linkifyjs": "4.1.3",
"lodash": "^4.17.21",
"pluralize": "^8.0.0",
"qs": "^6.9.4",
diff --git a/packages/core-components/src/components/CodeSnippet/CodeSnippet.tsx b/packages/core-components/src/components/CodeSnippet/CodeSnippet.tsx
index 3270cac176..6eb274b015 100644
--- a/packages/core-components/src/components/CodeSnippet/CodeSnippet.tsx
+++ b/packages/core-components/src/components/CodeSnippet/CodeSnippet.tsx
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { BackstageTheme } from '@backstage/theme';
+
import Box from '@material-ui/core/Box';
import { useTheme } from '@material-ui/core/styles';
import React from 'react';
@@ -83,7 +83,7 @@ export function CodeSnippet(props: CodeSnippetProps) {
customStyle,
showCopyCodeButton = false,
} = props;
- const theme = useTheme();
+ const theme = useTheme();
const mode = theme.palette.type === 'dark' ? dark : docco;
const highlightColor = theme.palette.type === 'dark' ? '#256bf3' : '#e6ffed';
diff --git a/packages/core-components/src/components/CreateButton/CreateButton.tsx b/packages/core-components/src/components/CreateButton/CreateButton.tsx
index d5d7080851..beca7e8c73 100644
--- a/packages/core-components/src/components/CreateButton/CreateButton.tsx
+++ b/packages/core-components/src/components/CreateButton/CreateButton.tsx
@@ -14,13 +14,13 @@
* limitations under the License.
*/
-import { BackstageTheme } from '@backstage/theme';
import Button from '@material-ui/core/Button';
import IconButton from '@material-ui/core/IconButton';
import useMediaQuery from '@material-ui/core/useMediaQuery';
import React from 'react';
import { Link as RouterLink, LinkProps } from 'react-router-dom';
import AddCircleOutline from '@material-ui/icons/AddCircleOutline';
+import { Theme } from '@material-ui/core/styles';
/**
* Properties for {@link CreateButton}
@@ -38,7 +38,7 @@ export type CreateButtonProps = {
*/
export function CreateButton(props: CreateButtonProps) {
const { title, to } = props;
- const isXSScreen = useMediaQuery(theme =>
+ const isXSScreen = useMediaQuery(theme =>
theme.breakpoints.down('xs'),
);
diff --git a/packages/core-components/src/components/DependencyGraph/DefaultLabel.tsx b/packages/core-components/src/components/DependencyGraph/DefaultLabel.tsx
index 677782cf57..d4a91e50db 100644
--- a/packages/core-components/src/components/DependencyGraph/DefaultLabel.tsx
+++ b/packages/core-components/src/components/DependencyGraph/DefaultLabel.tsx
@@ -16,14 +16,13 @@
import React from 'react';
import makeStyles from '@material-ui/core/styles/makeStyles';
-import { BackstageTheme } from '@backstage/theme';
import { DependencyGraphTypes as Types } from './types';
/** @public */
export type DependencyGraphDefaultLabelClassKey = 'text';
const useStyles = makeStyles(
- (theme: BackstageTheme) => ({
+ theme => ({
text: {
fill: theme.palette.textContrast,
},
diff --git a/packages/core-components/src/components/DependencyGraph/DefaultNode.tsx b/packages/core-components/src/components/DependencyGraph/DefaultNode.tsx
index 5c28375118..81a918c7b8 100644
--- a/packages/core-components/src/components/DependencyGraph/DefaultNode.tsx
+++ b/packages/core-components/src/components/DependencyGraph/DefaultNode.tsx
@@ -16,14 +16,13 @@
import React from 'react';
import { makeStyles } from '@material-ui/core/styles';
-import { BackstageTheme } from '@backstage/theme';
import { DependencyGraphTypes as Types } from './types';
/** @public */
export type DependencyGraphDefaultNodeClassKey = 'node' | 'text';
const useStyles = makeStyles(
- (theme: BackstageTheme) => ({
+ theme => ({
node: {
fill: theme.palette.primary.light,
stroke: theme.palette.primary.light,
diff --git a/packages/core-components/src/components/DependencyGraph/DependencyGraph.tsx b/packages/core-components/src/components/DependencyGraph/DependencyGraph.tsx
index 3dd1025b8f..d4907d2a18 100644
--- a/packages/core-components/src/components/DependencyGraph/DependencyGraph.tsx
+++ b/packages/core-components/src/components/DependencyGraph/DependencyGraph.tsx
@@ -20,7 +20,6 @@ import * as d3Selection from 'd3-selection';
import useTheme from '@material-ui/core/styles/useTheme';
import dagre from 'dagre';
import debounce from 'lodash/debounce';
-import { BackstageTheme } from '@backstage/theme';
import { DependencyGraphTypes as Types } from './types';
import { Node } from './Node';
import { Edge, GraphEdge } from './Edge';
@@ -205,7 +204,7 @@ export function DependencyGraph(
fit = 'grow',
...svgProps
} = props;
- const theme: BackstageTheme = useTheme();
+ const theme = useTheme();
const [containerWidth, setContainerWidth] = React.useState(100);
const [containerHeight, setContainerHeight] = React.useState(100);
diff --git a/packages/core-components/src/components/DependencyGraph/Edge.tsx b/packages/core-components/src/components/DependencyGraph/Edge.tsx
index 4e3ba96e48..05e068eca4 100644
--- a/packages/core-components/src/components/DependencyGraph/Edge.tsx
+++ b/packages/core-components/src/components/DependencyGraph/Edge.tsx
@@ -18,7 +18,6 @@ import React from 'react';
import * as d3Shape from 'd3-shape';
import isFinite from 'lodash/isFinite';
import makeStyles from '@material-ui/core/styles/makeStyles';
-import { BackstageTheme } from '@backstage/theme';
import { DependencyGraphTypes as Types } from './types';
import { EDGE_TEST_ID, LABEL_TEST_ID } from './constants';
import { DefaultLabel } from './DefaultLabel';
@@ -42,7 +41,7 @@ export type GraphEdge = Types.DependencyEdge &
export type DependencyGraphEdgeClassKey = 'path' | 'label';
const useStyles = makeStyles(
- (theme: BackstageTheme) => ({
+ theme => ({
path: {
strokeWidth: 1,
stroke: theme.palette.textSubtle,
diff --git a/packages/core-components/src/components/DismissableBanner/DismissableBanner.tsx b/packages/core-components/src/components/DismissableBanner/DismissableBanner.tsx
index 9b74b1e9b8..649043fc21 100644
--- a/packages/core-components/src/components/DismissableBanner/DismissableBanner.tsx
+++ b/packages/core-components/src/components/DismissableBanner/DismissableBanner.tsx
@@ -19,7 +19,6 @@ import { useApi, storageApiRef } from '@backstage/core-plugin-api';
import useObservable from 'react-use/lib/useObservable';
import classNames from 'classnames';
import { makeStyles } from '@material-ui/core/styles';
-import { BackstageTheme } from '@backstage/theme';
import Snackbar from '@material-ui/core/Snackbar';
import SnackbarContent from '@material-ui/core/SnackbarContent';
import IconButton from '@material-ui/core/IconButton';
@@ -42,7 +41,7 @@ export type DismissableBannerClassKey =
export type DismissbleBannerClassKey = DismissableBannerClassKey;
const useStyles = makeStyles(
- (theme: BackstageTheme) => ({
+ theme => ({
root: {
padding: theme.spacing(0),
marginBottom: theme.spacing(0),
diff --git a/packages/core-components/src/components/EmptyState/MissingAnnotationEmptyState.tsx b/packages/core-components/src/components/EmptyState/MissingAnnotationEmptyState.tsx
index efbfdb9999..81b4714531 100644
--- a/packages/core-components/src/components/EmptyState/MissingAnnotationEmptyState.tsx
+++ b/packages/core-components/src/components/EmptyState/MissingAnnotationEmptyState.tsx
@@ -14,7 +14,6 @@
* limitations under the License.
*/
-import { BackstageTheme } from '@backstage/theme';
import Box from '@material-ui/core/Box';
import Button from '@material-ui/core/Button';
import { makeStyles } from '@material-ui/core/styles';
@@ -54,7 +53,7 @@ type Props = {
*/
export type MissingAnnotationEmptyStateClassKey = 'code';
-const useStyles = makeStyles(
+const useStyles = makeStyles(
theme => ({
code: {
borderRadius: 6,
diff --git a/packages/core-components/src/components/HeaderIconLinkRow/index.ts b/packages/core-components/src/components/HeaderIconLinkRow/index.ts
index f2207d32c5..84bb915945 100644
--- a/packages/core-components/src/components/HeaderIconLinkRow/index.ts
+++ b/packages/core-components/src/components/HeaderIconLinkRow/index.ts
@@ -15,6 +15,7 @@
*/
export { HeaderIconLinkRow } from './HeaderIconLinkRow';
+export { IconLinkVertical } from './IconLinkVertical';
export type { HeaderIconLinkRowClassKey } from './HeaderIconLinkRow';
export type {
IconLinkVerticalProps,
diff --git a/packages/core-components/src/components/HorizontalScrollGrid/HorizontalScrollGrid.tsx b/packages/core-components/src/components/HorizontalScrollGrid/HorizontalScrollGrid.tsx
index c4c9dadb78..e89b7896dd 100644
--- a/packages/core-components/src/components/HorizontalScrollGrid/HorizontalScrollGrid.tsx
+++ b/packages/core-components/src/components/HorizontalScrollGrid/HorizontalScrollGrid.tsx
@@ -16,7 +16,7 @@
import Box from '@material-ui/core/Box';
import Grid from '@material-ui/core/Grid';
import IconButton from '@material-ui/core/IconButton';
-import { makeStyles, Theme } from '@material-ui/core/styles';
+import { makeStyles } from '@material-ui/core/styles';
import ChevronLeftIcon from '@material-ui/icons/ChevronLeft';
import ChevronRightIcon from '@material-ui/icons/ChevronRight';
import classNames from 'classnames';
@@ -68,7 +68,7 @@ export type HorizontalScrollGridClassKey =
| 'buttonLeft'
| 'buttonRight';
-const useStyles = makeStyles(
+const useStyles = makeStyles(
theme => ({
root: {
position: 'relative',
diff --git a/packages/core-components/src/components/MarkdownContent/MarkdownContent.tsx b/packages/core-components/src/components/MarkdownContent/MarkdownContent.tsx
index 425e723052..b422a62b6c 100644
--- a/packages/core-components/src/components/MarkdownContent/MarkdownContent.tsx
+++ b/packages/core-components/src/components/MarkdownContent/MarkdownContent.tsx
@@ -18,13 +18,12 @@ import { makeStyles } from '@material-ui/core/styles';
import ReactMarkdown, { Options } from 'react-markdown';
import gfm from 'remark-gfm';
import React from 'react';
-import { BackstageTheme } from '@backstage/theme';
import { CodeSnippet } from '../CodeSnippet';
import { HeadingProps } from 'react-markdown/lib/ast-to-react';
export type MarkdownContentClassKey = 'markdown';
-const useStyles = makeStyles(
+const useStyles = makeStyles(
theme => ({
markdown: {
'& table': {
diff --git a/packages/core-components/src/components/OAuthRequestDialog/LoginRequestListItem.tsx b/packages/core-components/src/components/OAuthRequestDialog/LoginRequestListItem.tsx
index c7707c07d5..782624362d 100644
--- a/packages/core-components/src/components/OAuthRequestDialog/LoginRequestListItem.tsx
+++ b/packages/core-components/src/components/OAuthRequestDialog/LoginRequestListItem.tsx
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { makeStyles, Theme } from '@material-ui/core/styles';
+import { makeStyles } from '@material-ui/core/styles';
import ListItem from '@material-ui/core/ListItem';
import ListItemAvatar from '@material-ui/core/ListItemAvatar';
import ListItemText from '@material-ui/core/ListItemText';
@@ -26,7 +26,7 @@ import { PendingOAuthRequest } from '@backstage/core-plugin-api';
export type LoginRequestListItemClassKey = 'root';
-const useItemStyles = makeStyles(
+const useItemStyles = makeStyles(
theme => ({
root: {
paddingLeft: theme.spacing(3),
diff --git a/packages/core-components/src/components/OAuthRequestDialog/OAuthRequestDialog.tsx b/packages/core-components/src/components/OAuthRequestDialog/OAuthRequestDialog.tsx
index 8fdace3766..7c17e74e4a 100644
--- a/packages/core-components/src/components/OAuthRequestDialog/OAuthRequestDialog.tsx
+++ b/packages/core-components/src/components/OAuthRequestDialog/OAuthRequestDialog.tsx
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { makeStyles, Theme } from '@material-ui/core/styles';
+import { makeStyles } from '@material-ui/core/styles';
import Dialog from '@material-ui/core/Dialog';
import DialogActions from '@material-ui/core/DialogActions';
import DialogContent from '@material-ui/core/DialogContent';
@@ -37,7 +37,7 @@ export type OAuthRequestDialogClassKey =
| 'contentList'
| 'actionButtons';
-const useStyles = makeStyles(
+const useStyles = makeStyles(
theme => ({
dialog: {
paddingTop: theme.spacing(1),
diff --git a/packages/core-components/src/components/Progress/Progress.tsx b/packages/core-components/src/components/Progress/Progress.tsx
index eb17b9eb25..6d1281edd5 100644
--- a/packages/core-components/src/components/Progress/Progress.tsx
+++ b/packages/core-components/src/components/Progress/Progress.tsx
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { BackstageTheme } from '@backstage/theme';
+
import Box from '@material-ui/core/Box';
import LinearProgress, {
LinearProgressProps,
@@ -22,7 +22,7 @@ import { useTheme } from '@material-ui/core/styles';
import React, { PropsWithChildren, useEffect, useState } from 'react';
export function Progress(props: PropsWithChildren) {
- const theme = useTheme();
+ const theme = useTheme();
const [isVisible, setIsVisible] = useState(false);
useEffect(() => {
diff --git a/packages/core-components/src/components/ProgressBars/Gauge.tsx b/packages/core-components/src/components/ProgressBars/Gauge.tsx
index ca84a5f480..088e369e62 100644
--- a/packages/core-components/src/components/ProgressBars/Gauge.tsx
+++ b/packages/core-components/src/components/ProgressBars/Gauge.tsx
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { BackstagePalette, BackstageTheme } from '@backstage/theme';
+import { BackstagePalette } from '@backstage/theme';
import { makeStyles, useTheme } from '@material-ui/core/styles';
import { Circle } from 'rc-progress';
import React, { ReactNode, useEffect, useState } from 'react';
@@ -28,7 +28,7 @@ export type GaugeClassKey =
| 'circle'
| 'colorUnknown';
-const useStyles = makeStyles(
+const useStyles = makeStyles(
theme => ({
root: {
position: 'relative',
@@ -123,7 +123,7 @@ export function Gauge(props: GaugeProps) {
const [hoverRef, setHoverRef] = useState(null);
const { getColor = getProgressColor } = props;
const classes = useStyles(props);
- const { palette } = useTheme();
+ const { palette } = useTheme();
const { value, fractional, inverse, unit, max, description } = {
...defaultGaugeProps,
...props,
diff --git a/packages/core-components/src/components/ProgressBars/LinearGauge.tsx b/packages/core-components/src/components/ProgressBars/LinearGauge.tsx
index 8d45b83571..a41b41f3dc 100644
--- a/packages/core-components/src/components/ProgressBars/LinearGauge.tsx
+++ b/packages/core-components/src/components/ProgressBars/LinearGauge.tsx
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { BackstageTheme } from '@backstage/theme';
+
import { useTheme } from '@material-ui/core/styles';
import Tooltip from '@material-ui/core/Tooltip';
import Typography from '@material-ui/core/Typography';
@@ -33,7 +33,7 @@ type Props = {
export function LinearGauge(props: Props) {
const { value, getColor = getProgressColor, width = 'thick' } = props;
- const { palette } = useTheme();
+ const { palette } = useTheme();
if (isNaN(value)) {
return null;
}
diff --git a/packages/core-components/src/components/Status/Status.tsx b/packages/core-components/src/components/Status/Status.tsx
index 364193e383..615eed6aec 100644
--- a/packages/core-components/src/components/Status/Status.tsx
+++ b/packages/core-components/src/components/Status/Status.tsx
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { BackstageTheme } from '@backstage/theme';
+
import { makeStyles } from '@material-ui/core/styles';
import Typography from '@material-ui/core/Typography';
import classNames from 'classnames';
@@ -28,7 +28,7 @@ export type StatusClassKey =
| 'running'
| 'aborted';
-const useStyles = makeStyles(
+const useStyles = makeStyles(
theme => ({
status: {
fontWeight: theme.typography.fontWeightMedium,
diff --git a/packages/core-components/src/components/SupportButton/SupportButton.tsx b/packages/core-components/src/components/SupportButton/SupportButton.tsx
index a07c03b7d2..726500c77e 100644
--- a/packages/core-components/src/components/SupportButton/SupportButton.tsx
+++ b/packages/core-components/src/components/SupportButton/SupportButton.tsx
@@ -13,8 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
import { useApp } from '@backstage/core-plugin-api';
-import { BackstageTheme } from '@backstage/theme';
import Box from '@material-ui/core/Box';
import Button from '@material-ui/core/Button';
import DialogActions from '@material-ui/core/DialogActions';
@@ -24,7 +24,7 @@ import ListItemText from '@material-ui/core/ListItemText';
import MenuItem from '@material-ui/core/MenuItem';
import MenuList from '@material-ui/core/MenuList';
import Popover from '@material-ui/core/Popover';
-import { makeStyles } from '@material-ui/core/styles';
+import { Theme, makeStyles } from '@material-ui/core/styles';
import Typography from '@material-ui/core/Typography';
import useMediaQuery from '@material-ui/core/useMediaQuery';
import React, { MouseEventHandler, useState } from 'react';
@@ -91,7 +91,7 @@ export function SupportButton(props: SupportButtonProps) {
const [popoverOpen, setPopoverOpen] = useState(false);
const [anchorEl, setAnchorEl] = useState(null);
const classes = useStyles();
- const isSmallScreen = useMediaQuery(theme =>
+ const isSmallScreen = useMediaQuery(theme =>
theme.breakpoints.down('sm'),
);
diff --git a/packages/core-components/src/components/Table/Filters.tsx b/packages/core-components/src/components/Table/Filters.tsx
index 24eaa98cb5..568b22b933 100644
--- a/packages/core-components/src/components/Table/Filters.tsx
+++ b/packages/core-components/src/components/Table/Filters.tsx
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { BackstageTheme } from '@backstage/theme';
+
import Box from '@material-ui/core/Box';
import Button from '@material-ui/core/Button';
import { makeStyles } from '@material-ui/core/styles';
@@ -24,7 +24,7 @@ import { SelectProps } from '../Select/Select';
export type TableFiltersClassKey = 'root' | 'value' | 'heder' | 'filters';
-const useFilterStyles = makeStyles(
+const useFilterStyles = makeStyles(
theme => ({
root: {
height: '100%',
diff --git a/packages/core-components/src/components/Table/SubvalueCell.tsx b/packages/core-components/src/components/Table/SubvalueCell.tsx
index cc577b7456..8ccebbe65a 100644
--- a/packages/core-components/src/components/Table/SubvalueCell.tsx
+++ b/packages/core-components/src/components/Table/SubvalueCell.tsx
@@ -13,14 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { BackstageTheme } from '@backstage/theme';
+
import Box from '@material-ui/core/Box';
import { makeStyles } from '@material-ui/core/styles';
import React from 'react';
export type SubvalueCellClassKey = 'value' | 'subvalue';
-const useSubvalueCellStyles = makeStyles(
+const useSubvalueCellStyles = makeStyles(
theme => ({
value: {
marginBottom: theme.spacing(0.75),
diff --git a/packages/core-components/src/components/Table/Table.tsx b/packages/core-components/src/components/Table/Table.tsx
index e4ee9b5995..4aba7445b3 100644
--- a/packages/core-components/src/components/Table/Table.tsx
+++ b/packages/core-components/src/components/Table/Table.tsx
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { BackstageTheme } from '@backstage/theme';
+
import MTable, {
Column,
Icons,
@@ -25,7 +25,12 @@ import MTable, {
} from '@material-table/core';
import Box from '@material-ui/core/Box';
import IconButton from '@material-ui/core/IconButton';
-import { makeStyles, useTheme, withStyles } from '@material-ui/core/styles';
+import {
+ makeStyles,
+ Theme,
+ useTheme,
+ withStyles,
+} from '@material-ui/core/styles';
import Typography from '@material-ui/core/Typography';
import AddBox from '@material-ui/icons/AddBox';
import ArrowUpward from '@material-ui/icons/ArrowUpward';
@@ -48,12 +53,13 @@ import React, {
ReactNode,
useCallback,
useEffect,
+ useMemo,
useState,
} from 'react';
import { SelectProps } from '../Select/Select';
import { Filter, Filters, SelectedFilters, Without } from './Filters';
-import CircularProgress from '@material-ui/core/CircularProgress';
+import { TableLoadingBody } from './TableLoadingBody';
// Material-table is not using the standard icons available in in material-ui. https://github.com/mbrn/material-table/issues/51
const tableIcons: Icons = {
@@ -136,7 +142,7 @@ const StyledMTableToolbar = withStyles(
/** @public */
export type FiltersContainerClassKey = 'root' | 'title';
-const useFilterStyles = makeStyles(
+const useFilterStyles = makeStyles(
theme => ({
root: {
display: 'flex',
@@ -154,7 +160,7 @@ const useFilterStyles = makeStyles(
export type TableClassKey = 'root';
-const useTableStyles = makeStyles(
+const useTableStyles = makeStyles(
() => ({
root: {
display: 'flex',
@@ -166,7 +172,7 @@ const useTableStyles = makeStyles(
function convertColumns(
columns: TableColumn[],
- theme: BackstageTheme,
+ theme: Theme,
): TableColumn[] {
return columns.map(column => {
const headerStyle: React.CSSProperties = column.headerStyle ?? {};
@@ -303,20 +309,21 @@ export function Table(props: TableProps) {
const {
data,
columns,
+ emptyContent,
options,
title,
subtitle,
+ localization,
filters,
initialState,
- emptyContent,
onStateChange,
components,
- isLoading: isLoading,
+ isLoading: loading,
...restProps
} = props;
const tableClasses = useTableStyles();
- const theme = useTheme();
+ const theme = useTheme();
const calculatedInitialState = { ...defaultInitialState, ...initialState };
@@ -327,14 +334,11 @@ export function Table(props: TableProps) {
() => setFiltersOpen(v => !v),
[setFiltersOpen],
);
- const [selectedFiltersLength, setSelectedFiltersLength] = useState(0);
- const [tableData, setTableData] = useState(data as any[]);
+
const [selectedFilters, setSelectedFilters] = useState(
calculatedInitialState.filters,
);
- const MTColumns = convertColumns(columns, theme);
-
const [search, setSearch] = useState(calculatedInitialState.search);
useEffect(() => {
@@ -352,25 +356,15 @@ export function Table(props: TableProps) {
}
}, [search, filtersOpen, selectedFilters, onStateChange]);
- const defaultOptions: Options = {
- headerStyle: {
- textTransform: 'uppercase',
- },
- };
-
const getFieldByTitle = useCallback(
(titleValue: string | keyof T) =>
columns.find(el => el.title === titleValue)?.field,
[columns],
);
- useEffect(() => {
- if (typeof data === 'function') {
- return;
- }
- if (!selectedFilters) {
- setTableData(data as any[]);
- return;
+ const tableData = useMemo(() => {
+ if (typeof data === 'function' || !selectedFilters) {
+ return data;
}
const selectedFiltersArray = Object.values(selectedFilters);
@@ -396,62 +390,12 @@ export function Table(props: TableProps) {
return fieldValue === filterValue;
}),
);
- setTableData(newData);
- } else {
- setTableData(data as any[]);
+ return newData;
}
- setSelectedFiltersLength(selectedFiltersArray.flat().length);
+ return data;
}, [data, selectedFilters, getFieldByTitle]);
- const constructFilters = (
- filterConfig: TableFilter[],
- dataValue: any[] | undefined,
- ): Filter[] => {
- const extractDistinctValues = (field: string | keyof T): Set => {
- const distinctValues = new Set();
- const addValue = (value: any) => {
- if (value !== undefined && value !== null) {
- distinctValues.add(value);
- }
- };
-
- if (dataValue) {
- dataValue.forEach(el => {
- const value = extractValueByField(
- el,
- getFieldByTitle(field) as string,
- );
-
- if (Array.isArray(value)) {
- (value as []).forEach(addValue);
- } else {
- addValue(value);
- }
- });
- }
-
- return distinctValues;
- };
-
- const constructSelect = (
- filter: TableFilter,
- ): Without => {
- return {
- placeholder: 'All results',
- label: filter.column,
- multiple: filter.type === 'multiple-select',
- items: [...extractDistinctValues(filter.column)].sort().map(value => ({
- label: value,
- value,
- })),
- };
- };
-
- return filterConfig.map(filter => ({
- type: filter.type,
- element: constructSelect(filter),
- }));
- };
+ const selectedFiltersLength = Object.values(selectedFilters).flat().length;
const hasFilters = !!filters?.length;
const Toolbar = useCallback(
@@ -471,50 +415,16 @@ export function Table(props: TableProps) {
const hasNoRows = typeof data !== 'function' && data.length === 0;
const columnCount = columns.length;
- const Body = useCallback(
- (bodyProps: any /* no type for this in material-table */) => {
- if (isLoading) {
- return (
-
-
- |
-
-
-
- |
-
-
- );
- }
-
- if (emptyContent && hasNoRows) {
- return (
-
-
- | {emptyContent} |
-
-
- );
- }
-
- return ;
- },
- [hasNoRows, emptyContent, columnCount, isLoading],
+ const Body = useMemo(
+ () => makeBody({ hasNoRows, emptyContent, columnCount, loading }),
+ [hasNoRows, emptyContent, columnCount, loading],
);
return (
{filtersOpen && data && typeof data !== 'function' && filters?.length && (
@@ -522,12 +432,12 @@ export function Table(props: TableProps) {
components={{
Header: StyledMTableHeader,
- Toolbar,
Body,
+ Toolbar,
...components,
}}
- options={{ ...defaultOptions, ...options }}
- columns={MTColumns}
+ options={{ headerStyle: { textTransform: 'uppercase' }, ...options }}
+ columns={convertColumns(columns, theme)}
icons={tableIcons}
title={
<>
@@ -541,10 +451,11 @@ export function Table(props: TableProps) {
)}
>
}
- data={typeof data === 'function' ? data : tableData}
+ data={tableData}
style={{ width: '100%' }}
localization={{
toolbar: { searchPlaceholder: 'Filter', searchTooltip: 'Filter' },
+ ...localization,
}}
{...restProps}
/>
@@ -553,3 +464,84 @@ export function Table(props: TableProps) {
}
Table.icons = Object.freeze(tableIcons);
+
+function makeBody({
+ columnCount,
+ emptyContent,
+ hasNoRows,
+ loading,
+}: {
+ hasNoRows: boolean;
+ emptyContent: ReactNode;
+ columnCount: number;
+ loading?: boolean;
+}) {
+ return (bodyProps: any /* no type for this in material-table */) => {
+ if (loading) {
+ return ;
+ }
+
+ if (emptyContent && hasNoRows) {
+ return (
+
+
+ | {emptyContent} |
+
+
+ );
+ }
+
+ return ;
+ };
+}
+
+function constructFilters(
+ filterConfig: TableFilter[],
+ dataValue: any[] | undefined,
+ columns: TableColumn[],
+): Filter[] {
+ const extractDistinctValues = (field: string | keyof T): Set => {
+ const distinctValues = new Set();
+ const addValue = (value: any) => {
+ if (value !== undefined && value !== null) {
+ distinctValues.add(value);
+ }
+ };
+
+ if (dataValue) {
+ dataValue.forEach(el => {
+ const value = extractValueByField(
+ el,
+ columns.find(c => c.title === field)?.field as string,
+ );
+
+ if (Array.isArray(value)) {
+ (value as []).forEach(addValue);
+ } else {
+ addValue(value);
+ }
+ });
+ }
+
+ return distinctValues;
+ };
+
+ const constructSelect = (
+ filter: TableFilter,
+ ): Without => {
+ return {
+ placeholder: 'All results',
+ label: filter.column,
+ multiple: filter.type === 'multiple-select',
+ items: [...extractDistinctValues(filter.column)].sort().map(value => ({
+ label: value,
+ value,
+ })),
+ };
+ };
+
+ return filterConfig.map(filter => ({
+ type: filter.type,
+ element: constructSelect(filter),
+ }));
+}
diff --git a/packages/core-components/src/components/Table/TableLoadingBody.tsx b/packages/core-components/src/components/Table/TableLoadingBody.tsx
new file mode 100644
index 0000000000..3671bf1c51
--- /dev/null
+++ b/packages/core-components/src/components/Table/TableLoadingBody.tsx
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2023 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import Box from '@material-ui/core/Box';
+import CircularProgress from '@material-ui/core/CircularProgress';
+import React from 'react';
+
+/**
+ * @internal
+ */
+export function TableLoadingBody(props: { colSpan?: number }) {
+ return (
+
+
+ |
+
+
+
+ |
+
+
+ );
+}
diff --git a/packages/core-components/src/components/TrendLine/TrendLine.tsx b/packages/core-components/src/components/TrendLine/TrendLine.tsx
index b59d748c2c..25d1045e09 100644
--- a/packages/core-components/src/components/TrendLine/TrendLine.tsx
+++ b/packages/core-components/src/components/TrendLine/TrendLine.tsx
@@ -21,10 +21,9 @@ import {
SparklinesLineProps,
SparklinesProps,
} from 'react-sparklines';
-import { useTheme } from '@material-ui/core/styles';
-import { BackstageTheme } from '@backstage/theme';
+import { Theme, useTheme } from '@material-ui/core/styles';
-function color(data: number[], theme: BackstageTheme): string | undefined {
+function color(data: number[], theme: Theme): string | undefined {
const lastNum = data[data.length - 1];
if (!lastNum) return undefined;
if (lastNum >= 0.9) return theme.palette.status.ok;
@@ -36,7 +35,7 @@ export function TrendLine(
props: SparklinesProps &
Pick & { title?: string },
) {
- const theme = useTheme();
+ const theme = useTheme();
if (!props.data) return null;
return (
diff --git a/packages/core-components/src/components/WarningPanel/WarningPanel.tsx b/packages/core-components/src/components/WarningPanel/WarningPanel.tsx
index 1ed356024c..634b8f7b09 100644
--- a/packages/core-components/src/components/WarningPanel/WarningPanel.tsx
+++ b/packages/core-components/src/components/WarningPanel/WarningPanel.tsx
@@ -13,8 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { BackstageTheme } from '@backstage/theme';
-import { makeStyles, darken, lighten } from '@material-ui/core/styles';
+
+import { makeStyles, darken, lighten, Theme } from '@material-ui/core/styles';
import Accordion from '@material-ui/core/Accordion';
import AccordionSummary from '@material-ui/core/AccordionSummary';
import AccordionDetails from '@material-ui/core/AccordionDetails';
@@ -27,7 +27,7 @@ import { MarkdownContent } from '../MarkdownContent';
const getWarningTextColor = (
severity: NonNullable,
- theme: BackstageTheme,
+ theme: Theme,
) => {
const getColor = theme.palette.type === 'light' ? darken : lighten;
return getColor(theme.palette[severity].light, 0.6);
@@ -35,13 +35,13 @@ const getWarningTextColor = (
const getWarningBackgroundColor = (
severity: NonNullable,
- theme: BackstageTheme,
+ theme: Theme,
) => {
const getBackgroundColor = theme.palette.type === 'light' ? lighten : darken;
return getBackgroundColor(theme.palette[severity].light, 0.9);
};
-const useErrorOutlineStyles = makeStyles(theme => ({
+const useErrorOutlineStyles = makeStyles(theme => ({
root: {
marginRight: theme.spacing(1),
fill: ({ severity }: WarningProps) =>
@@ -68,7 +68,7 @@ export type WarningPanelClassKey =
| 'message'
| 'details';
-const useStyles = makeStyles(
+const useStyles = makeStyles(
theme => ({
panel: {
backgroundColor: ({ severity }: WarningProps) =>
diff --git a/packages/core-components/src/layout/BottomLink/BottomLink.tsx b/packages/core-components/src/layout/BottomLink/BottomLink.tsx
index f385d9ce11..ea706002a4 100644
--- a/packages/core-components/src/layout/BottomLink/BottomLink.tsx
+++ b/packages/core-components/src/layout/BottomLink/BottomLink.tsx
@@ -14,7 +14,6 @@
* limitations under the License.
*/
-import { BackstageTheme } from '@backstage/theme';
import Box from '@material-ui/core/Box';
import Divider from '@material-ui/core/Divider';
import { makeStyles } from '@material-ui/core/styles';
@@ -26,7 +25,7 @@ import { Link } from '../../components/Link';
/** @public */
export type BottomLinkClassKey = 'root' | 'boxTitle' | 'arrow';
-const useStyles = makeStyles(
+const useStyles = makeStyles(
theme => ({
root: {
maxWidth: 'fit-content',
diff --git a/packages/core-components/src/layout/ErrorPage/ErrorPage.tsx b/packages/core-components/src/layout/ErrorPage/ErrorPage.tsx
index 9cfaac363e..73acf1f650 100644
--- a/packages/core-components/src/layout/ErrorPage/ErrorPage.tsx
+++ b/packages/core-components/src/layout/ErrorPage/ErrorPage.tsx
@@ -14,7 +14,6 @@
* limitations under the License.
*/
-import { BackstageTheme } from '@backstage/theme';
import Grid from '@material-ui/core/Grid';
import { makeStyles } from '@material-ui/core/styles';
import Typography from '@material-ui/core/Typography';
@@ -34,7 +33,7 @@ interface IErrorPageProps {
/** @public */
export type ErrorPageClassKey = 'container' | 'title' | 'subtitle';
-const useStyles = makeStyles(
+const useStyles = makeStyles(
theme => ({
container: {
padding: theme.spacing(8),
diff --git a/packages/core-components/src/layout/Header/Header.tsx b/packages/core-components/src/layout/Header/Header.tsx
index cd2f281d65..c6ceda1070 100644
--- a/packages/core-components/src/layout/Header/Header.tsx
+++ b/packages/core-components/src/layout/Header/Header.tsx
@@ -15,7 +15,6 @@
*/
import { configApiRef, useApi } from '@backstage/core-plugin-api';
-import { BackstageTheme } from '@backstage/theme';
import Box from '@material-ui/core/Box';
import Grid from '@material-ui/core/Grid';
import { makeStyles } from '@material-ui/core/styles';
@@ -39,7 +38,7 @@ export type HeaderClassKey =
| 'breadcrumbType'
| 'breadcrumbTitle';
-const useStyles = makeStyles(
+const useStyles = makeStyles(
theme => ({
header: {
gridArea: 'pageHeader',
diff --git a/packages/core-components/src/layout/HeaderLabel/HeaderLabel.tsx b/packages/core-components/src/layout/HeaderLabel/HeaderLabel.tsx
index fdd48a5d81..6a41d6aedc 100644
--- a/packages/core-components/src/layout/HeaderLabel/HeaderLabel.tsx
+++ b/packages/core-components/src/layout/HeaderLabel/HeaderLabel.tsx
@@ -14,7 +14,6 @@
* limitations under the License.
*/
-import { BackstageTheme } from '@backstage/theme';
import Grid from '@material-ui/core/Grid';
import { alpha, makeStyles } from '@material-ui/core/styles';
import Typography from '@material-ui/core/Typography';
@@ -24,7 +23,7 @@ import { Link } from '../../components/Link';
/** @public */
export type HeaderLabelClassKey = 'root' | 'label' | 'value';
-const useStyles = makeStyles(
+const useStyles = makeStyles(
theme => ({
root: {
textAlign: 'left',
diff --git a/packages/core-components/src/layout/ItemCard/ItemCardHeader.tsx b/packages/core-components/src/layout/ItemCard/ItemCardHeader.tsx
index 1e4e7dc316..6dc4725f09 100644
--- a/packages/core-components/src/layout/ItemCard/ItemCardHeader.tsx
+++ b/packages/core-components/src/layout/ItemCard/ItemCardHeader.tsx
@@ -13,16 +13,21 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { BackstageTheme } from '@backstage/theme';
+
import Box from '@material-ui/core/Box';
-import { createStyles, makeStyles, WithStyles } from '@material-ui/core/styles';
+import {
+ createStyles,
+ makeStyles,
+ Theme,
+ WithStyles,
+} from '@material-ui/core/styles';
import Typography from '@material-ui/core/Typography';
import React from 'react';
/** @public */
export type ItemCardHeaderClassKey = 'root';
-const styles = (theme: BackstageTheme) =>
+const styles = (theme: Theme) =>
createStyles({
root: {
color: theme.palette.common.white,
diff --git a/packages/core-components/src/layout/Page/Page.tsx b/packages/core-components/src/layout/Page/Page.tsx
index e388e0366a..28194b2889 100644
--- a/packages/core-components/src/layout/Page/Page.tsx
+++ b/packages/core-components/src/layout/Page/Page.tsx
@@ -15,12 +15,11 @@
*/
import React from 'react';
-import { BackstageTheme } from '@backstage/theme';
-import { makeStyles, ThemeProvider } from '@material-ui/core/styles';
+import { makeStyles, Theme, ThemeProvider } from '@material-ui/core/styles';
export type PageClassKey = 'root';
-const useStyles = makeStyles(
+const useStyles = makeStyles(
theme => ({
root: {
display: 'grid',
@@ -53,7 +52,7 @@ export function Page(props: Props) {
const classes = useStyles();
return (
({
+ theme={(baseTheme: Theme) => ({
...baseTheme,
page: baseTheme.getPageTheme({ themeId }),
})}
diff --git a/packages/core-components/src/layout/Sidebar/Bar.tsx b/packages/core-components/src/layout/Sidebar/Bar.tsx
index 8cb43ba007..550c614522 100644
--- a/packages/core-components/src/layout/Sidebar/Bar.tsx
+++ b/packages/core-components/src/layout/Sidebar/Bar.tsx
@@ -14,10 +14,9 @@
* limitations under the License.
*/
-import { BackstageTheme } from '@backstage/theme';
import Box from '@material-ui/core/Box';
import Button from '@material-ui/core/Button';
-import { makeStyles } from '@material-ui/core/styles';
+import { makeStyles, Theme } from '@material-ui/core/styles';
import useMediaQuery from '@material-ui/core/useMediaQuery';
import classnames from 'classnames';
import React, { useContext, useRef, useState } from 'react';
@@ -38,7 +37,7 @@ import { useSidebarPinState } from './SidebarPinStateContext';
/** @public */
export type SidebarClassKey = 'drawer' | 'drawerOpen';
-const useStyles = makeStyles(
+const useStyles = makeStyles(
theme => ({
drawer: {
display: 'flex',
@@ -133,7 +132,7 @@ const DesktopSidebar = (props: DesktopSidebarProps) => {
} = props;
const classes = useStyles({ sidebarConfig });
- const isSmallScreen = useMediaQuery(
+ const isSmallScreen = useMediaQuery(
theme => theme.breakpoints.down('md'),
{ noSsr: true },
);
diff --git a/packages/core-components/src/layout/Sidebar/Intro.tsx b/packages/core-components/src/layout/Sidebar/Intro.tsx
index 824e9e42fd..6f75f096fb 100644
--- a/packages/core-components/src/layout/Sidebar/Intro.tsx
+++ b/packages/core-components/src/layout/Sidebar/Intro.tsx
@@ -13,11 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { BackstageTheme } from '@backstage/theme';
+
import Box from '@material-ui/core/Box';
import Collapse from '@material-ui/core/Collapse';
import IconButton from '@material-ui/core/IconButton';
-import { makeStyles } from '@material-ui/core/styles';
+import { makeStyles, Theme } from '@material-ui/core/styles';
import Typography from '@material-ui/core/Typography';
import CloseIcon from '@material-ui/icons/Close';
import { useLocalStorageValue } from '@react-hookz/web';
@@ -39,7 +39,7 @@ export type SidebarIntroClassKey =
| 'introDismissText'
| 'introDismissIcon';
-const useStyles = makeStyles(
+const useStyles = makeStyles(
theme => ({
introCard: props => ({
color: '#b5b5b5',
diff --git a/packages/core-components/src/layout/Sidebar/Items.tsx b/packages/core-components/src/layout/Sidebar/Items.tsx
index aba4c79181..f9970749d0 100644
--- a/packages/core-components/src/layout/Sidebar/Items.tsx
+++ b/packages/core-components/src/layout/Sidebar/Items.tsx
@@ -13,12 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
import {
IconComponent,
useAnalytics,
useElementFilter,
} from '@backstage/core-plugin-api';
-import { BackstageTheme } from '@backstage/theme';
import Badge from '@material-ui/core/Badge';
import Box from '@material-ui/core/Box';
import { makeStyles, styled, Theme } from '@material-ui/core/styles';
@@ -90,7 +90,7 @@ export type SidebarItemClassKey =
| 'selected';
const makeSidebarStyles = (sidebarConfig: SidebarConfig) =>
- makeStyles(
+ makeStyles(
theme => ({
root: {
color: theme.palette.navigation.color,
@@ -501,7 +501,7 @@ const SidebarItemWithSubmenu = ({
const [isHoveredOn, setIsHoveredOn] = useState(false);
const location = useLocation();
const isActive = useLocationMatch(children, location);
- const isSmallScreen = useMediaQuery((theme: BackstageTheme) =>
+ const isSmallScreen = useMediaQuery((theme: Theme) =>
theme.breakpoints.down('sm'),
);
@@ -726,7 +726,7 @@ export const SidebarExpandButton = () => {
const { sidebarConfig } = useContext(SidebarConfigContext);
const classes = useMemoStyles(sidebarConfig);
const { isOpen, setOpen } = useSidebarOpenState();
- const isSmallScreen = useMediaQuery(
+ const isSmallScreen = useMediaQuery(
theme => theme.breakpoints.down('md'),
{ noSsr: true },
);
diff --git a/packages/core-components/src/layout/Sidebar/MobileSidebar.test.tsx b/packages/core-components/src/layout/Sidebar/MobileSidebar.test.tsx
index 7012afabf1..c29276a6cf 100644
--- a/packages/core-components/src/layout/Sidebar/MobileSidebar.test.tsx
+++ b/packages/core-components/src/layout/Sidebar/MobileSidebar.test.tsx
@@ -14,7 +14,8 @@
* limitations under the License.
*/
-import { mockBreakpoint, renderInTestApp } from '@backstage/test-utils';
+import { renderInTestApp } from '@backstage/test-utils';
+import { mockBreakpoint } from '@backstage/core-components/testUtils';
import CreateComponentIcon from '@material-ui/icons/AddCircleOutline';
import HomeIcon from '@material-ui/icons/Home';
import LayersIcon from '@material-ui/icons/Layers';
diff --git a/packages/core-components/src/layout/Sidebar/MobileSidebar.tsx b/packages/core-components/src/layout/Sidebar/MobileSidebar.tsx
index 88d653cea5..bc38f5510c 100644
--- a/packages/core-components/src/layout/Sidebar/MobileSidebar.tsx
+++ b/packages/core-components/src/layout/Sidebar/MobileSidebar.tsx
@@ -15,11 +15,10 @@
*/
import { useElementFilter } from '@backstage/core-plugin-api';
-import { BackstageTheme } from '@backstage/theme';
import BottomNavigation from '@material-ui/core/BottomNavigation';
import Box from '@material-ui/core/Box';
import IconButton from '@material-ui/core/IconButton';
-import { makeStyles } from '@material-ui/core/styles';
+import { Theme, makeStyles } from '@material-ui/core/styles';
import Drawer from '@material-ui/core/Drawer';
import Typography from '@material-ui/core/Typography';
import CloseIcon from '@material-ui/icons/Close';
@@ -51,7 +50,7 @@ type OverlayMenuProps = {
children?: React.ReactNode;
};
-const useStyles = makeStyles(
+const useStyles = makeStyles(
theme => ({
root: {
position: 'fixed',
diff --git a/packages/core-components/src/layout/Sidebar/Page.tsx b/packages/core-components/src/layout/Sidebar/Page.tsx
index c0107256e1..09d5bd910c 100644
--- a/packages/core-components/src/layout/Sidebar/Page.tsx
+++ b/packages/core-components/src/layout/Sidebar/Page.tsx
@@ -13,9 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { BackstageTheme } from '@backstage/theme';
+
import Box from '@material-ui/core/Box';
-import { makeStyles } from '@material-ui/core/styles';
+import { makeStyles, Theme } from '@material-ui/core/styles';
import useMediaQuery from '@material-ui/core/useMediaQuery';
import React, {
createContext,
@@ -34,7 +34,7 @@ import { SidebarPinStateProvider } from './SidebarPinStateContext';
export type SidebarPageClassKey = 'root';
const useStyles = makeStyles<
- BackstageTheme,
+ Theme,
{ sidebarConfig: SidebarConfig; isPinned: boolean }
>(
theme => ({
@@ -107,10 +107,9 @@ export function SidebarPage(props: SidebarPageProps) {
LocalStorage.setSidebarPinState(isPinned);
}, [isPinned]);
- const isMobile = useMediaQuery(
- theme => theme.breakpoints.down('xs'),
- { noSsr: true },
- );
+ const isMobile = useMediaQuery(theme => theme.breakpoints.down('xs'), {
+ noSsr: true,
+ });
const toggleSidebarPinState = () => setIsPinned(!isPinned);
diff --git a/packages/core-components/src/layout/Sidebar/SidebarGroup.test.tsx b/packages/core-components/src/layout/Sidebar/SidebarGroup.test.tsx
index 687140277a..b59395efab 100644
--- a/packages/core-components/src/layout/Sidebar/SidebarGroup.test.tsx
+++ b/packages/core-components/src/layout/Sidebar/SidebarGroup.test.tsx
@@ -14,7 +14,8 @@
* limitations under the License.
*/
-import { mockBreakpoint, renderInTestApp } from '@backstage/test-utils';
+import { mockBreakpoint } from '@backstage/core-components/testUtils';
+import { renderInTestApp } from '@backstage/test-utils';
import HomeIcon from '@material-ui/icons/Home';
import LayersIcon from '@material-ui/icons/Layers';
import LibraryBooks from '@material-ui/icons/LibraryBooks';
diff --git a/packages/core-components/src/layout/Sidebar/SidebarGroup.tsx b/packages/core-components/src/layout/Sidebar/SidebarGroup.tsx
index 2ce68cd51a..7944728af6 100644
--- a/packages/core-components/src/layout/Sidebar/SidebarGroup.tsx
+++ b/packages/core-components/src/layout/Sidebar/SidebarGroup.tsx
@@ -15,11 +15,10 @@
* limitations under the License.
*/
-import { BackstageTheme } from '@backstage/theme';
import BottomNavigationAction, {
BottomNavigationActionProps,
} from '@material-ui/core/BottomNavigationAction';
-import { makeStyles } from '@material-ui/core/styles';
+import { Theme, makeStyles } from '@material-ui/core/styles';
import React, { useContext } from 'react';
import { useLocation } from 'react-router-dom';
import { Link } from '../../components/Link/Link';
@@ -48,7 +47,7 @@ export interface SidebarGroupProps extends BottomNavigationActionProps {
children?: React.ReactNode;
}
-const useStyles = makeStyles(
+const useStyles = makeStyles(
theme => ({
root: {
flexGrow: 0,
diff --git a/packages/core-components/src/layout/Sidebar/SidebarSubmenu.tsx b/packages/core-components/src/layout/Sidebar/SidebarSubmenu.tsx
index 85ba923672..6b6951d023 100644
--- a/packages/core-components/src/layout/Sidebar/SidebarSubmenu.tsx
+++ b/packages/core-components/src/layout/Sidebar/SidebarSubmenu.tsx
@@ -13,9 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { BackstageTheme } from '@backstage/theme';
+
import Box from '@material-ui/core/Box';
-import { makeStyles } from '@material-ui/core/styles';
+import { makeStyles, Theme } from '@material-ui/core/styles';
import Typography from '@material-ui/core/Typography';
import classnames from 'classnames';
import React, { ReactNode, useContext, useEffect, useState } from 'react';
@@ -28,7 +28,7 @@ import {
import { useSidebarOpenState } from './SidebarOpenStateContext';
const useStyles = makeStyles<
- BackstageTheme,
+ Theme,
{ submenuConfig: SubmenuConfig; left: number }
>(
theme => ({
diff --git a/packages/core-components/src/layout/Sidebar/SidebarSubmenuItem.tsx b/packages/core-components/src/layout/Sidebar/SidebarSubmenuItem.tsx
index d1925036bb..0503f58f1c 100644
--- a/packages/core-components/src/layout/Sidebar/SidebarSubmenuItem.tsx
+++ b/packages/core-components/src/layout/Sidebar/SidebarSubmenuItem.tsx
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
import React, { useContext, useState } from 'react';
import { resolvePath, useLocation, useResolvedPath } from 'react-router-dom';
import { makeStyles } from '@material-ui/core/styles';
@@ -21,7 +22,6 @@ import Typography from '@material-ui/core/Typography';
import { Link } from '../../components/Link';
import { IconComponent } from '@backstage/core-plugin-api';
import classnames from 'classnames';
-import { BackstageTheme } from '@backstage/theme';
import ArrowDropDownIcon from '@material-ui/icons/ArrowDropDown';
import ArrowDropUpIcon from '@material-ui/icons/ArrowDropUp';
import { SidebarItemWithSubmenuContext } from './config';
@@ -29,7 +29,7 @@ import { isLocationMatch } from './utils';
import Box from '@material-ui/core/Box';
import Button from '@material-ui/core/Button';
-const useStyles = makeStyles(
+const useStyles = makeStyles(
theme => ({
item: {
height: 48,
diff --git a/packages/core-components/src/overridableComponents.ts b/packages/core-components/src/overridableComponents.ts
index c872317049..750303e4df 100644
--- a/packages/core-components/src/overridableComponents.ts
+++ b/packages/core-components/src/overridableComponents.ts
@@ -173,3 +173,8 @@ export type BackstageOverrides = Overrides & {
StyleRules
>;
};
+
+declare module '@backstage/theme' {
+ interface OverrideComponentNameToClassKeys
+ extends BackstageComponentsNameToClassKey {}
+}
diff --git a/packages/core-components/src/testUtils.ts b/packages/core-components/src/testUtils.ts
new file mode 100644
index 0000000000..17bb0db63b
--- /dev/null
+++ b/packages/core-components/src/testUtils.ts
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2023 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * This is a mocking method suggested in the Jest docs, as it is not implemented in JSDOM yet.
+ * It can be used to mock values for the Material UI `useMediaQuery` hook if it is used in a tested component.
+ *
+ * For issues checkout the documentation:
+ * https://jestjs.io/docs/manual-mocks#mocking-methods-which-are-not-implemented-in-jsdom
+ *
+ * If there are any updates from Material UI React on testing `useMediaQuery` this mock should be replaced
+ * https://mui.com/material-ui/react-use-media-query/#testing
+ *
+ * @public
+ */
+export function mockBreakpoint(options: { matches: boolean }) {
+ Object.defineProperty(window, 'matchMedia', {
+ writable: true,
+ value: jest.fn().mockImplementation(query => ({
+ matches: options.matches ?? false,
+ media: query,
+ onchange: null,
+ addListener: jest.fn(), // deprecated
+ removeListener: jest.fn(), // deprecated
+ addEventListener: jest.fn(),
+ removeEventListener: jest.fn(),
+ dispatchEvent: jest.fn(),
+ })),
+ });
+}
diff --git a/packages/core-plugin-api/CHANGELOG.md b/packages/core-plugin-api/CHANGELOG.md
index 8633872a15..eab6b0519b 100644
--- a/packages/core-plugin-api/CHANGELOG.md
+++ b/packages/core-plugin-api/CHANGELOG.md
@@ -1,5 +1,25 @@
# @backstage/core-plugin-api
+## 1.8.1-next.1
+
+### Patch Changes
+
+- 0c93dc37b2: The `createTranslationRef` function from the `/alpha` subpath can now also accept a nested object structure of default translation messages, which will be flatted using `.` separators.
+- Updated dependencies
+ - @backstage/config@1.1.1
+ - @backstage/types@1.1.1
+ - @backstage/version-bridge@1.0.7
+
+## 1.8.1-next.0
+
+### Patch Changes
+
+- 03d0b6dcdc: Removed the alpha `convertLegacyRouteRef` utility, which as been moved to `@backstage/core-compat-api`
+- Updated dependencies
+ - @backstage/config@1.1.1
+ - @backstage/types@1.1.1
+ - @backstage/version-bridge@1.0.7
+
## 1.8.0
### Minor Changes
diff --git a/packages/core-plugin-api/alpha-api-report.md b/packages/core-plugin-api/api-report-alpha.md
similarity index 92%
rename from packages/core-plugin-api/alpha-api-report.md
rename to packages/core-plugin-api/api-report-alpha.md
index ba3583142f..c0f0d02535 100644
--- a/packages/core-plugin-api/alpha-api-report.md
+++ b/packages/core-plugin-api/api-report-alpha.md
@@ -39,19 +39,17 @@ export function createTranslationMessages<
// @alpha (undocumented)
export function createTranslationRef<
TId extends string,
- const TMessages extends {
- [key in string]: string;
- },
+ const TNestedMessages extends AnyNestedMessages,
TTranslations extends {
[language in string]: () => Promise<{
default: {
- [key in keyof TMessages]: string | null;
+ [key in keyof FlattenedMessages]: string | null;
};
}>;
},
>(
- config: TranslationRefOptions,
-): TranslationRef;
+ config: TranslationRefOptions,
+): TranslationRef>;
// @alpha (undocumented)
export function createTranslationResource<
@@ -169,13 +167,11 @@ export interface TranslationRef<
// @alpha (undocumented)
export interface TranslationRefOptions<
TId extends string,
- TMessages extends {
- [key in string]: string;
- },
+ TNestedMessages extends AnyNestedMessages,
TTranslations extends {
[language in string]: () => Promise<{
default: {
- [key in keyof TMessages]: string | null;
+ [key in keyof FlattenedMessages]: string | null;
};
}>;
},
@@ -183,7 +179,7 @@ export interface TranslationRefOptions<
// (undocumented)
id: TId;
// (undocumented)
- messages: TMessages;
+ messages: TNestedMessages;
// (undocumented)
translations?: TTranslations;
}
diff --git a/packages/core-plugin-api/package.json b/packages/core-plugin-api/package.json
index 57e3c35b95..3b0bc1f34b 100644
--- a/packages/core-plugin-api/package.json
+++ b/packages/core-plugin-api/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/core-plugin-api",
"description": "Core API used by Backstage plugins",
- "version": "1.8.0",
+ "version": "1.8.1-next.1",
"publishConfig": {
"access": "public"
},
diff --git a/packages/core-plugin-api/src/translation/TranslationRef.test.ts b/packages/core-plugin-api/src/translation/TranslationRef.test.ts
index b73b521774..d1fde0810f 100644
--- a/packages/core-plugin-api/src/translation/TranslationRef.test.ts
+++ b/packages/core-plugin-api/src/translation/TranslationRef.test.ts
@@ -34,6 +34,40 @@ describe('TranslationRefImpl', () => {
expect(internalRef.getDefaultMessages()).toEqual({ key: 'value' });
});
+ it('should create a TranslationRef instance with nested messages', () => {
+ const ref = createTranslationRef({
+ id: 'test',
+ messages: {
+ key: 'value',
+ 'nested.conflict1': 'outer conflict1',
+ nested: {
+ key: 'nested value',
+ key2: 'nested value2',
+ conflict1: 'inner conflict1',
+ conflict2: 'inner conflict2',
+ inner: {
+ key: 'inner value',
+ },
+ },
+ 'nested.conflict2': 'outer conflict2',
+ },
+ });
+
+ const internalRef = toInternalTranslationRef(ref);
+
+ expect(internalRef.$$type).toBe('@backstage/TranslationRef');
+ expect(internalRef.version).toBe('v1');
+ expect(internalRef.id).toBe('test');
+ expect(internalRef.getDefaultMessages()).toEqual({
+ key: 'value',
+ 'nested.key': 'nested value',
+ 'nested.key2': 'nested value2',
+ 'nested.conflict1': 'inner conflict1',
+ 'nested.inner.key': 'inner value',
+ 'nested.conflict2': 'outer conflict2',
+ });
+ });
+
it('should be created with lazy translations', async () => {
const ref = createTranslationRef({
id: 'test',
diff --git a/packages/core-plugin-api/src/translation/TranslationRef.ts b/packages/core-plugin-api/src/translation/TranslationRef.ts
index 345a9c3f65..c2ac7a5ce3 100644
--- a/packages/core-plugin-api/src/translation/TranslationRef.ts
+++ b/packages/core-plugin-api/src/translation/TranslationRef.ts
@@ -34,6 +34,43 @@ export interface TranslationRef<
/** @internal */
type AnyMessages = { [key in string]: string };
+/** @ignore */
+type AnyNestedMessages = { [key in string]: AnyNestedMessages | string };
+
+/**
+ * Flattens a nested message declaration into a flat object with dot-separated keys.
+ *
+ * @ignore
+ */
+type FlattenedMessages =
+ // Flatten out object keys into a union structure of objects, e.g. { a: 'a', b: 'b' } -> { a: 'a' } | { b: 'b' }
+ // Any nested object will be flattened into the individual unions, e.g. { a: 'a', b: { x: 'x', y: 'y' } } -> { a: 'a' } | { 'b.x': 'x', 'b.y': 'y' }
+ // We create this structure by first nesting the desired union types into the original object, and
+ // then extract them by indexing with `keyof TMessages` to form the union.
+ // Throughout this the objects are wrapped up in a function parameter, which allows us to have the
+ // final step of flipping this unions around to an intersection by inferring the function parameter.
+ {
+ [TKey in keyof TMessages]: (
+ _: TMessages[TKey] extends infer TValue // "local variable" for the value
+ ? TValue extends AnyNestedMessages
+ ? FlattenedMessages extends infer TNested // Recurse into nested messages, "local variable" for the result
+ ? {
+ [TNestedKey in keyof TNested as `${TKey & string}.${TNestedKey &
+ string}`]: TNested[TNestedKey];
+ }
+ : never
+ : { [_ in TKey]: TValue } // Primitive object values are passed through with the same key
+ : never,
+ ) => void;
+ // The `[keyof TMessages]` extracts the object values union from our flattened structure, still wrapped up in function parameters.
+ // The `extends (_: infer TIntersection) => void` flips the union to an intersection, at which point we have the correct type.
+ }[keyof TMessages] extends (_: infer TIntersection) => void
+ ? // This object mapping just expands similar to the Expand<> utility type, providing nicer type hints
+ {
+ readonly [TExpandKey in keyof TIntersection]: TIntersection[TExpandKey];
+ }
+ : never;
+
/** @internal */
export interface InternalTranslationRef<
TId extends string = string,
@@ -49,31 +86,53 @@ export interface InternalTranslationRef<
/** @alpha */
export interface TranslationRefOptions<
TId extends string,
- TMessages extends { [key in string]: string },
+ TNestedMessages extends AnyNestedMessages,
TTranslations extends {
[language in string]: () => Promise<{
- default: { [key in keyof TMessages]: string | null };
+ default: {
+ [key in keyof FlattenedMessages]: string | null;
+ };
}>;
},
> {
id: TId;
- messages: TMessages;
+ messages: TNestedMessages;
translations?: TTranslations;
}
+function flattenMessages(nested: AnyNestedMessages): AnyMessages {
+ const entries = new Array<[string, string]>();
+
+ function visit(obj: AnyNestedMessages, prefix: string): void {
+ for (const [key, value] of Object.entries(obj)) {
+ if (typeof value === 'string') {
+ entries.push([prefix + key, value]);
+ } else {
+ visit(value, `${prefix}${key}.`);
+ }
+ }
+ }
+
+ visit(nested, '');
+
+ return Object.fromEntries(entries);
+}
+
/** @internal */
class TranslationRefImpl<
TId extends string,
- TMessages extends { [key in string]: string },
-> implements InternalTranslationRef
+ TNestedMessages extends AnyNestedMessages,
+> implements InternalTranslationRef>
{
#id: TId;
- #messages: TMessages;
+ #messages: FlattenedMessages;
#resources: TranslationResource | undefined;
- constructor(options: TranslationRefOptions) {
+ constructor(options: TranslationRefOptions) {
this.#id = options.id;
- this.#messages = options.messages;
+ this.#messages = flattenMessages(
+ options.messages,
+ ) as FlattenedMessages;
}
$$type = '@backstage/TranslationRef' as const;
@@ -108,15 +167,17 @@ class TranslationRefImpl<
/** @alpha */
export function createTranslationRef<
TId extends string,
- const TMessages extends { [key in string]: string },
+ const TNestedMessages extends AnyNestedMessages,
TTranslations extends {
[language in string]: () => Promise<{
- default: { [key in keyof TMessages]: string | null };
+ default: {
+ [key in keyof FlattenedMessages]: string | null;
+ };
}>;
},
>(
- config: TranslationRefOptions,
-): TranslationRef {
+ config: TranslationRefOptions,
+): TranslationRef> {
const ref = new TranslationRefImpl(config);
if (config.translations) {
ref.setDefaultResource(
@@ -132,7 +193,7 @@ export function createTranslationRef<
/** @internal */
export function toInternalTranslationRef<
TId extends string,
- TMessages extends { [key in string]: string },
+ TMessages extends AnyMessages,
>(ref: TranslationRef): InternalTranslationRef {
const r = ref as InternalTranslationRef;
if (r.$$type !== '@backstage/TranslationRef') {
diff --git a/packages/create-app/CHANGELOG.md b/packages/create-app/CHANGELOG.md
index f6951e72e6..5407afcbe8 100644
--- a/packages/create-app/CHANGELOG.md
+++ b/packages/create-app/CHANGELOG.md
@@ -1,5 +1,35 @@
# @backstage/create-app
+## 0.5.8-next.3
+
+### Patch Changes
+
+- a96c2d4: Include the `` for group entities by default
+- Updated dependencies
+ - @backstage/cli-common@0.1.13
+
+## 0.5.8-next.2
+
+### Patch Changes
+
+- 375b6f7d68: CircelCI plugin moved permanently
+- Updated dependencies
+ - @backstage/cli-common@0.1.13
+
+## 0.5.8-next.1
+
+### Patch Changes
+
+- Bumped create-app version.
+
+## 0.5.8-next.0
+
+### Patch Changes
+
+- Bumped create-app version.
+- Updated dependencies
+ - @backstage/cli-common@0.1.13
+
## 0.5.7
### Patch Changes
diff --git a/packages/create-app/package.json b/packages/create-app/package.json
index 3b6d5ec8b5..c4deda095f 100644
--- a/packages/create-app/package.json
+++ b/packages/create-app/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/create-app",
"description": "A CLI that helps you create your own Backstage app",
- "version": "0.5.7",
+ "version": "0.5.8-next.3",
"publishConfig": {
"access": "public"
},
diff --git a/packages/create-app/src/lib/versions.ts b/packages/create-app/src/lib/versions.ts
index 81bae3c0b8..03ef32d921 100644
--- a/packages/create-app/src/lib/versions.ts
+++ b/packages/create-app/src/lib/versions.ts
@@ -58,7 +58,6 @@ import { version as pluginCatalogBackend } from '../../../../plugins/catalog-bac
import { version as pluginCatalogBackendModuleScaffolderEntityModel } from '../../../../plugins/catalog-backend-module-scaffolder-entity-model/package.json';
import { version as pluginCatalogGraph } from '../../../../plugins/catalog-graph/package.json';
import { version as pluginCatalogImport } from '../../../../plugins/catalog-import/package.json';
-import { version as pluginCircleci } from '../../../../plugins/circleci/package.json';
import { version as pluginExplore } from '../../../../plugins/explore/package.json';
import { version as pluginGithubActions } from '../../../../plugins/github-actions/package.json';
import { version as pluginLighthouse } from '../../../../plugins/lighthouse/package.json';
@@ -111,7 +110,6 @@ export const packageVersions = {
pluginCatalogBackendModuleScaffolderEntityModel,
'@backstage/plugin-catalog-graph': pluginCatalogGraph,
'@backstage/plugin-catalog-import': pluginCatalogImport,
- '@backstage/plugin-circleci': pluginCircleci,
'@backstage/plugin-explore': pluginExplore,
'@backstage/plugin-github-actions': pluginGithubActions,
'@backstage/plugin-lighthouse': pluginLighthouse,
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 6722ea2d03..7c6a71e070 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
@@ -300,9 +300,12 @@ const groupPage = (
-
+
+
+
+
diff --git a/packages/dev-utils/CHANGELOG.md b/packages/dev-utils/CHANGELOG.md
index f9020aaf13..bcb9033618 100644
--- a/packages/dev-utils/CHANGELOG.md
+++ b/packages/dev-utils/CHANGELOG.md
@@ -1,5 +1,47 @@
# @backstage/dev-utils
+## 1.0.25-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.5.0-next.1
+ - @backstage/plugin-catalog-react@1.9.2-next.2
+ - @backstage/app-defaults@1.4.6-next.2
+ - @backstage/catalog-model@1.4.3
+ - @backstage/core-app-api@1.11.2-next.1
+ - @backstage/core-components@0.13.9-next.2
+ - @backstage/core-plugin-api@1.8.1-next.1
+ - @backstage/integration-react@1.1.22-next.1
+
+## 1.0.25-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-components@0.13.9-next.1
+ - @backstage/core-plugin-api@1.8.1-next.1
+ - @backstage/plugin-catalog-react@1.9.2-next.1
+ - @backstage/core-app-api@1.11.2-next.1
+ - @backstage/app-defaults@1.4.6-next.1
+ - @backstage/integration-react@1.1.22-next.1
+ - @backstage/catalog-model@1.4.3
+ - @backstage/theme@0.5.0-next.0
+
+## 1.0.25-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-plugin-api@1.8.1-next.0
+ - @backstage/plugin-catalog-react@1.9.2-next.0
+ - @backstage/core-components@0.13.9-next.0
+ - @backstage/theme@0.5.0-next.0
+ - @backstage/app-defaults@1.4.6-next.0
+ - @backstage/core-app-api@1.11.2-next.0
+ - @backstage/integration-react@1.1.22-next.0
+ - @backstage/catalog-model@1.4.3
+
## 1.0.23
### Patch Changes
diff --git a/packages/dev-utils/package.json b/packages/dev-utils/package.json
index fe25d1c0ef..914dccf0a5 100644
--- a/packages/dev-utils/package.json
+++ b/packages/dev-utils/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/dev-utils",
"description": "Utilities for developing Backstage plugins.",
- "version": "1.0.23",
+ "version": "1.0.25-next.2",
"publishConfig": {
"access": "public",
"main": "dist/index.esm.js",
diff --git a/packages/dev-utils/src/components/EntityGridItem/EntityGridItem.tsx b/packages/dev-utils/src/components/EntityGridItem/EntityGridItem.tsx
index a66550a4d2..55bac24a34 100644
--- a/packages/dev-utils/src/components/EntityGridItem/EntityGridItem.tsx
+++ b/packages/dev-utils/src/components/EntityGridItem/EntityGridItem.tsx
@@ -16,11 +16,10 @@
import { Entity } from '@backstage/catalog-model';
import { EntityProvider } from '@backstage/plugin-catalog-react';
-import { BackstageTheme } from '@backstage/theme';
-import { Grid, GridProps, makeStyles } from '@material-ui/core';
+import { Grid, GridProps, Theme, makeStyles } from '@material-ui/core';
import React from 'react';
-const useStyles = makeStyles(theme => ({
+const useStyles = makeStyles(theme => ({
root: ({ entity }) => ({
position: 'relative',
diff --git a/packages/e2e-test-utils/playwright-api-report.md b/packages/e2e-test-utils/api-report-playwright.md
similarity index 100%
rename from packages/e2e-test-utils/playwright-api-report.md
rename to packages/e2e-test-utils/api-report-playwright.md
diff --git a/packages/e2e-test/CHANGELOG.md b/packages/e2e-test/CHANGELOG.md
index 988b7c95ae..f412da8738 100644
--- a/packages/e2e-test/CHANGELOG.md
+++ b/packages/e2e-test/CHANGELOG.md
@@ -1,5 +1,39 @@
# e2e-test
+## 0.2.10-next.3
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/create-app@0.5.8-next.3
+ - @backstage/cli-common@0.1.13
+ - @backstage/errors@1.2.3
+
+## 0.2.10-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/create-app@0.5.8-next.2
+ - @backstage/cli-common@0.1.13
+ - @backstage/errors@1.2.3
+
+## 0.2.10-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/create-app@0.5.8-next.1
+
+## 0.2.10-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/create-app@0.5.8-next.0
+ - @backstage/cli-common@0.1.13
+ - @backstage/errors@1.2.3
+
## 0.2.9
### Patch Changes
diff --git a/packages/e2e-test/package.json b/packages/e2e-test/package.json
index 018c679132..c4ea45fb91 100644
--- a/packages/e2e-test/package.json
+++ b/packages/e2e-test/package.json
@@ -1,7 +1,7 @@
{
"name": "e2e-test",
"description": "E2E test for verifying Backstage packages",
- "version": "0.2.9",
+ "version": "0.2.10-next.3",
"private": true,
"backstage": {
"role": "cli"
diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md
index 56ed54388a..6335f22f3c 100644
--- a/packages/eslint-plugin/CHANGELOG.md
+++ b/packages/eslint-plugin/CHANGELOG.md
@@ -1,5 +1,11 @@
# @backstage/eslint-plugin
+## 0.1.4-next.0
+
+### Patch Changes
+
+- 107dc46ab1: The `no-undeclared-imports` rule will now prefer using version queries that already exist en the repo for the same dependency type when installing new packages.
+
## 0.1.3
### Patch Changes
diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json
index 34a8205868..656ec78580 100644
--- a/packages/eslint-plugin/package.json
+++ b/packages/eslint-plugin/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/eslint-plugin",
"description": "Backstage ESLint plugin",
- "version": "0.1.3",
+ "version": "0.1.4-next.0",
"publishConfig": {
"access": "public"
},
diff --git a/packages/eslint-plugin/rules/no-undeclared-imports.js b/packages/eslint-plugin/rules/no-undeclared-imports.js
index c986b52c65..d2c92ccb8a 100644
--- a/packages/eslint-plugin/rules/no-undeclared-imports.js
+++ b/packages/eslint-plugin/rules/no-undeclared-imports.js
@@ -121,6 +121,39 @@ function getAddFlagForDepsField(depsField) {
}
}
+/**
+ * Looks up the most common version range for a dependency if it already exists in the repo.
+ *
+ * @param {string} name
+ * @param {string} flag
+ * @param {getPackageMap.PackageMap} packages
+ * @returns {string}
+ */
+function addVersionQuery(name, flag, packages) {
+ const rangeCounts = new Map();
+
+ for (const pkg of packages.list) {
+ const deps =
+ flag === '--dev'
+ ? pkg.packageJson.devDependencies
+ : flag === '--peer'
+ ? pkg.packageJson.peerDependencies
+ : pkg.packageJson.dependencies;
+ const range = deps?.[name];
+ if (range) {
+ rangeCounts.set(range, (rangeCounts.get(range) ?? 0) + 1);
+ }
+ }
+
+ const mostCommonRange = [...rangeCounts.entries()].sort(
+ (a, b) => b[1] - a[1],
+ )[0]?.[0];
+ if (!mostCommonRange) {
+ return name;
+ }
+ return `${name}@${mostCommonRange}`;
+}
+
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
@@ -177,13 +210,22 @@ module.exports = {
}
for (const [flag, names] of Object.entries(byFlag)) {
+ // Look up existing version queries in the repo for the same dependency
+ const namesWithQuery = [...names].map(name =>
+ addVersionQuery(name, flag, packages),
+ );
+
// The security implication of this is a bit interesting, as crafted add-import
// directives could be used to install malicious packages. However, the same is true
- // for adding malicious packages to package.json, so there's significant difference.
- execFileSync('yarn', ['add', ...(flag ? [flag] : []), ...names], {
- cwd: localPkg.dir,
- stdio: 'inherit',
- });
+ // for adding malicious packages to package.json, so there's no significant difference.
+ execFileSync(
+ 'yarn',
+ ['add', ...(flag ? [flag] : []), ...namesWithQuery],
+ {
+ cwd: localPkg.dir,
+ stdio: 'inherit',
+ },
+ );
}
// This switches all import directives back to the original import.
diff --git a/packages/frontend-app-api/CHANGELOG.md b/packages/frontend-app-api/CHANGELOG.md
index 80fd99e370..5867ae9ab5 100644
--- a/packages/frontend-app-api/CHANGELOG.md
+++ b/packages/frontend-app-api/CHANGELOG.md
@@ -1,5 +1,68 @@
# @backstage/frontend-app-api
+## 0.4.0-next.2
+
+### Minor Changes
+
+- ea06590: The app no longer provides the `AppContext` from `@backstage/core-plugin-api`. Components that require this context to be available should use the `compatWrapper` helper from `@backstage/core-compat-api`.
+
+### Patch Changes
+
+- aeb8008: Add support for translation extensions.
+- b7adf24: Use the new plugin type for error boundary components.
+- 8f5d6c1: Updates to match the new extension input wrapping.
+- cb4197a: Forward ` node`` instead of `extensionId` to resolved extension inputs.
+- 8837a96: Updates to match the introduction of `ExtensionDefinition` and new extension ID naming patterns.
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.4.0-next.2
+ - @backstage/theme@0.5.0-next.1
+ - @backstage/config@1.1.1
+ - @backstage/core-app-api@1.11.2-next.1
+ - @backstage/core-components@0.13.9-next.2
+ - @backstage/core-plugin-api@1.8.1-next.1
+ - @backstage/types@1.1.1
+ - @backstage/version-bridge@1.0.7
+
+## 0.4.0-next.1
+
+### Minor Changes
+
+- e539735435: Updated core extension structure to make space for the sign-in page by adding `core.router`.
+
+### Patch Changes
+
+- 5eb6b8a7bc: Added the nav logo extension for customization of sidebar logo
+- 1f12fb762c: Create a core components extension that allows adopters to override core app components such as `Progress`, `BootErrorPage`, `NotFoundErrorPage` and `ErrorBoundaryFallback`.
+- 59709286b3: Collect and register feature flags from plugins and extension overrides.
+- f27ee7d937: Migrate analytics route tracker component.
+- a5a04739e1: Updates to provide `node` to extension factories instead of `id` and `source`.
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.4.0-next.1
+ - @backstage/core-components@0.13.9-next.1
+ - @backstage/core-plugin-api@1.8.1-next.1
+ - @backstage/core-app-api@1.11.2-next.1
+ - @backstage/config@1.1.1
+ - @backstage/theme@0.5.0-next.0
+ - @backstage/types@1.1.1
+ - @backstage/version-bridge@1.0.7
+
+## 0.3.1-next.0
+
+### Patch Changes
+
+- 60d6eb544e: Removed `@backstage/plugin-graphiql` dependency.
+- 9ad4039efa: Bringing over apis from core-plugin-api
+- b8cb7804c8: Added `createSpecializedApp`, which is a synchronous version of `createApp` where config and features already need to be loaded.
+- Updated dependencies
+ - @backstage/core-plugin-api@1.8.1-next.0
+ - @backstage/core-components@0.13.9-next.0
+ - @backstage/theme@0.5.0-next.0
+ - @backstage/frontend-plugin-api@0.3.1-next.0
+ - @backstage/core-app-api@1.11.2-next.0
+ - @backstage/config@1.1.1
+ - @backstage/types@1.1.1
+ - @backstage/version-bridge@1.0.7
+
## 0.3.0
### Minor Changes
diff --git a/packages/frontend-app-api/api-report.md b/packages/frontend-app-api/api-report.md
index 3d6d936018..3ca51f6331 100644
--- a/packages/frontend-app-api/api-report.md
+++ b/packages/frontend-app-api/api-report.md
@@ -41,6 +41,15 @@ export function createApp(options?: {
// @public (undocumented)
export function createExtensionTree(options: { config: Config }): ExtensionTree;
+// @public
+export function createSpecializedApp(options?: {
+ features?: (BackstagePlugin | ExtensionOverrides)[];
+ config?: ConfigApi;
+ bindRoutes?(context: { bind: AppRouteBinder }): void;
+}): {
+ createRoot(): JSX_2.Element;
+};
+
// @public (undocumented)
export interface ExtensionTree {
// (undocumented)
diff --git a/packages/frontend-app-api/package.json b/packages/frontend-app-api/package.json
index 9d35a7daf4..64c1b9defc 100644
--- a/packages/frontend-app-api/package.json
+++ b/packages/frontend-app-api/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/frontend-app-api",
- "version": "0.3.0",
+ "version": "0.4.0-next.2",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/packages/frontend-app-api/src/apis/implementations/ComponentsApi/ComponentsApi.ts b/packages/frontend-app-api/src/apis/implementations/ComponentsApi/ComponentsApi.ts
new file mode 100644
index 0000000000..4d8bc47e24
--- /dev/null
+++ b/packages/frontend-app-api/src/apis/implementations/ComponentsApi/ComponentsApi.ts
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2023 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { ComponentType } from 'react';
+import { ComponentRef, ComponentsApi } from '@backstage/frontend-plugin-api';
+
+/**
+ * Implementation for the {@linkComponentApi}
+ *
+ * @internal
+ */
+export class DefaultComponentsApi implements ComponentsApi {
+ #components: Map, ComponentType