Merge branch '21901-file-log-throttling'
[arvados.git] / services / workbench2 / 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     RESUBMITTED = 'Resubmitted',
32 }
33
34 export enum ProcessProperties {
35     FAILED_CONTAINER_RESUBMITTED = "arv:failed_container_resubmitted",
36 }
37
38 /**
39  * Gets a process from the store using container request uuid
40  * @param uuid container request associated with process
41  * @returns a Process object with containerRequest and optional container or undefined
42  */
43 export const getProcess = (uuid: string) => (resources: ResourcesState): Process | undefined => {
44     if (extractUuidKind(uuid) === ResourceKind.CONTAINER_REQUEST) {
45         const containerRequest = getResource<ContainerRequestResource>(uuid)(resources);
46         if (containerRequest) {
47             if (containerRequest.containerUuid) {
48                 const container = getResource<ContainerResource>(containerRequest.containerUuid)(resources);
49                 if (container) {
50                     return { containerRequest, container };
51                 }
52             }
53             return { containerRequest };
54         }
55     }
56     return;
57 };
58
59 export const getSubprocesses = (uuid: string) => (resources: ResourcesState) => {
60     const process = getProcess(uuid)(resources);
61     if (process && process.container) {
62         const containerRequests = filterResources(isSubprocess(process.container.uuid))(resources) as ContainerRequestResource[];
63         return containerRequests.reduce((subprocesses, { uuid }) => {
64             const process = getProcess(uuid)(resources);
65             return process
66                 ? [...subprocesses, process]
67                 : subprocesses;
68         }, []);
69     }
70     return [];
71 };
72
73 export const getProcessRuntime = ({ container }: Process) => {
74     if (container) {
75         if (container.startedAt === null) {
76             return 0;
77         }
78         if (container.finishedAt === null) {
79             // Count it from now
80             return new Date().getTime() - new Date(container.startedAt).getTime();
81         }
82         return getTimeDiff(container.finishedAt, container.startedAt);
83     } else {
84         return 0;
85     }
86 };
87
88
89 export const getProcessStatusStyles = (status: string, theme: ArvadosTheme): React.CSSProperties => {
90     let color = theme.customs.colors.grey500;
91     let running = false;
92     switch (status) {
93         case ProcessStatus.RUNNING:
94             color = theme.customs.colors.green800;
95             running = true;
96             break;
97         case ProcessStatus.COMPLETED:
98         case ProcessStatus.REUSED:
99             color = theme.customs.colors.green800;
100             break;
101         case ProcessStatus.WARNING:
102             color = theme.customs.colors.green800;
103             running = true;
104             break;
105         case ProcessStatus.RESUBMITTED:
106             color = theme.customs.colors.darkOrange;
107             break;
108         case ProcessStatus.FAILING:
109             color = theme.customs.colors.red900;
110             running = true;
111             break;
112         case ProcessStatus.CANCELLING:
113             color = theme.customs.colors.red900;
114             running = true;
115             break;
116         case ProcessStatus.CANCELLED:
117         case ProcessStatus.FAILED:
118             color = theme.customs.colors.red900;
119             break;
120         case ProcessStatus.QUEUED:
121             color = theme.customs.colors.grey600;
122             running = true;
123             break;
124         default:
125             color = theme.customs.colors.grey600;
126             break;
127     }
128
129     // Using color and running we build the text, border, and background style properties
130     return {
131         // Set background color when not running, otherwise use white
132         backgroundColor: running ? theme.palette.common.white : color,
133         // Set text color to status color when running, else use white text for solid button
134         color: running ? color : theme.palette.common.white,
135         // Set border color when running, else omit the style entirely
136         ...(running ? { border: `2px solid ${color}` } : {}),
137     };
138 };
139
140 export const getProcessStatus = ({ containerRequest, container }: Process): ProcessStatus => {
141     switch (true) {
142         case containerRequest.containerUuid && !container:
143             return ProcessStatus.UNKNOWN;
144
145         case containerRequest.state === ContainerRequestState.UNCOMMITTED:
146             return ProcessStatus.DRAFT;
147
148         case containerRequest.state === ContainerRequestState.FINAL &&
149                    containerRequest.properties &&
150                    Boolean(containerRequest.properties[ProcessProperties.FAILED_CONTAINER_RESUBMITTED]):
151                    // Failed but a new container request for the same work was resubmitted.
152                    return ProcessStatus.RESUBMITTED;
153
154         case containerRequest.state === ContainerRequestState.FINAL &&
155                           container?.state === ContainerState.RUNNING:
156                           // It is about to be completed but we haven't
157                           // gotten the updated container record yet,
158                           // if we don't catch this and show it as "Running"
159                           // it will flicker "Cancelled" briefly
160                           return ProcessStatus.RUNNING;
161
162         case containerRequest.state === ContainerRequestState.FINAL &&
163                                  container?.state !== ContainerState.COMPLETE:
164                                  // Request was finalized before its container started (or the
165                                  // container was cancelled)
166             return ProcessStatus.CANCELLED;
167
168         case container && container.state === ContainerState.COMPLETE:
169             if (container?.exitCode === 0) {
170                 if (containerRequest && container.finishedAt) {
171                     // don't compare on createdAt because the container can
172                     // have a slightly earlier creation time when it is created
173                     // in the same transaction as the container request.
174                     // use finishedAt because most people will assume "reused" means
175                     // no additional work needed to be done, it's possible
176                     // to share a running container but calling it "reused" in that case
177                     // is more likely to just be confusing.
178                     const finishedAt = new Date(container.finishedAt).getTime();
179                     const createdAt = new Date(containerRequest.createdAt).getTime();
180                     if (finishedAt < createdAt) {
181                         return ProcessStatus.REUSED;
182                     }
183                 }
184                 return ProcessStatus.COMPLETED;
185             }
186             return ProcessStatus.FAILED;
187
188         case container?.state === ContainerState.CANCELLED:
189             return ProcessStatus.CANCELLED;
190
191         case container?.state === ContainerState.QUEUED ||
192             container?.state === ContainerState.LOCKED:
193             if (containerRequest.priority === 0) {
194                 return ProcessStatus.ONHOLD;
195             }
196             return ProcessStatus.QUEUED;
197
198         case container?.state === ContainerState.RUNNING:
199             if (container?.priority === 0) {
200                 return ProcessStatus.CANCELLING;
201             }
202             if (!!container?.runtimeStatus.error) {
203                 return ProcessStatus.FAILING;
204             }
205             if (!!container?.runtimeStatus.warning) {
206                 return ProcessStatus.WARNING;
207             }
208             return ProcessStatus.RUNNING;
209
210         default:
211             return ProcessStatus.UNKNOWN;
212     }
213 };
214
215 export const isProcessRunning = ({ container }: Process): boolean => (
216     container?.state === ContainerState.RUNNING
217 );
218
219 export const isProcessRunnable = ({ containerRequest }: Process): boolean => (
220     containerRequest.state === ContainerRequestState.UNCOMMITTED
221 );
222
223 export const isProcessResumable = ({ containerRequest, container }: Process): boolean => (
224     containerRequest.state === ContainerRequestState.COMMITTED &&
225     containerRequest.priority === 0 &&
226     // Don't show run button when container is present & running or cancelled
227     !(container && (container.state === ContainerState.RUNNING ||
228         container.state === ContainerState.CANCELLED ||
229         container.state === ContainerState.COMPLETE))
230 );
231
232 export const isProcessCancelable = ({ containerRequest, container }: Process): boolean => (
233     containerRequest.priority !== null &&
234     containerRequest.priority > 0 &&
235     container !== undefined &&
236     (container.state === ContainerState.QUEUED ||
237         container.state === ContainerState.LOCKED ||
238         container.state === ContainerState.RUNNING)
239 );
240
241 const isSubprocess = (containerUuid: string) => (resource: Resource) =>
242     resource.kind === ResourceKind.CONTAINER_REQUEST
243     && (resource as ContainerRequestResource).requestingContainerUuid === containerUuid;