[ARVADOS-WORKBENCH2] created: 2.4.0-14-ga5750c26

Git user git at public.arvados.org
Fri Apr 8 15:44:04 UTC 2022


        at  a5750c261be0991d8ebbe107115c9c5b01236f8d (commit)


commit a5750c261be0991d8ebbe107115c9c5b01236f8d
Author: Lucas Di Pentima <lucas.dipentima at curii.com>
Date:   Fri Apr 8 11:28:00 2022 -0300

    18881: Adds integration test for error & warning runtime status indicators.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas.dipentima at curii.com>

diff --git a/cypress/integration/process.spec.js b/cypress/integration/process.spec.js
index 75c318db..3234f7c4 100644
--- a/cypress/integration/process.spec.js
+++ b/cypress/integration/process.spec.js
@@ -191,4 +191,58 @@ describe('Process tests', function() {
             });
         });
     });
+
+    it('should show runtime status indicators', function() {
+        // Setup running container with runtime_status error & warning messages
+        createContainerRequest(
+            activeUser,
+            'test_container_request',
+            'arvados/jobs',
+            ['echo', 'hello world'],
+            false, 'Committed')
+        .as('containerRequest')
+        .then(function(containerRequest) {
+            expect(containerRequest.state).to.equal('Committed');
+            expect(containerRequest.container_uuid).not.to.be.equal('');
+
+            cy.getContainer(activeUser.token, containerRequest.container_uuid)
+            .then(function(queuedContainer) {
+                expect(queuedContainer.state).to.be.equal('Queued');
+            });
+            cy.updateContainer(adminUser.token, containerRequest.container_uuid, {
+                state: 'Locked'
+            }).then(function(lockedContainer) {
+                expect(lockedContainer.state).to.be.equal('Locked');
+
+                cy.updateContainer(adminUser.token, lockedContainer.uuid, {
+                    state: 'Running',
+                    runtime_status: {
+                        error: 'Something went wrong',
+                        errorDetail: 'Process exited with status 1',
+                        warning: 'Free disk space is low',
+                    }
+                })
+                .as('runningContainer')
+                .then(function(runningContainer) {
+                    expect(runningContainer.state).to.be.equal('Running');
+                    expect(runningContainer.runtime_status).to.be.deep.equal({
+                        'error': 'Something went wrong',
+                        'errorDetail': 'Process exited with status 1',
+                        'warning': 'Free disk space is low',
+                    });
+                });
+            })
+        });
+        // Test that the UI shows the error and warning messages
+        cy.getAll('@containerRequest', '@runningContainer').then(function([containerRequest]) {
+            cy.loginAs(activeUser);
+            cy.goToPath(`/processes/${containerRequest.uuid}`);
+            cy.get('[data-cy=process-runtime-status-error]')
+                .should('contain', 'Something went wrong')
+                .and('contain', 'Process exited with status 1');
+            cy.get('[data-cy=process-runtime-status-warning]')
+                .should('contain', 'Free disk space is low')
+                .and('contain', 'No additional warning details available');
+        });
+    });
 });
