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