Merge branch '21128-toolbar-context-menu'
[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.UNCOMMITTED:
133             return ProcessStatus.DRAFT;
134
135         case containerRequest.state === ContainerRequestState.FINAL &&
136             container?.state === ContainerState.RUNNING:
137             // It is about to be completed but we haven't
138             // gotten the updated container record yet,
139             // if we don't catch this and show it as "Running"
140             // it will flicker "Cancelled" briefly
141             return ProcessStatus.RUNNING;
142
143         case containerRequest.state === ContainerRequestState.FINAL &&
144             container?.state !== ContainerState.COMPLETE:
145             // Request was finalized before its container started (or the
146             // container was cancelled)
147             return ProcessStatus.CANCELLED;
148
149         case container && container.state === ContainerState.COMPLETE:
150             if (container?.exitCode === 0) {
151                 if (containerRequest && container.finishedAt) {
152                     // don't compare on createdAt because the container can
153                     // have a slightly earlier creation time when it is created
154                     // in the same transaction as the container request.
155                     // use finishedAt because most people will assume "reused" means
156                     // no additional work needed to be done, it's possible
157                     // to share a running container but calling it "reused" in that case
158                     // is more likely to just be confusing.
159                     const finishedAt = new Date(container.finishedAt).getTime();
160                     const createdAt = new Date(containerRequest.createdAt).getTime();
161                     if (finishedAt < createdAt) {
162                         return ProcessStatus.REUSED;
163                     }
164                 }
165                 return ProcessStatus.COMPLETED;
166             }
167             return ProcessStatus.FAILED;
168
169         case container?.state === ContainerState.CANCELLED:
170             return ProcessStatus.CANCELLED;
171
172         case container?.state === ContainerState.QUEUED ||
173             container?.state === ContainerState.LOCKED:
174             if (containerRequest.priority === 0) {
175                 return ProcessStatus.ONHOLD;
176             }
177             return ProcessStatus.QUEUED;
178
179         case container?.state === ContainerState.RUNNING:
180             if (container?.priority === 0) {
181                 return ProcessStatus.CANCELLING;
182             }
183             if (!!container?.runtimeStatus.error) {
184                 return ProcessStatus.FAILING;
185             }
186             if (!!container?.runtimeStatus.warning) {
187                 return ProcessStatus.WARNING;
188             }
189             return ProcessStatus.RUNNING;
190
191         default:
192             return ProcessStatus.UNKNOWN;
193     }
194 };
195
196 export const isProcessRunning = ({ container }: Process): boolean => (
197     container?.state === ContainerState.RUNNING
198 );
199
200 export const isProcessRunnable = ({ containerRequest }: Process): boolean => (
201     containerRequest.state === ContainerRequestState.UNCOMMITTED
202 );
203
204 export const isProcessResumable = ({ containerRequest, container }: Process): boolean => (
205     containerRequest.state === ContainerRequestState.COMMITTED &&
206     containerRequest.priority === 0 &&
207     // Don't show run button when container is present & running or cancelled
208     !(container && (container.state === ContainerState.RUNNING ||
209         container.state === ContainerState.CANCELLED ||
210         container.state === ContainerState.COMPLETE))
211 );
212
213 export const isProcessCancelable = ({ containerRequest, container }: Process): boolean => (
214     containerRequest.priority !== null &&
215     containerRequest.priority > 0 &&
216     container !== undefined &&
217     (container.state === ContainerState.QUEUED ||
218         container.state === ContainerState.LOCKED ||
219         container.state === ContainerState.RUNNING)
220 );
221
222 const isSubprocess = (containerUuid: string) => (resource: Resource) =>
223     resource.kind === ResourceKind.CONTAINER_REQUEST
224     && (resource as ContainerRequestResource).requestingContainerUuid === containerUuid;