Merge pull request #11017 from backstage/permission-docs

Permission framework documentation
This commit is contained in:
Vincenzo Scamporlino
2022-05-02 17:29:07 +02:00
committed by GitHub
44 changed files with 2213 additions and 0 deletions
@@ -0,0 +1,3 @@
module.exports = {
extends: [require.resolve('@backstage/cli/config/eslint.backend')],
};
@@ -0,0 +1,3 @@
# todo-list-backend
This package provides a starting point to demonstrate how plugin authors can use the Backstage permission framework. Refer to the [documentation](https://backstage.io/docs/permission/plugin-authors/01-setup) to get started.
@@ -0,0 +1,22 @@
## API Report File for "@internal/plugin-todo-list-backend"
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
```ts
import express from 'express';
import { IdentityClient } from '@backstage/plugin-auth-node';
import { Logger } from 'winston';
// @public
export function createRouter(options: RouterOptions): Promise<express.Router>;
// @public
export interface RouterOptions {
// (undocumented)
identity: IdentityClient;
// (undocumented)
logger: Logger;
}
// (No @packageDocumentation comment for this package)
```
@@ -0,0 +1,45 @@
{
"name": "@internal/plugin-todo-list-backend",
"version": "1.0.0",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
"private": true,
"publishConfig": {
"access": "public",
"main": "dist/index.cjs.js",
"types": "dist/index.d.ts"
},
"scripts": {
"start": "backstage-cli package start",
"build": "backstage-cli package build",
"lint": "backstage-cli package lint",
"test": "backstage-cli package test",
"prepack": "backstage-cli package prepack",
"postpack": "backstage-cli package postpack",
"clean": "backstage-cli package clean"
},
"dependencies": {
"@backstage/backend-common": "^0.13.3-next.0",
"@backstage/config": "^1.0.0",
"@backstage/errors": "^1.0.0",
"@backstage/plugin-auth-node": "^0.2.1-next.0",
"@types/express": "^4.17.6",
"cross-fetch": "^3.1.5",
"express": "^4.17.1",
"express-promise-router": "^4.1.0",
"uuid": "^8.3.2",
"winston": "^3.2.1",
"yn": "^4.0.0"
},
"devDependencies": {
"@backstage/cli": "^0.17.1-next.0",
"@types/supertest": "^2.0.8",
"@types/uuid": "^8.0.0",
"msw": "^0.35.0",
"supertest": "^6.1.6"
},
"files": [
"dist"
]
}
@@ -0,0 +1,17 @@
/*
* Copyright 2020 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 * from './service/router';
@@ -0,0 +1,33 @@
/*
* Copyright 2020 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 { getRootLogger } from '@backstage/backend-common';
import yn from 'yn';
import { startStandaloneServer } from './service/standaloneServer';
const port = process.env.PLUGIN_PORT ? Number(process.env.PLUGIN_PORT) : 7007;
const enableCors = yn(process.env.PLUGIN_CORS, { default: false });
const logger = getRootLogger();
startStandaloneServer({ port, enableCors, logger }).catch(err => {
logger.error(err);
process.exit(1);
});
process.on('SIGINT', () => {
logger.info('CTRL+C pressed; exiting.');
process.exit(0);
});
@@ -0,0 +1,47 @@
/*
* Copyright 2020 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 { getVoidLogger } from '@backstage/backend-common';
import { IdentityClient } from '@backstage/plugin-auth-node';
import express from 'express';
import request from 'supertest';
import { createRouter } from './router';
describe('createRouter', () => {
let app: express.Express;
beforeAll(async () => {
const router = await createRouter({
logger: getVoidLogger(),
identity: {} as IdentityClient,
});
app = express().use(router);
});
beforeEach(() => {
jest.resetAllMocks();
});
describe('GET /health', () => {
it('returns ok', async () => {
const response = await request(app).get('/health');
expect(response.status).toEqual(200);
expect(response.body).toEqual({ status: 'ok' });
});
});
});
@@ -0,0 +1,100 @@
/*
* Copyright 2020 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 { errorHandler } from '@backstage/backend-common';
import express from 'express';
import Router from 'express-promise-router';
import { Logger } from 'winston';
import {
IdentityClient,
getBearerTokenFromAuthorizationHeader,
} from '@backstage/plugin-auth-node';
import { add, getAll, update } from './todos';
import { InputError } from '@backstage/errors';
/**
* Dependencies of the todo-list router
*
* @public
*/
export interface RouterOptions {
logger: Logger;
identity: IdentityClient;
}
/**
* Creates an express.Router with some endpoints
* for creating, editing and deleting todo items.
*
* @public
* @param options - the dependencies of the router
* @returns an express.Router
*
*/
export async function createRouter(
options: RouterOptions,
): Promise<express.Router> {
const { logger, identity } = options;
const router = Router();
router.use(express.json());
router.get('/health', (_, response) => {
logger.info('PONG!');
response.send({ status: 'ok' });
});
router.get('/todos', async (_req, res) => {
res.json(getAll());
});
router.post('/todos', async (req, res) => {
const token = getBearerTokenFromAuthorizationHeader(
req.header('authorization'),
);
let author: string | undefined = undefined;
const user = token ? await identity.authenticate(token) : undefined;
author = user?.identity.userEntityRef;
if (!isTodoCreateRequest(req.body)) {
throw new InputError('Invalid payload');
}
const todo = add({ title: req.body.title, author });
res.json(todo);
});
router.put('/todos', async (req, res) => {
if (!isTodoUpdateRequest(req.body)) {
throw new InputError('Invalid payload');
}
res.json(update(req.body));
});
router.use(errorHandler());
return router;
}
function isTodoCreateRequest(request: any): request is { title: string } {
return typeof request?.title === 'string';
}
function isTodoUpdateRequest(
request: any,
): request is { title: string; id: string } {
return typeof request?.id === 'string' && isTodoCreateRequest(request);
}
@@ -0,0 +1,61 @@
/*
* Copyright 2020 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 {
createServiceBuilder,
loadBackendConfig,
SingleHostDiscovery,
} from '@backstage/backend-common';
import { IdentityClient } from '@backstage/plugin-auth-node';
import { Server } from 'http';
import { Logger } from 'winston';
import { createRouter } from './router';
export interface ServerOptions {
port: number;
enableCors: boolean;
logger: Logger;
}
export async function startStandaloneServer(
options: ServerOptions,
): Promise<Server> {
const logger = options.logger.child({ service: 'todo-list-backend' });
logger.debug('Starting application server...');
const config = await loadBackendConfig({ logger, argv: process.argv });
const discovery = SingleHostDiscovery.fromConfig(config);
const router = await createRouter({
logger,
identity: IdentityClient.create({
discovery,
issuer: await discovery.getExternalBaseUrl('auth'),
}),
});
let service = createServiceBuilder(module)
.setPort(options.port)
.addRouter('/todo-list', router);
if (options.enableCors) {
service = service.enableCors({ origin: 'http://localhost:3000' });
}
return await service.start().catch(err => {
logger.error(err);
process.exit(1);
});
}
module.hot?.accept();
@@ -0,0 +1,92 @@
/*
* Copyright 2022 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 { v4 as uuid } from 'uuid';
import { NotFoundError } from '@backstage/errors';
export type Todo = {
title: string;
author?: string;
id: string;
timestamp: number;
};
export type TodoFilter = {
property: Exclude<keyof Todo, 'timestamp'>;
values: Array<string | undefined>;
};
export type TodoFilters =
| {
anyOf: TodoFilters[];
}
| { allOf: TodoFilters[] }
| { not: TodoFilters }
| TodoFilter;
const todos: { [key: string]: Todo } = {};
const matches = (todo: Todo, filters?: TodoFilters): boolean => {
if (!filters) {
return true;
}
if ('allOf' in filters) {
return filters.allOf.every(filter => matches(todo, filter));
}
if ('anyOf' in filters) {
return filters.anyOf.some(filter => matches(todo, filter));
}
if ('not' in filters) {
return !matches(todo, filters.not);
}
return filters.values.includes(todo[filters.property]);
};
export function add(todo: Omit<Todo, 'id' | 'timestamp'>) {
const id = uuid();
const obj: Todo = { ...todo, id, timestamp: Date.now() };
todos[id] = obj;
return obj;
}
export function getTodo(id: string) {
return todos[id];
}
export function update({ id, title }: { id: string; title: string }) {
let todo = todos[id];
if (!todo) {
throw new NotFoundError('Item not found');
}
todo = { ...todo, title, timestamp: Date.now() };
todos[id] = todo;
return todo;
}
export function getAll(filter?: TodoFilters) {
return Object.values(todos)
.filter(value => matches(value, filter))
.sort((a, b) => b.timestamp - a.timestamp);
}
// prepopulate the db
add({ title: 'just a note' });
add({ title: 'another note' });
@@ -0,0 +1,17 @@
/*
* Copyright 2020 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 {};
@@ -0,0 +1 @@
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
@@ -0,0 +1,3 @@
# todo-list-common
This package provides a starting point to demonstrate how plugin authors can use the Backstage permission framework. Refer to the [documentation](https://backstage.io/docs/permission/plugin-authors/01-setup) to get started.
@@ -0,0 +1,12 @@
## API Report File for "@internal/plugin-todo-list-common"
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
```ts
import { BasicPermission } from '@backstage/plugin-permission-common';
// @public
export const tempExamplePermission: BasicPermission;
// (No @packageDocumentation comment for this package)
```
@@ -0,0 +1,40 @@
{
"name": "@internal/plugin-todo-list-common",
"version": "1.0.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/plugin-permission-common": "^0.6.0"
},
"devDependencies": {
"@backstage/cli": "^0.17.1-next.0",
"@backstage/core-app-api": "^1.0.1",
"@backstage/dev-utils": "^1.0.2-next.0",
"@backstage/test-utils": "^1.0.2-next.0",
"@types/node": "^16.11.26",
"msw": "^0.35.0",
"cross-fetch": "^3.1.5"
},
"files": [
"dist"
]
}
@@ -0,0 +1,16 @@
/*
* Copyright 2022 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 * from './permissions';
@@ -0,0 +1,27 @@
/*
* Copyright 2022 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 { createPermission } from '@backstage/plugin-permission-common';
/**
* An example of a permission.
*
* @public
*/
export const tempExamplePermission = createPermission({
name: 'temp.example.noop',
attributes: {},
});
+14
View File
@@ -0,0 +1,14 @@
module.exports = {
extends: [require.resolve('@backstage/cli/config/eslint')],
rules: {
'import/no-extraneous-dependencies': [
'error',
{
devDependencies: true,
optionalDependencies: true,
peerDependencies: true,
bundledDependencies: true,
},
],
},
};
+3
View File
@@ -0,0 +1,3 @@
# todo-list
This package provides a starting point to demonstrate how plugin authors can use the Backstage permission framework. Refer to the [documentation](https://backstage.io/docs/permission/plugin-authors/01-setup) to get started.
+23
View File
@@ -0,0 +1,23 @@
## API Report File for "@internal/plugin-todo-list"
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
```ts
/// <reference types="react" />
import { BackstagePlugin } from '@backstage/core-plugin-api';
import { RouteRef } from '@backstage/core-plugin-api';
// @public
export const TodoListPage: () => JSX.Element;
// @public
export const todoListPlugin: BackstagePlugin<
{
root: RouteRef<undefined>;
},
{}
>;
// (No @packageDocumentation comment for this package)
```
+50
View File
@@ -0,0 +1,50 @@
{
"name": "@internal/plugin-todo-list",
"version": "1.0.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"
},
"scripts": {
"build": "backstage-cli package build",
"start": "backstage-cli package start",
"lint": "backstage-cli package lint",
"test": "backstage-cli package test",
"diff": "backstage-cli plugin:diff",
"prepack": "backstage-cli package prepack",
"postpack": "backstage-cli package postpack",
"clean": "backstage-cli package clean"
},
"dependencies": {
"@backstage/core-components": "^0.9.3",
"@backstage/core-plugin-api": "^1.0.1",
"@backstage/theme": "^0.2.15",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.57",
"react-use": "^17.2.4"
},
"peerDependencies": {
"react": "^16.13.1 || ^17.0.0"
},
"devDependencies": {
"@backstage/cli": "^0.17.1-next.0",
"@backstage/core-app-api": "^1.0.1",
"@backstage/dev-utils": "^1.0.2-next.0",
"@backstage/test-utils": "^1.0.2-next.0",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^12.1.3",
"@testing-library/user-event": "^14.0.0",
"@types/jest": "^26.0.7",
"@types/node": "^16.11.26",
"msw": "^0.35.0"
},
"files": [
"dist"
]
}
@@ -0,0 +1,88 @@
/*
* Copyright 2022 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 { Table, TableColumn, Progress } from '@backstage/core-components';
import Alert from '@material-ui/lab/Alert';
import useAsync from 'react-use/lib/useAsync';
import {
discoveryApiRef,
fetchApiRef,
useApi,
} from '@backstage/core-plugin-api';
import { Button } from '@material-ui/core';
export type Todo = {
title: string;
id: string;
author?: string;
timestamp: number;
};
type TodosTableProps = {
todos: Todo[];
onEdit(todo: Todo): any;
};
export const TodoList = ({ onEdit }: { onEdit(todo: Todo): any }) => {
const discoveryApi = useApi(discoveryApiRef);
const { fetch } = useApi(fetchApiRef);
const { value, loading, error } = useAsync(async (): Promise<Todo[]> => {
const response = await fetch(
`${await discoveryApi.getBaseUrl('todolist')}/todos`,
);
return response.json();
}, []);
if (loading) {
return <Progress />;
} else if (error) {
return <Alert severity="error">{error.message}</Alert>;
}
return <TodosTable todos={value || []} onEdit={onEdit} />;
};
export function TodosTable({ todos, onEdit }: TodosTableProps) {
const columns: TableColumn<Todo>[] = [
{ title: 'Title', field: 'title' },
{ title: 'Author', field: 'author' },
{
title: 'Last edit',
field: 'timestamp',
render: e => new Date(e.timestamp).toLocaleString(),
},
{
title: 'Action',
render: todo => {
return (
<Button variant="contained" onClick={() => onEdit(todo)}>
Edit
</Button>
);
},
},
];
return (
<Table
title="Todos"
options={{ search: false, paging: false }}
columns={columns}
data={todos}
/>
);
}
@@ -0,0 +1,17 @@
/*
* Copyright 2022 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 { TodoList } from './TodoList';
export type { Todo } from './TodoList';
@@ -0,0 +1,187 @@
/*
* Copyright 2022 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, { useReducer, useRef, useState } from 'react';
import {
Typography,
Grid,
TextField,
Button,
Dialog,
Box,
DialogTitle,
DialogContent,
DialogActions,
} from '@material-ui/core';
import {
Header,
Page,
Content,
ContentHeader,
HeaderLabel,
SupportButton,
} from '@backstage/core-components';
import { Todo, TodoList } from '../TodoList';
import {
alertApiRef,
discoveryApiRef,
fetchApiRef,
useApi,
} from '@backstage/core-plugin-api';
export const TodoListPage = () => {
const discoveryApi = useApi(discoveryApiRef);
const { fetch } = useApi(fetchApiRef);
const alertApi = useApi(alertApiRef);
const title = useRef('');
const [key, refetchTodos] = useReducer(i => i + 1, 0);
const [editElement, setEdit] = useState<Todo | undefined>();
const handleAdd = async () => {
try {
const response = await fetch(
`${await discoveryApi.getBaseUrl('todolist')}/todos`,
{
method: 'POST',
body: JSON.stringify({ title: title.current }),
headers: {
'Content-Type': 'application/json',
},
},
);
if (!response.ok) {
const { error } = await response.json();
alertApi.post({
message: error.message,
severity: 'error',
});
return;
}
refetchTodos();
} catch (e: any) {
alertApi.post({ message: e.message, severity: 'error' });
}
};
const handleEdit = async (todo: Todo) => {
setEdit(undefined);
try {
const response = await fetch(
`${await discoveryApi.getBaseUrl('todolist')}/todos`,
{
method: 'PUT',
body: JSON.stringify({ title: todo.title, id: todo.id }),
headers: {
'Content-Type': 'application/json',
},
},
);
if (!response.ok) {
const { error } = await response.json();
alertApi.post({
message: error.message,
severity: 'error',
});
return;
}
refetchTodos();
} catch (e: any) {
alertApi.post({ message: e.message, severity: 'error' });
}
};
return (
<Page themeId="tool">
<Header
title="Welcome to todo-list!"
subtitle="Just a CRU todo list plugin"
>
<HeaderLabel label="Owner" value="Team X" />
<HeaderLabel label="Lifecycle" value="Alpha" />
</Header>
<Content>
<ContentHeader title="Todo List">
<SupportButton>A description of your plugin goes here.</SupportButton>
</ContentHeader>
<Grid container spacing={3} direction="column">
<Grid item>
<Typography variant="body1">Add todo</Typography>
<Box
component="span"
alignItems="flex-end"
display="flex"
flexDirection="row"
>
<TextField
placeholder="Write something here..."
onChange={e => (title.current = e.target.value)}
/>
<Button variant="contained" onClick={handleAdd}>
Add
</Button>
</Box>
</Grid>
<Grid item>
<TodoList key={key} onEdit={setEdit} />
</Grid>
</Grid>
</Content>
{!!editElement && (
<EditModal
todo={editElement}
onSubmit={handleEdit}
onCancel={() => setEdit(undefined)}
/>
)}
</Page>
);
};
function EditModal({
todo,
onCancel,
onSubmit,
}: {
todo?: Todo;
onSubmit(todo: Todo): any;
onCancel(): any;
}) {
const title = useRef('');
return (
<Dialog open>
<DialogTitle id="form-dialog-title">Edit item</DialogTitle>
<DialogContent>
<TextField
placeholder="Write something here..."
defaultValue={todo?.title || ''}
onChange={e => (title.current = e.target.value)}
margin="dense"
fullWidth
/>
</DialogContent>
<DialogActions>
<Button color="primary" onClick={onCancel}>
Cancel
</Button>
<Button
onClick={() => onSubmit({ ...todo!, title: title.current })}
color="primary"
>
Save
</Button>
</DialogActions>
</Dialog>
);
}
@@ -0,0 +1,16 @@
/*
* Copyright 2022 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 { TodoListPage } from './TodoListPage';
+16
View File
@@ -0,0 +1,16 @@
/*
* Copyright 2022 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 { todoListPlugin, TodoListPage } from './plugin';
@@ -0,0 +1,22 @@
/*
* Copyright 2022 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 { todoListPlugin } from './plugin';
describe('todo-list', () => {
it('should export plugin', () => {
expect(todoListPlugin).toBeDefined();
});
});
+47
View File
@@ -0,0 +1,47 @@
/*
* Copyright 2022 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 {
createPlugin,
createRoutableExtension,
} from '@backstage/core-plugin-api';
import { rootRouteRef } from './routes';
/**
* The todo-list plugin instance
*
* @public
*/
export const todoListPlugin = createPlugin({
id: 'todolist',
routes: {
root: rootRouteRef,
},
});
/**
* The Router and main entrypoint to the todo-list plugin.
*
* @public
*/
export const TodoListPage = todoListPlugin.provide(
createRoutableExtension({
name: 'TodoListPage',
component: () =>
import('./components/TodoListPage').then(m => m.TodoListPage),
mountPoint: rootRouteRef,
}),
);
+20
View File
@@ -0,0 +1,20 @@
/*
* Copyright 2022 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 { createRouteRef } from '@backstage/core-plugin-api';
export const rootRouteRef = createRouteRef({
id: 'todo-list',
});
@@ -0,0 +1,16 @@
/*
* Copyright 2022 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 '@testing-library/jest-dom';