Backstage: add scaffolding (#46634)

* initial plugin scaffolding as generated by backstage

* ignore backstage for prettier

* update eslint-plugin-react

* fix react backstage versioning issue

* review feedback and licensing

* add missing config for startup

* ignore more licenses from deps pulled in by backstage

* add node_modules to prettier ignore
This commit is contained in:
William Bezuidenhout 2023-01-20 12:12:14 +02:00 committed by GitHub
parent b724b071ca
commit e1852a7dfb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 5747 additions and 668 deletions

View File

@ -41,3 +41,5 @@ client/jetbrains/.gradle
code-intel-extensions.json
.direnv
pnpm-lock.yaml
client/plugin-backstage/
node_modules/

View File

@ -5,3 +5,4 @@
**/*.svg
node_modules/
__mocks__/
client/plugin-backstage/

View File

@ -5,7 +5,7 @@
"description": "Custom rules and recommended config for consumers of the Wildcard component library",
"main": "lib/index.js",
"peerDependencies": {
"eslint-plugin-react": "^7.21.1"
"eslint-plugin-react": "^7.32.1"
},
"license": "Apache-2.0"
}

View File

@ -0,0 +1 @@
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);

1
client/plugin-backstage/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
node_modules

View File

@ -0,0 +1,13 @@
# sourcegraph
Welcome to the sourcegraph plugin!
_This plugin was created through the Backstage CLI_
## Getting started
Your plugin has been added to the example app in this repository, meaning you'll be able to access it by running `yarn start` in the root directory, and then navigating to [/sourcegraph](http://localhost:3000/sourcegraph).
You can also serve the plugin in isolation by running `yarn start` in the plugin directory.
This method of serving the plugin provides quicker iteration speed and a faster startup and hot reloads.
It is only meant for local development, and the setup for it can be found inside the [/dev](./dev) directory.

View File

@ -0,0 +1,104 @@
app:
title: Scaffolded Backstage App
baseUrl: http://localhost:3000
organization:
name: My Company
backend:
# Used for enabling authentication, secret is shared by all backend plugins
# See https://backstage.io/docs/tutorials/backend-to-backend-auth for
# information on the format
# auth:
# keys:
# - secret: ${BACKEND_SECRET}
baseUrl: http://localhost:7007
listen:
port: 7007
# Uncomment the following host directive to bind to specific interfaces
# host: 127.0.0.1
csp:
connect-src: ["'self'", 'http:', 'https:']
# Content-Security-Policy directives follow the Helmet format: https://helmetjs.github.io/#reference
# Default Helmet Content-Security-Policy values can be removed by setting the key to false
cors:
origin: http://localhost:3000
methods: [GET, HEAD, PATCH, POST, PUT, DELETE]
credentials: true
# This is for local development only, it is not recommended to use this in production
# The production database configuration is stored in app-config.production.yaml
database:
client: better-sqlite3
connection: ':memory:'
cache:
store: memory
# workingDirectory: /tmp # Use this to configure a working directory for the scaffolder, defaults to the OS temp-dir
integrations:
github:
- host: github.com
# This is a Personal Access Token or PAT from GitHub. You can find out how to generate this token, and more information
# about setting up the GitHub integration here: https://backstage.io/docs/getting-started/configuration#setting-up-a-github-integration
token: ${GITHUB_TOKEN}
### Example for how to add your GitHub Enterprise instance using the API:
# - host: ghe.example.net
# apiBaseUrl: https://ghe.example.net/api/v3
# token: ${GHE_TOKEN}
proxy:
### Example for how to add a proxy endpoint for the frontend.
### A typical reason to do this is to handle HTTPS and CORS for internal services.
# '/test':
# target: 'https://example.com'
# changeOrigin: true
# Reference documentation http://backstage.io/docs/features/techdocs/configuration
# Note: After experimenting with basic setup, use CI/CD to generate docs
# and an external cloud storage when deploying TechDocs for production use-case.
# https://backstage.io/docs/features/techdocs/how-to-guides#how-to-migrate-from-techdocs-basic-to-recommended-deployment-approach
techdocs:
builder: 'local' # Alternatives - 'external'
generator:
runIn: 'docker' # Alternatives - 'local'
publisher:
type: 'local' # Alternatives - 'googleGcs' or 'awsS3'. Read documentation for using alternatives.
auth:
# see https://backstage.io/docs/auth/ to learn about auth providers
providers: {}
scaffolder:
# see https://backstage.io/docs/features/software-templates/configuration for software template options
catalog:
import:
entityFilename: catalog-info.yaml
pullRequestBranchName: backstage-integration
rules:
- allow: [Component, System, API, Resource, Location]
locations:
# Local example data, file locations are relative to the backend process, typically `packages/backend`
- type: file
target: ../../examples/entities.yaml
# Local example template
- type: file
target: ../../examples/template/template.yaml
rules:
- allow: [Template]
# Local example organizational data
- type: file
target: ../../examples/org.yaml
rules:
- allow: [User, Group]
## Uncomment these lines to add more example data
# - type: url
# target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all.yaml
## Uncomment these lines to add an example org
# - type: url
# target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme-corp.yaml
# rules:
# - allow: [User, Group]

View File

@ -0,0 +1,12 @@
import React from 'react';
import { createDevApp } from '@backstage/dev-utils';
import { sourcegraphPlugin, SourcegraphPage } from '../src/plugin';
createDevApp()
.registerPlugin(sourcegraphPlugin)
.addPage({
element: <SourcegraphPage />,
title: 'Root Page',
path: '/sourcegraph'
})
.render();

View File

@ -0,0 +1 @@
export {};

View File

@ -0,0 +1 @@
export declare const ExampleComponent: () => JSX.Element;

View File

@ -0,0 +1 @@
export { ExampleComponent } from './ExampleComponent';

View File

@ -0,0 +1,29 @@
type User = {
gender: string;
name: {
title: string;
first: string;
last: string;
};
location: object;
email: string;
login: object;
dob: object;
registered: object;
phone: string;
cell: string;
id: {
name: string;
value: string;
};
picture: {
medium: string;
};
nat: string;
};
type DenseTableProps = {
users: User[];
};
export declare const DenseTable: ({ users }: DenseTableProps) => JSX.Element;
export declare const ExampleFetchComponent: () => JSX.Element;
export {};

View File

@ -0,0 +1 @@
export { ExampleFetchComponent } from './ExampleFetchComponent';

View File

@ -0,0 +1 @@
export { sourcegraphPlugin, SourcegraphPage } from './plugin';

View File

@ -0,0 +1,4 @@
export declare const sourcegraphPlugin: import("@backstage/core-plugin-api").BackstagePlugin<{
root: import("@backstage/core-plugin-api").RouteRef<undefined>;
}, {}, {}>;
export declare const SourcegraphPage: () => JSX.Element;

View File

@ -0,0 +1 @@
export {};

View File

@ -0,0 +1 @@
export declare const rootRouteRef: import("@backstage/core-plugin-api").RouteRef<undefined>;

View File

@ -0,0 +1,2 @@
import '@testing-library/jest-dom';
import 'cross-fetch/polyfill';

View File

@ -0,0 +1,62 @@
{
"name": "@sourcegraph/backstage-plugin",
"version": "0.1.0",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
"private": true,
"publishConfig": {
"access": "public",
"main": "dist/index.esm.js",
"types": "dist/index.d.ts"
},
"backstage": {
"role": "frontend-plugin"
},
"scripts": {
"start": "backstage-cli package start",
"build": "backstage-cli package build",
"lint": "backstage-cli package lint",
"test": "backstage-cli package test",
"clean": "backstage-cli package clean",
"prepack": "backstage-cli package prepack",
"postpack": "backstage-cli package postpack"
},
"dependencies": {
"@backstage/core-components": "^0.12.3",
"@backstage/core-plugin-api": "^1.3.0",
"@backstage/theme": "^0.2.16",
"@internal/plugin-sourcegraph": "link:",
"@material-ui/core": "^4.9.13",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.57",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.7.0",
"react-use": "^17.2.4"
},
"peerDependencies": {
"react": "^16.13.1 || ^17.0.0"
},
"devDependencies": {
"@backstage/cli": "^0.22.0",
"@backstage/core-app-api": "^1.4.0",
"@backstage/dev-utils": "^1.0.11",
"@backstage/test-utils": "^1.2.3",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^12.1.3",
"@testing-library/user-event": "^14.0.0",
"@types/node": "*",
"cross-fetch": "^3.1.5",
"msw": "^0.49.0"
},
"resolutions": {
"react": "^17",
"react-dom": "^17",
"@types/react": "^17",
"@types/react-dom": "^17"
},
"files": [
"dist"
]
}

View File

@ -0,0 +1,27 @@
import React from 'react';
import { ExampleComponent } from './ExampleComponent';
import { rest } from 'msw';
import { setupServer } from 'msw/node';
import { screen } from '@testing-library/react';
import {
setupRequestMockHandlers,
renderInTestApp,
} from "@backstage/test-utils";
describe('ExampleComponent', () => {
const server = setupServer();
// Enable sane handlers for network requests
setupRequestMockHandlers(server);
// setup mock response
beforeEach(() => {
server.use(
rest.get('/*', (_, res, ctx) => res(ctx.status(200), ctx.json({}))),
);
});
it('should render', async () => {
await renderInTestApp(<ExampleComponent />);
expect(screen.getByText('Welcome to sourcegraph!')).toBeInTheDocument();
});
});

View File

@ -0,0 +1,38 @@
import React from 'react';
import { Typography, Grid } from '@material-ui/core';
import {
InfoCard,
Header,
Page,
Content,
ContentHeader,
HeaderLabel,
SupportButton,
} from '@backstage/core-components';
import { ExampleFetchComponent } from '../ExampleFetchComponent';
export const ExampleComponent = () => (
<Page themeId="tool">
<Header title="Welcome to sourcegraph!" subtitle="Optional subtitle">
<HeaderLabel label="Owner" value="Team X" />
<HeaderLabel label="Lifecycle" value="Alpha" />
</Header>
<Content>
<ContentHeader title="Plugin title">
<SupportButton>A description of your plugin goes here.</SupportButton>
</ContentHeader>
<Grid container spacing={3} direction="column">
<Grid item>
<InfoCard title="Information card">
<Typography variant="body1">
All content should be wrapped in a card like this.
</Typography>
</InfoCard>
</Grid>
<Grid item>
<ExampleFetchComponent />
</Grid>
</Grid>
</Content>
</Page>
);

View File

@ -0,0 +1 @@
export { ExampleComponent } from './ExampleComponent';

View File

@ -0,0 +1,25 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import { ExampleFetchComponent } from './ExampleFetchComponent';
import { rest } from 'msw';
import { setupServer } from 'msw/node';
import { setupRequestMockHandlers } from '@backstage/test-utils';
describe('ExampleFetchComponent', () => {
const server = setupServer();
// Enable sane handlers for network requests
setupRequestMockHandlers(server);
// setup mock response
beforeEach(() => {
server.use(
rest.get('https://randomuser.me/*', (_, res, ctx) =>
res(ctx.status(200), ctx.delay(2000), ctx.json({})),
),
);
});
it('should render', async () => {
await render(<ExampleFetchComponent />);
expect(await screen.findByTestId('progress')).toBeInTheDocument();
});
});

View File

@ -0,0 +1,90 @@
import React from 'react';
import { makeStyles } from '@material-ui/core/styles';
import { Table, TableColumn, Progress } from '@backstage/core-components';
import Alert from '@material-ui/lab/Alert';
import useAsync from 'react-use/lib/useAsync';
const useStyles = makeStyles({
avatar: {
height: 32,
width: 32,
borderRadius: '50%',
},
});
type User = {
gender: string; // "male"
name: {
title: string; // "Mr",
first: string; // "Duane",
last: string; // "Reed"
};
location: object; // {street: {number: 5060, name: "Hickory Creek Dr"}, city: "Albany", state: "New South Wales",…}
email: string; // "duane.reed@example.com"
login: object; // {uuid: "4b785022-9a23-4ab9-8a23-cb3fb43969a9", username: "blackdog796", password: "patch",…}
dob: object; // {date: "1983-06-22T12:30:23.016Z", age: 37}
registered: object; // {date: "2006-06-13T18:48:28.037Z", age: 14}
phone: string; // "07-2154-5651"
cell: string; // "0405-592-879"
id: {
name: string; // "TFN",
value: string; // "796260432"
};
picture: { medium: string }; // {medium: "https://randomuser.me/api/portraits/men/95.jpg",…}
nat: string; // "AU"
};
type DenseTableProps = {
users: User[];
};
export const DenseTable = ({ users }: DenseTableProps) => {
const classes = useStyles();
const columns: TableColumn[] = [
{ title: 'Avatar', field: 'avatar' },
{ title: 'Name', field: 'name' },
{ title: 'Email', field: 'email' },
{ title: 'Nationality', field: 'nationality' },
];
const data = users.map(user => {
return {
avatar: (
<img
src={user.picture.medium}
className={classes.avatar}
alt={user.name.first}
/>
),
name: `${user.name.first} ${user.name.last}`,
email: user.email,
nationality: user.nat,
};
});
return (
<Table
title="Example User List (fetching data from randomuser.me)"
options={{ search: false, paging: false }}
columns={columns}
data={data}
/>
);
};
export const ExampleFetchComponent = () => {
const { value, loading, error } = useAsync(async (): Promise<User[]> => {
const response = await fetch('https://randomuser.me/api/?results=20');
const data = await response.json();
return data.results;
}, []);
if (loading) {
return <Progress />;
} else if (error) {
return <Alert severity="error">{error.message}</Alert>;
}
return <DenseTable users={value || []} />;
};

View File

@ -0,0 +1 @@
export { ExampleFetchComponent } from './ExampleFetchComponent';

View File

@ -0,0 +1 @@
export { sourcegraphPlugin, SourcegraphPage } from './plugin';

View File

@ -0,0 +1,7 @@
import { sourcegraphPlugin } from './plugin';
describe('sourcegraph', () => {
it('should export plugin', () => {
expect(sourcegraphPlugin).toBeDefined();
});
});

View File

@ -0,0 +1,19 @@
import { createPlugin, createRoutableExtension } from '@backstage/core-plugin-api';
import { rootRouteRef } from './routes';
export const sourcegraphPlugin = createPlugin({
id: 'sourcegraph',
routes: {
root: rootRouteRef,
},
});
export const SourcegraphPage = sourcegraphPlugin.provide(
createRoutableExtension({
name: 'SourcegraphPage',
component: () =>
import('./components/ExampleComponent').then(m => m.ExampleComponent),
mountPoint: rootRouteRef,
}),
);

View File

@ -0,0 +1,5 @@
import { createRouteRef } from '@backstage/core-plugin-api';
export const rootRouteRef = createRouteRef({
id: 'sourcegraph',
});

View File

@ -0,0 +1,2 @@
import '@testing-library/jest-dom';
import 'cross-fetch/polyfill';

View File

@ -0,0 +1,15 @@
{
"extends": "@backstage/cli/config/tsconfig.json",
"include": [
"src",
"dev",
"migrations"
],
"exclude": [
"node_modules"
],
"compilerOptions": {
"outDir": "dist-types",
"rootDir": "."
}
}

View File

@ -452,6 +452,42 @@
:why: Inference broken, LICENSE file lives at https://www.npmjs.com/package/stdin#license
:versions: []
:when: 2023-01-09 04:58:41.642155000 Z
- - :ignore
- eslint-plugin-deprecation
- :who:
:why: dependency from backstage
:versions: []
:when: 2023-01-19 20:03:11.764982000 Z
- - :ignore
- fast-shallow-equal
- :who:
:why: dependency from backstage
:versions: []
:when: 2023-01-19 20:03:33.000495000 Z
- - :ignore
- pako
- :who:
:why: dependency from backstage
:versions: []
:when: 2023-01-19 20:03:51.213298000 Z
- - :ignore
- react-universal-interface
- :who:
:why: dependency from backstage
:versions: []
:when: 2023-01-19 20:04:06.690991000 Z
- - :ignore
- tosource
- :who:
:why: dependency from backstage
:versions: []
:when: 2023-01-19 20:25:48.126482000 Z
- - :ignore
- rollup-plugin-dts
- :who:
:why: dependency from backstage
:versions: []
:when: 2023-01-19 20:26:01.138835000 Z
- - :license
- github.com/xi2/xz
- Public domain

File diff suppressed because it is too large Load Diff