19295: Add "Cancelling" state.
[arvados-workbench2.git] / src / store / processes / process.ts
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 import { ContainerRequestResource, ContainerRequestState } from '../../models/container-request';
6 import { ContainerResource, ContainerState } from '../../models/container';
7 import { ResourcesState, getResource } from 'store/resources/resources';
8 import { filterResources } from '../resources/resources';
9 import { ResourceKind, Resource, extractUuidKind } from 'models/resource';
10 import { getTimeDiff } from 'common/formatters';
11 import { ArvadosTheme } from 'common/custom-theme';
12
13 export interface Process {
14     containerRequest: ContainerRequestResource;
15     container?: ContainerResource;
16 }
17
18 export enum ProcessStatus {
19     CANCELLED = 'Cancelled',
20     COMPLETED = 'Completed',
21     DRAFT = 'Draft',
22     FAILING = 'Failing',
23     FAILED = 'Failed',
24     ONHOLD = 'On hold',
25     QUEUED = 'Queued',
26     RUNNING = 'Running',
27     WARNING = 'Warning',
28     UNKNOWN = 'Unknown',
29     REUSED = 'Reused',
30     CANCELLING = 'Cancelling',
31 }
32
33 export const getProcess = (uuid: string) => (resources: ResourcesState): Process | undefined => {
34     if (extractUuidKind(uuid) === ResourceKind.CONTAINER_REQUEST) {
35         const containerRequest = getResource<ContainerRequestResource>(uuid)(resources);
36         if (containerRequest) {
37             if (containerRequest.containerUuid) {
38                 const container = getResource<ContainerResource>(containerRequest.containerUuid)(resources);
39                 if (container) {
40                     return { containerRequest, container };
41                 }
42             }
43             return { containerRequest };
44         }
45     }
46     return;
47 };
48
49 export const getSubprocesses = (uuid: string) => (resources: ResourcesState) => {
50     const process = getProcess(uuid)(resources);
51     if (process && process.container) {
52         const containerRequests = filterResources(isSubprocess(process.container.uuid))(resources) as ContainerRequestResource[];
53         return containerRequests.reduce((subprocesses, { uuid }) => {
54             const process = getProcess(uuid)(resources);
55             return process
56                 ? [...subprocesses, process]
57                 : subprocesses;
58         }, []);
59     }
60     return [];
61 };
62
63 export const getProcessRuntime = ({ container }: Process) => {
64     if (container) {
65         if (container.startedAt === null) {
66             return 0;
67         }
68         if (container.finishedAt === null) {
69             // Count it from now
70             return new Date().getTime() - new Date(container.startedAt).getTime();
71         }
72         return getTimeDiff(container.finishedAt, container.startedAt);
73     } else {
74         return 0;
75     }
76 };
77
78
79 export const getProcessStatusStyles = (status: string, theme: ArvadosTheme): React.CSSProperties => {
80     let color = theme.customs.colors.grey500;
81     let running = false;
82     switch (status) {
83         case ProcessStatus.RUNNING:
84             color = theme.customs.colors.green800;
85             running = true;
86             break;
87         case ProcessStatus.COMPLETED:
88         case ProcessStatus.REUSED:
89             color = theme.customs.colors.green800;
90             break;
91         case ProcessStatus.WARNING:
92             color = theme.customs.colors.green800;
93             running = true;
94             break;
95         case ProcessStatus.FAILING:
96             color = theme.customs.colors.red900;
97             running = true;
98             break;
99         case ProcessStatus.CANCELLING:
100             color = theme.customs.colors.red900;
101             running = true;
102             break;
103         case ProcessStatus.CANCELLED:
104         case ProcessStatus.FAILED:
105             color = theme.customs.colors.red900;
106             break;
107         case ProcessStatus.QUEUED:
108             color = theme.customs.colors.grey600;
109             running = true;
110             break;
111         default:
112             color = theme.customs.colors.grey600;
113             break;
114     }
115
116     // Using color and running we build the text, border, and background style properties
117     return {
118         // Set background color when not running, otherwise use white
119         backgroundColor: running ? theme.palette.common.white : color,
120         // Set text color to status color when running, else use white text for solid button
121         color: running ? color : theme.palette.common.white,
122         // Set border color when running, else omit the style entirely
123         ...(running ? { border: `2px solid ${color}` } : {}),
124     };
125 };
126
127 export const getProcessStatus = ({ containerRequest, container }: Process): ProcessStatus => {
128     switch (true) {
129         case containerRequest.containerUuid && !container:
130             return ProcessStatus.UNKNOWN;
131
132         case containerRequest.state === ContainerRequestState.FINAL &&
133             container?.state !== ContainerState.COMPLETE:
134             // Request was finalized before its container started (or the
135             // container was cancelled)
136             return ProcessStatus.CANCELLED;
137
138         case containerRequest.state === ContainerRequestState.UNCOMMITTED:
139             return ProcessStatus.DRAFT;
140
141         case container && container.state === ContainerState.COMPLETE:
142             if (container?.exitCode === 0) {
143                 if (containerRequest && container.finishedAt) {
144                     // don't compare on createdAt because the container can
145                     // have a slightly earlier creation time when it is created
146                     // in the same transaction as the container request.
147                     // use finishedAt because most people will assume "reused" means
148                     // no additional work needed to be done, it's possible
149                     // to share a running container but calling it "reused" in that case
150                     // is more likely to just be confusing.
151                     const finishedAt = new Date(container.finishedAt).getTime();
152                     const createdAt = new Date(containerRequest.createdAt).getTime();
153                     if (finishedAt < createdAt) {
154                         return ProcessStatus.REUSED;
155                     }
156                 }
157                 return ProcessStatus.COMPLETED;
158             }
159             return ProcessStatus.FAILED;
160
161         case container?.state === ContainerState.CANCELLED:
162             return ProcessStatus.CANCELLED;
163
164         case container?.state === ContainerState.QUEUED ||
165             container?.state === ContainerState.LOCKED:
166             if (containerRequest.priority === 0) {
167                 return ProcessStatus.ONHOLD;
168             }
169             return ProcessStatus.QUEUED;
170
171         case container?.state === ContainerState.RUNNING:
172             if (container?.priority === 0) {
173                 return ProcessStatus.CANCELLING;
174             }
175             if (!!container?.runtimeStatus.error) {
176                 return ProcessStatus.FAILING;
177             }
178             if (!!container?.runtimeStatus.warning) {
179                 return ProcessStatus.WARNING;
180             }
181             return ProcessStatus.RUNNING;
182
183         default:
184             return ProcessStatus.UNKNOWN;
185     }
186 };
187
188 export const isProcessRunnable = ({ containerRequest }: Process): boolean => (
189     containerRequest.state === ContainerRequestState.UNCOMMITTED
190 );
191
192 export const isProcessResumable = ({ containerRequest, container }: Process): boolean => (
193     containerRequest.state === ContainerRequestState.COMMITTED &&
194     containerRequest.priority === 0 &&
195     // Don't show run button when container is present & running or cancelled
196     !(container && (container.state === ContainerState.RUNNING ||
197         container.state === ContainerState.CANCELLED ||
198         container.state === ContainerState.COMPLETE))
199 );
200
201 export const isProcessCancelable = ({ containerRequest, container }: Process): boolean => (
202     containerRequest.priority !== null &&
203     containerRequest.priority > 0 &&
204     container !== undefined &&
205     (container.state === ContainerState.QUEUED ||
206         container.state === ContainerState.LOCKED ||
207         container.state === ContainerState.RUNNING)
208 );
209
210 const isSubprocess = (containerUuid: string) => (resource: Resource) =>
211     resource.kind === ResourceKind.CONTAINER_REQUEST
212     && (resource as ContainerRequestResource).requestingContainerUuid === containerUuid;