\ No newline at end of file
diff --git a/src/views-components/process-runtime-status/process-runtime-status.tsx b/src/views-components/process-runtime-status/process-runtime-status.tsx
index fdd635d2..26e0459d 100644
--- a/src/views-components/process-runtime-status/process-runtime-status.tsx
+++ b/src/views-components/process-runtime-status/process-runtime-status.tsx
@@ -55,7 +55,7 @@ export const ProcessRuntimeStatus = withStyles(styles)(
     ({ runtimeStatus, classes }: ProcessRuntimeStatusProps) => {
     return <>
         { runtimeStatus?.error &&
-        <ExpansionPanel className={classes.error} elevation={0}>
+        <div data-cy='process-runtime-status-error'><ExpansionPanel className={classes.error} elevation={0}>
             <ExpansionPanelSummary className={classes.summary} expandIcon={<ExpandMoreIcon />}>
                 <Typography className={classNames(classes.heading, classes.errorColor)}>
                     {`Error: ${runtimeStatus.error }`}
@@ -66,10 +66,10 @@ export const ProcessRuntimeStatus = withStyles(styles)(
                     {runtimeStatus?.errorDetail || 'No additional error details available'}
                 </Typography>
             </ExpansionPanelDetails>
-        </ExpansionPanel>
+        </ExpansionPanel></div>
         }
         { runtimeStatus?.warning &&
-        <ExpansionPanel className={classes.warning} elevation={0}>
+        <div data-cy='process-runtime-status-warning' ><ExpansionPanel className={classes.warning} elevation={0}>
             <ExpansionPanelSummary className={classes.summary} expandIcon={<ExpandMoreIcon />}>
                 <Typography className={classNames(classes.heading, classes.warningColor)}>
                     {`Warning: ${runtimeStatus.warning }`}
@@ -80,7 +80,7 @@ export const ProcessRuntimeStatus = withStyles(styles)(
                     {runtimeStatus?.warningDetail || 'No additional warning details available'}
                 </Typography>
             </ExpansionPanelDetails>
-        </ExpansionPanel>
+        </ExpansionPanel></div>
         }
     </>
 });
\ No newline at end of file

commit 0d61292f1ce718f5cc252f45d6e220c70246c922
Author: Lucas Di Pentima <lucas.dipentima at curii.com>
Date:   Fri Apr 8 11:27:30 2022 -0300

    18881: Improves & expands resource handling commands.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas.dipentima at curii.com>

diff --git a/cypress/support/commands.js b/cypress/support/commands.js
index 5a2428b2..a28308e3 100644
--- a/cypress/support/commands.js
+++ b/cypress/support/commands.js
@@ -135,11 +135,7 @@ Cypress.Commands.add(
 
 Cypress.Commands.add(
     "getCollection", (token, uuid) => {
-        return cy.doRequest('GET', `/arvados/v1/collections/${uuid}`, null, {}, token)
-            .its('body')
-            .then(function (theCollection) {
-                return theCollection;
-            })
+        return cy.getResource(token, 'collections', uuid)
     }
 )
 
@@ -160,6 +156,20 @@ Cypress.Commands.add(
     }
 )
 
+Cypress.Commands.add(
+    "getContainer", (token, uuid) => {
+        return cy.getResource(token, 'containers', uuid)
+    }
+)
+
+Cypress.Commands.add(
+    "updateContainer", (token, uuid, data) => {
+        return cy.updateResource(token, 'containers', uuid, {
+            container: JSON.stringify(data)
+        })
+    }
+)
+
 Cypress.Commands.add(
     'createContainerRequest', (token, data) => {
         return cy.createResource(token, 'container_requests', {
@@ -212,13 +222,23 @@ Cypress.Commands.add(
     }
 )
 
+Cypress.Commands.add(
+    "getResource", (token, suffix, uuid) => {
+        return cy.doRequest('GET', `/arvados/v1/${suffix}/${uuid}`, null, {}, token)
+            .its('body')
+            .then(function (resource) {
+                return resource;
+            })
+    }
+)
+
 Cypress.Commands.add(
     "createResource", (token, suffix, data) => {
         return cy.doRequest('POST', '/arvados/v1/' + suffix, data, null, token, true)
-            .its('body').as('resource')
-            .then(function () {
-                createdResources.push({suffix, uuid: this.resource.uuid});
-                return this.resource;
+            .its('body')
+            .then(function (resource) {
+                createdResources.push({suffix, uuid: resource.uuid});
+                return resource;
             })
     }
 )
@@ -226,19 +246,19 @@ Cypress.Commands.add(
 Cypress.Commands.add(
     "deleteResource", (token, suffix, uuid, failOnStatusCode = true) => {
         return cy.doRequest('DELETE', '/arvados/v1/' + suffix + '/' + uuid, null, null, token, false, true, failOnStatusCode)
-            .its('body').as('resource')
-            .then(function () {
-                return this.resource;
+            .its('body')
+            .then(function (resource) {
+                return resource;
             })
     }
 )
 
 Cypress.Commands.add(
     "updateResource", (token, suffix, uuid, data) => {
-        return cy.doRequest('PUT', '/arvados/v1/' + suffix + '/' + uuid, data, null, token, true)
-            .its('body').as('resource')
-            .then(function () {
-                return this.resource;
+        return cy.doRequest('PATCH', '/arvados/v1/' + suffix + '/' + uuid, data, null, token, true)
+            .its('body')
+            .then(function (resource) {
+                return resource;
             })
     }
 )

commit b812133ea0d9c9a4c52b200731deaba1045478e3
Author: Lucas Di Pentima <lucas.dipentima at curii.com>
Date:   Thu Apr 7 18:15:33 2022 -0300

    18881: Adds runtime_status indicator to the process info card.
    
    Also, improves a bit the layout and reclaims some padding space.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas.dipentima at curii.com>

diff --git a/src/common/custom-theme.ts b/src/common/custom-theme.ts
index cff18538..b0703237 100644
--- a/src/common/custom-theme.ts
+++ b/src/common/custom-theme.ts
@@ -23,7 +23,10 @@ export interface ArvadosTheme extends Theme {
 
 interface Colors {
     green700: string;
+    yellow100: string;
     yellow700: string;
+    yellow900: string;
+    red100: string;
     red900: string;
     blue500: string;
     grey500: string;
@@ -43,7 +46,10 @@ export const themeOptions: ArvadosThemeOptions = {
     customs: {
         colors: {
             green700: green["700"],
+            yellow100: yellow["100"],
             yellow700: yellow["700"],
+            yellow900: yellow["900"],
+            red100: red["100"],
             red900: red['900'],
             blue500: blue['500'],
             grey500: grey500,
diff --git a/src/views-components/process-runtime-status/process-runtime-status.tsx b/src/views-components/process-runtime-status/process-runtime-status.tsx
new file mode 100644
index 00000000..fdd635d2
--- /dev/null
+++ b/src/views-components/process-runtime-status/process-runtime-status.tsx
@@ -0,0 +1,86 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import React from 'react';
+import {
+    ExpansionPanel,
+    ExpansionPanelDetails,
+    ExpansionPanelSummary,
+    StyleRulesCallback,
+    Typography,
+    withStyles,
+    WithStyles
+} from "@material-ui/core";
+import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
+import { RuntimeStatus } from "models/runtime-status";
+import { ArvadosTheme } from 'common/custom-theme';
+import classNames from 'classnames';
+
+type CssRules = 'heading' | 'summary' | 'details' | 'error' | 'errorColor' | 'warning' | 'warningColor';
+
+const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
+    heading: {
+        fontSize: '1rem',
+    },
+    summary: {
+        paddingLeft: theme.spacing.unit * 1,
+        paddingRight: theme.spacing.unit * 1,
+    },
+    details: {
+        paddingLeft: theme.spacing.unit * 1,
+        paddingRight: theme.spacing.unit * 1,
+    },
+    errorColor: {
+        color: theme.customs.colors.red900,
+    },
+    error: {
+        backgroundColor: theme.customs.colors.red100,
+
+    },
+    warning: {
+        backgroundColor: theme.customs.colors.yellow100,
+    },
+    warningColor: {
+        color: theme.customs.colors.yellow900,
+    },
+});
+export interface ProcessRuntimeStatusDataProps {
+    runtimeStatus: RuntimeStatus | undefined;
+}
+
+type ProcessRuntimeStatusProps = ProcessRuntimeStatusDataProps & WithStyles<CssRules>;
+
+export const ProcessRuntimeStatus = withStyles(styles)(
+    ({ runtimeStatus, classes }: ProcessRuntimeStatusProps) => {
+    return <>
+        { runtimeStatus?.error &&
+        <ExpansionPanel className={classes.error} elevation={0}>
+            <ExpansionPanelSummary className={classes.summary} expandIcon={<ExpandMoreIcon />}>
+                <Typography className={classNames(classes.heading, classes.errorColor)}>
+                    {`Error: ${runtimeStatus.error }`}
+                </Typography>
+            </ExpansionPanelSummary>
+            <ExpansionPanelDetails className={classes.details}>
+                <Typography className={classes.errorColor}>
+                    {runtimeStatus?.errorDetail || 'No additional error details available'}
+                </Typography>
+            </ExpansionPanelDetails>
+        </ExpansionPanel>
+        }
+        { runtimeStatus?.warning &&
+        <ExpansionPanel className={classes.warning} elevation={0}>
+            <ExpansionPanelSummary className={classes.summary} expandIcon={<ExpandMoreIcon />}>
+                <Typography className={classNames(classes.heading, classes.warningColor)}>
+                    {`Warning: ${runtimeStatus.warning }`}
+                </Typography>
+            </ExpansionPanelSummary>
+            <ExpansionPanelDetails className={classes.details}>
+                <Typography className={classes.warningColor}>
+                    {runtimeStatus?.warningDetail || 'No additional warning details available'}
+                </Typography>
+            </ExpansionPanelDetails>
+        </ExpansionPanel>
+        }
+    </>
+});
\ No newline at end of file
diff --git a/src/views/process-panel/process-information-card.tsx b/src/views/process-panel/process-information-card.tsx
index fc34a31c..8f16db70 100644
--- a/src/views/process-panel/process-information-card.tsx
+++ b/src/views/process-panel/process-information-card.tsx
@@ -16,6 +16,7 @@ import { formatDate } from 'common/formatters';
 import classNames from 'classnames';
 import { ContainerState } from 'models/container';
 import { MPVPanelProps } from 'components/multi-panel-view/multi-panel-view';
+import { ProcessRuntimeStatus } from 'views-components/process-runtime-status/process-runtime-status';
 
 type CssRules = 'card' | 'iconHeader' | 'label' | 'value' | 'chip' | 'link' | 'content' | 'title' | 'avatar' | 'cancelButton' | 'header';
 
@@ -37,7 +38,7 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     },
     label: {
         display: 'flex',
-        justifyContent: 'flex-end',
+        justifyContent: 'flex-start',
         fontSize: '0.875rem',
         marginRight: theme.spacing.unit * 3,
         paddingRight: theme.spacing.unit
@@ -61,8 +62,11 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
         borderRadius: theme.spacing.unit * 0.625,
     },
     content: {
+        paddingTop: '0px',
+        paddingLeft: theme.spacing.unit * 1,
+        paddingRight: theme.spacing.unit * 1,
         '&:last-child': {
-            paddingBottom: theme.spacing.unit * 2,
+            paddingBottom: theme.spacing.unit * 1,
         }
     },
     title: {
@@ -123,27 +127,28 @@ export const ProcessInformationCard = withStyles(styles, { withTheme: true })(
                         </Tooltip> }
                     </div>
                 }
-                title={
-                    <Tooltip title={process.containerRequest.name} placement="bottom-start">
-                        <Typography noWrap variant='h6' color='inherit'>
-                            {process.containerRequest.name}
-                        </Typography>
-                    </Tooltip>
+                title={ !!process.containerRequest.name &&
+                    <Typography noWrap variant='h6' color='inherit'>
+                        {process.containerRequest.name}
+                    </Typography>
                 }
                 subheader={
-                    <Tooltip title={getDescription(process)} placement="bottom-start">
-                        <Typography noWrap variant='body1' color='inherit'>
-                            {getDescription(process)}
-                        </Typography>
-                    </Tooltip>} />
+                    <Typography noWrap variant='body1' color='inherit'>
+                        {process.containerRequest.description}
+                    </Typography>
+                }
+            />
             <CardContent className={classes.content}>
                 <Grid container>
+                    <Grid item xs={12}>
+                        <ProcessRuntimeStatus runtimeStatus={process.container?.runtimeStatus} />
+                    </Grid>
                     <Grid item xs={6}>
                         <DetailsAttribute classLabel={classes.label} classValue={classes.value}
-                            label='From'
+                            label='Started at'
                             value={startedAt} />
                         <DetailsAttribute classLabel={classes.label} classValue={classes.value}
-                            label='To'
+                            label='Finished at'
                             value={finishedAt} />
                         {process.containerRequest.properties.workflowUuid &&
                             <span onClick={() => openWorkflow(process.containerRequest.properties.workflowUuid)}>
@@ -164,6 +169,3 @@ export const ProcessInformationCard = withStyles(styles, { withTheme: true })(
         </Card>;
     }
 );
-
-const getDescription = (process: Process) =>
-    process.containerRequest.description || '(no-description)';

commit 5de4c8e78a96433482063a53dfce0056902da654
Author: Lucas Di Pentima <lucas.dipentima at curii.com>
Date:   Wed Apr 6 18:08:27 2022 -0300

    18881: Improves process filtering by status. Adds tests.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas.dipentima at curii.com>

diff --git a/src/store/processes/process.ts b/src/store/processes/process.ts
index 37cdd2b3..b72a0c2b 100644
--- a/src/store/processes/process.ts
+++ b/src/store/processes/process.ts
@@ -92,36 +92,40 @@ export const getProcessStatusColor = (status: string, { customs }: ArvadosTheme)
 
 export const getProcessStatus = ({ containerRequest, container }: Process): ProcessStatus => {
     switch (true) {
+        case containerRequest.state === ContainerRequestState.FINAL &&
+            container?.state !== ContainerState.COMPLETE:
+            // Request was finalized before its container started (or the
+            // container was cancelled)
+            return ProcessStatus.CANCELLED;
+
         case containerRequest.state === ContainerRequestState.UNCOMMITTED:
             return ProcessStatus.DRAFT;
 
-        case containerRequest.priority === 0:
-            return ProcessStatus.ONHOLD;
-
-        case container && container.state === ContainerState.COMPLETE && container.exitCode === 0:
-            return ProcessStatus.COMPLETED;
+        case container?.state === ContainerState.COMPLETE:
+            if (container?.exitCode === 0) {
+                return ProcessStatus.COMPLETED;
+            }
+            return ProcessStatus.FAILED;
 
-        case container && container.state === ContainerState.CANCELLED:
+        case container?.state === ContainerState.CANCELLED:
             return ProcessStatus.CANCELLED;
 
-        case container && (container.state === ContainerState.QUEUED ||
-            container.state === ContainerState.LOCKED):
+        case container?.state === ContainerState.QUEUED ||
+            container?.state === ContainerState.LOCKED:
+            if (containerRequest.priority === 0) {
+                return ProcessStatus.ONHOLD;
+            }
             return ProcessStatus.QUEUED;
 
-        case container && container.state === ContainerState.RUNNING &&
-            !!container.runtimeStatus.error:
-            return ProcessStatus.FAILING;
-
-        case container && container.state === ContainerState.RUNNING &&
-            !!container.runtimeStatus.warning:
-            return ProcessStatus.WARNING;
-
-        case container && container.state === ContainerState.RUNNING:
+        case container?.state === ContainerState.RUNNING:
+            if (!!container?.runtimeStatus.error) {
+                return ProcessStatus.FAILING;
+            }
+            if (!!container?.runtimeStatus.warning) {
+                return ProcessStatus.WARNING;
+            }
             return ProcessStatus.RUNNING;
 
-        case container && container.state === ContainerState.COMPLETE && container.exitCode !== 0:
-            return ProcessStatus.FAILED;
-
         default:
             return ProcessStatus.UNKNOWN;
     }
diff --git a/src/store/project-panel/project-panel-middleware-service.ts b/src/store/project-panel/project-panel-middleware-service.ts
index be569b49..ccfa4fff 100644
--- a/src/store/project-panel/project-panel-middleware-service.ts
+++ b/src/store/project-panel/project-panel-middleware-service.ts
@@ -17,7 +17,11 @@ import { OrderBuilder, OrderDirection } from "services/api/order-builder";
 import { FilterBuilder, joinFilters } from "services/api/filter-builder";
 import { GroupContentsResource, GroupContentsResourcePrefix } from "services/groups-service/groups-service";
 import { updateFavorites } from "store/favorites/favorites-actions";
-import { IS_PROJECT_PANEL_TRASHED, projectPanelActions, getProjectPanelCurrentUuid } from 'store/project-panel/project-panel-action';
+import {
+    IS_PROJECT_PANEL_TRASHED,
+    projectPanelActions,
+    getProjectPanelCurrentUuid
+} from 'store/project-panel/project-panel-action';
 import { Dispatch, MiddlewareAPI } from "redux";
 import { ProjectResource } from "models/project";
 import { updateResources } from "store/resources/resources-actions";
@@ -29,7 +33,10 @@ import { ListResults } from 'services/common-service/common-service';
 import { loadContainers } from 'store/processes/processes-actions';
 import { ResourceKind } from 'models/resource';
 import { getSortColumn } from "store/data-explorer/data-explorer-reducer";
-import { serializeResourceTypeFilters, ProcessStatusFilter } from 'store/resource-type-filters/resource-type-filters';
+import {
+    serializeResourceTypeFilters,
+    buildProcessStatusFilters
+} from 'store/resource-type-filters/resource-type-filters';
 import { updatePublicFavorites } from 'store/public-favorites/public-favorites-actions';
 
 export class ProjectPanelMiddlewareService extends DataExplorerMiddlewareService {
@@ -116,27 +123,10 @@ export const getFilters = (dataExplorer: DataExplorer) => {
         .getFilters();
 
     // Filter by container status
-    const fb = new FilterBuilder();
-    switch (activeStatusFilter) {
-        case ProcessStatusFilter.COMPLETED: {
-            fb.addEqual('container.state', 'Complete', GroupContentsResourcePrefix.PROCESS);
-            fb.addEqual('container.exit_code', '0', GroupContentsResourcePrefix.PROCESS);
-            break;
-        }
-        case ProcessStatusFilter.FAILED: {
-            fb.addEqual('container.state', 'Complete', GroupContentsResourcePrefix.PROCESS);
-            fb.addDistinct('container.exit_code', '0', GroupContentsResourcePrefix.PROCESS);
-            break;
-        }
-        case ProcessStatusFilter.CANCELLED:
-        case ProcessStatusFilter.LOCKED:
-        case ProcessStatusFilter.QUEUED:
-        case ProcessStatusFilter.RUNNING: {
-            fb.addEqual('container.state', activeStatusFilter, GroupContentsResourcePrefix.PROCESS);
-            break;
-        }
-    }
-    const statusFilters = fb.getFilters();
+    const statusFilters = buildProcessStatusFilters(
+        new FilterBuilder(),
+        activeStatusFilter || '',
+        GroupContentsResourcePrefix.PROCESS).getFilters();
 
     return joinFilters(
         statusFilters,
diff --git a/src/store/resource-type-filters/resource-type-filters.test.ts b/src/store/resource-type-filters/resource-type-filters.test.ts
index 71b00b2e..698515bd 100644
--- a/src/store/resource-type-filters/resource-type-filters.test.ts
+++ b/src/store/resource-type-filters/resource-type-filters.test.ts
@@ -2,10 +2,29 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import { getInitialResourceTypeFilters, serializeResourceTypeFilters, ObjectTypeFilter, CollectionTypeFilter, ProcessTypeFilter, GroupTypeFilter } from './resource-type-filters';
+import { getInitialResourceTypeFilters, serializeResourceTypeFilters, ObjectTypeFilter, CollectionTypeFilter, ProcessTypeFilter, GroupTypeFilter, buildProcessStatusFilters, ProcessStatusFilter } from './resource-type-filters';
 import { ResourceKind } from 'models/resource';
 import { deselectNode } from 'models/tree';
 import { pipe } from 'lodash/fp';
+import { FilterBuilder } from 'services/api/filter-builder';
+
+describe("buildProcessStatusFilters", () => {
+    [
+        [ProcessStatusFilter.ALL, ""],
+        [ProcessStatusFilter.ONHOLD, `["state","!=","Final"],["priority","=","0"],["container.state","in",["Queued","Locked"]]`],
+        [ProcessStatusFilter.COMPLETED, `["container.state","=","Complete"],["container.exit_code","=","0"]`],
+        [ProcessStatusFilter.FAILED, `["container.state","=","Complete"],["container.exit_code","!=","0"]`],
+        [ProcessStatusFilter.QUEUED, `["container.state","=","Queued"],["priority","!=","0"]`],
+        [ProcessStatusFilter.CANCELLED, `["container.state","=","Cancelled"]`],
+        [ProcessStatusFilter.RUNNING, `["container.state","=","Running"]`],
+    ].forEach(([status, expected]) => {
+        it(`can filter "${status}" processes`, () => {
+            const filters = buildProcessStatusFilters(new FilterBuilder(), status);
+            expect(filters.getFilters())
+                .toEqual(expected);
+        })
+    });
+});
 
 describe("serializeResourceTypeFilters", () => {
     it("should serialize all filters", () => {
diff --git a/src/store/resource-type-filters/resource-type-filters.ts b/src/store/resource-type-filters/resource-type-filters.ts
index e42a16d8..a39807d5 100644
--- a/src/store/resource-type-filters/resource-type-filters.ts
+++ b/src/store/resource-type-filters/resource-type-filters.ts
@@ -11,6 +11,7 @@ import { getSelectedNodes } from 'models/tree';
 import { CollectionType } from 'models/collection';
 import { GroupContentsResourcePrefix } from 'services/groups-service/groups-service';
 import { ContainerState } from 'models/container';
+import { ContainerRequestState } from 'models/container-request';
 
 export enum ProcessStatusFilter {
     ALL = 'All',
@@ -18,7 +19,7 @@ export enum ProcessStatusFilter {
     FAILED = 'Failed',
     COMPLETED = 'Completed',
     CANCELLED = 'Cancelled',
-    LOCKED = 'Locked',
+    ONHOLD = 'On hold',
     QUEUED = 'Queued'
 }
 
@@ -95,12 +96,12 @@ export const getInitialProcessStatusFilters = pipe(
     (): DataTableFilters => createTree<DataTableFilterItem>(),
     pipe(
         initFilter(ProcessStatusFilter.ALL, '', true),
+        initFilter(ProcessStatusFilter.ONHOLD, '', false),
+        initFilter(ProcessStatusFilter.QUEUED, '', false),
         initFilter(ProcessStatusFilter.RUNNING, '', false),
-        initFilter(ProcessStatusFilter.FAILED, '', false),
         initFilter(ProcessStatusFilter.COMPLETED, '', false),
         initFilter(ProcessStatusFilter.CANCELLED, '', false),
-        initFilter(ProcessStatusFilter.QUEUED, '', false),
-        initFilter(ProcessStatusFilter.LOCKED, '', false),
+        initFilter(ProcessStatusFilter.FAILED, '', false),
     ),
 );
 
@@ -272,27 +273,32 @@ export const serializeSimpleObjectTypeFilters = (filters: Tree<DataTableFilterIt
         .map(objectTypeToResourceKind);
 };
 
-export const buildProcessStatusFilters = ( fb:FilterBuilder, activeStatusFilter:string ): FilterBuilder => {
+export const buildProcessStatusFilters = ( fb: FilterBuilder, activeStatusFilter: string, resourcePrefix?: string ): FilterBuilder => {
     switch (activeStatusFilter) {
+        case ProcessStatusFilter.ONHOLD: {
+            fb.addDistinct('state', ContainerRequestState.FINAL, resourcePrefix);
+            fb.addEqual('priority', '0', resourcePrefix);
+            fb.addIn('container.state', [ContainerState.QUEUED, ContainerState.LOCKED], resourcePrefix);
+            break;
+        }
         case ProcessStatusFilter.COMPLETED: {
-            fb.addEqual('container.state', ContainerState.COMPLETE);
-            fb.addEqual('container.exit_code', '0');
+            fb.addEqual('container.state', ContainerState.COMPLETE, resourcePrefix);
+            fb.addEqual('container.exit_code', '0', resourcePrefix);
             break;
         }
         case ProcessStatusFilter.FAILED: {
-            fb.addEqual('container.state', ContainerState.COMPLETE);
-            fb.addDistinct('container.exit_code', '0');
+            fb.addEqual('container.state', ContainerState.COMPLETE, resourcePrefix);
+            fb.addDistinct('container.exit_code', '0', resourcePrefix);
             break;
         }
         case ProcessStatusFilter.QUEUED: {
-            fb.addEqual('container.state', ContainerState.QUEUED);
-            fb.addDistinct('container.priority', '0');
+            fb.addEqual('container.state', ContainerState.QUEUED, resourcePrefix);
+            fb.addDistinct('priority', '0', resourcePrefix);
             break;
         }
         case ProcessStatusFilter.CANCELLED:
-        case ProcessStatusFilter.LOCKED:
         case ProcessStatusFilter.RUNNING: {
-            fb.addEqual('container.state', activeStatusFilter);
+            fb.addEqual('container.state', activeStatusFilter, resourcePrefix);
             break;
         }
     }

commit dec2560060035f165662cff34b3a8916927a7ee6
Author: Lucas Di Pentima <lucas.dipentima at curii.com>
Date:   Fri Apr 1 12:20:40 2022 -0300

    18881: Fixes process state indicator, with tests.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas.dipentima at curii.com>

diff --git a/src/common/custom-theme.ts b/src/common/custom-theme.ts
index 74dee7f6..cff18538 100644
--- a/src/common/custom-theme.ts
+++ b/src/common/custom-theme.ts
@@ -26,10 +26,12 @@ interface Colors {
     yellow700: string;
     red900: string;
     blue500: string;
+    grey500: string;
     purple: string;
 }
 
 const arvadosPurple = '#361336';
+const grey500 = grey["500"];
 const grey600 = grey["600"];
 const grey700 = grey["700"];
 const grey900 = grey["900"];
@@ -44,6 +46,7 @@ export const themeOptions: ArvadosThemeOptions = {
             yellow700: yellow["700"],
             red900: red['900'],
             blue500: blue['500'],
+            grey500: grey500,
             purple: arvadosPurple
         }
     },
diff --git a/src/store/process-panel/process-panel-actions.ts b/src/store/process-panel/process-panel-actions.ts
index 962f5dfc..e77c300d 100644
--- a/src/store/process-panel/process-panel-actions.ts
+++ b/src/store/process-panel/process-panel-actions.ts
@@ -56,6 +56,8 @@ export const initProcessPanelFilters = processPanelActions.SET_PROCESS_PANEL_FIL
     ProcessStatus.COMPLETED,
     ProcessStatus.FAILED,
     ProcessStatus.RUNNING,
-    ProcessStatus.LOCKED,
+    ProcessStatus.ONHOLD,
+    ProcessStatus.FAILING,
+    ProcessStatus.WARNING,
     ProcessStatus.CANCELLED
 ]);
diff --git a/src/store/processes/process.ts b/src/store/processes/process.ts
index 60505be0..37cdd2b3 100644
--- a/src/store/processes/process.ts
+++ b/src/store/processes/process.ts
@@ -19,10 +19,12 @@ export enum ProcessStatus {
     CANCELLED = 'Cancelled',
     COMPLETED = 'Completed',
     DRAFT = 'Draft',
+    FAILING = 'Failing',
     FAILED = 'Failed',
-    LOCKED = 'Locked',
+    ONHOLD = 'On hold',
     QUEUED = 'Queued',
     RUNNING = 'Running',
+    WARNING = 'Warning',
     UNKNOWN = 'Unknown',
 }
 
@@ -71,17 +73,20 @@ export const getProcessRuntime = ({ container }: Process) => {
     }
 };
 
-export const getProcessStatusColor = (status: string, { customs, palette }: ArvadosTheme) => {
+export const getProcessStatusColor = (status: string, { customs }: ArvadosTheme) => {
     switch (status) {
         case ProcessStatus.RUNNING:
             return customs.colors.blue500;
         case ProcessStatus.COMPLETED:
             return customs.colors.green700;
+        case ProcessStatus.WARNING:
+            return customs.colors.yellow700;
+        case ProcessStatus.FAILING:
         case ProcessStatus.CANCELLED:
         case ProcessStatus.FAILED:
             return customs.colors.red900;
         default:
-            return palette.grey["500"];
+            return customs.colors.grey500;
     }
 };
 
@@ -90,18 +95,26 @@ export const getProcessStatus = ({ containerRequest, container }: Process): Proc
         case containerRequest.state === ContainerRequestState.UNCOMMITTED:
             return ProcessStatus.DRAFT;
 
+        case containerRequest.priority === 0:
+            return ProcessStatus.ONHOLD;
+
         case container && container.state === ContainerState.COMPLETE && container.exitCode === 0:
             return ProcessStatus.COMPLETED;
 
-        case containerRequest.priority === 0:
         case container && container.state === ContainerState.CANCELLED:
             return ProcessStatus.CANCELLED;
 
-        case container && container.state === ContainerState.QUEUED:
+        case container && (container.state === ContainerState.QUEUED ||
+            container.state === ContainerState.LOCKED):
             return ProcessStatus.QUEUED;
 
-        case container && container.state === ContainerState.LOCKED:
-            return ProcessStatus.LOCKED;
+        case container && container.state === ContainerState.RUNNING &&
+            !!container.runtimeStatus.error:
+            return ProcessStatus.FAILING;
+
+        case container && container.state === ContainerState.RUNNING &&
+            !!container.runtimeStatus.warning:
+            return ProcessStatus.WARNING;
 
         case container && container.state === ContainerState.RUNNING:
             return ProcessStatus.RUNNING;
diff --git a/src/views-components/data-explorer/renderers.test.tsx b/src/views-components/data-explorer/renderers.test.tsx
index f0efdf74..fc9325bd 100644
--- a/src/views-components/data-explorer/renderers.test.tsx
+++ b/src/views-components/data-explorer/renderers.test.tsx
@@ -4,11 +4,14 @@
 
 import React from 'react';
 import { mount, configure } from 'enzyme';
-import { ResourceFileSize } from './renderers';
+import { ProcessStatus, ResourceFileSize } from './renderers';
 import Adapter from "enzyme-adapter-react-16";
 import { Provider } from 'react-redux';
 import configureMockStore from 'redux-mock-store'
 import { ResourceKind } from '../../models/resource';
+import { ContainerRequestState as CR } from '../../models/container-request';
+import { ContainerState as C } from '../../models/container';
+import { ProcessStatus as PS } from '../../store/processes/process';
 
 const middlewares = [];
 const mockStore = configureMockStore(middlewares);
@@ -18,6 +21,65 @@ configure({ adapter: new Adapter() });
 describe('renderers', () => {
     let props = null;
 
+    describe('ProcessStatus', () => {
+        props = {
+            uuid: 'zzzzz-xvhdp-zzzzzzzzzzzzzzz',
+            theme: {
+                customs: {
+                    colors: {
+                        // Color values are arbitrary, but they should be
+                        // representative of the colors used in the UI.
+                        blue500: 'rgb(0, 0, 255)',
+                        green700: 'rgb(0, 255, 0)',
+                        yellow700: 'rgb(255, 255, 0)',
+                        red900: 'rgb(255, 0, 0)',
+                        grey500: 'rgb(128, 128, 128)',
+                    }
+                }
+            },
+        };
+
+        [
+            // CR Status ; Priority ; C Status ; Exit Code ; C RuntimeStatus ; Expected label ; Expected Color
+            [CR.COMMITTED, 1, C.RUNNING, null, {}, PS.RUNNING, props.theme.customs.colors.blue500],
+            [CR.COMMITTED, 1, C.RUNNING, null, {error: 'whoops'}, PS.FAILING, props.theme.customs.colors.red900],
+            [CR.COMMITTED, 1, C.RUNNING, null, {warning: 'watch out!'}, PS.WARNING, props.theme.customs.colors.yellow700],
+            [CR.FINAL, 1, C.CANCELLED, null, {}, PS.CANCELLED, props.theme.customs.colors.red900],
+            [CR.FINAL, 1, C.COMPLETE, 137, {}, PS.FAILED, props.theme.customs.colors.red900],
+            [CR.FINAL, 1, C.COMPLETE, 0, {}, PS.COMPLETED, props.theme.customs.colors.green700],
+            [CR.COMMITTED, 0, C.LOCKED, null, {}, PS.ONHOLD, props.theme.customs.colors.grey500],
+            [CR.COMMITTED, 0, C.QUEUED, null, {}, PS.ONHOLD, props.theme.customs.colors.grey500],
+            [CR.COMMITTED, 1, C.LOCKED, null, {}, PS.QUEUED, props.theme.customs.colors.grey500],
+            [CR.COMMITTED, 1, C.QUEUED, null, {}, PS.QUEUED, props.theme.customs.colors.grey500],
+        ].forEach(([crState, crPrio, cState, exitCode, rs, eLabel, eColor]) => {
+            it(`should render the state label '${eLabel}' and color '${eColor}' for CR state=${crState}, priority=${crPrio}, C state=${cState}, exitCode=${exitCode} and RuntimeStatus=${JSON.stringify(rs)}`, () => {
+                const containerUuid = 'zzzzz-dz642-zzzzzzzzzzzzzzz';
+                const store = mockStore({ resources: {
+                    [props.uuid]: {
+                        kind: ResourceKind.CONTAINER_REQUEST,
+                        state: crState,
+                        containerUuid: containerUuid,
+                        priority: crPrio,
+                    },
+                    [containerUuid]: {
+                        kind: ResourceKind.CONTAINER,
+                        state: cState,
+                        runtimeStatus: rs,
+                        exitCode: exitCode,
+                    },
+                }});
+
+                const wrapper = mount(<Provider store={store}>
+                        <ProcessStatus {...props} />
+                    </Provider>);
+
+                expect(wrapper.text()).toEqual(eLabel);
+                expect(getComputedStyle(wrapper.getDOMNode())
+                    .getPropertyValue('color')).toEqual(eColor);
+            });
+        })
+    });
+
     describe('ResourceFileSize', () => {
         beforeEach(() => {
             props = {

commit 890f88cf8828ae1d8dde8cb8c104226837187353
Author: Lucas Di Pentima <lucas.dipentima at curii.com>
Date:   Fri Apr 1 12:10:32 2022 -0300

    18881: Adds runtime_status support on container model.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas.dipentima at curii.com>

diff --git a/src/models/container.ts b/src/models/container.ts
index e931c4bf..127c2508 100644
--- a/src/models/container.ts
+++ b/src/models/container.ts
@@ -6,6 +6,7 @@ import { Resource, ResourceKind } from "./resource";
 import { MountType } from 'models/mount-types';
 import { RuntimeConstraints } from "models/runtime-constraints";
 import { SchedulingParameters } from './scheduling-parameters';
+import { RuntimeStatus } from "./runtime-status";
 
 export enum ContainerState {
     QUEUED = 'Queued',
@@ -27,6 +28,7 @@ export interface ContainerResource extends Resource {
     outputPath: string;
     mounts: MountType[];
     runtimeConstraints: RuntimeConstraints;
+    runtimeStatus: RuntimeStatus;
     schedulingParameters: SchedulingParameters;
     output: string | null;
     containerImage: string;
diff --git a/src/models/runtime-status.ts b/src/models/runtime-status.ts
new file mode 100644
index 00000000..c659930d
--- /dev/null
+++ b/src/models/runtime-status.ts
@@ -0,0 +1,11 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+export interface RuntimeStatus {
+    error?: string;
+    warning?: string;
+    activity?: string;
+    errorDetail?: string;
+    warningDetail?: string;
+}

-----------------------------------------------------------------------


hooks/post-receive
-- 




More information about the arvados-commits mailing list