feat(pipelien-tasks): 添加 部署详情页。
This commit is contained in:
parent
32421599ea
commit
45844817c0
23
.vscode/launch.json
vendored
Normal file
23
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
// 使用 IntelliSense 了解相关属性。
|
||||
// 悬停以查看现有属性的描述。
|
||||
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"name": "Launch Chrome against localhost",
|
||||
"url": "http://fennec.localhost/",
|
||||
"webRoot": "${workspaceFolder}/src",
|
||||
"sourceMaps": true,
|
||||
"sourceMapPathOverrides": {
|
||||
"webpack:///./*": "${workspaceFolder}/src/*",
|
||||
"webpack:///src/*": "${workspaceFolder}/src/*",
|
||||
"webpack:///node_modules/*": "${workspaceFolder}/node_modules/*",
|
||||
"webpack:///../node_modules/*": "${workspaceFolder}/node_modules/*"
|
||||
},
|
||||
"showAsyncStacks": true
|
||||
}
|
||||
]
|
||||
}
|
@ -636,7 +636,7 @@
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "ENUM",
|
||||
"name": "PipelineUnits",
|
||||
"name": "TaskStatuses",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
@ -689,6 +689,41 @@
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "ENUM",
|
||||
"name": "TaskStatuses",
|
||||
"description": "任务状态",
|
||||
"fields": null,
|
||||
"inputFields": null,
|
||||
"interfaces": null,
|
||||
"enumValues": [
|
||||
{
|
||||
"name": "success",
|
||||
"description": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "failed",
|
||||
"description": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "working",
|
||||
"description": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "pending",
|
||||
"description": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "SCALAR",
|
||||
"name": "DateTime",
|
||||
@ -862,41 +897,6 @@
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "ENUM",
|
||||
"name": "TaskStatuses",
|
||||
"description": "任务状态",
|
||||
"fields": null,
|
||||
"inputFields": null,
|
||||
"interfaces": null,
|
||||
"enumValues": [
|
||||
{
|
||||
"name": "success",
|
||||
"description": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "failed",
|
||||
"description": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "working",
|
||||
"description": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "pending",
|
||||
"description": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "PipelineTaskLogMessage",
|
||||
@ -906,12 +906,40 @@
|
||||
"name": "unit",
|
||||
"description": null,
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "ENUM",
|
||||
"name": "PipelineUnits",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "time",
|
||||
"description": null,
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "ENUM",
|
||||
"name": "PipelineUnits",
|
||||
"kind": "SCALAR",
|
||||
"name": "DateTime",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "message",
|
||||
"description": null,
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
@ -1169,6 +1197,80 @@
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "listPipelineTaskByPipelineId",
|
||||
"description": null,
|
||||
"args": [
|
||||
{
|
||||
"name": "pipelineId",
|
||||
"description": null,
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "LIST",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "OBJECT",
|
||||
"name": "PipelineTask",
|
||||
"ofType": null
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "findPipelineTask",
|
||||
"description": null,
|
||||
"args": [
|
||||
{
|
||||
"name": "id",
|
||||
"description": null,
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "OBJECT",
|
||||
"name": "PipelineTask",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
|
38
package-lock.json
generated
38
package-lock.json
generated
@ -14,6 +14,7 @@
|
||||
"@fortawesome/react-fontawesome": "^0.1.14",
|
||||
"@tailwindcss/postcss7-compat": "^2.0.3",
|
||||
"@types/classnames": "^2.2.11",
|
||||
"@types/ramda": "^0.27.39",
|
||||
"autoprefixer": "^9.8.6",
|
||||
"classnames": "^2.2.6",
|
||||
"formik": "^2.2.6",
|
||||
@ -26,6 +27,7 @@
|
||||
"preact-markup": "^2.1.1",
|
||||
"preact-render-to-string": "^5.1.4",
|
||||
"preact-router": "^3.2.1",
|
||||
"ramda": "^0.27.1",
|
||||
"subscriptions-transport-ws": "^0.9.18",
|
||||
"tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.0.3"
|
||||
},
|
||||
@ -3308,6 +3310,14 @@
|
||||
"integrity": "sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/ramda": {
|
||||
"version": "0.27.39",
|
||||
"resolved": "https://registry.npmjs.org/@types/ramda/-/ramda-0.27.39.tgz",
|
||||
"integrity": "sha512-od24Hng0uS1NcMSPo6ZzKXN2WJxjbF7IBzQhRCtSDyIShdEJTAWXgQlyDg998aylNrXbVvQxkFJmuaLHQcfczg==",
|
||||
"dependencies": {
|
||||
"ts-toolbelt": "^6.15.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "17.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.2.tgz",
|
||||
@ -18018,6 +18028,11 @@
|
||||
"integrity": "sha1-635iZ1SN3t+4mcG5Dlc3RVnN234=",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/ramda": {
|
||||
"version": "0.27.1",
|
||||
"resolved": "https://registry.npmjs.org/ramda/-/ramda-0.27.1.tgz",
|
||||
"integrity": "sha512-PgIdVpn5y5Yns8vqb8FzBUEYn98V3xcPgawAkkgj0YJ0qDsnHCiNmZYfOGMgOvoB0eWFLpYbhxUR3mxfDIMvpw=="
|
||||
},
|
||||
"node_modules/randexp": {
|
||||
"version": "0.4.6",
|
||||
"resolved": "https://registry.npmjs.org/randexp/-/randexp-0.4.6.tgz",
|
||||
@ -21460,6 +21475,11 @@
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ts-toolbelt": {
|
||||
"version": "6.15.5",
|
||||
"resolved": "https://registry.npmjs.org/ts-toolbelt/-/ts-toolbelt-6.15.5.tgz",
|
||||
"integrity": "sha512-FZIXf1ksVyLcfr7M317jbB67XFJhOO1YqdTcuGaq9q5jLUoTikukZ+98TPjKiP2jC5CgmYdWWYs0s2nLSU0/1A=="
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
|
||||
@ -27487,6 +27507,14 @@
|
||||
"integrity": "sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/ramda": {
|
||||
"version": "0.27.39",
|
||||
"resolved": "https://registry.npmjs.org/@types/ramda/-/ramda-0.27.39.tgz",
|
||||
"integrity": "sha512-od24Hng0uS1NcMSPo6ZzKXN2WJxjbF7IBzQhRCtSDyIShdEJTAWXgQlyDg998aylNrXbVvQxkFJmuaLHQcfczg==",
|
||||
"requires": {
|
||||
"ts-toolbelt": "^6.15.1"
|
||||
}
|
||||
},
|
||||
"@types/react": {
|
||||
"version": "17.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.2.tgz",
|
||||
@ -39881,6 +39909,11 @@
|
||||
"integrity": "sha1-635iZ1SN3t+4mcG5Dlc3RVnN234=",
|
||||
"dev": true
|
||||
},
|
||||
"ramda": {
|
||||
"version": "0.27.1",
|
||||
"resolved": "https://registry.npmjs.org/ramda/-/ramda-0.27.1.tgz",
|
||||
"integrity": "sha512-PgIdVpn5y5Yns8vqb8FzBUEYn98V3xcPgawAkkgj0YJ0qDsnHCiNmZYfOGMgOvoB0eWFLpYbhxUR3mxfDIMvpw=="
|
||||
},
|
||||
"randexp": {
|
||||
"version": "0.4.6",
|
||||
"resolved": "https://registry.npmjs.org/randexp/-/randexp-0.4.6.tgz",
|
||||
@ -42797,6 +42830,11 @@
|
||||
"yn": "3.1.1"
|
||||
}
|
||||
},
|
||||
"ts-toolbelt": {
|
||||
"version": "6.15.5",
|
||||
"resolved": "https://registry.npmjs.org/ts-toolbelt/-/ts-toolbelt-6.15.5.tgz",
|
||||
"integrity": "sha512-FZIXf1ksVyLcfr7M317jbB67XFJhOO1YqdTcuGaq9q5jLUoTikukZ+98TPjKiP2jC5CgmYdWWYs0s2nLSU0/1A=="
|
||||
},
|
||||
"tslib": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
|
||||
|
@ -31,6 +31,7 @@
|
||||
"@fortawesome/react-fontawesome": "^0.1.14",
|
||||
"@tailwindcss/postcss7-compat": "^2.0.3",
|
||||
"@types/classnames": "^2.2.11",
|
||||
"@types/ramda": "^0.27.39",
|
||||
"autoprefixer": "^9.8.6",
|
||||
"classnames": "^2.2.6",
|
||||
"formik": "^2.2.6",
|
||||
@ -43,6 +44,7 @@
|
||||
"preact-markup": "^2.1.1",
|
||||
"preact-render-to-string": "^5.1.4",
|
||||
"preact-router": "^3.2.1",
|
||||
"ramda": "^0.27.1",
|
||||
"subscriptions-transport-ws": "^0.9.18",
|
||||
"tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.0.3"
|
||||
},
|
||||
|
@ -21,10 +21,11 @@ import {
|
||||
} from '@fortawesome/free-solid-svg-icons';
|
||||
import { useLocalObservable } from 'mobx-react';
|
||||
import { PipelineTask, PipelineUnits } from '../../generated/graphql';
|
||||
import { route } from 'preact-router';
|
||||
|
||||
const CREATE_PIPELINE_TASK = gql`
|
||||
mutation CreatePipelineTask($task: CreatePipelineTaskInput!) {
|
||||
pipelineTask: createPipelineTask(task: $task) {
|
||||
task: createPipelineTask(task: $task) {
|
||||
id
|
||||
units
|
||||
commit
|
||||
@ -50,16 +51,19 @@ class Store {
|
||||
|
||||
export const CommitActions = ({ pipeline, commit }: Props) => {
|
||||
const [createTask] = useMutation<
|
||||
PipelineTask,
|
||||
{ task: PipelineTask },
|
||||
{ task: CreatePipelineTaskInput }
|
||||
>(CREATE_PIPELINE_TASK);
|
||||
|
||||
const doWork = async (units: PipelineUnits[]) => {
|
||||
await createTask({
|
||||
const { data } = await createTask({
|
||||
variables: {
|
||||
task: { pipelineId: pipeline.id, commit, units }
|
||||
}
|
||||
});
|
||||
route(
|
||||
`/projects/${pipeline.projectId}/pipelines/${pipeline.id}/tasks/${data?.task.id}`
|
||||
);
|
||||
};
|
||||
|
||||
const commitActionsStore = useLocalObservable(() => new CommitActionsStore());
|
||||
|
25
src/components/pipeline-tasks/pipeline-task-details.scss
Normal file
25
src/components/pipeline-tasks/pipeline-task-details.scss
Normal file
@ -0,0 +1,25 @@
|
||||
.details {
|
||||
@apply pt-2 flex flex-col w-full;
|
||||
}
|
||||
|
||||
.navContainer {
|
||||
@apply bg-white p-2 text-gray-700;
|
||||
@apply flex;
|
||||
}
|
||||
.navItem {
|
||||
@apply w-full flex-auto text-center;
|
||||
&:not(:first-child) {
|
||||
@apply border-l border-gray-200;
|
||||
}
|
||||
}
|
||||
.LogListOfUnit {
|
||||
@apply overflow-scroll;
|
||||
}
|
||||
.unitLogsContainer {
|
||||
@apply bg-gray-200;
|
||||
}
|
||||
.unitLogs {
|
||||
@apply whitespace-pre-line font-mono text-sm;
|
||||
@apply p-1 my-px;
|
||||
@apply bg-gray-100;
|
||||
}
|
15
src/components/pipeline-tasks/pipeline-task-details.scss.d.ts
vendored
Normal file
15
src/components/pipeline-tasks/pipeline-task-details.scss.d.ts
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
// This file is automatically generated from your CSS. Any edits will be overwritten.
|
||||
declare namespace PipelineTaskDetailsScssNamespace {
|
||||
export interface IPipelineTaskDetailsScss {
|
||||
LogListOfUnit: string;
|
||||
details: string;
|
||||
navContainer: string;
|
||||
navItem: string;
|
||||
unitLogs: string;
|
||||
unitLogsContainer: string;
|
||||
}
|
||||
}
|
||||
|
||||
declare const PipelineTaskDetailsScssModule: PipelineTaskDetailsScssNamespace.IPipelineTaskDetailsScss;
|
||||
|
||||
export = PipelineTaskDetailsScssModule;
|
145
src/components/pipeline-tasks/pipeline-task-details.tsx
Normal file
145
src/components/pipeline-tasks/pipeline-task-details.tsx
Normal file
@ -0,0 +1,145 @@
|
||||
import { gql, useSubscription, useQuery } from '@apollo/client';
|
||||
import { h } from 'preact';
|
||||
import { useMemo } from 'preact/hooks';
|
||||
import { find, propEq } from 'ramda';
|
||||
import { observer, useLocalObservable, useObserver } from 'mobx-react';
|
||||
import { makeAutoObservable } from 'mobx';
|
||||
import styles from './pipeline-task-details.scss';
|
||||
import {
|
||||
PipelineTaskLogs,
|
||||
TaskStatuses,
|
||||
PipelineUnits,
|
||||
PipelineTask,
|
||||
PipelineTaskLogMessage
|
||||
} from '../../generated/graphql';
|
||||
interface Props {
|
||||
taskId: string;
|
||||
}
|
||||
|
||||
class Store {
|
||||
logs: PipelineTaskLogs[] = [];
|
||||
task?: PipelineTask;
|
||||
constructor() {
|
||||
makeAutoObservable(this);
|
||||
}
|
||||
|
||||
addLogsFromTask(task: PipelineTask) {
|
||||
for (const log of task.logs) {
|
||||
const taskLog = find(propEq('unit', log.unit), this.logs);
|
||||
if (!taskLog) {
|
||||
this.logs.push(log);
|
||||
} else {
|
||||
taskLog.logs = log.logs + taskLog.logs;
|
||||
}
|
||||
}
|
||||
}
|
||||
addLog(log: PipelineTaskLogMessage) {
|
||||
const taskLog = find(propEq('unit', log.unit), this.logs);
|
||||
if (!taskLog) {
|
||||
this.logs.push({
|
||||
unit: (log.unit as unknown) as PipelineUnits,
|
||||
status: TaskStatuses.Working,
|
||||
startedAt: log.time,
|
||||
logs: log.message
|
||||
});
|
||||
} else {
|
||||
taskLog.logs += log.message;
|
||||
}
|
||||
}
|
||||
|
||||
setTask(task: PipelineTask) {
|
||||
this.task = task;
|
||||
}
|
||||
}
|
||||
|
||||
const FIND_PIPELINE_TASK = gql`
|
||||
query FindPipelineTask($taskId: String!) {
|
||||
task: findPipelineTask(id: $taskId) {
|
||||
id
|
||||
units
|
||||
commit
|
||||
logs {
|
||||
unit
|
||||
logs
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const PIPELINE_TASK_LOG = gql`
|
||||
subscription PipelineTaskLog($taskId: String!) {
|
||||
log: pipelineTaskLog(taskId: $taskId) {
|
||||
unit
|
||||
time
|
||||
message
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const PipelineTaskDetails = ({ taskId }: Props) => {
|
||||
const { data } = useQuery<{ task: PipelineTask }, { taskId: string }>(
|
||||
FIND_PIPELINE_TASK,
|
||||
{ variables: { taskId } }
|
||||
);
|
||||
|
||||
const store = useLocalObservable(() => new Store());
|
||||
|
||||
useMemo(() => {
|
||||
store.task = data?.task;
|
||||
if (!data?.task.logs) {
|
||||
return;
|
||||
}
|
||||
store.addLogsFromTask(data.task);
|
||||
}, [data]);
|
||||
|
||||
useSubscription<
|
||||
{
|
||||
log: PipelineTaskLogMessage;
|
||||
},
|
||||
{ taskId: string }
|
||||
>(PIPELINE_TASK_LOG, {
|
||||
variables: { taskId },
|
||||
onSubscriptionData: ({ subscriptionData }) => {
|
||||
const log = subscriptionData.data?.log;
|
||||
if (!log) {
|
||||
return;
|
||||
}
|
||||
store.addLog(log);
|
||||
}
|
||||
});
|
||||
return useObserver(() => (
|
||||
<section className={styles.details}>
|
||||
<UnitList store={store} />
|
||||
|
||||
<LogListOfUnit store={store} />
|
||||
</section>
|
||||
));
|
||||
};
|
||||
|
||||
const UnitList = observer(({ store }: { store: Store }) => {
|
||||
if (!store.task) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
const tabs = store.task.units.map(unit => (
|
||||
<li key={unit} className={styles.navItem}>
|
||||
{unit}
|
||||
</li>
|
||||
));
|
||||
|
||||
return <ol className={styles.navContainer}>{tabs}</ol>;
|
||||
});
|
||||
|
||||
const LogListOfUnit = observer(({ store }: { store: Store }) => {
|
||||
const tabs = store.logs.map(unitLogs => (
|
||||
<li key={unitLogs.unit} className={styles.unitLogs}>
|
||||
{unitLogs.logs}
|
||||
</li>
|
||||
));
|
||||
|
||||
return (
|
||||
<div className={styles.LogListOfUnit}>
|
||||
<ol className={styles.unitLogsContainer}>{tabs}</ol>
|
||||
</div>
|
||||
);
|
||||
});
|
90
src/components/pipeline-tasks/pipeline-task-list.tsx
Normal file
90
src/components/pipeline-tasks/pipeline-task-list.tsx
Normal file
@ -0,0 +1,90 @@
|
||||
import { h } from 'preact';
|
||||
import styles from './pipeline-tasks.scss';
|
||||
import { gql, useQuery } from '@apollo/client';
|
||||
import { PipelineTask, TaskStatuses } from '../../generated/graphql';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import {
|
||||
faCheckCircle,
|
||||
faClock,
|
||||
faExclamationCircle,
|
||||
faRunning
|
||||
} from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
const LIST_TASKS = gql`
|
||||
query ListTasks($pipelineId: String!) {
|
||||
tasks: listPipelineTaskByPipelineId(pipelineId: $pipelineId) {
|
||||
id
|
||||
pipelineId
|
||||
commit
|
||||
units
|
||||
logs {
|
||||
unit
|
||||
status
|
||||
startedAt
|
||||
endedAt
|
||||
}
|
||||
status
|
||||
startedAt
|
||||
endedAt
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
interface Props {
|
||||
pipelineId: string;
|
||||
}
|
||||
|
||||
export const PipelineTaskList = ({ pipelineId }: Props) => {
|
||||
const { data, loading } = useQuery<
|
||||
{ tasks: PipelineTask[] },
|
||||
{ pipelineId: string }
|
||||
>(LIST_TASKS, {
|
||||
variables: { pipelineId }
|
||||
});
|
||||
|
||||
const items = data?.tasks.map(item => <Item key={item.id} task={item} />);
|
||||
const list = loading ? (
|
||||
<span>Loading...</span>
|
||||
) : (
|
||||
<ol className={styles.list}>{items}</ol>
|
||||
);
|
||||
|
||||
return <section className={styles.taskList}>{list}</section>;
|
||||
};
|
||||
|
||||
const Item = ({ task }: { task: PipelineTask }) => {
|
||||
return (
|
||||
<li key={task.id} className={styles.item}>
|
||||
<Status status={task.status} />
|
||||
<span>{task.id}</span>
|
||||
<span>{task.commit}</span>
|
||||
<span>{task.startedAt}</span>
|
||||
<ol className={styles.units}>
|
||||
{task.units.map((unit, index) => (
|
||||
<li key={index}>{unit}</li>
|
||||
))}
|
||||
</ol>
|
||||
<section className={styles.details}>{JSON.stringify(task.logs)}</section>
|
||||
</li>
|
||||
);
|
||||
};
|
||||
|
||||
const Status = ({ status }: { status: TaskStatuses }) => {
|
||||
let icon;
|
||||
switch (status) {
|
||||
case TaskStatuses.Failed:
|
||||
icon = faExclamationCircle;
|
||||
break;
|
||||
case TaskStatuses.Success:
|
||||
icon = faCheckCircle;
|
||||
break;
|
||||
case TaskStatuses.Pending:
|
||||
icon = faClock;
|
||||
break;
|
||||
case TaskStatuses.Working:
|
||||
icon = faRunning;
|
||||
break;
|
||||
}
|
||||
|
||||
return <FontAwesomeIcon icon={icon} />;
|
||||
};
|
17
src/components/pipeline-tasks/pipeline-tasks.scss
Normal file
17
src/components/pipeline-tasks/pipeline-tasks.scss
Normal file
@ -0,0 +1,17 @@
|
||||
.taskList {
|
||||
@apply bg-white w-full;
|
||||
}
|
||||
|
||||
.list {
|
||||
@apply bg-gray-200 max-h-full overflow-y-auto;
|
||||
}
|
||||
.item {
|
||||
@apply bg-white py-2 px-4 my-px;
|
||||
@apply grid;
|
||||
}
|
||||
.units {
|
||||
@apply inline text-gray-700;
|
||||
}
|
||||
.details {
|
||||
@apply bg-red-100;
|
||||
}
|
14
src/components/pipeline-tasks/pipeline-tasks.scss.d.ts
vendored
Normal file
14
src/components/pipeline-tasks/pipeline-tasks.scss.d.ts
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
// This file is automatically generated from your CSS. Any edits will be overwritten.
|
||||
declare namespace PipelineTasksScssNamespace {
|
||||
export interface IPipelineTasksScss {
|
||||
details: string;
|
||||
item: string;
|
||||
list: string;
|
||||
taskList: string;
|
||||
units: string;
|
||||
}
|
||||
}
|
||||
|
||||
declare const PipelineTasksScssModule: PipelineTasksScssNamespace.IPipelineTasksScss;
|
||||
|
||||
export = PipelineTasksScssModule;
|
@ -81,12 +81,20 @@ export type LogList = {
|
||||
export type PipelineTaskLogs = {
|
||||
__typename?: 'PipelineTaskLogs';
|
||||
unit: PipelineUnits;
|
||||
status: PipelineUnits;
|
||||
status: TaskStatuses;
|
||||
startedAt?: Maybe<Scalars['DateTime']>;
|
||||
endedAt?: Maybe<Scalars['DateTime']>;
|
||||
logs: Scalars['String'];
|
||||
};
|
||||
|
||||
/** 任务状态 */
|
||||
export enum TaskStatuses {
|
||||
Success = 'success',
|
||||
Failed = 'failed',
|
||||
Working = 'working',
|
||||
Pending = 'pending'
|
||||
}
|
||||
|
||||
|
||||
export type PipelineTask = {
|
||||
__typename?: 'PipelineTask';
|
||||
@ -101,17 +109,11 @@ export type PipelineTask = {
|
||||
endedAt?: Maybe<Scalars['DateTime']>;
|
||||
};
|
||||
|
||||
/** 任务状态 */
|
||||
export enum TaskStatuses {
|
||||
Success = 'success',
|
||||
Failed = 'failed',
|
||||
Working = 'working',
|
||||
Pending = 'pending'
|
||||
}
|
||||
|
||||
export type PipelineTaskLogMessage = {
|
||||
__typename?: 'PipelineTaskLogMessage';
|
||||
unit: PipelineUnits;
|
||||
unit?: Maybe<PipelineUnits>;
|
||||
time: Scalars['DateTime'];
|
||||
message: Scalars['String'];
|
||||
};
|
||||
|
||||
export type WorkUnitInput = {
|
||||
@ -131,6 +133,8 @@ export type Query = {
|
||||
findProject: Project;
|
||||
listPipelines: Array<Pipeline>;
|
||||
findPipeline: Pipeline;
|
||||
listPipelineTaskByPipelineId: Array<PipelineTask>;
|
||||
findPipelineTask: PipelineTask;
|
||||
};
|
||||
|
||||
|
||||
@ -148,6 +152,16 @@ export type QueryFindPipelineArgs = {
|
||||
id: Scalars['String'];
|
||||
};
|
||||
|
||||
|
||||
export type QueryListPipelineTaskByPipelineIdArgs = {
|
||||
pipelineId: Scalars['String'];
|
||||
};
|
||||
|
||||
|
||||
export type QueryFindPipelineTaskArgs = {
|
||||
id: Scalars['String'];
|
||||
};
|
||||
|
||||
export type Mutation = {
|
||||
__typename?: 'Mutation';
|
||||
createProject: Project;
|
||||
|
@ -11,6 +11,8 @@ import { Observer, useLocalObservable } from 'mobx-react';
|
||||
import Router, { RoutableProps, Route } from 'preact-router';
|
||||
import { gql, useQuery } from '@apollo/client';
|
||||
import { PipelineList } from '../../components/pipelines/pipeline-list';
|
||||
import { PipelineTaskList } from '../../components/pipeline-tasks/pipeline-task-list';
|
||||
import { PipelineTaskDetails } from '../../components/pipeline-tasks/pipeline-task-details';
|
||||
|
||||
const FIND_PROJECT = gql`
|
||||
query FindProject($id: String!) {
|
||||
@ -86,6 +88,10 @@ export const ProjectDetails = ({ id, path }: Props) => {
|
||||
<Observer>
|
||||
{(): any => (
|
||||
<Router>
|
||||
<Route
|
||||
path="/projects/:projectId/pipelines/:pipelineId/tasks/:taskId"
|
||||
component={PipelineTaskDetails}
|
||||
/>
|
||||
<Route
|
||||
path="/projects/:projectId/pipelines/:pipelineId"
|
||||
component={CommitLogList}
|
||||
|
Loading…
Reference in New Issue
Block a user