Merge branch 'master' into devtools-bui-config
This commit is contained in:
@@ -16,18 +16,25 @@
|
||||
|
||||
import {
|
||||
createFrontendPlugin,
|
||||
coreExtensionData,
|
||||
discoveryApiRef,
|
||||
fetchApiRef,
|
||||
ApiBlueprint,
|
||||
PageBlueprint,
|
||||
NavItemBlueprint,
|
||||
createExtensionInput,
|
||||
coreExtensionData,
|
||||
SubPageBlueprint,
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
|
||||
import { devToolsApiRef, DevToolsClient } from '../api';
|
||||
import BuildIcon from '@material-ui/icons/Build';
|
||||
import { Content } from '@backstage/core-components';
|
||||
import { rootRouteRef } from '../routes';
|
||||
import {
|
||||
devToolsConfigReadPermission,
|
||||
devToolsInfoReadPermission,
|
||||
} from '@backstage/plugin-devtools-common';
|
||||
import { devToolsTaskSchedulerReadPermission } from '@backstage/plugin-devtools-common/alpha';
|
||||
import { RequirePermission } from '@backstage/plugin-permission-react';
|
||||
|
||||
/** @alpha */
|
||||
export const devToolsApi = ApiBlueprint.make({
|
||||
@@ -45,34 +52,84 @@ export const devToolsApi = ApiBlueprint.make({
|
||||
|
||||
/** @alpha */
|
||||
export const devToolsPage = PageBlueprint.makeWithOverrides({
|
||||
inputs: {
|
||||
contents: createExtensionInput(
|
||||
[
|
||||
coreExtensionData.reactElement,
|
||||
coreExtensionData.routePath,
|
||||
coreExtensionData.routeRef.optional(),
|
||||
coreExtensionData.title,
|
||||
],
|
||||
{
|
||||
optional: true,
|
||||
},
|
||||
),
|
||||
},
|
||||
factory(originalFactory, { inputs }) {
|
||||
return originalFactory({
|
||||
path: '/devtools',
|
||||
routeRef: rootRouteRef,
|
||||
loader: () => {
|
||||
const contents = inputs.contents.map(content => ({
|
||||
path: content.get(coreExtensionData.routePath),
|
||||
title: content.get(coreExtensionData.title),
|
||||
children: content.get(coreExtensionData.reactElement),
|
||||
}));
|
||||
return import('../components/DevToolsPage').then(m => (
|
||||
<m.DevToolsPage contents={contents} />
|
||||
));
|
||||
},
|
||||
const pages = [...inputs.pages].sort((left, right) => {
|
||||
const leftPath = left.get(coreExtensionData.routePath);
|
||||
const rightPath = right.get(coreExtensionData.routePath);
|
||||
|
||||
if (leftPath === 'info' && rightPath !== 'info') {
|
||||
return -1;
|
||||
}
|
||||
if (leftPath !== 'info' && rightPath === 'info') {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
});
|
||||
|
||||
return originalFactory(
|
||||
{
|
||||
path: '/devtools',
|
||||
routeRef: rootRouteRef,
|
||||
title: 'DevTools',
|
||||
},
|
||||
{
|
||||
inputs: {
|
||||
pages,
|
||||
},
|
||||
},
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
/** @alpha */
|
||||
export const devToolsInfoPage = SubPageBlueprint.make({
|
||||
name: 'info',
|
||||
params: {
|
||||
path: 'info',
|
||||
title: 'Info',
|
||||
loader: () =>
|
||||
import('../components/Content').then(m => (
|
||||
<Content>
|
||||
<RequirePermission permission={devToolsInfoReadPermission}>
|
||||
<m.InfoContent />
|
||||
</RequirePermission>
|
||||
</Content>
|
||||
)),
|
||||
},
|
||||
});
|
||||
|
||||
/** @alpha */
|
||||
export const devToolsConfigPage = SubPageBlueprint.make({
|
||||
name: 'config',
|
||||
params: {
|
||||
path: 'config',
|
||||
title: 'Config',
|
||||
loader: () =>
|
||||
import('../components/Content').then(m => (
|
||||
<Content>
|
||||
<RequirePermission permission={devToolsConfigReadPermission}>
|
||||
<m.ConfigContent />
|
||||
</RequirePermission>
|
||||
</Content>
|
||||
)),
|
||||
},
|
||||
});
|
||||
|
||||
/** @alpha */
|
||||
export const devToolsScheduledTasksPage = SubPageBlueprint.make({
|
||||
name: 'scheduled-tasks',
|
||||
params: {
|
||||
path: 'scheduled-tasks',
|
||||
title: 'Scheduled Tasks',
|
||||
loader: () =>
|
||||
import('../components/Content').then(m => (
|
||||
<Content>
|
||||
<RequirePermission permission={devToolsTaskSchedulerReadPermission}>
|
||||
<m.ScheduledTasksContent />
|
||||
</RequirePermission>
|
||||
</Content>
|
||||
)),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -89,10 +146,17 @@ export const devToolsNavItem = NavItemBlueprint.make({
|
||||
export default createFrontendPlugin({
|
||||
pluginId: 'devtools',
|
||||
title: 'DevTools',
|
||||
icon: <BuildIcon />,
|
||||
icon: <BuildIcon fontSize="inherit" />,
|
||||
info: { packageJson: () => import('../../package.json') },
|
||||
routes: {
|
||||
root: rootRouteRef,
|
||||
},
|
||||
extensions: [devToolsApi, devToolsPage, devToolsNavItem],
|
||||
extensions: [
|
||||
devToolsApi,
|
||||
devToolsPage,
|
||||
devToolsInfoPage,
|
||||
devToolsConfigPage,
|
||||
devToolsScheduledTasksPage,
|
||||
devToolsNavItem,
|
||||
],
|
||||
});
|
||||
|
||||
@@ -38,4 +38,5 @@ export interface DevToolsApi {
|
||||
plugin: string,
|
||||
taskId: string,
|
||||
): Promise<TriggerScheduledTask>;
|
||||
cancelScheduledTask(plugin: string, taskId: string): Promise<void>;
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ import {
|
||||
ScheduledTasks,
|
||||
TriggerScheduledTask,
|
||||
} from '@backstage/plugin-devtools-common/alpha';
|
||||
import { ResponseError } from '@backstage/errors';
|
||||
import { ResponseError, NotFoundError, ConflictError } from '@backstage/errors';
|
||||
import { DevToolsApi } from './DevToolsApi';
|
||||
|
||||
export class DevToolsClient implements DevToolsApi {
|
||||
@@ -82,7 +82,31 @@ export class DevToolsClient implements DevToolsApi {
|
||||
throw await ResponseError.fromResponse(response);
|
||||
}
|
||||
|
||||
return response.json() as Promise<TriggerScheduledTask>;
|
||||
return {};
|
||||
}
|
||||
|
||||
public async cancelScheduledTask(
|
||||
plugin: string,
|
||||
taskId: string,
|
||||
): Promise<void> {
|
||||
const baseUrl = `${await this.discoveryApi.getBaseUrl(plugin)}/`;
|
||||
const url = new URL(
|
||||
`.backstage/scheduler/v1/tasks/${encodeURIComponent(taskId)}/cancel`,
|
||||
baseUrl,
|
||||
);
|
||||
|
||||
const response = await this.fetchApi.fetch(url.toString(), {
|
||||
method: 'POST',
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
if (response.status === 404) {
|
||||
throw new NotFoundError(`Task ${taskId} not found`);
|
||||
} else if (response.status === 409) {
|
||||
throw new ConflictError(`Task ${taskId} is not running`);
|
||||
}
|
||||
throw await ResponseError.fromResponse(response);
|
||||
}
|
||||
}
|
||||
|
||||
public async getExternalDependencies(): Promise<
|
||||
|
||||
+52
-26
@@ -29,10 +29,11 @@ import {
|
||||
TableColumn,
|
||||
} from '@backstage/core-components';
|
||||
import Alert from '@material-ui/lab/Alert';
|
||||
import { useScheduledTasks, useTriggerScheduledTask } from '../../../hooks';
|
||||
import { useScheduledTasks, useScheduledTasksOperations } from '../../../hooks';
|
||||
import { TaskApiTasksResponse } from '@backstage/plugin-devtools-common/alpha';
|
||||
import { alertApiRef, configApiRef, useApi } from '@backstage/core-plugin-api';
|
||||
import RefreshIcon from '@material-ui/icons/Refresh';
|
||||
import StopIcon from '@material-ui/icons/Stop';
|
||||
import NightsStay from '@material-ui/icons/NightsStay';
|
||||
import ErrorIcon from '@material-ui/icons/Error';
|
||||
import BlockIcon from '@material-ui/icons/Block';
|
||||
@@ -105,7 +106,7 @@ export const ScheduledTasksContent = () => {
|
||||
configApi.getOptionalStringArray('devTools.scheduledTasks.plugins') || [];
|
||||
const [selectedPlugin, setSelectedPlugin] = useState(plugins[0] || '');
|
||||
const { scheduledTasks, loading, error } = useScheduledTasks(selectedPlugin);
|
||||
const { triggerTask, isTriggering, triggerError } = useTriggerScheduledTask();
|
||||
const { triggerTask, cancelTask, isLoading } = useScheduledTasksOperations();
|
||||
|
||||
const [inputValue, setInputValue] = useState('');
|
||||
|
||||
@@ -209,28 +210,52 @@ export const ScheduledTasksContent = () => {
|
||||
permission={devToolsTaskSchedulerCreatePermission}
|
||||
errorPage={<CreateNotAllowed />}
|
||||
>
|
||||
<Tooltip title="Run Task">
|
||||
<IconButton
|
||||
aria-label="Trigger"
|
||||
disabled={isTriggering}
|
||||
onClick={() => {
|
||||
triggerTask(selectedPlugin, rowData.taskId);
|
||||
if (triggerError) {
|
||||
alertApi.post({
|
||||
message: `Error triggering task ${rowData.taskId}: ${error}`,
|
||||
severity: 'error',
|
||||
});
|
||||
} else {
|
||||
alertApi.post({
|
||||
message: `Successfully triggered task ${rowData.taskId}`,
|
||||
severity: 'success',
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
<RefreshIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Box display="flex" justifyContent="center">
|
||||
<Tooltip title="Run Task">
|
||||
<IconButton
|
||||
aria-label="Trigger"
|
||||
disabled={isLoading}
|
||||
onClick={async () => {
|
||||
try {
|
||||
await triggerTask(selectedPlugin, rowData.taskId);
|
||||
alertApi.post({
|
||||
message: `Successfully triggered task ${rowData.taskId}`,
|
||||
severity: 'success',
|
||||
});
|
||||
} catch (e) {
|
||||
alertApi.post({
|
||||
message: `Error triggering task ${rowData.taskId}: ${e.message}`,
|
||||
severity: 'error',
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
<RefreshIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip title="Cancel Task">
|
||||
<IconButton
|
||||
aria-label="Cancel"
|
||||
disabled={isLoading}
|
||||
onClick={async () => {
|
||||
try {
|
||||
await cancelTask(selectedPlugin, rowData.taskId);
|
||||
alertApi.post({
|
||||
message: `Successfully cancelled task ${rowData.taskId}`,
|
||||
severity: 'success',
|
||||
});
|
||||
} catch (e) {
|
||||
alertApi.post({
|
||||
message: `Error cancelling task ${rowData.taskId}: ${e.message}`,
|
||||
severity: 'error',
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
<StopIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
</RequirePermission>
|
||||
),
|
||||
sorting: false,
|
||||
@@ -263,7 +288,7 @@ export const ScheduledTasksContent = () => {
|
||||
)}
|
||||
/>
|
||||
|
||||
{loading && <Progress />}
|
||||
{loading && !scheduledTasks && <Progress />}
|
||||
|
||||
{error && (
|
||||
<ErrorPanel
|
||||
@@ -292,7 +317,7 @@ export const ScheduledTasksContent = () => {
|
||||
</ErrorPanel>
|
||||
)}
|
||||
|
||||
{!loading && !error && (
|
||||
{scheduledTasks && (
|
||||
<Table
|
||||
title={`Scheduled Tasks (${selectedPlugin})`}
|
||||
options={{
|
||||
@@ -300,6 +325,7 @@ export const ScheduledTasksContent = () => {
|
||||
search: true,
|
||||
sorting: true,
|
||||
searchFieldAlignment: 'right',
|
||||
padding: 'dense',
|
||||
}}
|
||||
columns={columns}
|
||||
data={scheduledTasks || []}
|
||||
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
|
||||
import { ConfigContent } from '../Content';
|
||||
import { devToolsTaskSchedulerReadPermission } from '@backstage/plugin-devtools-common/alpha';
|
||||
import { DevToolsLayout } from '../DevToolsLayout';
|
||||
import { DevToolsLayout } from '../DevToolsLayout/DevToolsLayout';
|
||||
import { InfoContent } from '../Content';
|
||||
import { RequirePermission } from '@backstage/plugin-permission-react';
|
||||
import { ScheduledTasksContent } from '../Content/ScheduledTasksContent';
|
||||
|
||||
@@ -18,4 +18,4 @@ export { useConfig } from './useConfig';
|
||||
export { useExternalDependencies } from './useExternalDependencies';
|
||||
export { useInfo } from './useInfo';
|
||||
export { useScheduledTasks } from './useScheduledTasks';
|
||||
export { useTriggerScheduledTask } from './useTriggerScheduledTask';
|
||||
export { useScheduledTasksOperations } from './useScheduledTasksOperations';
|
||||
|
||||
+25
-6
@@ -17,22 +17,40 @@ import { useState, useCallback } from 'react';
|
||||
import { devToolsApiRef } from '../api';
|
||||
import { useApi } from '@backstage/core-plugin-api';
|
||||
|
||||
export const useTriggerScheduledTask = () => {
|
||||
export const useScheduledTasksOperations = () => {
|
||||
const api = useApi(devToolsApiRef);
|
||||
const [isTriggering, setIsTriggering] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState<Error | undefined>();
|
||||
|
||||
const triggerTask = useCallback(
|
||||
async (plugin: string, taskId: string) => {
|
||||
setIsTriggering(true);
|
||||
setIsLoading(true);
|
||||
setError(undefined);
|
||||
|
||||
try {
|
||||
await api.triggerScheduledTask(plugin, taskId);
|
||||
} catch (e) {
|
||||
setError(e);
|
||||
throw e;
|
||||
} finally {
|
||||
setIsTriggering(false);
|
||||
setIsLoading(false);
|
||||
}
|
||||
},
|
||||
[api],
|
||||
);
|
||||
|
||||
const cancelTask = useCallback(
|
||||
async (plugin: string, taskId: string) => {
|
||||
setIsLoading(true);
|
||||
setError(undefined);
|
||||
|
||||
try {
|
||||
await api.cancelScheduledTask(plugin, taskId);
|
||||
} catch (e) {
|
||||
setError(e);
|
||||
throw e;
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
},
|
||||
[api],
|
||||
@@ -40,7 +58,8 @@ export const useTriggerScheduledTask = () => {
|
||||
|
||||
return {
|
||||
triggerTask,
|
||||
isTriggering,
|
||||
triggerError: error?.message,
|
||||
cancelTask,
|
||||
isLoading,
|
||||
error: error?.message,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user