Merge pull request #5032 from backstage/timbonicus/create-app-updates

Update create-app template for better parity with example app
This commit is contained in:
Tim
2021-03-25 06:23:36 -06:00
committed by GitHub
17 changed files with 301 additions and 91 deletions
+41
View File
@@ -0,0 +1,41 @@
---
'@backstage/create-app': patch
---
Adds plugin-org and more capability to the default EntityPage to display Users, Groups and Systems.
To update an existing application, add the org plugin:
```shell
cd packages/app
yarn add @backstage/plugin-org
```
Then add the example systems locations to your `app-config.yaml`:
```diff
catalog:
rules:
- - allow: [Component, API, Group, User, Template, Location]
+ - allow: [Component, System, API, Group, User, Template, Location]
locations:
# Backstage example components
- type: url
target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-components.yaml
+ # Backstage example systems
+ - type: url
+ target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-systems.yaml
+
# Backstage example APIs
```
Additionally, the default app sidebar was updated to parity with the Backstage
repo. You can see these changes in the template
[App.tsx](https://github.com/backstage/backstage/blob/8817a87cdd5c881fbe8a43557ba7f9df0f9e3258/packages/create-app/templates/default-app/packages/app/src/App.tsx#L70)
referencing a new `Root` component.
Finally, compare your `packages/app/src/components/catalog/EntityPage.tsx` to
[EntityPage](https://github.com/backstage/backstage/blob/8817a87cdd5c881fbe8a43557ba7f9df0f9e3258/packages/create-app/templates/default-app/packages/app/src/components/catalog/EntityPage.tsx)
from the `@backstage/create-app` default template to pick up additional
changes there.
@@ -75,7 +75,7 @@ const app = createApp({
You may also want to add a link to the catalog index page to your sidebar:
```tsx
// packages/app/src/sidebar.tsx
// packages/app/src/components/Root.tsx
import HomeIcon from '@material-ui/icons/Home';
// Somewhere within the <Sidebar>
@@ -68,8 +68,8 @@ proxy:
In a standard Backstage app created with
[@backstage/create-app](./create-an-app.md), the sidebar is managed inside
`packages/app/src/sidebar.tsx`. The file exports the entire `Sidebar` element of
your app, which you can extend with additional entries by adding new
`packages/app/src/components/Root.tsx`. The file exports the entire `Sidebar`
element of your app, which you can extend with additional entries by adding new
`SidebarItem` elements.
For example, if you install the `api-docs` plugin, a matching `SidebarItem`
+1 -1
View File
@@ -49,7 +49,7 @@ title: Adding Custom Plugin to Existing Monorepo App
Let's add a shortcut.
1. Open and modify `root: packages > app > src > sidebar.tsx` with the
1. Open and modify `root: packages > app > src > components > Root.tsx` with the
following:
```tsx
+1 -1
View File
@@ -49,7 +49,7 @@ import { hot } from 'react-hot-loader/root';
import { Navigate, Route } from 'react-router';
import { apis } from './apis';
import { EntityPage } from './components/catalog/EntityPage';
import Root from './components/Root';
import { Root } from './components/Root';
import { providers } from './identityProviders';
import * as plugins from './plugins';
+1 -3
View File
@@ -73,7 +73,7 @@ const SidebarLogo = () => {
);
};
const Root = ({ children }: PropsWithChildren<{}>) => (
export const Root = ({ children }: PropsWithChildren<{}>) => (
<SidebarPage>
<Sidebar>
<SidebarLogo />
@@ -98,5 +98,3 @@ const Root = ({ children }: PropsWithChildren<{}>) => (
{children}
</SidebarPage>
);
export default Root;
+1 -1
View File
@@ -14,4 +14,4 @@
* limitations under the License.
*/
export { default } from './Root';
export { Root } from './Root';
@@ -513,6 +513,9 @@ export const EntityPage = () => {
return <SystemEntityPage entity={entity} />;
case 'domain':
return <DomainEntityPage entity={entity} />;
case 'location':
case 'resource':
case 'template':
default:
return <DefaultEntityPage entity={entity} />;
}
+2
View File
@@ -52,6 +52,7 @@ import { version as pluginCircleci } from '../../../../plugins/circleci/package.
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';
import { version as pluginOrg } from '../../../../plugins/org/package.json';
import { version as pluginProxyBackend } from '../../../../plugins/proxy-backend/package.json';
import { version as pluginRollbarBackend } from '../../../../plugins/rollbar-backend/package.json';
import { version as pluginScaffolder } from '../../../../plugins/scaffolder/package.json';
@@ -82,6 +83,7 @@ export const packageVersions = {
'@backstage/plugin-explore': pluginExplore,
'@backstage/plugin-github-actions': pluginGithubActions,
'@backstage/plugin-lighthouse': pluginLighthouse,
'@backstage/plugin-org': pluginOrg,
'@backstage/plugin-proxy-backend': pluginProxyBackend,
'@backstage/plugin-rollbar-backend': pluginRollbarBackend,
'@backstage/plugin-scaffolder': pluginScaffolder,
@@ -79,12 +79,16 @@ scaffolder:
catalog:
rules:
- allow: [Component, API, Group, User, Template, Location]
- allow: [Component, System, API, Group, User, Template, Location]
locations:
# Backstage example components
- type: url
target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-components.yaml
# Backstage example systems
- type: url
target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-systems.yaml
# Backstage example APIs
- type: url
target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-apis.yaml
@@ -4,24 +4,25 @@
"private": true,
"bundled": true,
"dependencies": {
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@backstage/catalog-model": "^{{version '@backstage/catalog-model'}}",
"@backstage/cli": "^{{version '@backstage/cli'}}",
"@backstage/core": "^{{version '@backstage/core'}}",
"@backstage/integration-react": "^{{version '@backstage/integration-react'}}",
"@backstage/plugin-api-docs": "^{{version '@backstage/plugin-api-docs'}}",
"@backstage/plugin-catalog": "^{{version '@backstage/plugin-catalog'}}",
"@backstage/plugin-catalog-react": "^{{version '@backstage/plugin-catalog-react'}}",
"@backstage/plugin-catalog-import": "^{{version '@backstage/plugin-catalog-import'}}",
"@backstage/plugin-scaffolder": "^{{version '@backstage/plugin-scaffolder'}}",
"@backstage/plugin-techdocs": "^{{version '@backstage/plugin-techdocs'}}",
"@backstage/catalog-model": "^{{version '@backstage/catalog-model'}}",
"@backstage/plugin-tech-radar": "^{{version '@backstage/plugin-tech-radar'}}",
"@backstage/plugin-catalog-react": "^{{version '@backstage/plugin-catalog-react'}}",
"@backstage/plugin-github-actions": "^{{version '@backstage/plugin-github-actions'}}",
"@backstage/plugin-user-settings": "^{{version '@backstage/plugin-user-settings'}}",
"@backstage/plugin-org": "^{{version '@backstage/plugin-org'}}",
"@backstage/plugin-scaffolder": "^{{version '@backstage/plugin-scaffolder'}}",
"@backstage/plugin-search": "^{{version '@backstage/plugin-search'}}",
"@backstage/integration-react": "^{{version '@backstage/integration-react'}}",
"@backstage/plugin-tech-radar": "^{{version '@backstage/plugin-tech-radar'}}",
"@backstage/plugin-techdocs": "^{{version '@backstage/plugin-techdocs'}}",
"@backstage/plugin-user-settings": "^{{version '@backstage/plugin-user-settings'}}",
"@backstage/test-utils": "^{{version '@backstage/test-utils'}}",
"@backstage/theme": "^{{version '@backstage/theme'}}",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"history": "^5.0.0",
"react": "^16.13.1",
"react-dom": "^16.13.1",
@@ -1,9 +1,10 @@
import React from 'react';
import { Navigate, Route } from 'react-router';
import {
AlertDisplay,
createApp,
FlatRoutes,
OAuthRequestDialog,
SidebarPage,
} from '@backstage/core';
import { apiDocsPlugin, ApiExplorerPage } from '@backstage/plugin-api-docs';
import {
@@ -17,12 +18,10 @@ import { SearchPage } from '@backstage/plugin-search';
import { TechRadarPage } from '@backstage/plugin-tech-radar';
import { TechdocsPage } from '@backstage/plugin-techdocs';
import { UserSettingsPage } from '@backstage/plugin-user-settings';
import React from 'react';
import { Navigate, Route } from 'react-router';
import { apis } from './apis';
import { EntityPage } from './components/catalog/EntityPage';
import { Root } from './components/Root';
import * as plugins from './plugins';
import { AppSidebar } from './sidebar';
const app = createApp({
apis,
@@ -40,34 +39,35 @@ const app = createApp({
const AppProvider = app.getProvider();
const AppRouter = app.getRouter();
const routes = (
<FlatRoutes>
<Navigate key="/" to="/catalog" />
<Route path="/catalog" element={<CatalogIndexPage />} />
<Route
path="/catalog/:namespace/:kind/:name"
element={<CatalogEntityPage />}
>
<EntityPage />
</Route>
<Route path="/docs" element={<TechdocsPage />} />
<Route path="/create" element={<ScaffolderPage />} />
<Route path="/api-docs" element={<ApiExplorerPage />} />
<Route
path="/tech-radar"
element={<TechRadarPage width={1500} height={800} />}
/>
<Route path="/catalog-import" element={<CatalogImportPage />} />
<Route path="/search" element={<SearchPage />} />
<Route path="/settings" element={<UserSettingsPage />} />
</FlatRoutes>
);
const App = () => (
<AppProvider>
<AlertDisplay />
<OAuthRequestDialog />
<AppRouter>
<SidebarPage>
<AppSidebar />
<FlatRoutes>
<Navigate key="/" to="/catalog" />
<Route path="/catalog" element={<CatalogIndexPage />} />
<Route
path="/catalog/:namespace/:kind/:name"
element={<CatalogEntityPage />}
>
<EntityPage />
</Route>
<Route path="/docs" element={<TechdocsPage />} />
<Route path="/create" element={<ScaffolderPage />} />
<Route path="/api-docs" element={<ApiExplorerPage />} />
<Route
path="/tech-radar"
element={<TechRadarPage width={1500} height={800} />}
/>
<Route path="/catalog-import" element={<CatalogImportPage />} />
<Route path="/search" element={<SearchPage />} />
<Route path="/settings" element={<UserSettingsPage />} />
</FlatRoutes>
</SidebarPage>
<Root>{routes}</Root>
</AppRouter>
</AppProvider>
);
@@ -1,26 +1,41 @@
import React, { useContext } from 'react';
import HomeIcon from '@material-ui/icons/Home';
import LibraryBooks from '@material-ui/icons/LibraryBooks';
import ExtensionIcon from '@material-ui/icons/Extension';
import CreateComponentIcon from '@material-ui/icons/AddCircleOutline';
import MapIcon from '@material-ui/icons/MyLocation';
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React, { useContext, PropsWithChildren } from 'react';
import { Link, makeStyles } from '@material-ui/core';
import { NavLink } from 'react-router-dom';
import HomeIcon from '@material-ui/icons/Home';
import ExtensionIcon from '@material-ui/icons/Extension';
import MapIcon from '@material-ui/icons/MyLocation';
import LibraryBooks from '@material-ui/icons/LibraryBooks';
import CreateComponentIcon from '@material-ui/icons/AddCircleOutline';
import LogoFull from './LogoFull';
import LogoIcon from './LogoIcon';
import { Settings as SidebarSettings } from '@backstage/plugin-user-settings';
import {
Sidebar,
SidebarItem,
SidebarDivider,
SidebarPage,
sidebarConfig,
SidebarContext,
SidebarItem,
SidebarDivider,
SidebarSpace,
} from '@backstage/core';
import { NavLink } from 'react-router-dom';
import { Settings as SidebarSettings } from '@backstage/plugin-user-settings';
import { SidebarSearch } from '@backstage/plugin-search';
const useSidebarLogoStyles = makeStyles({
root: {
width: sidebarConfig.drawerWidthClosed,
@@ -54,22 +69,24 @@ const SidebarLogo = () => {
);
};
export const AppSidebar = () => (
<Sidebar>
<SidebarLogo />
<SidebarSearch />
<SidebarDivider />
{/* Global nav, not org-specific */}
<SidebarItem icon={HomeIcon} to="./" text="Home" />
<SidebarItem icon={ExtensionIcon} to="api-docs" text="APIs" />
<SidebarItem icon={LibraryBooks} to="/docs" text="Docs" />
<SidebarItem icon={CreateComponentIcon} to="create" text="Create..." />
<SidebarDivider />
<SidebarItem icon={MapIcon} to="tech-radar" text="Tech Radar" />
{/* End global nav */}
<SidebarDivider />
<SidebarSpace />
<SidebarDivider />
<SidebarSettings />
</Sidebar>
export const Root = ({ children }: PropsWithChildren<{}>) => (
<SidebarPage>
<Sidebar>
<SidebarLogo />
<SidebarSearch />
<SidebarDivider />
{/* Global nav, not org-specific */}
<SidebarItem icon={HomeIcon} to="/catalog" text="Home" />
<SidebarItem icon={ExtensionIcon} to="api-docs" text="APIs" />
<SidebarItem icon={LibraryBooks} to="docs" text="Docs" />
<SidebarItem icon={CreateComponentIcon} to="create" text="Create..." />
{/* End global nav */}
<SidebarDivider />
<SidebarItem icon={MapIcon} to="tech-radar" text="Tech Radar" />
<SidebarSpace />
<SidebarDivider />
<SidebarSettings />
</Sidebar>
{children}
</SidebarPage>
);
@@ -0,0 +1,17 @@
/*
* 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.
*/
export { Root } from './Root';
@@ -13,27 +13,43 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ApiEntity, Entity } from '@backstage/catalog-model';
import { WarningPanel } from '@backstage/core';
import React from 'react';
import { Button, Grid } from '@material-ui/core';
import {
ApiEntity,
DomainEntity,
Entity,
GroupEntity,
SystemEntity,
UserEntity,
} from '@backstage/catalog-model';
import { EmptyState } from '@backstage/core';
import {
ApiDefinitionCard,
ConsumedApisCard,
ConsumingComponentsCard,
EntityHasApisCard,
ProvidedApisCard,
ProvidingComponentsCard
ProvidingComponentsCard,
} from '@backstage/plugin-api-docs';
import {
AboutCard, EntityPageLayout,
AboutCard,
EntityHasComponentsCard,
EntityHasSystemsCard,
EntityPageLayout,
} from '@backstage/plugin-catalog';
import { useEntity } from '@backstage/plugin-catalog-react';
import {
useEntity
} from '@backstage/plugin-catalog-react';
import {
isPluginApplicableToEntity as isGitHubActionsAvailable, Router as GitHubActionsRouter
isPluginApplicableToEntity as isGitHubActionsAvailable,
Router as GitHubActionsRouter,
} from '@backstage/plugin-github-actions';
import {
GroupProfileCard,
MembersListCard,
OwnershipCard,
UserProfileCard,
} from '@backstage/plugin-org';
import { EmbeddedDocsRouter as DocsRouter } from '@backstage/plugin-techdocs';
import { Grid } from '@material-ui/core';
import React from 'react';
const CICDSwitcher = ({ entity }: { entity: Entity }) => {
@@ -44,17 +60,27 @@ const CICDSwitcher = ({ entity }: { entity: Entity }) => {
return <GitHubActionsRouter entity={entity} />;
default:
return (
<WarningPanel title="CI/CD switcher:">
No CI/CD is available for this entity. Check corresponding
annotations!
</WarningPanel>
<EmptyState
title="No CI/CD available for this entity"
missing="info"
description="You need to add an annotation to your component if you want to enable CI/CD for it. You can read more about annotations in Backstage by clicking the button below."
action={
<Button
variant="contained"
color="primary"
href="https://backstage.io/docs/features/software-catalog/well-known-annotations"
>
Read more
</Button>
}
/>
);
}
};
const OverviewContent = ({ entity }: { entity: Entity }) => (
<Grid container spacing={3} alignItems="stretch">
<Grid item>
<Grid item md={6}>
<AboutCard entity={entity} variant="gridItem" />
</Grid>
</Grid>
@@ -181,6 +207,96 @@ const ApiEntityPage = ({ entity }: { entity: Entity }) => (
</EntityPageLayout>
);
const UserOverviewContent = ({ entity }: { entity: UserEntity }) => (
<Grid container spacing={3}>
<Grid item xs={12} md={6}>
<UserProfileCard entity={entity} variant="gridItem" />
</Grid>
<Grid item xs={12} md={6}>
<OwnershipCard entity={entity} variant="gridItem" />
</Grid>
</Grid>
);
const UserEntityPage = ({ entity }: { entity: Entity }) => (
<EntityPageLayout>
<EntityPageLayout.Content
path="/*"
title="Overview"
element={<UserOverviewContent entity={entity as UserEntity} />}
/>
</EntityPageLayout>
);
const GroupOverviewContent = ({ entity }: { entity: GroupEntity }) => (
<Grid container spacing={3}>
<Grid item xs={12} md={6}>
<GroupProfileCard entity={entity} variant="gridItem" />
</Grid>
<Grid item xs={12} md={6}>
<OwnershipCard entity={entity} variant="gridItem" />
</Grid>
<Grid item xs={12}>
<MembersListCard entity={entity} />
</Grid>
</Grid>
);
const GroupEntityPage = ({ entity }: { entity: Entity }) => (
<EntityPageLayout>
<EntityPageLayout.Content
path="/*"
title="Overview"
element={<GroupOverviewContent entity={entity as GroupEntity} />}
/>
</EntityPageLayout>
);
const SystemOverviewContent = ({ entity }: { entity: SystemEntity }) => (
<Grid container spacing={3} alignItems="stretch">
<Grid item md={6}>
<AboutCard entity={entity} variant="gridItem" />
</Grid>
<Grid item md={6}>
<EntityHasComponentsCard variant="gridItem" />
</Grid>
<Grid item md={6}>
<EntityHasApisCard variant="gridItem" />
</Grid>
</Grid>
);
const SystemEntityPage = ({ entity }: { entity: Entity }) => (
<EntityPageLayout>
<EntityPageLayout.Content
path="/*"
title="Overview"
element={<SystemOverviewContent entity={entity as SystemEntity} />}
/>
</EntityPageLayout>
);
const DomainOverviewContent = ({ entity }: { entity: DomainEntity }) => (
<Grid container spacing={3} alignItems="stretch">
<Grid item md={6}>
<AboutCard entity={entity} variant="gridItem" />
</Grid>
<Grid item md={6}>
<EntityHasSystemsCard variant="gridItem" />
</Grid>
</Grid>
);
const DomainEntityPage = ({ entity }: { entity: Entity }) => (
<EntityPageLayout>
<EntityPageLayout.Content
path="/*"
title="Overview"
element={<DomainOverviewContent entity={entity as DomainEntity} />}
/>
</EntityPageLayout>
);
export const EntityPage = () => {
const { entity } = useEntity();
@@ -189,6 +305,17 @@ export const EntityPage = () => {
return <ComponentEntityPage entity={entity} />;
case 'api':
return <ApiEntityPage entity={entity} />;
case 'group':
return <GroupEntityPage entity={entity} />;
case 'user':
return <UserEntityPage entity={entity} />;
case 'system':
return <SystemEntityPage entity={entity} />;
case 'domain':
return <DomainEntityPage entity={entity} />;
case 'location':
case 'resource':
case 'template':
default:
return <DefaultEntityPage entity={entity} />;
}