feat: update tasks page
Signed-off-by: Rogerio Angeliski <angeliski@hotmail.com>
This commit is contained in:
@@ -25,7 +25,11 @@ import {
|
||||
import React from 'react';
|
||||
import { scaffolderApiRef, ScaffolderClient } from '../src';
|
||||
import { ScaffolderPage } from '../src/plugin';
|
||||
import { discoveryApiRef, fetchApiRef } from '@backstage/core-plugin-api';
|
||||
import {
|
||||
discoveryApiRef,
|
||||
fetchApiRef,
|
||||
identityApiRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { CatalogEntityPage } from '@backstage/plugin-catalog';
|
||||
|
||||
createDevApp()
|
||||
@@ -49,9 +53,15 @@ createDevApp()
|
||||
discoveryApi: discoveryApiRef,
|
||||
fetchApi: fetchApiRef,
|
||||
scmIntegrationsApi: scmIntegrationsApiRef,
|
||||
identityApi: identityApiRef,
|
||||
},
|
||||
factory: ({ discoveryApi, fetchApi, scmIntegrationsApi }) =>
|
||||
new ScaffolderClient({ discoveryApi, fetchApi, scmIntegrationsApi }),
|
||||
factory: ({ discoveryApi, fetchApi, scmIntegrationsApi, identityApi }) =>
|
||||
new ScaffolderClient({
|
||||
discoveryApi,
|
||||
fetchApi,
|
||||
scmIntegrationsApi,
|
||||
identityApi,
|
||||
}),
|
||||
})
|
||||
.addPage({
|
||||
path: '/create',
|
||||
|
||||
@@ -33,6 +33,13 @@ describe('api', () => {
|
||||
|
||||
const discoveryApi = { getBaseUrl: async () => mockBaseUrl };
|
||||
const fetchApi = new MockFetchApi();
|
||||
const identityApi = {
|
||||
getBackstageIdentity: jest.fn(),
|
||||
getProfileInfo: jest.fn(),
|
||||
getCredentials: jest.fn(),
|
||||
signOut: jest.fn(),
|
||||
};
|
||||
|
||||
const scmIntegrationsApi = ScmIntegrations.fromConfig(
|
||||
new ConfigReader({
|
||||
integrations: {
|
||||
@@ -51,6 +58,7 @@ describe('api', () => {
|
||||
scmIntegrationsApi,
|
||||
discoveryApi,
|
||||
fetchApi,
|
||||
identityApi,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -129,6 +137,7 @@ describe('api', () => {
|
||||
scmIntegrationsApi,
|
||||
discoveryApi,
|
||||
fetchApi,
|
||||
identityApi,
|
||||
useLongPollingLogs: true,
|
||||
});
|
||||
});
|
||||
@@ -305,4 +314,75 @@ describe('api', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('listTasks', () => {
|
||||
it('should list all tasks', async () => {
|
||||
server.use(
|
||||
rest.get(`${mockBaseUrl}/v2/tasks`, (req, res, ctx) => {
|
||||
const createdBy = req.url.searchParams.get('createdBy');
|
||||
|
||||
if (createdBy) {
|
||||
return res(
|
||||
ctx.json([
|
||||
{
|
||||
createdBy,
|
||||
},
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
return res(
|
||||
ctx.json([
|
||||
{
|
||||
createdBy: null,
|
||||
},
|
||||
{
|
||||
createdBy: null,
|
||||
},
|
||||
]),
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
const result = await apiClient.listTasks('all');
|
||||
expect(identityApi.getBackstageIdentity).not.toBeCalled();
|
||||
expect(result).toHaveLength(2);
|
||||
});
|
||||
it('should list task using the current user as owner', async () => {
|
||||
server.use(
|
||||
rest.get(`${mockBaseUrl}/v2/tasks`, (req, res, ctx) => {
|
||||
const createdBy = req.url.searchParams.get('createdBy');
|
||||
|
||||
if (createdBy) {
|
||||
return res(
|
||||
ctx.json([
|
||||
{
|
||||
createdBy,
|
||||
},
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
return res(
|
||||
ctx.json([
|
||||
{
|
||||
createdBy: null,
|
||||
},
|
||||
{
|
||||
createdBy: null,
|
||||
},
|
||||
]),
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
identityApi.getBackstageIdentity.mockResolvedValueOnce({
|
||||
userEntityRef: 'user:default/foo',
|
||||
});
|
||||
|
||||
const result = await apiClient.listTasks('owned');
|
||||
expect(identityApi.getBackstageIdentity).toBeCalled();
|
||||
expect(result).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
createApiRef,
|
||||
DiscoveryApi,
|
||||
FetchApi,
|
||||
IdentityApi,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { ResponseError } from '@backstage/errors';
|
||||
import { ScmIntegrationRegistry } from '@backstage/integration';
|
||||
@@ -36,6 +37,7 @@ import {
|
||||
ScaffolderGetIntegrationsListOptions,
|
||||
ScaffolderGetIntegrationsListResponse,
|
||||
ScaffolderTask,
|
||||
TasksOwnerFilterKind,
|
||||
} from './types';
|
||||
|
||||
/**
|
||||
@@ -56,11 +58,13 @@ export class ScaffolderClient implements ScaffolderApi {
|
||||
private readonly discoveryApi: DiscoveryApi;
|
||||
private readonly scmIntegrationsApi: ScmIntegrationRegistry;
|
||||
private readonly fetchApi: FetchApi;
|
||||
private readonly identityApi: IdentityApi;
|
||||
private readonly useLongPollingLogs: boolean;
|
||||
|
||||
constructor(options: {
|
||||
discoveryApi: DiscoveryApi;
|
||||
fetchApi: FetchApi;
|
||||
identityApi: IdentityApi;
|
||||
scmIntegrationsApi: ScmIntegrationRegistry;
|
||||
useLongPollingLogs?: boolean;
|
||||
}) {
|
||||
@@ -68,11 +72,19 @@ export class ScaffolderClient implements ScaffolderApi {
|
||||
this.fetchApi = options.fetchApi ?? { fetch };
|
||||
this.scmIntegrationsApi = options.scmIntegrationsApi;
|
||||
this.useLongPollingLogs = options.useLongPollingLogs ?? false;
|
||||
this.identityApi = options.identityApi;
|
||||
}
|
||||
|
||||
async listTasks(): Promise<ScaffolderTask[]> {
|
||||
async listTasks(createdBy: TasksOwnerFilterKind): Promise<ScaffolderTask[]> {
|
||||
const baseUrl = await this.discoveryApi.getBaseUrl('scaffolder');
|
||||
const url = `${baseUrl}/v2/tasks`;
|
||||
|
||||
let query = '';
|
||||
if (createdBy === 'owned') {
|
||||
const { userEntityRef } = await this.identityApi.getBackstageIdentity();
|
||||
query = `createdBy=${encodeURIComponent(userEntityRef)}`;
|
||||
}
|
||||
|
||||
const url = `${baseUrl}/v2/tasks?${query}`;
|
||||
|
||||
const response = await this.fetchApi.fetch(url);
|
||||
if (!response.ok) {
|
||||
|
||||
@@ -28,6 +28,7 @@ const scaffolderApiMock: jest.Mocked<ScaffolderApi> = {
|
||||
getTask: jest.fn(),
|
||||
streamLogs: jest.fn(),
|
||||
listActions: jest.fn(),
|
||||
listTasks: jest.fn(),
|
||||
};
|
||||
|
||||
const apis = TestApiRegistry.from([scaffolderApiRef, scaffolderApiMock]);
|
||||
|
||||
@@ -0,0 +1,226 @@
|
||||
/*
|
||||
* 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 { Entity } from '@backstage/catalog-model';
|
||||
import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
|
||||
import {
|
||||
CatalogApi,
|
||||
catalogApiRef,
|
||||
entityRouteRef,
|
||||
} from '@backstage/plugin-catalog-react';
|
||||
import React from 'react';
|
||||
import { identityApiRef } from '@backstage/core-plugin-api';
|
||||
import { ListTasksPage } from './ListTasksPage';
|
||||
import { ScaffolderApi } from '../../types';
|
||||
import { scaffolderApiRef } from '../../api';
|
||||
import { rootRouteRef } from '../../routes';
|
||||
import { act, fireEvent } from '@testing-library/react';
|
||||
|
||||
describe('<ListTasksPage />', () => {
|
||||
const catalogApi: jest.Mocked<CatalogApi> = {
|
||||
getEntityByRef: jest.fn(),
|
||||
} as any;
|
||||
|
||||
const identityApi = {
|
||||
getBackstageIdentity: jest.fn(),
|
||||
getProfileInfo: jest.fn(),
|
||||
getCredentials: jest.fn(),
|
||||
signOut: jest.fn(),
|
||||
};
|
||||
|
||||
const scaffolderApiMock: jest.Mocked<ScaffolderApi> = {
|
||||
scaffold: jest.fn(),
|
||||
getTemplateParameterSchema: jest.fn(),
|
||||
listTasks: jest.fn(),
|
||||
} as any;
|
||||
|
||||
it('should render the page', async () => {
|
||||
const entity: Entity = {
|
||||
apiVersion: 'v1',
|
||||
kind: 'service',
|
||||
metadata: {
|
||||
name: 'test',
|
||||
},
|
||||
spec: {
|
||||
profile: {
|
||||
displayName: 'BackUser',
|
||||
},
|
||||
},
|
||||
};
|
||||
catalogApi.getEntityByRef.mockResolvedValue(entity);
|
||||
|
||||
scaffolderApiMock.listTasks.mockResolvedValue([]);
|
||||
|
||||
const { getByText } = await renderInTestApp(
|
||||
<TestApiProvider
|
||||
apis={[
|
||||
[catalogApiRef, catalogApi],
|
||||
[identityApiRef, identityApi],
|
||||
[scaffolderApiRef, scaffolderApiMock],
|
||||
]}
|
||||
>
|
||||
<ListTasksPage />
|
||||
</TestApiProvider>,
|
||||
{
|
||||
mountedRoutes: {
|
||||
'/catalog/:namespace/:kind/:name': entityRouteRef,
|
||||
'/root': rootRouteRef,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
expect(getByText('List template tasks')).toBeInTheDocument();
|
||||
expect(getByText('All tasks that have been started')).toBeInTheDocument();
|
||||
expect(getByText('Tasks')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render the task I am owner', async () => {
|
||||
const entity: Entity = {
|
||||
apiVersion: 'v1',
|
||||
kind: 'User',
|
||||
metadata: {
|
||||
name: 'foo',
|
||||
},
|
||||
spec: {
|
||||
profile: {
|
||||
displayName: 'BackUser',
|
||||
},
|
||||
},
|
||||
};
|
||||
catalogApi.getEntityByRef.mockResolvedValue(entity);
|
||||
scaffolderApiMock.listTasks.mockResolvedValue([
|
||||
{
|
||||
id: 'a-random-id',
|
||||
spec: {
|
||||
createdBy: 'user:default/foo',
|
||||
} as any,
|
||||
status: 'completed',
|
||||
createdAt: '',
|
||||
lastHeartbeatAt: '',
|
||||
},
|
||||
]);
|
||||
scaffolderApiMock.getTemplateParameterSchema.mockResolvedValue({
|
||||
title: 'One Template',
|
||||
steps: [],
|
||||
});
|
||||
|
||||
const { getByText } = await renderInTestApp(
|
||||
<TestApiProvider
|
||||
apis={[
|
||||
[catalogApiRef, catalogApi],
|
||||
[identityApiRef, identityApi],
|
||||
[scaffolderApiRef, scaffolderApiMock],
|
||||
]}
|
||||
>
|
||||
<ListTasksPage />
|
||||
</TestApiProvider>,
|
||||
{
|
||||
mountedRoutes: {
|
||||
'/catalog/:namespace/:kind/:name': entityRouteRef,
|
||||
'/root': rootRouteRef,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
expect(scaffolderApiMock.listTasks).toBeCalledWith('owned');
|
||||
expect(getByText('List template tasks')).toBeInTheDocument();
|
||||
expect(getByText('All tasks that have been started')).toBeInTheDocument();
|
||||
expect(getByText('Tasks')).toBeInTheDocument();
|
||||
expect(getByText('One Template')).toBeInTheDocument();
|
||||
expect(getByText('BackUser')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render all tasks', async () => {
|
||||
const entity: Entity = {
|
||||
apiVersion: 'v1',
|
||||
kind: 'User',
|
||||
metadata: {
|
||||
name: 'foo',
|
||||
},
|
||||
spec: {
|
||||
profile: {
|
||||
displayName: 'BackUser',
|
||||
},
|
||||
},
|
||||
};
|
||||
catalogApi.getEntityByRef
|
||||
.mockResolvedValue(entity)
|
||||
.mockResolvedValue(entity)
|
||||
.mockResolvedValue({
|
||||
...entity,
|
||||
spec: {
|
||||
profile: {
|
||||
displayName: 'OtherUser',
|
||||
},
|
||||
},
|
||||
});
|
||||
scaffolderApiMock.listTasks
|
||||
.mockResolvedValue([
|
||||
{
|
||||
id: 'a-random-id',
|
||||
spec: {
|
||||
createdBy: 'user:default/foo',
|
||||
} as any,
|
||||
status: 'completed',
|
||||
createdAt: '',
|
||||
lastHeartbeatAt: '',
|
||||
},
|
||||
])
|
||||
.mockResolvedValue([
|
||||
{
|
||||
id: 'b-random-id',
|
||||
spec: {
|
||||
createdBy: 'user:default/boo',
|
||||
} as any,
|
||||
status: 'completed',
|
||||
createdAt: '',
|
||||
lastHeartbeatAt: '',
|
||||
},
|
||||
]);
|
||||
|
||||
scaffolderApiMock.getTemplateParameterSchema.mockResolvedValue({
|
||||
title: 'One Template',
|
||||
steps: [],
|
||||
});
|
||||
|
||||
const { getByText } = await renderInTestApp(
|
||||
<TestApiProvider
|
||||
apis={[
|
||||
[catalogApiRef, catalogApi],
|
||||
[identityApiRef, identityApi],
|
||||
[scaffolderApiRef, scaffolderApiMock],
|
||||
]}
|
||||
>
|
||||
<ListTasksPage />
|
||||
</TestApiProvider>,
|
||||
{
|
||||
mountedRoutes: {
|
||||
'/catalog/:namespace/:kind/:name': entityRouteRef,
|
||||
'/root': rootRouteRef,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
const allButton = await getByText('All');
|
||||
fireEvent.click(allButton);
|
||||
});
|
||||
|
||||
expect(scaffolderApiMock.listTasks).toBeCalledWith('all');
|
||||
expect(getByText('One Template')).toBeInTheDocument();
|
||||
expect(getByText('OtherUser')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* 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 {
|
||||
Content,
|
||||
EmptyState,
|
||||
ErrorPanel,
|
||||
Header,
|
||||
Lifecycle,
|
||||
Link,
|
||||
Page,
|
||||
Progress,
|
||||
} from '@backstage/core-components';
|
||||
import { useApi, useRouteRef } from '@backstage/core-plugin-api';
|
||||
import { CatalogFilterLayout } from '@backstage/plugin-catalog-react';
|
||||
import useAsync from 'react-use/lib/useAsync';
|
||||
import MaterialTable from '@material-table/core';
|
||||
import React, { useState } from 'react';
|
||||
import { scaffolderApiRef } from '../../api';
|
||||
import { rootRouteRef } from '../../routes';
|
||||
import { OwnerListPicker } from './OwnerListPicker';
|
||||
import { TasksOwnerFilterKind } from '../../types';
|
||||
import {
|
||||
CreatedAtColumn,
|
||||
OwnerEntityColumn,
|
||||
TaskStatusColumn,
|
||||
TemplateTitleColumn,
|
||||
} from './columns';
|
||||
|
||||
export interface MyTaskPageProps {
|
||||
initiallySelectedFilter?: TasksOwnerFilterKind;
|
||||
}
|
||||
|
||||
const ListTaskPageContent = (props: MyTaskPageProps) => {
|
||||
const { initiallySelectedFilter = 'owned' } = props;
|
||||
|
||||
const scaffolderApi = useApi(scaffolderApiRef);
|
||||
const rootLink = useRouteRef(rootRouteRef);
|
||||
|
||||
const [ownerFilter, setOwnerFilter] = useState(initiallySelectedFilter);
|
||||
const { value, loading, error } = useAsync(
|
||||
() => scaffolderApi.listTasks(ownerFilter),
|
||||
[scaffolderApi, ownerFilter],
|
||||
);
|
||||
|
||||
if (loading) {
|
||||
return <Progress />;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<>
|
||||
<ErrorPanel error={error} />
|
||||
<EmptyState
|
||||
missing="info"
|
||||
title="No information to display"
|
||||
description="There is no Tasks or there was an issue communicating with backend."
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<CatalogFilterLayout>
|
||||
<CatalogFilterLayout.Filters>
|
||||
<OwnerListPicker
|
||||
filter={ownerFilter}
|
||||
onSelectOwner={id => setOwnerFilter(id)}
|
||||
/>
|
||||
</CatalogFilterLayout.Filters>
|
||||
<CatalogFilterLayout.Content>
|
||||
<MaterialTable
|
||||
data={value!}
|
||||
title="Tasks"
|
||||
columns={[
|
||||
{
|
||||
title: 'Task ID',
|
||||
field: 'id',
|
||||
render: row => (
|
||||
<Link to={`${rootLink()}/tasks/${row.id}`}>{row.id}</Link>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Template',
|
||||
render: row => (
|
||||
<TemplateTitleColumn
|
||||
entityRef={row.spec.templateInfo?.entityRef}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Created',
|
||||
field: 'createdAt',
|
||||
render: row => <CreatedAtColumn createdAt={row.createdAt} />,
|
||||
},
|
||||
{
|
||||
title: 'Owner',
|
||||
field: 'createdBy',
|
||||
render: row => (
|
||||
<OwnerEntityColumn entityRef={row.spec?.createdBy} />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Status',
|
||||
field: 'status',
|
||||
render: row => <TaskStatusColumn status={row.status} />,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</CatalogFilterLayout.Content>
|
||||
</CatalogFilterLayout>
|
||||
);
|
||||
};
|
||||
|
||||
export const ListTasksPage = (props: MyTaskPageProps) => {
|
||||
return (
|
||||
<Page themeId="home">
|
||||
<Header
|
||||
pageTitleOverride="Templates Tasks"
|
||||
title={
|
||||
<>
|
||||
List template tasks <Lifecycle shorthand alpha />
|
||||
</>
|
||||
}
|
||||
subtitle="All tasks that have been started"
|
||||
/>
|
||||
<Content>
|
||||
<ListTaskPageContent {...props} />
|
||||
</Content>
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
@@ -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 { renderInTestApp } from '@backstage/test-utils';
|
||||
|
||||
import React from 'react';
|
||||
import { OwnerListPicker } from './OwnerListPicker';
|
||||
import { fireEvent } from '@testing-library/react';
|
||||
|
||||
describe('<OwnerListPicker />', () => {
|
||||
it('should render the tasks owner filter', async () => {
|
||||
const props = {
|
||||
filter: 'owned',
|
||||
onSelectOwner: jest.fn(),
|
||||
};
|
||||
|
||||
const { getByText } = await renderInTestApp(<OwnerListPicker {...props} />);
|
||||
|
||||
expect(await getByText('Owned')).toBeDefined();
|
||||
expect(await getByText('All')).toBeDefined();
|
||||
});
|
||||
|
||||
it('should call the function on select other item', async () => {
|
||||
const props = {
|
||||
filter: 'owned',
|
||||
onSelectOwner: jest.fn(),
|
||||
};
|
||||
|
||||
const { getByText } = await renderInTestApp(<OwnerListPicker {...props} />);
|
||||
|
||||
fireEvent.click(await getByText('All'));
|
||||
expect(props.onSelectOwner).toBeCalledWith('all');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* 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 { IconComponent } from '@backstage/core-plugin-api';
|
||||
import {
|
||||
Card,
|
||||
List,
|
||||
ListItemIcon,
|
||||
ListItemText,
|
||||
makeStyles,
|
||||
MenuItem,
|
||||
Theme,
|
||||
Typography,
|
||||
} from '@material-ui/core';
|
||||
import SettingsIcon from '@material-ui/icons/Settings';
|
||||
import React, { Fragment } from 'react';
|
||||
|
||||
import AllIcon from '@material-ui/icons/FontDownload';
|
||||
|
||||
import { TasksOwnerFilterKind } from '../../types';
|
||||
|
||||
const useStyles = makeStyles<Theme>(
|
||||
theme => ({
|
||||
root: {
|
||||
backgroundColor: 'rgba(0, 0, 0, .11)',
|
||||
boxShadow: 'none',
|
||||
margin: theme.spacing(1, 0, 1, 0),
|
||||
},
|
||||
title: {
|
||||
margin: theme.spacing(1, 0, 0, 1),
|
||||
textTransform: 'uppercase',
|
||||
fontSize: 12,
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
listIcon: {
|
||||
minWidth: 30,
|
||||
color: theme.palette.text.primary,
|
||||
},
|
||||
menuItem: {
|
||||
minHeight: theme.spacing(6),
|
||||
},
|
||||
groupWrapper: {
|
||||
margin: theme.spacing(1, 1, 2, 1),
|
||||
},
|
||||
}),
|
||||
{
|
||||
name: 'ScaffolderReactOwnerListPicker',
|
||||
},
|
||||
);
|
||||
|
||||
export type ButtonGroup = {
|
||||
name: string;
|
||||
items: {
|
||||
id: 'owned' | 'starred' | 'all';
|
||||
label: string;
|
||||
icon?: IconComponent;
|
||||
}[];
|
||||
};
|
||||
|
||||
function getFilterGroups(): ButtonGroup[] {
|
||||
return [
|
||||
{
|
||||
name: 'Task Owner',
|
||||
items: [
|
||||
{
|
||||
id: 'owned',
|
||||
label: 'Owned',
|
||||
icon: SettingsIcon,
|
||||
},
|
||||
{
|
||||
id: 'all',
|
||||
label: 'All',
|
||||
icon: AllIcon,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export const OwnerListPicker = (props: {
|
||||
filter: string;
|
||||
onSelectOwner: (id: TasksOwnerFilterKind) => void;
|
||||
}) => {
|
||||
const { filter, onSelectOwner } = props;
|
||||
const classes = useStyles();
|
||||
|
||||
const filterGroups = getFilterGroups();
|
||||
return (
|
||||
<Card className={classes.root}>
|
||||
{filterGroups.map(group => (
|
||||
<Fragment key={group.name}>
|
||||
<Typography variant="subtitle2" className={classes.title}>
|
||||
{group.name}
|
||||
</Typography>
|
||||
<Card className={classes.groupWrapper}>
|
||||
<List disablePadding dense>
|
||||
{group.items.map(item => (
|
||||
<MenuItem
|
||||
key={item.id}
|
||||
button
|
||||
divider
|
||||
onClick={() => onSelectOwner(item.id as TasksOwnerFilterKind)}
|
||||
selected={item.id === filter}
|
||||
className={classes.menuItem}
|
||||
data-testid={`owner-picker-${item.id}`}
|
||||
>
|
||||
{item.icon && (
|
||||
<ListItemIcon className={classes.listIcon}>
|
||||
<item.icon fontSize="small" />
|
||||
</ListItemIcon>
|
||||
)}
|
||||
<ListItemText>
|
||||
<Typography variant="body1">{item.label}</Typography>
|
||||
</ListItemText>
|
||||
</MenuItem>
|
||||
))}
|
||||
</List>
|
||||
</Card>
|
||||
</Fragment>
|
||||
))}
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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 { renderInTestApp } from '@backstage/test-utils';
|
||||
|
||||
import React from 'react';
|
||||
import { CreatedAtColumn } from './CreatedAtColumn';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
describe('<CreatedAtColumn />', () => {
|
||||
it('should render the column with the time', async () => {
|
||||
const props = {
|
||||
createdAt: DateTime.now().toISO(),
|
||||
};
|
||||
|
||||
const { getByText } = await renderInTestApp(<CreatedAtColumn {...props} />);
|
||||
|
||||
const text = getByText('0 seconds ago');
|
||||
expect(text).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -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 { DateTime, Interval } from 'luxon';
|
||||
import humanizeDuration from 'humanize-duration';
|
||||
import React from 'react';
|
||||
|
||||
export const CreatedAtColumn = ({ createdAt }: { createdAt: string }) => {
|
||||
const createdAtTime = DateTime.fromISO(createdAt);
|
||||
const formatted = Interval.fromDateTimes(createdAtTime, DateTime.local())
|
||||
.toDuration()
|
||||
.valueOf();
|
||||
|
||||
return <p>{humanizeDuration(formatted, { round: true })} ago</p>;
|
||||
};
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* 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 { Entity } from '@backstage/catalog-model';
|
||||
import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
|
||||
import {
|
||||
CatalogApi,
|
||||
catalogApiRef,
|
||||
entityRouteRef,
|
||||
} from '@backstage/plugin-catalog-react';
|
||||
import React from 'react';
|
||||
import { OwnerEntityColumn } from './OwnerEntityColumn';
|
||||
import { identityApiRef } from '@backstage/core-plugin-api';
|
||||
|
||||
describe('<OwnerEntityColumn />', () => {
|
||||
const catalogApi: jest.Mocked<CatalogApi> = {
|
||||
getEntityByRef: jest.fn(),
|
||||
} as any;
|
||||
|
||||
const identityApi = {
|
||||
getBackstageIdentity: jest.fn(),
|
||||
getProfileInfo: jest.fn(),
|
||||
getCredentials: jest.fn(),
|
||||
signOut: jest.fn(),
|
||||
};
|
||||
|
||||
it('should render the column with the time', async () => {
|
||||
const props = {
|
||||
entityRef: 'user:default/foo',
|
||||
};
|
||||
|
||||
const entity: Entity = {
|
||||
apiVersion: 'v1',
|
||||
kind: 'User',
|
||||
metadata: {
|
||||
name: 'test',
|
||||
},
|
||||
spec: {
|
||||
profile: {
|
||||
displayName: 'BackUser',
|
||||
},
|
||||
},
|
||||
};
|
||||
catalogApi.getEntityByRef.mockResolvedValue(entity);
|
||||
|
||||
const { getByText, getByRole } = await renderInTestApp(
|
||||
<TestApiProvider
|
||||
apis={[
|
||||
[catalogApiRef, catalogApi],
|
||||
[identityApiRef, identityApi],
|
||||
]}
|
||||
>
|
||||
<OwnerEntityColumn {...props} />
|
||||
</TestApiProvider>,
|
||||
{
|
||||
mountedRoutes: {
|
||||
'/catalog/:namespace/:kind/:name': entityRouteRef,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
expect(getByRole('link')).toHaveAttribute(
|
||||
'href',
|
||||
'/catalog/default/user/foo',
|
||||
);
|
||||
const text = getByText('BackUser');
|
||||
expect(text).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* 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 { Link } from '@backstage/core-components';
|
||||
import { useApi, useRouteRef } from '@backstage/core-plugin-api';
|
||||
import React from 'react';
|
||||
|
||||
import useAsync from 'react-use/lib/useAsync';
|
||||
import { ListItemText } from '@material-ui/core';
|
||||
|
||||
import { catalogApiRef, entityRouteRef } from '@backstage/plugin-catalog-react';
|
||||
import { parseEntityRef, UserEntity } from '@backstage/catalog-model';
|
||||
|
||||
export const OwnerEntityColumn = ({
|
||||
entityRef,
|
||||
}: {
|
||||
entityRef?: string | null;
|
||||
}) => {
|
||||
const catalogApi = useApi(catalogApiRef);
|
||||
const catalogEntityRoute = useRouteRef(entityRouteRef);
|
||||
|
||||
const { value, loading, error } = useAsync(
|
||||
() => catalogApi.getEntityByRef(entityRef || ''),
|
||||
[catalogApi, entityRef],
|
||||
);
|
||||
|
||||
if (loading || error) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Link to={catalogEntityRoute(parseEntityRef(entityRef || ''))}>
|
||||
<ListItemText
|
||||
primary={(value as UserEntity)?.spec?.profile?.displayName}
|
||||
/>
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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 { renderInTestApp } from '@backstage/test-utils';
|
||||
|
||||
import React from 'react';
|
||||
import { TaskStatusColumn } from './TaskStatusColumn';
|
||||
|
||||
describe('<TaskStatusColumn />', () => {
|
||||
it.each(['processing', 'error', 'completed'])(
|
||||
'should render the column with the status %s',
|
||||
async status => {
|
||||
const props = {
|
||||
status: status,
|
||||
};
|
||||
|
||||
const { getByText } = await renderInTestApp(
|
||||
<TaskStatusColumn {...props} />,
|
||||
);
|
||||
|
||||
const text = getByText(status);
|
||||
expect(text).toBeDefined();
|
||||
},
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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 {
|
||||
StatusError,
|
||||
StatusOK,
|
||||
StatusPending,
|
||||
} from '@backstage/core-components';
|
||||
import React from 'react';
|
||||
|
||||
export const TaskStatusColumn = ({ status }: { status: string }) => {
|
||||
switch (status) {
|
||||
case 'processing':
|
||||
return <StatusPending>{status}</StatusPending>;
|
||||
case 'completed':
|
||||
return <StatusOK>{status}</StatusOK>;
|
||||
case 'error':
|
||||
default:
|
||||
return <StatusError>{status}</StatusError>;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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 { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
|
||||
|
||||
import React from 'react';
|
||||
import { TemplateTitleColumn } from './TemplateTitleColumn';
|
||||
import { scaffolderApiRef } from '../../../api';
|
||||
import { ScaffolderApi } from '../../../types';
|
||||
|
||||
describe('<TemplateTitleColumn />', () => {
|
||||
const scaffolderApiMock: jest.Mocked<ScaffolderApi> = {
|
||||
scaffold: jest.fn(),
|
||||
getTemplateParameterSchema: jest.fn(),
|
||||
} as any;
|
||||
|
||||
it('should render the column with the time', async () => {
|
||||
const props = {
|
||||
entityRef: 'template:default/one-template',
|
||||
};
|
||||
scaffolderApiMock.getTemplateParameterSchema.mockResolvedValue({
|
||||
title: 'One Template',
|
||||
steps: [],
|
||||
});
|
||||
|
||||
const { getByText } = await renderInTestApp(
|
||||
<TestApiProvider apis={[[scaffolderApiRef, scaffolderApiMock]]}>
|
||||
<TemplateTitleColumn {...props} />
|
||||
</TestApiProvider>,
|
||||
);
|
||||
|
||||
const text = getByText('One Template');
|
||||
expect(text).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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 { useApi } from '@backstage/core-plugin-api';
|
||||
import React from 'react';
|
||||
import { scaffolderApiRef } from '../../../api';
|
||||
import useAsync from 'react-use/lib/useAsync';
|
||||
|
||||
export const TemplateTitleColumn = ({ entityRef }: { entityRef?: string }) => {
|
||||
const scaffolder = useApi(scaffolderApiRef);
|
||||
const { value, loading, error } = useAsync(
|
||||
() => scaffolder.getTemplateParameterSchema(entityRef || ''),
|
||||
[scaffolder, entityRef],
|
||||
);
|
||||
|
||||
if (loading || error) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <p>{value?.title}</p>;
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* 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 { CreatedAtColumn } from './CreatedAtColumn';
|
||||
export { OwnerEntityColumn } from './OwnerEntityColumn';
|
||||
export { TaskStatusColumn } from './TaskStatusColumn';
|
||||
export { TemplateTitleColumn } from './TemplateTitleColumn';
|
||||
+1
-1
@@ -13,4 +13,4 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
export { MyTaskPage } from './MyTaskPage';
|
||||
export { ListTasksPage } from './ListTasksPage';
|
||||
@@ -1,135 +0,0 @@
|
||||
/*
|
||||
* 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 {
|
||||
Content,
|
||||
Header,
|
||||
Lifecycle,
|
||||
Link,
|
||||
Page,
|
||||
Progress,
|
||||
} from '@backstage/core-components';
|
||||
import { useApi, useRouteRef } from '@backstage/core-plugin-api';
|
||||
import { Grid } from '@material-ui/core';
|
||||
import React from 'react';
|
||||
import { scaffolderApiRef } from '../../api';
|
||||
import useAsync from 'react-use/lib/useAsync';
|
||||
import MaterialTable, { Column } from '@material-table/core';
|
||||
import { rootRouteRef } from '../../routes';
|
||||
import { Duration, Interval, DateTime } from 'luxon';
|
||||
import humanizeDuration from 'humanize-duration';
|
||||
import {
|
||||
StatusError,
|
||||
StatusPending,
|
||||
StatusOK,
|
||||
} from '@backstage/core-components';
|
||||
|
||||
const CreatedAtColumn = ({ createdAt }: { createdAt: string }) => {
|
||||
const createdAtTime = DateTime.fromISO(createdAt);
|
||||
const formatted = Interval.fromDateTimes(createdAtTime, DateTime.local())
|
||||
.toDuration()
|
||||
.valueOf();
|
||||
|
||||
return <p>{humanizeDuration(formatted, { round: true })} ago</p>;
|
||||
};
|
||||
|
||||
const TemplateTitle = ({ templateName }: { templateName?: string }) => {
|
||||
const scaffolder = useApi(scaffolderApiRef);
|
||||
const { value, loading, error } = useAsync(
|
||||
() =>
|
||||
scaffolder.getTemplateParameterSchema({
|
||||
kind: 'Template',
|
||||
namespace: 'default',
|
||||
name: templateName,
|
||||
}),
|
||||
[scaffolder, templateName],
|
||||
);
|
||||
|
||||
if (loading) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <p>{value?.title}</p>;
|
||||
};
|
||||
|
||||
const Status = ({ status }) => {
|
||||
switch (status) {
|
||||
case 'processing':
|
||||
return <StatusPending>{status}</StatusPending>;
|
||||
case 'completed':
|
||||
return <StatusOK>{status}</StatusOK>;
|
||||
case 'error':
|
||||
default:
|
||||
return <StatusError>{status}</StatusError>;
|
||||
}
|
||||
};
|
||||
export const MyTaskPage = () => {
|
||||
const scaffolderApi = useApi(scaffolderApiRef);
|
||||
const { value, loading, error } = useAsync(
|
||||
() => scaffolderApi.listTasks(),
|
||||
[scaffolderApi],
|
||||
);
|
||||
|
||||
const rootLink = useRouteRef(rootRouteRef);
|
||||
|
||||
return (
|
||||
<Page themeId="home">
|
||||
<Header
|
||||
pageTitleOverride="My Tasks"
|
||||
title={
|
||||
<>
|
||||
All my tasks <Lifecycle shorthand />
|
||||
</>
|
||||
}
|
||||
subtitle="All tasks that have been started by me"
|
||||
/>
|
||||
<Content>
|
||||
{loading ? (
|
||||
<Progress />
|
||||
) : (
|
||||
<MaterialTable
|
||||
data={value!}
|
||||
title="My Tasks"
|
||||
columns={[
|
||||
{
|
||||
title: 'Task ID',
|
||||
field: 'id',
|
||||
render: row => (
|
||||
<Link to={`${rootLink()}/tasks/${row.id}`}>{row.id}</Link>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Name',
|
||||
render: row => (
|
||||
<TemplateTitle templateName={row.spec.metadata?.name} />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Created',
|
||||
field: 'createdAt',
|
||||
render: row => <CreatedAtColumn createdAt={row.createdAt} />,
|
||||
},
|
||||
{
|
||||
title: 'Status',
|
||||
field: 'status',
|
||||
render: row => <Status status={row.status} />,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</Content>
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
@@ -35,10 +35,11 @@ import { useElementFilter } from '@backstage/core-plugin-api';
|
||||
import {
|
||||
actionsRouteRef,
|
||||
editRouteRef,
|
||||
scaffolderListTaskRouteRef,
|
||||
scaffolderTaskRouteRef,
|
||||
selectedTemplateRouteRef,
|
||||
} from '../routes';
|
||||
import { MyTaskPage } from './MyTasksPage';
|
||||
import { ListTasksPage } from './ListTasksPage';
|
||||
|
||||
/**
|
||||
* The props for the entrypoint `ScaffolderPage` component the plugin.
|
||||
@@ -119,7 +120,10 @@ export const Router = (props: RouterProps) => {
|
||||
</SecretsContextProvider>
|
||||
}
|
||||
/>
|
||||
<Route path="/tasks/me" element={<MyTaskPage />} />
|
||||
<Route
|
||||
path={scaffolderListTaskRouteRef.path}
|
||||
element={<ListTasksPage />}
|
||||
/>
|
||||
<Route path={scaffolderTaskRouteRef.path} element={<TaskPageElement />} />
|
||||
<Route path={actionsRouteRef.path} element={<ActionsPage />} />
|
||||
<Route
|
||||
|
||||
@@ -51,6 +51,7 @@ const scaffolderApiMock: jest.Mocked<ScaffolderApi> = {
|
||||
getTask: jest.fn(),
|
||||
streamLogs: jest.fn(),
|
||||
listActions: jest.fn(),
|
||||
listTasks: jest.fn(),
|
||||
};
|
||||
|
||||
const featureFlagsApiMock: jest.Mocked<FeatureFlagsApi> = {
|
||||
|
||||
@@ -30,6 +30,7 @@ import {
|
||||
createRoutableExtension,
|
||||
discoveryApiRef,
|
||||
fetchApiRef,
|
||||
identityApiRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { OwnedEntityPicker } from './components/fields/OwnedEntityPicker/OwnedEntityPicker';
|
||||
import { EntityTagsPicker } from './components/fields/EntityTagsPicker/EntityTagsPicker';
|
||||
@@ -47,12 +48,14 @@ export const scaffolderPlugin = createPlugin({
|
||||
discoveryApi: discoveryApiRef,
|
||||
scmIntegrationsApi: scmIntegrationsApiRef,
|
||||
fetchApi: fetchApiRef,
|
||||
identityApi: identityApiRef,
|
||||
},
|
||||
factory: ({ discoveryApi, scmIntegrationsApi, fetchApi }) =>
|
||||
factory: ({ discoveryApi, scmIntegrationsApi, fetchApi, identityApi }) =>
|
||||
new ScaffolderClient({
|
||||
discoveryApi,
|
||||
scmIntegrationsApi,
|
||||
fetchApi,
|
||||
identityApi,
|
||||
}),
|
||||
}),
|
||||
],
|
||||
|
||||
@@ -40,6 +40,12 @@ export const scaffolderTaskRouteRef = createSubRouteRef({
|
||||
path: '/tasks/:taskId',
|
||||
});
|
||||
|
||||
export const scaffolderListTaskRouteRef = createSubRouteRef({
|
||||
id: 'scaffolder/list-tasks',
|
||||
parent: rootRouteRef,
|
||||
path: '/tasks',
|
||||
});
|
||||
|
||||
export const actionsRouteRef = createSubRouteRef({
|
||||
id: 'scaffolder/actions',
|
||||
parent: rootRouteRef,
|
||||
|
||||
@@ -102,6 +102,13 @@ export type LogEvent = {
|
||||
taskId: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* The status of each task you can filter from `ScaffolderClient`
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type TasksOwnerFilterKind = 'owned' | 'all';
|
||||
|
||||
/**
|
||||
* The input options to the `scaffold` method of the `ScaffolderClient`.
|
||||
*
|
||||
@@ -171,6 +178,8 @@ export interface ScaffolderApi {
|
||||
|
||||
getTask(taskId: string): Promise<ScaffolderTask>;
|
||||
|
||||
listTasks(createdBy: TasksOwnerFilterKind): Promise<ScaffolderTask[]>;
|
||||
|
||||
getIntegrationsList(
|
||||
options: ScaffolderGetIntegrationsListOptions,
|
||||
): Promise<ScaffolderGetIntegrationsListResponse>;
|
||||
|
||||
Reference in New Issue
Block a user