[ARVADOS-WORKBENCH2] created: 2.3.0-216-gd1fa1c88

Git user git at public.arvados.org
Fri Mar 25 21:03:30 UTC 2022


        at  d1fa1c888be0d956178cc29cd78dbb924a7606c1 (commit)


commit d1fa1c888be0d956178cc29cd78dbb924a7606c1
Author: Lucas Di Pentima <lucas.dipentima at curii.com>
Date:   Fri Mar 25 17:13:57 2022 -0300

    16672: Adds Cypress tests on process logs viewing.
    
    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
new file mode 100644
index 00000000..75c318db
--- /dev/null
+++ b/cypress/integration/process.spec.js
@@ -0,0 +1,194 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+describe('Process tests', function() {
+    let activeUser;
+    let adminUser;
+
+    before(function() {
+        // Only set up common users once. These aren't set up as aliases because
+        // aliases are cleaned up after every test. Also it doesn't make sense
+        // to set the same users on beforeEach() over and over again, so we
+        // separate a little from Cypress' 'Best Practices' here.
+        cy.getUser('admin', 'Admin', 'User', true, true)
+            .as('adminUser').then(function() {
+                adminUser = this.adminUser;
+            }
+        );
+        cy.getUser('user', 'Active', 'User', false, true)
+            .as('activeUser').then(function() {
+                activeUser = this.activeUser;
+            }
+        );
+    });
+
+    beforeEach(function() {
+        cy.clearCookies();
+        cy.clearLocalStorage();
+    });
+
+    function setupDockerImage(image_name) {
+        // Create a collection that will be used as a docker image for the tests.
+        cy.createCollection(adminUser.token, {
+            name: 'docker_image',
+            manifest_text: ". d21353cfe035e3e384563ee55eadbb2f+67108864 5c77a43e329b9838cbec18ff42790e57+55605760 0:122714624:sha256:d8309758b8fe2c81034ffc8a10c36460b77db7bc5e7b448c4e5b684f9d95a678.tar\n"
+        }).as('dockerImage').then(function(dockerImage) {
+            // Give read permissions to the active user on the docker image.
+            cy.createLink(adminUser.token, {
+                link_class: 'permission',
+                name: 'can_read',
+                tail_uuid: activeUser.user.uuid,
+                head_uuid: dockerImage.uuid
+            }).as('dockerImagePermission').then(function() {
+                // Set-up docker image collection tags
+                cy.createLink(activeUser.token, {
+                    link_class: 'docker_image_repo+tag',
+                    name: image_name,
+                    head_uuid: dockerImage.uuid,
+                }).as('dockerImageRepoTag');
+                cy.createLink(activeUser.token, {
+                    link_class: 'docker_image_hash',
+                    name: 'sha256:d8309758b8fe2c81034ffc8a10c36460b77db7bc5e7b448c4e5b684f9d95a678',
+                    head_uuid: dockerImage.uuid,
+                }).as('dockerImageHash');
+            })
+        });
+        return cy.getAll('@dockerImage', '@dockerImageRepoTag', '@dockerImageHash',
+            '@dockerImagePermission').then(function([dockerImage]) {
+                return dockerImage;
+            });
+    }
+
+    function createContainerRequest(user, name, docker_image, command, reuse = false, state = 'Uncommitted') {
+        return setupDockerImage(docker_image).then(function(dockerImage) {
+            return cy.createContainerRequest(user.token, {
+                name: name,
+                command: command,
+                container_image: dockerImage.portable_data_hash, // for some reason, docker_image doesn't work here
+                output_path: 'stdout.txt',
+                priority: 1,
+                runtime_constraints: {
+                    vcpus: 1,
+                    ram: 1,
+                },
+                use_existing: reuse,
+                state: state,
+                mounts: {
+                    foo: {
+                        kind: 'tmp',
+                        path: '/tmp/foo',
+                    }
+                }
+            });
+        });
+    }
+
+    it('shows process logs', function() {
+        const crName = 'test_container_request';
+        createContainerRequest(
+            activeUser,
+            crName,
+            'arvados/jobs',
+            ['echo', 'hello world'],
+            false, 'Committed')
+        .then(function(containerRequest) {
+            cy.loginAs(activeUser);
+            cy.goToPath(`/processes/${containerRequest.uuid}`);
+            cy.get('[data-cy=process-info]').should('contain', crName);
+            cy.get('[data-cy=process-logs]')
+                .should('contain', 'No logs yet')
+                .and('not.contain', 'hello world');
+            cy.createLog(activeUser.token, {
+                object_uuid: containerRequest.container_uuid,
+                properties: {
+                    text: 'hello world'
+                },
+                event_type: 'stdout'
+            }).then(function(log) {
+                cy.get('[data-cy=process-logs]')
+                    .should('not.contain', 'No logs yet')
+                    .and('contain', 'hello world');
+            })
+        });
+    });
+
+    it('filters process logs by event type', function() {
+        const nodeInfoLogs = [
+            'Host Information',
+            'Linux compute-99cb150b26149780de44b929577e1aed-19rgca8vobuvc4p 5.4.0-1059-azure #62~18.04.1-Ubuntu SMP Tue Sep 14 17:53:18 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux',
+            'CPU Information',
+            'processor  : 0',
+            'vendor_id  : GenuineIntel',
+            'cpu family : 6',
+            'model      : 79',
+            'model name : Intel(R) Xeon(R) CPU E5-2673 v4 @ 2.30GHz'
+        ];
+        const crunchRunLogs = [
+            '2022-03-22T13:56:22.542417997Z using local keepstore process (pid 3733) at http://localhost:46837, writing logs to keepstore.txt in log collection',
+            '2022-03-22T13:56:26.237571754Z crunch-run 2.4.0~dev20220321141729 (go1.17.1) started',
+            '2022-03-22T13:56:26.244704134Z crunch-run process has uid=0(root) gid=0(root) groups=0(root)',
+            '2022-03-22T13:56:26.244862836Z Executing container \'zzzzz-dz642-1wokwvcct9s9du3\' using docker runtime',
+            '2022-03-22T13:56:26.245037738Z Executing on host \'compute-99cb150b26149780de44b929577e1aed-19rgca8vobuvc4p\'',
+        ];
+        const stdoutLogs = [
+            'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec dui nisi, hendrerit porta sapien a, pretium dignissim purus.',
+            'Integer viverra, mauris finibus aliquet ultricies, dui mauris cursus justo, ut venenatis nibh ex eget neque.',
+            'In hac habitasse platea dictumst.',
+            'Fusce fringilla turpis id accumsan faucibus. Donec congue congue ex non posuere. In semper mi quis tristique rhoncus.',
+            'Interdum et malesuada fames ac ante ipsum primis in faucibus.',
+            'Quisque fermentum tortor ex, ut suscipit velit feugiat faucibus.',
+            'Donec vitae porta risus, at luctus nulla. Mauris gravida iaculis ipsum, id sagittis tortor egestas ac.',
+            'Maecenas condimentum volutpat nulla. Integer lacinia maximus risus eu posuere.',
+            'Donec vitae leo id augue gravida bibendum.',
+            'Nam libero libero, pretium ac faucibus elementum, mattis nec ex.',
+            'Nullam id laoreet nibh. Vivamus tellus metus, pretium quis justo ut, bibendum varius metus. Pellentesque vitae accumsan lorem, quis tincidunt augue.',
+            'Aliquam viverra nisi nulla, et efficitur dolor mattis in.',
+            'Sed at enim sit amet nulla tincidunt mattis. Aenean eget aliquet ex, non ultrices ex. Nulla ex tortor, vestibulum aliquam tempor ac, aliquam vel est.',
+            'Fusce auctor faucibus libero id venenatis. Etiam sodales, odio eu cursus efficitur, quam sem blandit ex, quis porttitor enim dui quis lectus. In id tincidunt felis.',
+            'Phasellus non ex quis arcu tempus faucibus molestie in sapien.',
+            'Duis tristique semper dolor, vitae pulvinar risus.',
+            'Aliquam tortor elit, luctus nec tortor eget, porta tristique nulla.',
+            'Nulla eget mollis ipsum.',
+        ];
+
+        createContainerRequest(
+            activeUser,
+            'test_container_request',
+            'arvados/jobs',
+            ['echo', 'hello world'],
+            false, 'Committed')
+        .then(function(containerRequest) {
+            cy.logsForContainer(activeUser.token, containerRequest.container_uuid,
+                'node-info', nodeInfoLogs).as('nodeInfoLogs');
+            cy.logsForContainer(activeUser.token, containerRequest.container_uuid,
+                'crunch-run', crunchRunLogs).as('crunchRunLogs');
+            cy.logsForContainer(activeUser.token, containerRequest.container_uuid,
+                'stdout', stdoutLogs).as('stdoutLogs');
+            cy.getAll('@stdoutLogs', '@nodeInfoLogs', '@crunchRunLogs').then(function() {
+                cy.loginAs(activeUser);
+                cy.goToPath(`/processes/${containerRequest.uuid}`);
+                // Should should all logs
+                cy.get('[data-cy=process-logs-filter]').should('contain', 'All logs');
+                cy.get('[data-cy=process-logs]')
+                    .should('contain', stdoutLogs[Math.floor(Math.random() * stdoutLogs.length)])
+                    .and('contain', nodeInfoLogs[Math.floor(Math.random() * nodeInfoLogs.length)])
+                    .and('contain', crunchRunLogs[Math.floor(Math.random() * crunchRunLogs.length)]);
+                // Select 'node-info' logs
+                cy.get('[data-cy=process-logs-filter]').click();
+                cy.get('body').contains('li', 'node-info').click();
+                cy.get('[data-cy=process-logs]')
+                    .should('not.contain', stdoutLogs[Math.floor(Math.random() * stdoutLogs.length)])
+                    .and('contain', nodeInfoLogs[Math.floor(Math.random() * nodeInfoLogs.length)])
+                    .and('not.contain', crunchRunLogs[Math.floor(Math.random() * crunchRunLogs.length)]);
+                // Select 'stdout' logs
+                cy.get('[data-cy=process-logs-filter]').click();
+                cy.get('body').contains('li', 'stdout').click();
+                cy.get('[data-cy=process-logs]')
+                    .should('contain', stdoutLogs[Math.floor(Math.random() * stdoutLogs.length)])
+                    .and('not.contain', nodeInfoLogs[Math.floor(Math.random() * nodeInfoLogs.length)])
+                    .and('not.contain', crunchRunLogs[Math.floor(Math.random() * crunchRunLogs.length)]);
+            });
+        });
+    });
+});
\ No newline at end of file
diff --git a/cypress/support/commands.js b/cypress/support/commands.js
index cfdfa9ec..5a2428b2 100644
--- a/cypress/support/commands.js
+++ b/cypress/support/commands.js
@@ -133,6 +133,16 @@ 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;
+            })
+    }
+)
+
 Cypress.Commands.add(
     "createCollection", (token, data) => {
         return cy.createResource(token, 'collections', {
@@ -150,6 +160,49 @@ Cypress.Commands.add(
     }
 )
 
+Cypress.Commands.add(
+    'createContainerRequest', (token, data) => {
+        return cy.createResource(token, 'container_requests', {
+            container_request: JSON.stringify(data),
+            ensure_unique_name: true
+        })
+    }
+)
+
+Cypress.Commands.add(
+    "updateContainerRequest", (token, uuid, data) => {
+        return cy.updateResource(token, 'container_requests', uuid, {
+            container_request: JSON.stringify(data)
+        })
+    }
+)
+
+Cypress.Commands.add(
+    "createLog", (token, data) => {
+        return cy.createResource(token, 'logs', {
+            log: JSON.stringify(data)
+        })
+    }
+)
+
+Cypress.Commands.add(
+    "logsForContainer", (token, uuid, logType, logTextArray = []) => {
+        let logs = [];
+        for (const logText of logTextArray) {
+            logs.push(cy.createLog(token, {
+                object_uuid: uuid,
+                event_type: logType,
+                properties: {
+                    text: logText
+                }
+            }).as('lastLogRecord'))
+        }
+        cy.getAll('@lastLogRecord').then(function () {
+            return logs;
+        })
+    }
+)
+
 Cypress.Commands.add(
     "createVirtualMachine", (token, data) => {
         return cy.createResource(token, 'virtual_machines', {
diff --git a/src/components/multi-panel-view/multi-panel-view.tsx b/src/components/multi-panel-view/multi-panel-view.tsx
index de824990..2bff28cb 100644
--- a/src/components/multi-panel-view/multi-panel-view.tsx
+++ b/src/components/multi-panel-view/multi-panel-view.tsx
@@ -33,6 +33,7 @@ const styles: StyleRulesCallback<CssRules> = theme => ({
     },
     content: {
         overflow: 'auto',
+        display: 'contents',
     },
 });
 
diff --git a/src/views/process-panel/process-log-form.tsx b/src/views/process-panel/process-log-form.tsx
index 6a8e5221..1f63e28d 100644
--- a/src/views/process-panel/process-log-form.tsx
+++ b/src/views/process-panel/process-log-form.tsx
@@ -40,7 +40,7 @@ type ProcessLogFormProps = ProcessLogFormDataProps & ProcessLogFormActionProps &
 
 export const ProcessLogForm = withStyles(styles)(
     ({ classes, selectedFilter, onChange, filters }: ProcessLogFormProps) =>
-        <form autoComplete="off">
+        <form autoComplete="off" data-cy="process-logs-filter">
             <FormControl className={classes.formControl}>
                 <Select
                     value={selectedFilter.value}
diff --git a/src/views/process-panel/process-panel-root.tsx b/src/views/process-panel/process-panel-root.tsx
index 3e695a2f..416faec7 100644
--- a/src/views/process-panel/process-panel-root.tsx
+++ b/src/views/process-panel/process-panel-root.tsx
@@ -58,7 +58,7 @@ export const ProcessPanelRoot = withStyles(styles)(
     ({ process, processLogsPanel, ...props }: ProcessPanelRootProps) =>
     process
         ? <MPVContainer className={props.classes.root} spacing={8} panelStates={panelsData}  justify-content="flex-start" direction="column" wrap="nowrap">
-            <MPVPanelContent forwardProps xs="auto">
+            <MPVPanelContent forwardProps xs="auto" data-cy="process-info">
                 <ProcessInformationCard
                     process={process}
                     onContextMenu={event => props.onContextMenu(event, process)}
@@ -68,10 +68,10 @@ export const ProcessPanelRoot = withStyles(styles)(
                     cancelProcess={props.cancelProcess}
                 />
             </MPVPanelContent>
-            <MPVPanelContent forwardProps xs="auto">
+            <MPVPanelContent forwardProps xs="auto" data-cy="process-details">
                 <ProcessDetailsCard process={process} />
             </MPVPanelContent>
-            <MPVPanelContent forwardProps xs maxHeight='50%'>
+            <MPVPanelContent forwardProps xs maxHeight='50%' data-cy="process-logs">
                 <ProcessLogsCard
                     onCopy={props.onLogCopyToClipboard}
                     process={process}
@@ -87,7 +87,7 @@ export const ProcessPanelRoot = withStyles(styles)(
                     navigateToLog={props.navigateToLog}
                 />
             </MPVPanelContent>
-            <MPVPanelContent forwardProps xs maxHeight='50%'>
+            <MPVPanelContent forwardProps xs maxHeight='50%' data-cy="process-children">
                 <SubprocessPanel />
             </MPVPanelContent>
         </MPVContainer>

commit 35d85b5905b1bfed1d5b7a43882afe79780ee6d2
Author: Lucas Di Pentima <lucas.dipentima at curii.com>
Date:   Tue Mar 22 11:27:35 2022 -0300

    16672: Avoids log panel "blinking" when changing process view.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas.dipentima at curii.com>

diff --git a/src/store/process-panel/process-panel-actions.ts b/src/store/process-panel/process-panel-actions.ts
index d2921d73..962f5dfc 100644
--- a/src/store/process-panel/process-panel-actions.ts
+++ b/src/store/process-panel/process-panel-actions.ts
@@ -13,7 +13,7 @@ import { snackbarActions } from 'store/snackbar/snackbar-actions';
 import { SnackbarKind } from '../snackbar/snackbar-actions';
 import { showWorkflowDetails } from 'store/workflow-panel/workflow-panel-actions';
 import { loadSubprocessPanel } from "../subprocess-panel/subprocess-panel-actions";
-import { initProcessLogsPanel } from "store/process-logs-panel/process-logs-panel-actions";
+import { initProcessLogsPanel, processLogsPanelActions } from "store/process-logs-panel/process-logs-panel-actions";
 
 export const processPanelActions = unionize({
     SET_PROCESS_PANEL_CONTAINER_REQUEST_UUID: ofType<string>(),
@@ -27,6 +27,7 @@ export const toggleProcessPanelFilter = processPanelActions.TOGGLE_PROCESS_PANEL
 
 export const loadProcessPanel = (uuid: string) =>
     async (dispatch: Dispatch) => {
+        dispatch(processLogsPanelActions.RESET_PROCESS_LOGS_PANEL());
         dispatch<ProcessPanelAction>(processPanelActions.SET_PROCESS_PANEL_CONTAINER_REQUEST_UUID(uuid));
         await dispatch<any>(loadProcess(uuid));
         dispatch(initProcessPanelFilters);

commit 0c6cf2fccc1f1c2a175610312d4b1b83adf9df75
Author: Lucas Di Pentima <lucas.dipentima at curii.com>
Date:   Tue Mar 22 11:19:26 2022 -0300

    16672: Adds 'copy to clipboard' feature.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas.dipentima at curii.com>

diff --git a/src/views/process-panel/process-log-card.tsx b/src/views/process-panel/process-log-card.tsx
index 56ac4d92..ac409ec1 100644
--- a/src/views/process-panel/process-log-card.tsx
+++ b/src/views/process-panel/process-log-card.tsx
@@ -19,6 +19,7 @@ import { ArvadosTheme } from 'common/custom-theme';
 import {
     CloseIcon,
     CollectionIcon,
+    CopyIcon,
     LogIcon,
     MaximizeIcon,
     TextDecreaseIcon,
@@ -34,6 +35,7 @@ import {
 import { ProcessLogCodeSnippet } from 'views/process-panel/process-log-code-snippet';
 import { DefaultView } from 'components/default-view/default-view';
 import { CodeSnippetDataProps } from 'components/code-snippet/code-snippet';
+import CopyToClipboard from 'react-copy-to-clipboard';
 
 type CssRules = 'card' | 'content' | 'title' | 'iconHeader' | 'header' | 'root' | 'logViewer' | 'logViewerContainer';
 
@@ -77,6 +79,7 @@ export interface ProcessLogsCardDataProps {
 export interface ProcessLogsCardActionProps {
     onLogFilterChange: (filter: FilterOption) => void;
     navigateToLog: (uuid: string) => void;
+    onCopy: (text: string) => void;
 }
 
 type ProcessLogsCardProps = ProcessLogsCardDataProps
@@ -86,7 +89,8 @@ type ProcessLogsCardProps = ProcessLogsCardDataProps
     & MPVPanelProps;
 
 export const ProcessLogsCard = withStyles(styles)(
-    ({ classes, process, filters, selectedFilter, lines, onLogFilterChange, navigateToLog,
+    ({ classes, process, filters, selectedFilter, lines,
+        onLogFilterChange, navigateToLog, onCopy,
         doHidePanel, doMaximizePanel, panelMaximized, panelName }: ProcessLogsCardProps) => {
         const [wordWrapToggle, setWordWrapToggle] = useState<boolean>(true);
         const [fontSize, setFontSize] = useState<number>(3);
@@ -116,6 +120,15 @@ export const ProcessLogsCard = withStyles(styles)(
                                 </IconButton>
                             </Tooltip>
                         </Grid>
+                        <Grid item>
+                            <Tooltip title="Copy to clipboard" disableFocusListener>
+                                <IconButton>
+                                    <CopyToClipboard text={lines.join()} onCopy={() => onCopy("Log copied to clipboard")}>
+                                        <CopyIcon />
+                                    </CopyToClipboard>
+                                </IconButton>
+                            </Tooltip>
+                        </Grid>
                         <Grid item>
                             <Tooltip title="Toggle word wrapping" disableFocusListener>
                                 <IconButton onClick={() => setWordWrapToggle(!wordWrapToggle)}>
diff --git a/src/views/process-panel/process-panel-root.tsx b/src/views/process-panel/process-panel-root.tsx
index 862dbd68..3e695a2f 100644
--- a/src/views/process-panel/process-panel-root.tsx
+++ b/src/views/process-panel/process-panel-root.tsx
@@ -42,6 +42,7 @@ export interface ProcessPanelRootActionProps {
     cancelProcess: (uuid: string) => void;
     onLogFilterChange: (filter: FilterOption) => void;
     navigateToLog: (uuid: string) => void;
+    onLogCopyToClipboard: (uuid: string) => void;
 }
 
 export type ProcessPanelRootProps = ProcessPanelRootDataProps & ProcessPanelRootActionProps & WithStyles<CssRules>;
@@ -72,6 +73,7 @@ export const ProcessPanelRoot = withStyles(styles)(
             </MPVPanelContent>
             <MPVPanelContent forwardProps xs maxHeight='50%'>
                 <ProcessLogsCard
+                    onCopy={props.onLogCopyToClipboard}
                     process={process}
                     lines={getProcessPanelLogs(processLogsPanel)}
                     selectedFilter={{
diff --git a/src/views/process-panel/process-panel.tsx b/src/views/process-panel/process-panel.tsx
index 6dd02c9b..e0460292 100644
--- a/src/views/process-panel/process-panel.tsx
+++ b/src/views/process-panel/process-panel.tsx
@@ -25,6 +25,7 @@ import {
 import { openProcessInputDialog } from 'store/processes/process-input-actions';
 import { cancelRunningWorkflow } from 'store/processes/processes-actions';
 import { navigateToLogCollection, setProcessLogsPanelFilter } from 'store/process-logs-panel/process-logs-panel-actions';
+import { snackbarActions, SnackbarKind } from 'store/snackbar/snackbar-actions';
 
 const mapStateToProps = ({ router, resources, processPanel, processLogsPanel }: RootState): ProcessPanelRootDataProps => {
     const uuid = getProcessPanelCurrentUuid(router) || '';
@@ -38,6 +39,13 @@ const mapStateToProps = ({ router, resources, processPanel, processLogsPanel }:
 };
 
 const mapDispatchToProps = (dispatch: Dispatch): ProcessPanelRootActionProps => ({
+    onLogCopyToClipboard: (message: string) => {
+        dispatch<any>(snackbarActions.OPEN_SNACKBAR({
+            message,
+            hideDuration: 2000,
+            kind: SnackbarKind.SUCCESS,
+        }));
+    },
     onContextMenu: (event, process) => {
         dispatch<any>(openProcessContextMenu(event, process));
     },

commit 3826b30cb25c2a1cb22729981412f091a8443076
Author: Lucas Di Pentima <lucas.dipentima at curii.com>
Date:   Tue Mar 22 10:59:27 2022 -0300

    16672: Implements 'auto-follow' mode when log view is scrolled all the way down
    
    Restored the CodeSnippet component as it was needing too much customization
    for the log viewer use case.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas.dipentima at curii.com>

diff --git a/src/components/code-snippet/code-snippet.tsx b/src/components/code-snippet/code-snippet.tsx
index fd44b5fc..f0a2b213 100644
--- a/src/components/code-snippet/code-snippet.tsx
+++ b/src/components/code-snippet/code-snippet.tsx
@@ -24,24 +24,19 @@ export interface CodeSnippetDataProps {
     lines: string[];
     className?: string;
     apiResponse?: boolean;
-    containerClassName?: string;
-    fontSize?: number;
-    customRenderer?: (line: string) => React.ReactNode;
 }
 
 type CodeSnippetProps = CodeSnippetDataProps & WithStyles<CssRules>;
 
 export const CodeSnippet = withStyles(styles)(
-    ({ classes, lines, className, containerClassName,
-        apiResponse, fontSize, customRenderer }: CodeSnippetProps) =>
-        <Typography component="div"
-            className={classNames(classes.root, containerClassName, className)}>
-            { lines.map((line: string, index: number) => {
-            return <Typography key={index} style={{ fontSize: fontSize }}
-                className={apiResponse ? classes.space : className}
-                component="pre">
-                {customRenderer ? customRenderer(line) : line}
-            </Typography>;
-            }) }
+    ({ classes, lines, className, apiResponse }: CodeSnippetProps) =>
+        <Typography
+        component="div"
+        className={classNames(classes.root, className)}>
+            {
+                lines.map((line: string, index: number) => {
+                    return <Typography key={index} className={apiResponse ? classes.space : className} component="pre">{line}</Typography>;
+                })
+            }
         </Typography>
     );
\ No newline at end of file
diff --git a/src/views/process-panel/process-log-code-snippet.tsx b/src/views/process-panel/process-log-code-snippet.tsx
index 4f19f917..6ea628e6 100644
--- a/src/views/process-panel/process-log-code-snippet.tsx
+++ b/src/views/process-panel/process-log-code-snippet.tsx
@@ -2,25 +2,37 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import React from 'react';
-import { MuiThemeProvider, createMuiTheme, StyleRulesCallback, withStyles, WithStyles } from '@material-ui/core/styles';
-import { CodeSnippet } from 'components/code-snippet/code-snippet';
+import React, { useEffect, useRef, useState } from 'react';
+import {
+    MuiThemeProvider,
+    createMuiTheme,
+    StyleRulesCallback,
+    withStyles,
+    WithStyles
+} from '@material-ui/core/styles';
 import grey from '@material-ui/core/colors/grey';
 import { ArvadosTheme } from 'common/custom-theme';
 import { Link, Typography } from '@material-ui/core';
 import { navigateTo } from 'store/navigation/navigation-action';
 import { Dispatch } from 'redux';
 import { connect, DispatchProp } from 'react-redux';
+import classNames from 'classnames';
 
-type CssRules = 'wordWrap' | 'codeSnippetContainer';
+type CssRules = 'root' | 'wordWrap' | 'logText';
 
 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
+    root: {
+        boxSizing: 'border-box',
+        overflow: 'auto',
+        backgroundColor: '#000',
+        height: `calc(100% - ${theme.spacing.unit * 4}px)`, // so that horizontal scollbar is visible
+    },
+    logText: {
+        padding: theme.spacing.unit * 0.5,
+    },
     wordWrap: {
         whiteSpace: 'pre-wrap',
     },
-    codeSnippetContainer: {
-        height: `calc(100% - ${theme.spacing.unit * 4}px)`, // so that horizontal scollbar is visible
-    },
 });
 
 const theme = createMuiTheme({
@@ -28,9 +40,6 @@ const theme = createMuiTheme({
         MuiTypography: {
             body2: {
                 color: grey["200"]
-            },
-            root: {
-                backgroundColor: '#000'
             }
         }
     },
@@ -68,10 +77,33 @@ const renderLinks = (fontSize: number, dispatch: Dispatch) => (text: string) =>
 };
 
 export const ProcessLogCodeSnippet = withStyles(styles)(connect()(
-    (props: ProcessLogCodeSnippetProps & WithStyles<CssRules> & DispatchProp) =>
-        <MuiThemeProvider theme={theme}>
-            <CodeSnippet lines={props.lines} fontSize={props.fontSize}
-                customRenderer={renderLinks(props.fontSize, props.dispatch)}
-                className={props.wordWrap ? props.classes.wordWrap : undefined}
-                containerClassName={props.classes.codeSnippetContainer} />
-        </MuiThemeProvider>));
\ No newline at end of file
+    ({classes, lines, fontSize, dispatch, wordWrap}: ProcessLogCodeSnippetProps & WithStyles<CssRules> & DispatchProp) => {
+        const [followMode, setFollowMode] = useState<boolean>(false);
+        const scrollRef = useRef<HTMLDivElement>(null);
+
+        useEffect(() => {
+            if (followMode && scrollRef.current && lines.length > 0) {
+                // Scroll to bottom
+                scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
+            }
+        }, [followMode, lines, scrollRef]);
+
+        return <MuiThemeProvider theme={theme}>
+            <div ref={scrollRef} className={classes.root}
+                onScroll={(e) => {
+                    const elem = e.target as HTMLDivElement;
+                    if (elem.scrollTop + elem.clientHeight >= elem.scrollHeight) {
+                        setFollowMode(true);
+                    } else {
+                        setFollowMode(false);
+                    }
+                }}>
+                { lines.map((line: string, index: number) =>
+                <Typography key={index} component="pre"
+                    className={classNames(classes.logText, wordWrap ? classes.wordWrap : undefined)}>
+                    {renderLinks(fontSize, dispatch)(line)}
+                </Typography>
+                ) }
+            </div>
+        </MuiThemeProvider>
+    }));
\ No newline at end of file

commit 6ddc4f58120358fa12de736c49be69f8373b4068
Author: Lucas Di Pentima <lucas.dipentima at curii.com>
Date:   Mon Mar 21 16:37:33 2022 -0300

    16672: Adds renderer for UUID & PDH links on the log viewer.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas.dipentima at curii.com>

diff --git a/src/components/code-snippet/code-snippet.tsx b/src/components/code-snippet/code-snippet.tsx
index 6dc12401..fd44b5fc 100644
--- a/src/components/code-snippet/code-snippet.tsx
+++ b/src/components/code-snippet/code-snippet.tsx
@@ -26,17 +26,22 @@ export interface CodeSnippetDataProps {
     apiResponse?: boolean;
     containerClassName?: string;
     fontSize?: number;
+    customRenderer?: (line: string) => React.ReactNode;
 }
 
 type CodeSnippetProps = CodeSnippetDataProps & WithStyles<CssRules>;
 
 export const CodeSnippet = withStyles(styles)(
-    ({ classes, lines, className, containerClassName, apiResponse, fontSize }: CodeSnippetProps) =>
-        <Typography
-            component="div"
+    ({ classes, lines, className, containerClassName,
+        apiResponse, fontSize, customRenderer }: CodeSnippetProps) =>
+        <Typography component="div"
             className={classNames(classes.root, containerClassName, className)}>
-                { lines.map((line: string, index: number) => {
-                    return <Typography key={index} style={{ fontSize: fontSize }} className={apiResponse ? classes.space : className} component="pre">{line}</Typography>;
-                }) }
+            { lines.map((line: string, index: number) => {
+            return <Typography key={index} style={{ fontSize: fontSize }}
+                className={apiResponse ? classes.space : className}
+                component="pre">
+                {customRenderer ? customRenderer(line) : line}
+            </Typography>;
+            }) }
         </Typography>
     );
\ No newline at end of file
diff --git a/src/components/multi-panel-view/multi-panel-view.tsx b/src/components/multi-panel-view/multi-panel-view.tsx
index 48241c0b..de824990 100644
--- a/src/components/multi-panel-view/multi-panel-view.tsx
+++ b/src/components/multi-panel-view/multi-panel-view.tsx
@@ -88,12 +88,9 @@ export const MPVPanelContent = ({doHidePanel, doMaximizePanel, panelName,
         }
     }, [panelRef]);
 
-    // If maxHeight is set, only apply it when not maximized
-    const mh = maxHeight
-        ? panelMaximized
-            ? '100%'
-            : maxHeight
-        : undefined;
+    const mh = panelMaximized
+        ? '100%'
+        : maxHeight;
 
     return <Grid item style={{maxHeight: mh}} {...props}>
         <span ref={panelRef} /> {/* Element to scroll to when the panel is selected */}
diff --git a/src/views/process-panel/process-log-code-snippet.tsx b/src/views/process-panel/process-log-code-snippet.tsx
index 1ea83912..4f19f917 100644
--- a/src/views/process-panel/process-log-code-snippet.tsx
+++ b/src/views/process-panel/process-log-code-snippet.tsx
@@ -7,6 +7,10 @@ import { MuiThemeProvider, createMuiTheme, StyleRulesCallback, withStyles, WithS
 import { CodeSnippet } from 'components/code-snippet/code-snippet';
 import grey from '@material-ui/core/colors/grey';
 import { ArvadosTheme } from 'common/custom-theme';
+import { Link, Typography } from '@material-ui/core';
+import { navigateTo } from 'store/navigation/navigation-action';
+import { Dispatch } from 'redux';
+import { connect, DispatchProp } from 'react-redux';
 
 type CssRules = 'wordWrap' | 'codeSnippetContainer';
 
@@ -42,10 +46,32 @@ interface ProcessLogCodeSnippetProps {
     wordWrap?: boolean;
 }
 
-export const ProcessLogCodeSnippet = withStyles(styles)(
-    (props: ProcessLogCodeSnippetProps & WithStyles<CssRules>) =>
+const renderLinks = (fontSize: number, dispatch: Dispatch) => (text: string) => {
+    // Matches UUIDs & PDHs
+    const REGEX = /[a-z0-9]{5}-[a-z0-9]{5}-[a-z0-9]{15}|[0-9a-f]{32}\+\d+/g;
+    const links = text.match(REGEX);
+    if (!links) {
+        return <Typography style={{ fontSize: fontSize }}>{text}</Typography>;
+    }
+    return <Typography style={{ fontSize: fontSize }}>
+        {text.split(REGEX).map((part, index) =>
+        <React.Fragment key={index}>
+            {part}
+            {links[index] &&
+            <Link onClick={() => dispatch<any>(navigateTo(links[index]))}
+                style={ {cursor: 'pointer'} }>
+                {links[index]}
+            </Link>}
+        </React.Fragment>
+        )}
+    </Typography>;
+};
+
+export const ProcessLogCodeSnippet = withStyles(styles)(connect()(
+    (props: ProcessLogCodeSnippetProps & WithStyles<CssRules> & DispatchProp) =>
         <MuiThemeProvider theme={theme}>
             <CodeSnippet lines={props.lines} fontSize={props.fontSize}
+                customRenderer={renderLinks(props.fontSize, props.dispatch)}
                 className={props.wordWrap ? props.classes.wordWrap : undefined}
                 containerClassName={props.classes.codeSnippetContainer} />
-        </MuiThemeProvider>);
\ No newline at end of file
+        </MuiThemeProvider>));
\ No newline at end of file
diff --git a/src/views/process-panel/process-panel-root.tsx b/src/views/process-panel/process-panel-root.tsx
index 78c79f8f..862dbd68 100644
--- a/src/views/process-panel/process-panel-root.tsx
+++ b/src/views/process-panel/process-panel-root.tsx
@@ -85,7 +85,7 @@ export const ProcessPanelRoot = withStyles(styles)(
                     navigateToLog={props.navigateToLog}
                 />
             </MPVPanelContent>
-            <MPVPanelContent forwardProps xs>
+            <MPVPanelContent forwardProps xs maxHeight='50%'>
                 <SubprocessPanel />
             </MPVPanelContent>
         </MPVContainer>

commit d0c2059e292a1c7848f6a8b40e126cd812aeb5f2
Author: Lucas Di Pentima <lucas.dipentima at curii.com>
Date:   Mon Mar 21 11:11:56 2022 -0300

    16672: Adds 'container' type event to the logs viewer.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas.dipentima at curii.com>

diff --git a/src/models/log.ts b/src/models/log.ts
index 3397993b..20060f88 100644
--- a/src/models/log.ts
+++ b/src/models/log.ts
@@ -16,6 +16,7 @@ export enum LogEventType {
     ARV_MOUNT = 'arv-mount',
     STDOUT = 'stdout',
     STDERR = 'stderr',
+    CONTAINER = 'container',
 }
 
 export interface LogResource extends Resource, ResourceWithProperties {
diff --git a/src/store/process-logs-panel/process-logs-panel-actions.ts b/src/store/process-logs-panel/process-logs-panel-actions.ts
index a14437aa..b0ddd7ee 100644
--- a/src/store/process-logs-panel/process-logs-panel-actions.ts
+++ b/src/store/process-logs-panel/process-logs-panel-actions.ts
@@ -122,4 +122,5 @@ const PROCESS_PANEL_LOG_EVENT_TYPES = [
     LogEventType.NODE_INFO,
     LogEventType.STDERR,
     LogEventType.STDOUT,
+    LogEventType.CONTAINER,
 ];

commit cafed6458d46b99a1dc28deef0ea60b985af03dd
Author: Lucas Di Pentima <lucas.dipentima at curii.com>
Date:   Mon Mar 21 10:46:06 2022 -0300

    16672: Removes unused code, avoids requesting the CR twice.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas.dipentima at curii.com>

diff --git a/src/store/process-logs-panel/process-logs-panel-actions.ts b/src/store/process-logs-panel/process-logs-panel-actions.ts
index f0caf052..a14437aa 100644
--- a/src/store/process-logs-panel/process-logs-panel-actions.ts
+++ b/src/store/process-logs-panel/process-logs-panel-actions.ts
@@ -9,7 +9,6 @@ import { RootState } from 'store/store';
 import { ServiceRepository } from 'services/services';
 import { Dispatch } from 'redux';
 import { groupBy } from 'lodash';
-import { loadProcess } from 'store/processes/processes-actions';
 import { LogResource } from 'models/log';
 import { LogService } from 'services/log-service/log-service';
 import { ResourceEventMessage } from 'websocket/resource-event-message';
@@ -34,8 +33,8 @@ export const setProcessLogsPanelFilter = (filter: string) =>
 export const initProcessLogsPanel = (processUuid: string) =>
     async (dispatch: Dispatch, getState: () => RootState, { logService }: ServiceRepository) => {
         dispatch(processLogsPanelActions.RESET_PROCESS_LOGS_PANEL());
-        const process = await dispatch<any>(loadProcess(processUuid));
-        if (process.container) {
+        const process = getProcess(processUuid)(getState().resources);
+        if (process && process.container) {
             const logResources = await loadContainerLogs(process.container.uuid, logService);
             const initialState = createInitialLogPanelState(logResources);
             dispatch(processLogsPanelActions.INIT_PROCESS_LOGS_PANEL(initialState));
@@ -53,7 +52,7 @@ export const addProcessLogsPanelItem = (message: ResourceEventMessage<{ text: st
                     if (message.objectUuid === containerRequest.uuid
                         || (container && message.objectUuid === container.uuid)) {
                         dispatch(processLogsPanelActions.ADD_PROCESS_LOGS_PANEL_ITEM({
-                            logType: SUMMARIZED_FILTER_TYPE,
+                            logType: COMBINED_FILTER_TYPE,
                             log: message.properties.text
                         }));
                         dispatch(processLogsPanelActions.ADD_PROCESS_LOGS_PANEL_ITEM({
@@ -92,8 +91,8 @@ const createInitialLogPanelState = (logResources: LogResource[]) => {
             ...grouped,
             [key]: logsToLines(groupedLogResources[key])
         }), {});
-    const filters = [SUMMARIZED_FILTER_TYPE, ...Object.keys(groupedLogs)];
-    const logs = { [SUMMARIZED_FILTER_TYPE]: allLogs, ...groupedLogs };
+    const filters = [COMBINED_FILTER_TYPE, ...Object.keys(groupedLogs)];
+    const logs = { [COMBINED_FILTER_TYPE]: allLogs, ...groupedLogs };
     return { filters, logs };
 };
 
@@ -106,13 +105,13 @@ export const navigateToLogCollection = (uuid: string) =>
             await services.collectionService.get(uuid);
             dispatch<any>(navigateTo(uuid));
         } catch {
-            dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'This collection does not exists!', hideDuration: 2000, kind: SnackbarKind.ERROR }));
+            dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Could not request collection', hideDuration: 2000, kind: SnackbarKind.ERROR }));
         }
     };
 
 const MAX_AMOUNT_OF_LOGS = 10000;
 
-const SUMMARIZED_FILTER_TYPE = 'Summarized';
+const COMBINED_FILTER_TYPE = 'All logs';
 
 const PROCESS_PANEL_LOG_EVENT_TYPES = [
     LogEventType.ARV_MOUNT,
diff --git a/src/store/process-panel/process-panel-actions.ts b/src/store/process-panel/process-panel-actions.ts
index 2e5fb6b9..d2921d73 100644
--- a/src/store/process-panel/process-panel-actions.ts
+++ b/src/store/process-panel/process-panel-actions.ts
@@ -26,9 +26,9 @@ export type ProcessPanelAction = UnionOf<typeof processPanelActions>;
 export const toggleProcessPanelFilter = processPanelActions.TOGGLE_PROCESS_PANEL_FILTER;
 
 export const loadProcessPanel = (uuid: string) =>
-    (dispatch: Dispatch) => {
+    async (dispatch: Dispatch) => {
         dispatch<ProcessPanelAction>(processPanelActions.SET_PROCESS_PANEL_CONTAINER_REQUEST_UUID(uuid));
-        dispatch<any>(loadProcess(uuid));
+        await dispatch<any>(loadProcess(uuid));
         dispatch(initProcessPanelFilters);
         dispatch<any>(initProcessLogsPanel(uuid));
         dispatch<any>(loadSubprocessPanel());
diff --git a/src/store/workbench/workbench-actions.ts b/src/store/workbench/workbench-actions.ts
index 98508f75..7958463a 100644
--- a/src/store/workbench/workbench-actions.ts
+++ b/src/store/workbench/workbench-actions.ts
@@ -49,7 +49,6 @@ import * as processUpdateActions from 'store/processes/process-update-actions';
 import * as processCopyActions from 'store/processes/process-copy-actions';
 import { trashPanelColumns } from "views/trash-panel/trash-panel";
 import { loadTrashPanel, trashPanelActions } from "store/trash-panel/trash-panel-action";
-import { initProcessLogsPanel } from 'store/process-logs-panel/process-logs-panel-actions';
 import { loadProcessPanel } from 'store/process-panel/process-panel-actions';
 import {
     loadSharedWithMePanel,
@@ -417,15 +416,6 @@ export const copyProcess = (data: CopyFormDialogData) =>
         }
     };
 
-export const loadProcessLog = (uuid: string) =>
-    handleFirstTimeLoad(
-        async (dispatch: Dispatch) => {
-            const process = await dispatch<any>(processesActions.loadProcess(uuid));
-            dispatch<any>(setProcessBreadcrumbs(uuid));
-            dispatch<any>(initProcessLogsPanel(uuid));
-            await dispatch<any>(activateSidePanelTreeItem(process.containerRequest.ownerUuid));
-        });
-
 export const resourceIsNotLoaded = (uuid: string) =>
     snackbarActions.OPEN_SNACKBAR({
         message: `Resource identified by ${uuid} is not loaded.`,

commit cfe5ff035578ede95613b0c545708466da78cbea
Author: Lucas Di Pentima <lucas.dipentima at curii.com>
Date:   Fri Mar 18 18:31:42 2022 -0300

    16672: Adds font size control to the log viewer.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas.dipentima at curii.com>

diff --git a/src/components/code-snippet/code-snippet.tsx b/src/components/code-snippet/code-snippet.tsx
index d0cf6ab3..6dc12401 100644
--- a/src/components/code-snippet/code-snippet.tsx
+++ b/src/components/code-snippet/code-snippet.tsx
@@ -25,17 +25,18 @@ export interface CodeSnippetDataProps {
     className?: string;
     apiResponse?: boolean;
     containerClassName?: string;
+    fontSize?: number;
 }
 
 type CodeSnippetProps = CodeSnippetDataProps & WithStyles<CssRules>;
 
 export const CodeSnippet = withStyles(styles)(
-    ({ classes, lines, className, containerClassName, apiResponse }: CodeSnippetProps) =>
+    ({ classes, lines, className, containerClassName, apiResponse, fontSize }: CodeSnippetProps) =>
         <Typography
             component="div"
             className={classNames(classes.root, containerClassName, className)}>
                 { lines.map((line: string, index: number) => {
-                    return <Typography key={index} className={apiResponse ? classes.space : className} component="pre">{line}</Typography>;
+                    return <Typography key={index} style={{ fontSize: fontSize }} className={apiResponse ? classes.space : className} component="pre">{line}</Typography>;
                 }) }
         </Typography>
     );
\ No newline at end of file
diff --git a/src/components/icon/icon.tsx b/src/components/icon/icon.tsx
index 7fb74e81..557e22e7 100644
--- a/src/components/icon/icon.tsx
+++ b/src/components/icon/icon.tsx
@@ -66,6 +66,8 @@ import LinkOutlined from '@material-ui/icons/LinkOutlined';
 import RemoveRedEye from '@material-ui/icons/RemoveRedEye';
 import Computer from '@material-ui/icons/Computer';
 import WrapText from '@material-ui/icons/WrapText';
+import TextIncrease from '@material-ui/icons/ZoomIn';
+import TextDecrease from '@material-ui/icons/ZoomOut';
 
 // Import FontAwesome icons
 import { library } from '@fortawesome/fontawesome-svg-core';
@@ -175,3 +177,5 @@ export const CanWriteIcon: IconType = (props) => <Edit {...props} />;
 export const CanManageIcon: IconType = (props) => <Computer {...props} />;
 export const AddUserIcon: IconType = (props) => <PersonAdd {...props} />;
 export const WordWrapIcon: IconType = (props) => <WrapText {...props} />;
+export const TextIncreaseIcon: IconType = (props) => <TextIncrease {...props} />;
+export const TextDecreaseIcon: IconType = (props) => <TextDecrease {...props} />;
diff --git a/src/views/process-panel/process-log-card.tsx b/src/views/process-panel/process-log-card.tsx
index bbb4ff9d..56ac4d92 100644
--- a/src/views/process-panel/process-log-card.tsx
+++ b/src/views/process-panel/process-log-card.tsx
@@ -21,6 +21,8 @@ import {
     CollectionIcon,
     LogIcon,
     MaximizeIcon,
+    TextDecreaseIcon,
+    TextIncreaseIcon,
     WordWrapIcon
 } from 'components/icon/icon';
 import { Process } from 'store/processes/process';
@@ -87,6 +89,10 @@ export const ProcessLogsCard = withStyles(styles)(
     ({ classes, process, filters, selectedFilter, lines, onLogFilterChange, navigateToLog,
         doHidePanel, doMaximizePanel, panelMaximized, panelName }: ProcessLogsCardProps) => {
         const [wordWrapToggle, setWordWrapToggle] = useState<boolean>(true);
+        const [fontSize, setFontSize] = useState<number>(3);
+        const fontBaseSize = 10;
+        const fontStepSize = 1;
+
         return <Grid item className={classes.root} xs={12}>
             <Card className={classes.card}>
                 <CardHeader className={classes.header}
@@ -96,6 +102,20 @@ export const ProcessLogsCard = withStyles(styles)(
                             <ProcessLogForm selectedFilter={selectedFilter}
                                 filters={filters} onChange={onLogFilterChange} />
                         </Grid>
+                        <Grid item>
+                            <Tooltip title="Decrease font size" disableFocusListener>
+                                <IconButton onClick={() => fontSize > 1 && setFontSize(fontSize-1)}>
+                                    <TextDecreaseIcon />
+                                </IconButton>
+                            </Tooltip>
+                        </Grid>
+                        <Grid item>
+                            <Tooltip title="Increase font size" disableFocusListener>
+                                <IconButton onClick={() => fontSize < 5 && setFontSize(fontSize+1)}>
+                                    <TextIncreaseIcon />
+                                </IconButton>
+                            </Tooltip>
+                        </Grid>
                         <Grid item>
                             <Tooltip title="Toggle word wrapping" disableFocusListener>
                                 <IconButton onClick={() => setWordWrapToggle(!wordWrapToggle)}>
@@ -133,7 +153,7 @@ export const ProcessLogsCard = withStyles(styles)(
                             spacing={24}
                             direction='column'>
                             <Grid className={classes.logViewer} item xs>
-                                <ProcessLogCodeSnippet wordWrap={wordWrapToggle} lines={lines} />
+                                <ProcessLogCodeSnippet fontSize={fontBaseSize+(fontStepSize*fontSize)} wordWrap={wordWrapToggle} lines={lines} />
                             </Grid>
                         </Grid>
                         : <DefaultView
diff --git a/src/views/process-panel/process-log-code-snippet.tsx b/src/views/process-panel/process-log-code-snippet.tsx
index eb3ede6f..1ea83912 100644
--- a/src/views/process-panel/process-log-code-snippet.tsx
+++ b/src/views/process-panel/process-log-code-snippet.tsx
@@ -38,13 +38,14 @@ const theme = createMuiTheme({
 
 interface ProcessLogCodeSnippetProps {
     lines: string[];
+    fontSize: number;
     wordWrap?: boolean;
 }
 
 export const ProcessLogCodeSnippet = withStyles(styles)(
     (props: ProcessLogCodeSnippetProps & WithStyles<CssRules>) =>
         <MuiThemeProvider theme={theme}>
-            <CodeSnippet lines={props.lines}
+            <CodeSnippet lines={props.lines} fontSize={props.fontSize}
                 className={props.wordWrap ? props.classes.wordWrap : undefined}
                 containerClassName={props.classes.codeSnippetContainer} />
         </MuiThemeProvider>);
\ No newline at end of file

commit 3dad010ab289e3409827221ee2a337b4417ee5df
Author: Lucas Di Pentima <lucas.dipentima at curii.com>
Date:   Fri Mar 18 17:51:17 2022 -0300

    16672: Adds toggable word-wrapping to the log panel.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas.dipentima at curii.com>

diff --git a/src/components/icon/icon.tsx b/src/components/icon/icon.tsx
index 54b7bee6..7fb74e81 100644
--- a/src/components/icon/icon.tsx
+++ b/src/components/icon/icon.tsx
@@ -65,6 +65,7 @@ import VpnKey from '@material-ui/icons/VpnKey';
 import LinkOutlined from '@material-ui/icons/LinkOutlined';
 import RemoveRedEye from '@material-ui/icons/RemoveRedEye';
 import Computer from '@material-ui/icons/Computer';
+import WrapText from '@material-ui/icons/WrapText';
 
 // Import FontAwesome icons
 import { library } from '@fortawesome/fontawesome-svg-core';
@@ -173,3 +174,4 @@ export const CanReadIcon: IconType = (props) => <RemoveRedEye {...props} />;
 export const CanWriteIcon: IconType = (props) => <Edit {...props} />;
 export const CanManageIcon: IconType = (props) => <Computer {...props} />;
 export const AddUserIcon: IconType = (props) => <PersonAdd {...props} />;
+export const WordWrapIcon: IconType = (props) => <WrapText {...props} />;
diff --git a/src/views/process-panel/process-log-card.tsx b/src/views/process-panel/process-log-card.tsx
index bd700bcd..bbb4ff9d 100644
--- a/src/views/process-panel/process-log-card.tsx
+++ b/src/views/process-panel/process-log-card.tsx
@@ -2,7 +2,7 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import React from 'react';
+import React, { useState } from 'react';
 import {
     StyleRulesCallback,
     WithStyles,
@@ -20,7 +20,8 @@ import {
     CloseIcon,
     CollectionIcon,
     LogIcon,
-    MaximizeIcon
+    MaximizeIcon,
+    WordWrapIcon
 } from 'components/icon/icon';
 import { Process } from 'store/processes/process';
 import { MPVPanelProps } from 'components/multi-panel-view/multi-panel-view';
@@ -84,8 +85,9 @@ type ProcessLogsCardProps = ProcessLogsCardDataProps
 
 export const ProcessLogsCard = withStyles(styles)(
     ({ classes, process, filters, selectedFilter, lines, onLogFilterChange, navigateToLog,
-        doHidePanel, doMaximizePanel, panelMaximized, panelName }: ProcessLogsCardProps) =>
-        <Grid item className={classes.root} xs={12}>
+        doHidePanel, doMaximizePanel, panelMaximized, panelName }: ProcessLogsCardProps) => {
+        const [wordWrapToggle, setWordWrapToggle] = useState<boolean>(true);
+        return <Grid item className={classes.root} xs={12}>
             <Card className={classes.card}>
                 <CardHeader className={classes.header}
                     avatar={<LogIcon className={classes.iconHeader} />}
@@ -94,6 +96,13 @@ export const ProcessLogsCard = withStyles(styles)(
                             <ProcessLogForm selectedFilter={selectedFilter}
                                 filters={filters} onChange={onLogFilterChange} />
                         </Grid>
+                        <Grid item>
+                            <Tooltip title="Toggle word wrapping" disableFocusListener>
+                                <IconButton onClick={() => setWordWrapToggle(!wordWrapToggle)}>
+                                    <WordWrapIcon />
+                                </IconButton>
+                            </Tooltip>
+                        </Grid>
                         <Grid item>
                             <Tooltip title="Go to Log collection" disableFocusListener>
                                 <IconButton onClick={() => navigateToLog(process.containerRequest.logUuid!)}>
@@ -124,7 +133,7 @@ export const ProcessLogsCard = withStyles(styles)(
                             spacing={24}
                             direction='column'>
                             <Grid className={classes.logViewer} item xs>
-                                <ProcessLogCodeSnippet lines={lines} />
+                                <ProcessLogCodeSnippet wordWrap={wordWrapToggle} lines={lines} />
                             </Grid>
                         </Grid>
                         : <DefaultView
@@ -134,5 +143,5 @@ export const ProcessLogsCard = withStyles(styles)(
                 </CardContent>
             </Card>
         </Grid >
-);
+});
 
diff --git a/src/views/process-panel/process-log-code-snippet.tsx b/src/views/process-panel/process-log-code-snippet.tsx
index 01f5ca1c..eb3ede6f 100644
--- a/src/views/process-panel/process-log-code-snippet.tsx
+++ b/src/views/process-panel/process-log-code-snippet.tsx
@@ -8,10 +8,11 @@ import { CodeSnippet } from 'components/code-snippet/code-snippet';
 import grey from '@material-ui/core/colors/grey';
 import { ArvadosTheme } from 'common/custom-theme';
 
-type CssRules = 'codeSnippet' | 'codeSnippetContainer';
+type CssRules = 'wordWrap' | 'codeSnippetContainer';
 
 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
-    codeSnippet: {
+    wordWrap: {
+        whiteSpace: 'pre-wrap',
     },
     codeSnippetContainer: {
         height: `calc(100% - ${theme.spacing.unit * 4}px)`, // so that horizontal scollbar is visible
@@ -37,11 +38,13 @@ const theme = createMuiTheme({
 
 interface ProcessLogCodeSnippetProps {
     lines: string[];
+    wordWrap?: boolean;
 }
 
 export const ProcessLogCodeSnippet = withStyles(styles)(
     (props: ProcessLogCodeSnippetProps & WithStyles<CssRules>) =>
         <MuiThemeProvider theme={theme}>
-            <CodeSnippet lines={props.lines} className={props.classes.codeSnippet}
+            <CodeSnippet lines={props.lines}
+                className={props.wordWrap ? props.classes.wordWrap : undefined}
                 containerClassName={props.classes.codeSnippetContainer} />
         </MuiThemeProvider>);
\ No newline at end of file

commit 51f7337782c238ecb7d43490268bf90b856150a2
Author: Lucas Di Pentima <lucas.dipentima at curii.com>
Date:   Fri Mar 18 17:33:05 2022 -0300

    16672: Adds new prop to MPVContent: max height when not maximized.
    
    This applies to the Log panel so that it doesn't take the whole vertical
    space available when sharing the UI with other panels.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas.dipentima at curii.com>

diff --git a/src/components/multi-panel-view/multi-panel-view.tsx b/src/components/multi-panel-view/multi-panel-view.tsx
index b242f805..48241c0b 100644
--- a/src/components/multi-panel-view/multi-panel-view.tsx
+++ b/src/components/multi-panel-view/multi-panel-view.tsx
@@ -33,7 +33,6 @@ const styles: StyleRulesCallback<CssRules> = theme => ({
     },
     content: {
         overflow: 'auto',
-        height: '100%',
     },
 });
 
@@ -66,6 +65,7 @@ interface MPVPanelDataProps {
     panelIlluminated?: boolean;
     panelRef?: MutableRefObject<any>;
     forwardProps?: boolean;
+    maxHeight?: string;
 }
 
 interface MPVPanelActionProps {
@@ -79,14 +79,23 @@ export type MPVPanelProps = MPVPanelDataProps & MPVPanelActionProps;
 type MPVPanelContentProps = {children: ReactElement} & MPVPanelProps & GridProps;
 
 // Grid item compatible component for layout and MPV props passing
-export const MPVPanelContent = ({doHidePanel, doMaximizePanel, panelName, panelMaximized, panelIlluminated, panelRef, forwardProps, ...props}: MPVPanelContentProps) => {
+export const MPVPanelContent = ({doHidePanel, doMaximizePanel, panelName,
+    panelMaximized, panelIlluminated, panelRef, forwardProps, maxHeight,
+    ...props}: MPVPanelContentProps) => {
     useEffect(() => {
         if (panelRef && panelRef.current) {
             panelRef.current.scrollIntoView({behavior: 'smooth'});
         }
     }, [panelRef]);
 
-    return <Grid item style={{height: '100%'}} {...props}>
+    // If maxHeight is set, only apply it when not maximized
+    const mh = maxHeight
+        ? panelMaximized
+            ? '100%'
+            : maxHeight
+        : undefined;
+
+    return <Grid item style={{maxHeight: mh}} {...props}>
         <span ref={panelRef} /> {/* Element to scroll to when the panel is selected */}
         <Paper style={{height: '100%'}} elevation={panelIlluminated ? 8 : 0}>
             { forwardProps
diff --git a/src/views/process-panel/process-panel-root.tsx b/src/views/process-panel/process-panel-root.tsx
index cf32b50f..78c79f8f 100644
--- a/src/views/process-panel/process-panel-root.tsx
+++ b/src/views/process-panel/process-panel-root.tsx
@@ -70,7 +70,7 @@ export const ProcessPanelRoot = withStyles(styles)(
             <MPVPanelContent forwardProps xs="auto">
                 <ProcessDetailsCard process={process} />
             </MPVPanelContent>
-            <MPVPanelContent forwardProps xs>
+            <MPVPanelContent forwardProps xs maxHeight='50%'>
                 <ProcessLogsCard
                     process={process}
                     lines={getProcessPanelLogs(processLogsPanel)}

commit 4bc7ca180691fc56bee6f4e69288945313ab40d1
Author: Lucas Di Pentima <lucas.dipentima at curii.com>
Date:   Thu Mar 17 18:19:01 2022 -0300

    16672: Fixes panels' vertical space layout issues.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas.dipentima at curii.com>

diff --git a/src/components/code-snippet/code-snippet.tsx b/src/components/code-snippet/code-snippet.tsx
index f0a2b213..d0cf6ab3 100644
--- a/src/components/code-snippet/code-snippet.tsx
+++ b/src/components/code-snippet/code-snippet.tsx
@@ -24,19 +24,18 @@ export interface CodeSnippetDataProps {
     lines: string[];
     className?: string;
     apiResponse?: boolean;
+    containerClassName?: string;
 }
 
 type CodeSnippetProps = CodeSnippetDataProps & WithStyles<CssRules>;
 
 export const CodeSnippet = withStyles(styles)(
-    ({ classes, lines, className, apiResponse }: CodeSnippetProps) =>
+    ({ classes, lines, className, containerClassName, apiResponse }: CodeSnippetProps) =>
         <Typography
-        component="div"
-        className={classNames(classes.root, className)}>
-            {
-                lines.map((line: string, index: number) => {
+            component="div"
+            className={classNames(classes.root, containerClassName, className)}>
+                { lines.map((line: string, index: number) => {
                     return <Typography key={index} className={apiResponse ? classes.space : className} component="pre">{line}</Typography>;
-                })
-            }
+                }) }
         </Typography>
     );
\ No newline at end of file
diff --git a/src/components/multi-panel-view/multi-panel-view.tsx b/src/components/multi-panel-view/multi-panel-view.tsx
index 6fb3cc49..b242f805 100644
--- a/src/components/multi-panel-view/multi-panel-view.tsx
+++ b/src/components/multi-panel-view/multi-panel-view.tsx
@@ -33,6 +33,7 @@ const styles: StyleRulesCallback<CssRules> = theme => ({
     },
     content: {
         overflow: 'auto',
+        height: '100%',
     },
 });
 
@@ -85,7 +86,7 @@ export const MPVPanelContent = ({doHidePanel, doMaximizePanel, panelName, panelM
         }
     }, [panelRef]);
 
-    return <Grid item {...props}>
+    return <Grid item style={{height: '100%'}} {...props}>
         <span ref={panelRef} /> {/* Element to scroll to when the panel is selected */}
         <Paper style={{height: '100%'}} elevation={panelIlluminated ? 8 : 0}>
             { forwardProps
diff --git a/src/views/process-panel/process-details-card.tsx b/src/views/process-panel/process-details-card.tsx
index 18610781..d3349c3a 100644
--- a/src/views/process-panel/process-details-card.tsx
+++ b/src/views/process-panel/process-details-card.tsx
@@ -19,12 +19,16 @@ import { Process } from 'store/processes/process';
 import { MPVPanelProps } from 'components/multi-panel-view/multi-panel-view';
 import { ProcessDetailsAttributes } from './process-details-attributes';
 
-type CssRules = 'card' | 'content' | 'title';
+type CssRules = 'card' | 'content' | 'title' | 'header';
 
 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     card: {
         height: '100%'
     },
+    header: {
+        paddingTop: theme.spacing.unit,
+        paddingBottom: theme.spacing.unit,
+    },
     content: {
         '&:last-child': {
             paddingBottom: theme.spacing.unit * 2,
@@ -46,6 +50,7 @@ export const ProcessDetailsCard = withStyles(styles)(
     ({ classes, process, doHidePanel, panelName }: ProcessDetailsCardProps) => {
         return <Card className={classes.card}>
             <CardHeader
+                className={classes.header}
                 classes={{
                     content: classes.title,
                 }}
diff --git a/src/views/process-panel/process-information-card.tsx b/src/views/process-panel/process-information-card.tsx
index 4c938017..fc34a31c 100644
--- a/src/views/process-panel/process-information-card.tsx
+++ b/src/views/process-panel/process-information-card.tsx
@@ -17,12 +17,16 @@ import classNames from 'classnames';
 import { ContainerState } from 'models/container';
 import { MPVPanelProps } from 'components/multi-panel-view/multi-panel-view';
 
-type CssRules = 'card' | 'iconHeader' | 'label' | 'value' | 'chip' | 'link' | 'content' | 'title' | 'avatar' | 'cancelButton';
+type CssRules = 'card' | 'iconHeader' | 'label' | 'value' | 'chip' | 'link' | 'content' | 'title' | 'avatar' | 'cancelButton' | 'header';
 
 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     card: {
         height: '100%'
     },
+    header: {
+        paddingTop: theme.spacing.unit,
+        paddingBottom: theme.spacing.unit,
+    },
     iconHeader: {
         fontSize: '1.875rem',
         color: theme.customs.colors.green700,
@@ -93,6 +97,7 @@ export const ProcessInformationCard = withStyles(styles, { withTheme: true })(
         const finishedAt = container ? formatDate(container.finishedAt) : 'N/A';
         return <Card className={classes.card}>
             <CardHeader
+                className={classes.header}
                 classes={{
                     content: classes.title,
                     avatar: classes.avatar
diff --git a/src/views/process-panel/process-log-card.tsx b/src/views/process-panel/process-log-card.tsx
index b87bb6e4..bd700bcd 100644
--- a/src/views/process-panel/process-log-card.tsx
+++ b/src/views/process-panel/process-log-card.tsx
@@ -32,16 +32,25 @@ import { ProcessLogCodeSnippet } from 'views/process-panel/process-log-code-snip
 import { DefaultView } from 'components/default-view/default-view';
 import { CodeSnippetDataProps } from 'components/code-snippet/code-snippet';
 
-type CssRules = 'card' | 'content' | 'title' | 'iconHeader';
+type CssRules = 'card' | 'content' | 'title' | 'iconHeader' | 'header' | 'root' | 'logViewer' | 'logViewerContainer';
 
 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     card: {
         height: '100%'
     },
+    header: {
+        paddingTop: theme.spacing.unit,
+        paddingBottom: theme.spacing.unit,
+    },
     content: {
-        '&:last-child': {
-            paddingBottom: theme.spacing.unit * 2,
-        }
+        padding: theme.spacing.unit * 0,
+        height: '100%',
+    },
+    logViewer: {
+        height: '100%',
+    },
+    logViewerContainer: {
+        height: '100%',
     },
     title: {
         overflow: 'hidden',
@@ -51,6 +60,9 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
         fontSize: '1.875rem',
         color: theme.customs.colors.green700
     },
+    root: {
+        height: '100%',
+    },
 });
 
 export interface ProcessLogsCardDataProps {
@@ -73,9 +85,9 @@ type ProcessLogsCardProps = ProcessLogsCardDataProps
 export const ProcessLogsCard = withStyles(styles)(
     ({ classes, process, filters, selectedFilter, lines, onLogFilterChange, navigateToLog,
         doHidePanel, doMaximizePanel, panelMaximized, panelName }: ProcessLogsCardProps) =>
-        <Grid item xs={12}>
+        <Grid item className={classes.root} xs={12}>
             <Card className={classes.card}>
-                <CardHeader
+                <CardHeader className={classes.header}
                     avatar={<LogIcon className={classes.iconHeader} />}
                     action={<Grid container direction='row' alignItems='center'>
                         <Grid item>
@@ -107,10 +119,11 @@ export const ProcessLogsCard = withStyles(styles)(
                 <CardContent className={classes.content}>
                     {lines.length > 0
                         ? < Grid
+                            className={classes.logViewerContainer}
                             container
                             spacing={24}
                             direction='column'>
-                            <Grid item xs>
+                            <Grid className={classes.logViewer} item xs>
                                 <ProcessLogCodeSnippet lines={lines} />
                             </Grid>
                         </Grid>
diff --git a/src/views/process-panel/process-log-code-snippet.tsx b/src/views/process-panel/process-log-code-snippet.tsx
index d1756c77..01f5ca1c 100644
--- a/src/views/process-panel/process-log-code-snippet.tsx
+++ b/src/views/process-panel/process-log-code-snippet.tsx
@@ -6,13 +6,16 @@ import React from 'react';
 import { MuiThemeProvider, createMuiTheme, StyleRulesCallback, withStyles, WithStyles } from '@material-ui/core/styles';
 import { CodeSnippet } from 'components/code-snippet/code-snippet';
 import grey from '@material-ui/core/colors/grey';
+import { ArvadosTheme } from 'common/custom-theme';
 
-type CssRules = 'codeSnippet';
+type CssRules = 'codeSnippet' | 'codeSnippetContainer';
 
-const styles: StyleRulesCallback<CssRules> = () => ({
+const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     codeSnippet: {
-        maxHeight: '550px',
-    }
+    },
+    codeSnippetContainer: {
+        height: `calc(100% - ${theme.spacing.unit * 4}px)`, // so that horizontal scollbar is visible
+    },
 });
 
 const theme = createMuiTheme({
@@ -39,5 +42,6 @@ interface ProcessLogCodeSnippetProps {
 export const ProcessLogCodeSnippet = withStyles(styles)(
     (props: ProcessLogCodeSnippetProps & WithStyles<CssRules>) =>
         <MuiThemeProvider theme={theme}>
-            <CodeSnippet lines={props.lines} className={props.classes.codeSnippet} />
+            <CodeSnippet lines={props.lines} className={props.classes.codeSnippet}
+                containerClassName={props.classes.codeSnippetContainer} />
         </MuiThemeProvider>);
\ No newline at end of file
diff --git a/src/views/process-panel/process-panel-root.tsx b/src/views/process-panel/process-panel-root.tsx
index c3980648..cf32b50f 100644
--- a/src/views/process-panel/process-panel-root.tsx
+++ b/src/views/process-panel/process-panel-root.tsx
@@ -22,6 +22,7 @@ type CssRules = 'root';
 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     root: {
         width: '100%',
+        height: '100%',
     },
 });
 

commit 5c8a5dd07252ed2ac431c53fccc9e2fb649c3014
Author: Lucas Di Pentima <lucas.dipentima at curii.com>
Date:   Thu Mar 17 13:54:33 2022 -0300

    16672: Removes the old process logs panel & related code.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas.dipentima at curii.com>

diff --git a/src/routes/route-change-handlers.ts b/src/routes/route-change-handlers.ts
index 044a38bf..5e07e6e8 100644
--- a/src/routes/route-change-handlers.ts
+++ b/src/routes/route-change-handlers.ts
@@ -27,7 +27,6 @@ const handleLocationChange = (store: RootStore) => ({ pathname }: Location) => {
     const publicFavoritesMatch = Routes.matchPublicFavoritesRoute(pathname);
     const trashMatch = Routes.matchTrashRoute(pathname);
     const processMatch = Routes.matchProcessRoute(pathname);
-    const processLogMatch = Routes.matchProcessLogRoute(pathname);
     const repositoryMatch = Routes.matchRepositoriesRoute(pathname);
     const searchResultsMatch = Routes.matchSearchResultsRoute(pathname);
     const sharedWithMeMatch = Routes.matchSharedWithMeRoute(pathname);
@@ -71,8 +70,6 @@ const handleLocationChange = (store: RootStore) => ({ pathname }: Location) => {
         store.dispatch(WorkbenchActions.loadTrash());
     } else if (processMatch) {
         store.dispatch(WorkbenchActions.loadProcess(processMatch.params.id));
-    } else if (processLogMatch) {
-        store.dispatch(WorkbenchActions.loadProcessLog(processLogMatch.params.id));
     } else if (rootMatch) {
         store.dispatch(navigateToRootProject);
     } else if (sharedWithMeMatch) {
diff --git a/src/routes/routes.ts b/src/routes/routes.ts
index 41c71f7c..d7257b51 100644
--- a/src/routes/routes.ts
+++ b/src/routes/routes.ts
@@ -25,7 +25,6 @@ export const Routes = {
     PROCESSES: `/processes/:id(${RESOURCE_UUID_PATTERN})`,
     FAVORITES: '/favorites',
     TRASH: '/trash',
-    PROCESS_LOGS: `/process-logs/:id(${RESOURCE_UUID_PATTERN})`,
     REPOSITORIES: '/repositories',
     SHARED_WITH_ME: '/shared-with-me',
     RUN_PROCESS: '/run-process',
@@ -95,8 +94,6 @@ export const getNavUrl = (uuid: string, config: FederationConfig) => {
 
 export const getProcessUrl = (uuid: string) => `/processes/${uuid}`;
 
-export const getProcessLogUrl = (uuid: string) => `/process-logs/${uuid}`;
-
 export const getGroupUrl = (uuid: string) => `/group/${uuid}`;
 
 export interface ResourceRouteParams {
@@ -124,9 +121,6 @@ export const matchCollectionRoute = (route: string) =>
 export const matchProcessRoute = (route: string) =>
     matchPath<ResourceRouteParams>(route, { path: Routes.PROCESSES });
 
-export const matchProcessLogRoute = (route: string) =>
-    matchPath<ResourceRouteParams>(route, { path: Routes.PROCESS_LOGS });
-
 export const matchSharedWithMeRoute = (route: string) =>
     matchPath(route, { path: Routes.SHARED_WITH_ME });
 
diff --git a/src/store/navigation/navigation-action.ts b/src/store/navigation/navigation-action.ts
index 19cc36ae..49f56591 100644
--- a/src/store/navigation/navigation-action.ts
+++ b/src/store/navigation/navigation-action.ts
@@ -6,7 +6,7 @@ import { Dispatch, compose, AnyAction } from 'redux';
 import { push } from "react-router-redux";
 import { ResourceKind, extractUuidKind } from 'models/resource';
 import { SidePanelTreeCategory } from '../side-panel-tree/side-panel-tree-actions';
-import { Routes, getProcessLogUrl, getGroupUrl, getNavUrl } from 'routes/routes';
+import { Routes, getGroupUrl, getNavUrl } from 'routes/routes';
 import { RootState } from 'store/store';
 import { ServiceRepository } from 'services/services';
 import { pluginConfig } from 'plugins';
@@ -99,8 +99,6 @@ export const pushOrGoto = (url: string): AnyAction => {
 };
 
 
-export const navigateToProcessLogs = compose(push, getProcessLogUrl);
-
 export const navigateToRootProject = (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
     navigateTo(SidePanelTreeCategory.PROJECTS)(dispatch, getState);
 };
diff --git a/src/store/process-logs-panel/process-logs-panel.ts b/src/store/process-logs-panel/process-logs-panel.ts
index 74a18041..0ca5d679 100644
--- a/src/store/process-logs-panel/process-logs-panel.ts
+++ b/src/store/process-logs-panel/process-logs-panel.ts
@@ -2,7 +2,7 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import { matchProcessLogRoute, matchProcessRoute } from 'routes/routes';
+import { matchProcessRoute } from 'routes/routes';
 import { RouterState } from 'react-router-redux';
 
 export interface ProcessLogsPanel {
@@ -21,6 +21,6 @@ export const getProcessPanelLogs = ({ selectedFilter, logs }: ProcessLogsPanel)
 
 export const getProcessLogsPanelCurrentUuid = (router: RouterState) => {
     const pathname = router.location ? router.location.pathname : '';
-    const match = matchProcessLogRoute(pathname) || matchProcessRoute(pathname);
+    const match = matchProcessRoute(pathname);
     return match ? match.params.id : undefined;
 };
diff --git a/src/views-components/context-menu/action-sets/process-resource-action-set.ts b/src/views-components/context-menu/action-sets/process-resource-action-set.ts
index be2f82fd..55b2d31f 100644
--- a/src/views-components/context-menu/action-sets/process-resource-action-set.ts
+++ b/src/views-components/context-menu/action-sets/process-resource-action-set.ts
@@ -8,7 +8,7 @@ import { toggleFavorite } from "store/favorites/favorites-actions";
 import {
     RenameIcon, ShareIcon, MoveToIcon, CopyIcon, DetailsIcon,
     RemoveIcon, ReRunProcessIcon, InputIcon, OutputIcon, CommandIcon,
-    LogIcon, AdvancedIcon
+    AdvancedIcon
 } from "components/icon/icon";
 import { favoritePanelActions } from "store/favorite-panel/favorite-panel-action";
 import { openMoveProcessDialog } from 'store/processes/process-move-actions';
@@ -21,7 +21,6 @@ import { snackbarActions, SnackbarKind } from "store/snackbar/snackbar-actions";
 import { openProcessInputDialog } from "store/processes/process-input-actions";
 import { navigateToOutput } from "store/process-panel/process-panel-actions";
 import { openProcessCommandDialog } from "store/processes/process-command-actions";
-import { navigateToProcessLogs } from "store/navigation/navigation-action";
 import { openAdvancedTabDialog } from "store/advanced-tab/advanced-tab";
 import { TogglePublicFavoriteAction } from "../actions/public-favorite-action";
 import { togglePublicFavorite } from "store/public-favorites/public-favorites-actions";
@@ -77,13 +76,6 @@ export const readOnlyProcessResourceActionSet: ContextMenuActionSet = [[
             dispatch<any>(openProcessCommandDialog(resource.uuid));
         }
     },
-    {
-        icon: LogIcon,
-        name: "Log",
-        execute: (dispatch, resource) => {
-            dispatch<any>(navigateToProcessLogs(resource.uuid));
-        }
-    },
     {
         icon: DetailsIcon,
         name: "View details",
diff --git a/src/views/process-log-panel/process-log-main-card.tsx b/src/views/process-log-panel/process-log-main-card.tsx
deleted file mode 100644
index aab44da4..00000000
--- a/src/views/process-log-panel/process-log-main-card.tsx
+++ /dev/null
@@ -1,124 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-import React from 'react';
-import {
-    StyleRulesCallback,
-    WithStyles,
-    withStyles,
-    Card,
-    CardHeader,
-    IconButton,
-    CardContent,
-    Grid,
-    Typography,
-    Tooltip
-} from '@material-ui/core';
-import { Process } from 'store/processes/process';
-import { ProcessLogCodeSnippet } from 'views/process-log-panel/process-log-code-snippet';
-import {
-    ProcessLogForm,
-    ProcessLogFormDataProps,
-    ProcessLogFormActionProps
-} from 'views/process-log-panel/process-log-form';
-import { MoreOptionsIcon, LogIcon } from 'components/icon/icon';
-import { ArvadosTheme } from 'common/custom-theme';
-import { CodeSnippetDataProps } from 'components/code-snippet/code-snippet';
-import { DefaultView } from 'components/default-view/default-view';
-
-type CssRules = 'backLink' | 'backIcon' | 'card' | 'title' | 'iconHeader' | 'link';
-
-const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
-    backLink: {
-        fontSize: '14px',
-        fontWeight: 600,
-        display: 'flex',
-        alignItems: 'center',
-        textDecoration: 'none',
-        padding: theme.spacing.unit,
-        color: theme.palette.grey["700"],
-    },
-    backIcon: {
-        marginRight: theme.spacing.unit
-    },
-    card: {
-        width: '100%'
-    },
-    title: {
-        color: theme.palette.grey["700"]
-    },
-    iconHeader: {
-        fontSize: '1.875rem',
-        color: theme.customs.colors.green700
-    },
-    link: {
-        fontSize: '0.875rem',
-        color: theme.palette.primary.main,
-        textAlign: 'right',
-        '&:hover': {
-            cursor: 'pointer'
-        }
-    }
-});
-
-interface ProcessLogMainCardDataProps {
-    process: Process;
-}
-
-export interface ProcessLogMainCardActionProps {
-    onContextMenu: (event: React.MouseEvent<any>, process: Process) => void;
-    navigateToLogCollection: (uuid: string) => void;
-}
-
-export type ProcessLogMainCardProps = ProcessLogMainCardDataProps
-    & ProcessLogMainCardActionProps
-    & CodeSnippetDataProps
-    & ProcessLogFormDataProps
-    & ProcessLogFormActionProps;
-
-export const ProcessLogMainCard = withStyles(styles)(
-    ({ classes, process, selectedFilter, filters, onChange, lines, onContextMenu, navigateToLogCollection }: ProcessLogMainCardProps & WithStyles<CssRules>) =>
-        <Grid item xs={12}>
-            <Card className={classes.card}>
-                <CardHeader
-                    avatar={<LogIcon className={classes.iconHeader} />}
-                    action={
-                        <Tooltip title="More options" disableFocusListener>
-                            <IconButton onClick={event => onContextMenu(event, process)} aria-label="More options">
-                                <MoreOptionsIcon />
-                            </IconButton>
-                        </Tooltip>}
-                    title={
-                        <Typography noWrap variant='h6' className={classes.title}>
-                            Logs for {process.containerRequest.name}
-                        </Typography>}
-                />
-                <CardContent>
-                    {lines.length > 0
-                        ? < Grid
-                            container
-                            spacing={24}
-                            direction='column'>
-                            <Grid container item>
-                                <Grid item xs={6}>
-                                    <ProcessLogForm selectedFilter={selectedFilter} filters={filters} onChange={onChange} />
-                                </Grid>
-                                <Grid item xs={6} className={classes.link}>
-                                    <span onClick={() => navigateToLogCollection(process.containerRequest.logUuid!)}>
-                                        Go to Log collection
-                                    </span>
-                                </Grid>
-                            </Grid>
-                            <Grid item xs>
-                                <ProcessLogCodeSnippet lines={lines} />
-                            </Grid>
-                        </Grid>
-                        : <DefaultView
-                            icon={LogIcon}
-                            messages={['No logs yet']} />
-                    }
-                </CardContent>
-            </Card>
-        </Grid >
-);
\ No newline at end of file
diff --git a/src/views/process-log-panel/process-log-panel-root.tsx b/src/views/process-log-panel/process-log-panel-root.tsx
deleted file mode 100644
index be043722..00000000
--- a/src/views/process-log-panel/process-log-panel-root.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-import React from 'react';
-import { Grid } from '@material-ui/core';
-import { Process } from 'store/processes/process';
-import { ProcessLogMainCard } from 'views/process-log-panel/process-log-main-card';
-import { ProcessLogFormDataProps, ProcessLogFormActionProps } from 'views/process-log-panel/process-log-form';
-import { DefaultView } from 'components/default-view/default-view';
-import { ProcessIcon } from 'components/icon/icon';
-import { CodeSnippetDataProps } from 'components/code-snippet/code-snippet';
-import { ProcessLogMainCardActionProps } from './process-log-main-card';
-
-export type ProcessLogPanelRootDataProps = {
-    process?: Process;
-} & ProcessLogFormDataProps & CodeSnippetDataProps;
-
-export type ProcessLogPanelRootActionProps = ProcessLogMainCardActionProps & ProcessLogFormActionProps;
-
-export type ProcessLogPanelRootProps = ProcessLogPanelRootDataProps & ProcessLogPanelRootActionProps;
-
-export const ProcessLogPanelRoot = (props: ProcessLogPanelRootProps) =>
-    props.process
-        ? <Grid container spacing={16}>
-            <ProcessLogMainCard 
-                process={props.process} 
-                {...props} />
-        </Grid> 
-        : <Grid container
-            alignItems='center'
-            justify='center'>
-            <DefaultView
-                icon={ProcessIcon}
-                messages={['Process Log not found']} />
-        </Grid>;
diff --git a/src/views/process-log-panel/process-log-panel.tsx b/src/views/process-log-panel/process-log-panel.tsx
deleted file mode 100644
index b11d1432..00000000
--- a/src/views/process-log-panel/process-log-panel.tsx
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-import { RootState } from 'store/store';
-import { connect } from 'react-redux';
-import { getProcess } from 'store/processes/process';
-import { Dispatch } from 'redux';
-import { openProcessContextMenu } from 'store/context-menu/context-menu-actions';
-import { ProcessLogPanelRootDataProps, ProcessLogPanelRootActionProps, ProcessLogPanelRoot } from './process-log-panel-root';
-import { getProcessPanelLogs } from 'store/process-logs-panel/process-logs-panel';
-import { setProcessLogsPanelFilter, navigateToLogCollection } from 'store/process-logs-panel/process-logs-panel-actions';
-import { getProcessLogsPanelCurrentUuid } from 'store/process-logs-panel/process-logs-panel';
-
-export interface Log {
-    object_uuid: string;
-    event_at: string;
-    event_type: string;
-    summary: string;
-    properties: any;
-}
-
-export interface FilterOption {
-    label: string;
-    value: string;
-}
-
-const mapStateToProps = ({resources, processLogsPanel, router}: RootState): ProcessLogPanelRootDataProps => {
-    const uuid = getProcessLogsPanelCurrentUuid(router) || '';
-    return {
-        process: getProcess(uuid)(resources),
-        selectedFilter: { label: processLogsPanel.selectedFilter, value: processLogsPanel.selectedFilter },
-        filters: processLogsPanel.filters.map(filter => ({ label: filter, value: filter })),
-        lines: getProcessPanelLogs(processLogsPanel)
-    };
-};
-
-const mapDispatchToProps = (dispatch: Dispatch): ProcessLogPanelRootActionProps => ({
-    onContextMenu: (event, process) => {
-        dispatch<any>(openProcessContextMenu(event, process));
-    },
-    onChange: filter => {
-        dispatch(setProcessLogsPanelFilter(filter.value));
-    },
-    navigateToLogCollection: (uuid: string) => {
-        dispatch<any>(navigateToLogCollection(uuid));
-    }
-});
-
-export const ProcessLogPanel = connect(mapStateToProps, mapDispatchToProps)(ProcessLogPanelRoot);
diff --git a/src/views/process-panel/process-log-card.tsx b/src/views/process-panel/process-log-card.tsx
index 85195d40..b87bb6e4 100644
--- a/src/views/process-panel/process-log-card.tsx
+++ b/src/views/process-panel/process-log-card.tsx
@@ -24,9 +24,11 @@ import {
 } from 'components/icon/icon';
 import { Process } from 'store/processes/process';
 import { MPVPanelProps } from 'components/multi-panel-view/multi-panel-view';
-import { FilterOption } from 'views/process-log-panel/process-log-panel';
-import { ProcessLogForm } from 'views/process-log-panel/process-log-form';
-import { ProcessLogCodeSnippet } from 'views/process-log-panel/process-log-code-snippet';
+import {
+    FilterOption,
+    ProcessLogForm
+} from 'views/process-panel/process-log-form';
+import { ProcessLogCodeSnippet } from 'views/process-panel/process-log-code-snippet';
 import { DefaultView } from 'components/default-view/default-view';
 import { CodeSnippetDataProps } from 'components/code-snippet/code-snippet';
 
diff --git a/src/views/process-log-panel/process-log-code-snippet.tsx b/src/views/process-panel/process-log-code-snippet.tsx
similarity index 100%
rename from src/views/process-log-panel/process-log-code-snippet.tsx
rename to src/views/process-panel/process-log-code-snippet.tsx
diff --git a/src/views/process-log-panel/process-log-form.tsx b/src/views/process-panel/process-log-form.tsx
similarity index 95%
rename from src/views/process-log-panel/process-log-form.tsx
rename to src/views/process-panel/process-log-form.tsx
index 7f98c978..6a8e5221 100644
--- a/src/views/process-log-panel/process-log-form.tsx
+++ b/src/views/process-panel/process-log-form.tsx
@@ -13,7 +13,6 @@ import {
     Input
 } from '@material-ui/core';
 import { ArvadosTheme } from 'common/custom-theme';
-import { FilterOption } from './process-log-panel';
 
 type CssRules = 'formControl';
 
@@ -23,6 +22,11 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     }
 });
 
+export interface FilterOption {
+    label: string;
+    value: string;
+}
+
 export interface ProcessLogFormDataProps {
     selectedFilter: FilterOption;
     filters: FilterOption[];
diff --git a/src/views/process-panel/process-panel-root.tsx b/src/views/process-panel/process-panel-root.tsx
index 2bd115f1..c3980648 100644
--- a/src/views/process-panel/process-panel-root.tsx
+++ b/src/views/process-panel/process-panel-root.tsx
@@ -13,9 +13,9 @@ import { SubprocessFilterDataProps } from 'components/subprocess-filter/subproce
 import { MPVContainer, MPVPanelContent, MPVPanelState } from 'components/multi-panel-view/multi-panel-view';
 import { ArvadosTheme } from 'common/custom-theme';
 import { ProcessDetailsCard } from './process-details-card';
-import { FilterOption } from 'views/process-log-panel/process-log-panel';
 import { getProcessPanelLogs, ProcessLogsPanel } from 'store/process-logs-panel/process-logs-panel';
 import { ProcessLogsCard } from './process-log-card';
+import { FilterOption } from 'views/process-panel/process-log-form';
 
 type CssRules = 'root';
 
diff --git a/src/views/workbench/workbench.tsx b/src/views/workbench/workbench.tsx
index 49922202..fe97bd3b 100644
--- a/src/views/workbench/workbench.tsx
+++ b/src/views/workbench/workbench.tsx
@@ -20,7 +20,6 @@ import { MultipleFilesRemoveDialog } from 'views-components/file-remove-dialog/m
 import { Routes } from 'routes/routes';
 import { SidePanel } from 'views-components/side-panel/side-panel';
 import { ProcessPanel } from 'views/process-panel/process-panel';
-import { ProcessLogPanel } from 'views/process-log-panel/process-log-panel';
 import { ChangeWorkflowDialog } from 'views-components/run-process-dialog/change-workflow-dialog';
 import { CreateProjectDialog } from 'views-components/dialog-forms/create-project-dialog';
 import { CreateCollectionDialog } from 'views-components/dialog-forms/create-collection-dialog';
@@ -158,7 +157,6 @@ let routes = <>
     <Route path={Routes.ALL_PROCESSES} component={AllProcessesPanel} />
     <Route path={Routes.PROCESSES} component={ProcessPanel} />
     <Route path={Routes.TRASH} component={TrashPanel} />
-    <Route path={Routes.PROCESS_LOGS} component={ProcessLogPanel} />
     <Route path={Routes.SHARED_WITH_ME} component={SharedWithMePanel} />
     <Route path={Routes.RUN_PROCESS} component={RunProcessPanel} />
     <Route path={Routes.WORKFLOWS} component={WorkflowPanel} />

commit 4a2789c9974a1fa0f851a3bb2aa24bff5b029c48
Author: Lucas Di Pentima <lucas.dipentima at curii.com>
Date:   Thu Mar 17 13:29:17 2022 -0300

    16672: Process log card fully implemented in process panel.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas.dipentima at curii.com>

diff --git a/src/views/process-log-panel/process-log-form.tsx b/src/views/process-log-panel/process-log-form.tsx
index 946e575e..7f98c978 100644
--- a/src/views/process-log-panel/process-log-form.tsx
+++ b/src/views/process-log-panel/process-log-form.tsx
@@ -3,7 +3,15 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import React from 'react';
-import { withStyles, WithStyles, StyleRulesCallback, FormControl, InputLabel, Select, MenuItem, Input } from '@material-ui/core';
+import {
+    withStyles,
+    WithStyles,
+    StyleRulesCallback,
+    FormControl,
+    Select,
+    MenuItem,
+    Input
+} from '@material-ui/core';
 import { ArvadosTheme } from 'common/custom-theme';
 import { FilterOption } from './process-log-panel';
 
@@ -11,7 +19,7 @@ type CssRules = 'formControl';
 
 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     formControl: {
-        minWidth: 200
+        minWidth: theme.spacing.unit * 15,
     }
 });
 
@@ -30,9 +38,6 @@ export const ProcessLogForm = withStyles(styles)(
     ({ classes, selectedFilter, onChange, filters }: ProcessLogFormProps) =>
         <form autoComplete="off">
             <FormControl className={classes.formControl}>
-                <InputLabel shrink htmlFor="log-label-placeholder">
-                    Event Type
-                </InputLabel>
                 <Select
                     value={selectedFilter.value}
                     onChange={({ target }) => onChange({ label: target.innerText, value: target.value })}
diff --git a/src/views/process-panel/process-log-card.tsx b/src/views/process-panel/process-log-card.tsx
new file mode 100644
index 00000000..85195d40
--- /dev/null
+++ b/src/views/process-panel/process-log-card.tsx
@@ -0,0 +1,123 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import React from 'react';
+import {
+    StyleRulesCallback,
+    WithStyles,
+    withStyles,
+    Card,
+    CardHeader,
+    IconButton,
+    CardContent,
+    Tooltip,
+    Grid,
+    Typography,
+} from '@material-ui/core';
+import { ArvadosTheme } from 'common/custom-theme';
+import {
+    CloseIcon,
+    CollectionIcon,
+    LogIcon,
+    MaximizeIcon
+} from 'components/icon/icon';
+import { Process } from 'store/processes/process';
+import { MPVPanelProps } from 'components/multi-panel-view/multi-panel-view';
+import { FilterOption } from 'views/process-log-panel/process-log-panel';
+import { ProcessLogForm } from 'views/process-log-panel/process-log-form';
+import { ProcessLogCodeSnippet } from 'views/process-log-panel/process-log-code-snippet';
+import { DefaultView } from 'components/default-view/default-view';
+import { CodeSnippetDataProps } from 'components/code-snippet/code-snippet';
+
+type CssRules = 'card' | 'content' | 'title' | 'iconHeader';
+
+const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
+    card: {
+        height: '100%'
+    },
+    content: {
+        '&:last-child': {
+            paddingBottom: theme.spacing.unit * 2,
+        }
+    },
+    title: {
+        overflow: 'hidden',
+        paddingTop: theme.spacing.unit * 0.5
+    },
+    iconHeader: {
+        fontSize: '1.875rem',
+        color: theme.customs.colors.green700
+    },
+});
+
+export interface ProcessLogsCardDataProps {
+    process: Process;
+    selectedFilter: FilterOption;
+    filters: FilterOption[];
+}
+
+export interface ProcessLogsCardActionProps {
+    onLogFilterChange: (filter: FilterOption) => void;
+    navigateToLog: (uuid: string) => void;
+}
+
+type ProcessLogsCardProps = ProcessLogsCardDataProps
+    & ProcessLogsCardActionProps
+    & CodeSnippetDataProps
+    & WithStyles<CssRules>
+    & MPVPanelProps;
+
+export const ProcessLogsCard = withStyles(styles)(
+    ({ classes, process, filters, selectedFilter, lines, onLogFilterChange, navigateToLog,
+        doHidePanel, doMaximizePanel, panelMaximized, panelName }: ProcessLogsCardProps) =>
+        <Grid item xs={12}>
+            <Card className={classes.card}>
+                <CardHeader
+                    avatar={<LogIcon className={classes.iconHeader} />}
+                    action={<Grid container direction='row' alignItems='center'>
+                        <Grid item>
+                            <ProcessLogForm selectedFilter={selectedFilter}
+                                filters={filters} onChange={onLogFilterChange} />
+                        </Grid>
+                        <Grid item>
+                            <Tooltip title="Go to Log collection" disableFocusListener>
+                                <IconButton onClick={() => navigateToLog(process.containerRequest.logUuid!)}>
+                                    <CollectionIcon />
+                                </IconButton>
+                            </Tooltip>
+                        </Grid>
+                        { doMaximizePanel && !panelMaximized &&
+                        <Tooltip title={`Maximize ${panelName || 'panel'}`} disableFocusListener>
+                            <IconButton onClick={doMaximizePanel}><MaximizeIcon /></IconButton>
+                        </Tooltip> }
+                        { doHidePanel && <Grid item>
+                            <Tooltip title={`Close ${panelName || 'panel'}`} disableFocusListener>
+                                <IconButton onClick={doHidePanel}><CloseIcon /></IconButton>
+                            </Tooltip>
+                        </Grid> }
+                    </Grid>}
+                    title={
+                        <Typography noWrap variant='h6' className={classes.title}>
+                            Logs
+                        </Typography>}
+                />
+                <CardContent className={classes.content}>
+                    {lines.length > 0
+                        ? < Grid
+                            container
+                            spacing={24}
+                            direction='column'>
+                            <Grid item xs>
+                                <ProcessLogCodeSnippet lines={lines} />
+                            </Grid>
+                        </Grid>
+                        : <DefaultView
+                            icon={LogIcon}
+                            messages={['No logs yet']} />
+                    }
+                </CardContent>
+            </Card>
+        </Grid >
+);
+
diff --git a/src/views/process-panel/process-panel-root.tsx b/src/views/process-panel/process-panel-root.tsx
index 5a0b6b64..2bd115f1 100644
--- a/src/views/process-panel/process-panel-root.tsx
+++ b/src/views/process-panel/process-panel-root.tsx
@@ -14,8 +14,8 @@ import { MPVContainer, MPVPanelContent, MPVPanelState } from 'components/multi-p
 import { ArvadosTheme } from 'common/custom-theme';
 import { ProcessDetailsCard } from './process-details-card';
 import { FilterOption } from 'views/process-log-panel/process-log-panel';
-import { ProcessLogMainCard } from 'views/process-log-panel/process-log-main-card';
 import { getProcessPanelLogs, ProcessLogsPanel } from 'store/process-logs-panel/process-logs-panel';
+import { ProcessLogsCard } from './process-log-card';
 
 type CssRules = 'root';
 
@@ -69,8 +69,8 @@ export const ProcessPanelRoot = withStyles(styles)(
             <MPVPanelContent forwardProps xs="auto">
                 <ProcessDetailsCard process={process} />
             </MPVPanelContent>
-            <MPVPanelContent xs="auto">
-                <ProcessLogMainCard
+            <MPVPanelContent forwardProps xs>
+                <ProcessLogsCard
                     process={process}
                     lines={getProcessPanelLogs(processLogsPanel)}
                     selectedFilter={{
@@ -80,11 +80,8 @@ export const ProcessPanelRoot = withStyles(styles)(
                     filters={processLogsPanel.filters.map(
                         filter => ({ label: filter, value: filter })
                     )}
-                    onChange={props.onLogFilterChange}
-                    onContextMenu={function (event: any, process: Process): void {
-                        throw new Error('Function not implemented.');
-                    } }
-                    navigateToLogCollection={props.navigateToLog}
+                    onLogFilterChange={props.onLogFilterChange}
+                    navigateToLog={props.navigateToLog}
                 />
             </MPVPanelContent>
             <MPVPanelContent forwardProps xs>

commit 909844cdb1eda3d4cd31f3fa1818ee7eca62d319
Author: Lucas Di Pentima <lucas.dipentima at curii.com>
Date:   Tue Mar 15 16:25:05 2022 -0300

    16672: Initial Log viewer placement on process panel. WIP
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas.dipentima at curii.com>

diff --git a/src/store/process-panel/process-panel-actions.ts b/src/store/process-panel/process-panel-actions.ts
index de114a3e..2e5fb6b9 100644
--- a/src/store/process-panel/process-panel-actions.ts
+++ b/src/store/process-panel/process-panel-actions.ts
@@ -13,6 +13,7 @@ import { snackbarActions } from 'store/snackbar/snackbar-actions';
 import { SnackbarKind } from '../snackbar/snackbar-actions';
 import { showWorkflowDetails } from 'store/workflow-panel/workflow-panel-actions';
 import { loadSubprocessPanel } from "../subprocess-panel/subprocess-panel-actions";
+import { initProcessLogsPanel } from "store/process-logs-panel/process-logs-panel-actions";
 
 export const processPanelActions = unionize({
     SET_PROCESS_PANEL_CONTAINER_REQUEST_UUID: ofType<string>(),
@@ -29,6 +30,7 @@ export const loadProcessPanel = (uuid: string) =>
         dispatch<ProcessPanelAction>(processPanelActions.SET_PROCESS_PANEL_CONTAINER_REQUEST_UUID(uuid));
         dispatch<any>(loadProcess(uuid));
         dispatch(initProcessPanelFilters);
+        dispatch<any>(initProcessLogsPanel(uuid));
         dispatch<any>(loadSubprocessPanel());
     };
 
diff --git a/src/views/process-log-panel/process-log-main-card.tsx b/src/views/process-log-panel/process-log-main-card.tsx
index e6d4091d..aab44da4 100644
--- a/src/views/process-log-panel/process-log-main-card.tsx
+++ b/src/views/process-log-panel/process-log-main-card.tsx
@@ -3,18 +3,28 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import React from 'react';
-import { Link } from 'react-router-dom';
 import {
-    StyleRulesCallback, WithStyles, withStyles, Card,
-    CardHeader, IconButton, CardContent, Grid, Typography, Tooltip
+    StyleRulesCallback,
+    WithStyles,
+    withStyles,
+    Card,
+    CardHeader,
+    IconButton,
+    CardContent,
+    Grid,
+    Typography,
+    Tooltip
 } from '@material-ui/core';
 import { Process } from 'store/processes/process';
 import { ProcessLogCodeSnippet } from 'views/process-log-panel/process-log-code-snippet';
-import { ProcessLogForm, ProcessLogFormDataProps, ProcessLogFormActionProps } from 'views/process-log-panel/process-log-form';
-import { MoreOptionsIcon, ProcessIcon } from 'components/icon/icon';
+import {
+    ProcessLogForm,
+    ProcessLogFormDataProps,
+    ProcessLogFormActionProps
+} from 'views/process-log-panel/process-log-form';
+import { MoreOptionsIcon, LogIcon } from 'components/icon/icon';
 import { ArvadosTheme } from 'common/custom-theme';
 import { CodeSnippetDataProps } from 'components/code-snippet/code-snippet';
-import { BackIcon } from 'components/icon/icon';
 import { DefaultView } from 'components/default-view/default-view';
 
 type CssRules = 'backLink' | 'backIcon' | 'card' | 'title' | 'iconHeader' | 'link';
@@ -52,7 +62,6 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     }
 });
 
-
 interface ProcessLogMainCardDataProps {
     process: Process;
 }
@@ -71,12 +80,9 @@ export type ProcessLogMainCardProps = ProcessLogMainCardDataProps
 export const ProcessLogMainCard = withStyles(styles)(
     ({ classes, process, selectedFilter, filters, onChange, lines, onContextMenu, navigateToLogCollection }: ProcessLogMainCardProps & WithStyles<CssRules>) =>
         <Grid item xs={12}>
-            <Link to={`/processes/${process.containerRequest.uuid}`} className={classes.backLink}>
-                <BackIcon className={classes.backIcon} /> BACK
-            </Link>
             <Card className={classes.card}>
                 <CardHeader
-                    avatar={<ProcessIcon className={classes.iconHeader} />}
+                    avatar={<LogIcon className={classes.iconHeader} />}
                     action={
                         <Tooltip title="More options" disableFocusListener>
                             <IconButton onClick={event => onContextMenu(event, process)} aria-label="More options">
@@ -84,12 +90,10 @@ export const ProcessLogMainCard = withStyles(styles)(
                             </IconButton>
                         </Tooltip>}
                     title={
-                        <Tooltip title={process.containerRequest.name} placement="bottom-start">
-                            <Typography noWrap variant='h6' className={classes.title}>
-                                {process.containerRequest.name}
-                            </Typography>
-                        </Tooltip>}
-                    subheader={process.containerRequest.description} />
+                        <Typography noWrap variant='h6' className={classes.title}>
+                            Logs for {process.containerRequest.name}
+                        </Typography>}
+                />
                 <CardContent>
                     {lines.length > 0
                         ? < Grid
@@ -111,7 +115,7 @@ export const ProcessLogMainCard = withStyles(styles)(
                             </Grid>
                         </Grid>
                         : <DefaultView
-                            icon={ProcessIcon}
+                            icon={LogIcon}
                             messages={['No logs yet']} />
                     }
                 </CardContent>
diff --git a/src/views/process-panel/process-panel-root.tsx b/src/views/process-panel/process-panel-root.tsx
index 6fb9c09d..5a0b6b64 100644
--- a/src/views/process-panel/process-panel-root.tsx
+++ b/src/views/process-panel/process-panel-root.tsx
@@ -13,6 +13,9 @@ import { SubprocessFilterDataProps } from 'components/subprocess-filter/subproce
 import { MPVContainer, MPVPanelContent, MPVPanelState } from 'components/multi-panel-view/multi-panel-view';
 import { ArvadosTheme } from 'common/custom-theme';
 import { ProcessDetailsCard } from './process-details-card';
+import { FilterOption } from 'views/process-log-panel/process-log-panel';
+import { ProcessLogMainCard } from 'views/process-log-panel/process-log-main-card';
+import { getProcessPanelLogs, ProcessLogsPanel } from 'store/process-logs-panel/process-logs-panel';
 
 type CssRules = 'root';
 
@@ -26,6 +29,7 @@ export interface ProcessPanelRootDataProps {
     process?: Process;
     subprocesses: Array<Process>;
     filters: Array<SubprocessFilterDataProps>;
+    processLogsPanel: ProcessLogsPanel;
 }
 
 export interface ProcessPanelRootActionProps {
@@ -35,6 +39,8 @@ export interface ProcessPanelRootActionProps {
     navigateToOutput: (uuid: string) => void;
     navigateToWorkflow: (uuid: string) => void;
     cancelProcess: (uuid: string) => void;
+    onLogFilterChange: (filter: FilterOption) => void;
+    navigateToLog: (uuid: string) => void;
 }
 
 export type ProcessPanelRootProps = ProcessPanelRootDataProps & ProcessPanelRootActionProps & WithStyles<CssRules>;
@@ -42,10 +48,12 @@ export type ProcessPanelRootProps = ProcessPanelRootDataProps & ProcessPanelRoot
 const panelsData: MPVPanelState[] = [
     {name: "Info"},
     {name: "Details", visible: false},
+    {name: "Logs", visible: true},
     {name: "Subprocesses"},
 ];
 
-export const ProcessPanelRoot = withStyles(styles)(({ process, ...props }: ProcessPanelRootProps) =>
+export const ProcessPanelRoot = withStyles(styles)(
+    ({ process, processLogsPanel, ...props }: ProcessPanelRootProps) =>
     process
         ? <MPVContainer className={props.classes.root} spacing={8} panelStates={panelsData}  justify-content="flex-start" direction="column" wrap="nowrap">
             <MPVPanelContent forwardProps xs="auto">
@@ -61,6 +69,24 @@ export const ProcessPanelRoot = withStyles(styles)(({ process, ...props }: Proce
             <MPVPanelContent forwardProps xs="auto">
                 <ProcessDetailsCard process={process} />
             </MPVPanelContent>
+            <MPVPanelContent xs="auto">
+                <ProcessLogMainCard
+                    process={process}
+                    lines={getProcessPanelLogs(processLogsPanel)}
+                    selectedFilter={{
+                        label: processLogsPanel.selectedFilter,
+                        value: processLogsPanel.selectedFilter
+                    }}
+                    filters={processLogsPanel.filters.map(
+                        filter => ({ label: filter, value: filter })
+                    )}
+                    onChange={props.onLogFilterChange}
+                    onContextMenu={function (event: any, process: Process): void {
+                        throw new Error('Function not implemented.');
+                    } }
+                    navigateToLogCollection={props.navigateToLog}
+                />
+            </MPVPanelContent>
             <MPVPanelContent forwardProps xs>
                 <SubprocessPanel />
             </MPVPanelContent>
@@ -73,4 +99,3 @@ export const ProcessPanelRoot = withStyles(styles)(({ process, ...props }: Proce
                 icon={ProcessIcon}
                 messages={['Process not found']} />
         </Grid>);
-
diff --git a/src/views/process-panel/process-panel.tsx b/src/views/process-panel/process-panel.tsx
index 27acc869..6dd02c9b 100644
--- a/src/views/process-panel/process-panel.tsx
+++ b/src/views/process-panel/process-panel.tsx
@@ -24,14 +24,16 @@ import {
 } from 'store/process-panel/process-panel-actions';
 import { openProcessInputDialog } from 'store/processes/process-input-actions';
 import { cancelRunningWorkflow } from 'store/processes/processes-actions';
+import { navigateToLogCollection, setProcessLogsPanelFilter } from 'store/process-logs-panel/process-logs-panel-actions';
 
-const mapStateToProps = ({ router, resources, processPanel }: RootState): ProcessPanelRootDataProps => {
+const mapStateToProps = ({ router, resources, processPanel, processLogsPanel }: RootState): ProcessPanelRootDataProps => {
     const uuid = getProcessPanelCurrentUuid(router) || '';
     const subprocesses = getSubprocesses(uuid)(resources);
     return {
         process: getProcess(uuid)(resources),
         subprocesses: subprocesses.filter(subprocess => processPanel.filters[getProcessStatus(subprocess)]),
         filters: getFilters(processPanel, subprocesses),
+        processLogsPanel: processLogsPanel,
     };
 };
 
@@ -45,7 +47,9 @@ const mapDispatchToProps = (dispatch: Dispatch): ProcessPanelRootActionProps =>
     openProcessInputDialog: (uuid) => dispatch<any>(openProcessInputDialog(uuid)),
     navigateToOutput: (uuid) => dispatch<any>(navigateToOutput(uuid)),
     navigateToWorkflow: (uuid) => dispatch<any>(openWorkflow(uuid)),
-    cancelProcess: (uuid) => dispatch<any>(cancelRunningWorkflow(uuid))
+    cancelProcess: (uuid) => dispatch<any>(cancelRunningWorkflow(uuid)),
+    onLogFilterChange: (filter) => dispatch(setProcessLogsPanelFilter(filter.value)),
+    navigateToLog: (uuid) => dispatch<any>(navigateToLogCollection(uuid)),
 });
 
 const getFilters = (processPanel: ProcessPanelState, processes: Process[]) => {

commit 4aee3fa5225c21771b23666d29be9d796758a65f
Author: Lucas Di Pentima <lucas.dipentima at curii.com>
Date:   Thu Mar 10 18:57:29 2022 -0300

    16672: Rearranges code related to getting current process' UUID.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas.dipentima at curii.com>

diff --git a/src/store/process-logs-panel/process-logs-panel-actions.ts b/src/store/process-logs-panel/process-logs-panel-actions.ts
index ecbd030b..f0caf052 100644
--- a/src/store/process-logs-panel/process-logs-panel-actions.ts
+++ b/src/store/process-logs-panel/process-logs-panel-actions.ts
@@ -45,7 +45,7 @@ export const initProcessLogsPanel = (processUuid: string) =>
 export const addProcessLogsPanelItem = (message: ResourceEventMessage<{ text: string }>) =>
     async (dispatch: Dispatch, getState: () => RootState, { logService }: ServiceRepository) => {
         if (PROCESS_PANEL_LOG_EVENT_TYPES.indexOf(message.eventType) > -1) {
-            const uuid = getProcessLogsPanelCurrentUuid(getState());
+            const uuid = getProcessLogsPanelCurrentUuid(getState().router);
             if (uuid) {
                 const process = getProcess(uuid)(getState().resources);
                 if (process) {
diff --git a/src/store/process-logs-panel/process-logs-panel.ts b/src/store/process-logs-panel/process-logs-panel.ts
index 87b50bd2..74a18041 100644
--- a/src/store/process-logs-panel/process-logs-panel.ts
+++ b/src/store/process-logs-panel/process-logs-panel.ts
@@ -2,8 +2,8 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import { RootState } from '../store';
 import { matchProcessLogRoute, matchProcessRoute } from 'routes/routes';
+import { RouterState } from 'react-router-redux';
 
 export interface ProcessLogsPanel {
     filters: string[];
@@ -19,7 +19,7 @@ export const getProcessPanelLogs = ({ selectedFilter, logs }: ProcessLogsPanel)
     return logs[selectedFilter];
 };
 
-export const getProcessLogsPanelCurrentUuid = ({ router }: RootState) => {
+export const getProcessLogsPanelCurrentUuid = (router: RouterState) => {
     const pathname = router.location ? router.location.pathname : '';
     const match = matchProcessLogRoute(pathname) || matchProcessRoute(pathname);
     return match ? match.params.id : undefined;
diff --git a/src/store/process-panel/process-panel.ts b/src/store/process-panel/process-panel.ts
index 935cfa58..49c2691d 100644
--- a/src/store/process-panel/process-panel.ts
+++ b/src/store/process-panel/process-panel.ts
@@ -2,7 +2,16 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
+import { RouterState } from "react-router-redux";
+import { matchProcessRoute } from "routes/routes";
+
 export interface ProcessPanel {
     containerRequestUuid: string;
     filters: { [status: string]: boolean };
 }
+
+export const getProcessPanelCurrentUuid = (router: RouterState) => {
+    const pathname = router.location ? router.location.pathname : '';
+    const match = matchProcessRoute(pathname);
+    return match ? match.params.id : undefined;
+};
\ No newline at end of file
diff --git a/src/views/process-log-panel/process-log-panel.tsx b/src/views/process-log-panel/process-log-panel.tsx
index 9f61f8b6..b11d1432 100644
--- a/src/views/process-log-panel/process-log-panel.tsx
+++ b/src/views/process-log-panel/process-log-panel.tsx
@@ -25,9 +25,8 @@ export interface FilterOption {
     value: string;
 }
 
-const mapStateToProps = (state: RootState): ProcessLogPanelRootDataProps => {
-    const { resources, processLogsPanel } = state;
-    const uuid = getProcessLogsPanelCurrentUuid(state) || '';
+const mapStateToProps = ({resources, processLogsPanel, router}: RootState): ProcessLogPanelRootDataProps => {
+    const uuid = getProcessLogsPanelCurrentUuid(router) || '';
     return {
         process: getProcess(uuid)(resources),
         selectedFilter: { label: processLogsPanel.selectedFilter, value: processLogsPanel.selectedFilter },
diff --git a/src/views/process-panel/process-panel.tsx b/src/views/process-panel/process-panel.tsx
index 3364a8d6..27acc869 100644
--- a/src/views/process-panel/process-panel.tsx
+++ b/src/views/process-panel/process-panel.tsx
@@ -7,18 +7,26 @@ import { connect } from 'react-redux';
 import { getProcess, getSubprocesses, Process, getProcessStatus } from 'store/processes/process';
 import { Dispatch } from 'redux';
 import { openProcessContextMenu } from 'store/context-menu/context-menu-actions';
-import { matchProcessRoute } from 'routes/routes';
-import { ProcessPanelRootDataProps, ProcessPanelRootActionProps, ProcessPanelRoot } from './process-panel-root';
-import { ProcessPanel as ProcessPanelState} from 'store/process-panel/process-panel';
+import {
+    ProcessPanelRootDataProps,
+    ProcessPanelRootActionProps,
+    ProcessPanelRoot
+} from './process-panel-root';
+import {
+    getProcessPanelCurrentUuid,
+    ProcessPanel as ProcessPanelState
+} from 'store/process-panel/process-panel';
 import { groupBy } from 'lodash';
-import { toggleProcessPanelFilter, navigateToOutput, openWorkflow } from 'store/process-panel/process-panel-actions';
+import {
+    toggleProcessPanelFilter,
+    navigateToOutput,
+    openWorkflow
+} from 'store/process-panel/process-panel-actions';
 import { openProcessInputDialog } from 'store/processes/process-input-actions';
 import { cancelRunningWorkflow } from 'store/processes/processes-actions';
 
 const mapStateToProps = ({ router, resources, processPanel }: RootState): ProcessPanelRootDataProps => {
-    const pathname = router.location ? router.location.pathname : '';
-    const match = matchProcessRoute(pathname);
-    const uuid = match ? match.params.id : '';
+    const uuid = getProcessPanelCurrentUuid(router) || '';
     const subprocesses = getSubprocesses(uuid)(resources);
     return {
         process: getProcess(uuid)(resources),
@@ -40,9 +48,7 @@ const mapDispatchToProps = (dispatch: Dispatch): ProcessPanelRootActionProps =>
     cancelProcess: (uuid) => dispatch<any>(cancelRunningWorkflow(uuid))
 });
 
-export const ProcessPanel = connect(mapStateToProps, mapDispatchToProps)(ProcessPanelRoot);
-
-export const getFilters = (processPanel: ProcessPanelState, processes: Process[]) => {
+const getFilters = (processPanel: ProcessPanelState, processes: Process[]) => {
     const grouppedProcesses = groupBy(processes, getProcessStatus);
     return Object
         .keys(processPanel.filters)
@@ -52,4 +58,6 @@ export const getFilters = (processPanel: ProcessPanelState, processes: Process[]
             checked: processPanel.filters[filter],
             key: filter,
         }));
-    };
\ No newline at end of file
+    };
+
+export const ProcessPanel = connect(mapStateToProps, mapDispatchToProps)(ProcessPanelRoot);

commit 38fecec459a6ed195c771540bab9367835f968c8
Author: Lucas Di Pentima <lucas.dipentima at curii.com>
Date:   Thu Mar 10 18:00:05 2022 -0300

    16672: Improves UX by avoiding to instantly focus on a MPV panel.
    
    100ms seems "instant enough" for cases where the user is really attempting
    to use the multi-panel-view task bar. For other cases where the user
    casually hovers through any of the buttons because is trying to reach
    some different part of the UI, not making the UI "move" is a lot less
    confusing.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas.dipentima at curii.com>

diff --git a/src/components/multi-panel-view/multi-panel-view.tsx b/src/components/multi-panel-view/multi-panel-view.tsx
index 185c3b90..6fb3cc49 100644
--- a/src/components/multi-panel-view/multi-panel-view.tsx
+++ b/src/components/multi-panel-view/multi-panel-view.tsx
@@ -112,7 +112,7 @@ const MPVContainerComponent = ({children, panelStates, classes, ...props}: MPVCo
         children = [children];
     }
     const visibility = (children as ReactNodeArray).map((_, idx) =>
-        !!!panelStates || // if panelStates wasn't passed, default to all visible panels
+        !panelStates || // if panelStates wasn't passed, default to all visible panels
             (panelStates[idx] &&
                 (panelStates[idx].visible || panelStates[idx].visible === undefined)));
     const [panelVisibility, setPanelVisibility] = useState<boolean[]>(visibility);
@@ -159,13 +159,20 @@ const MPVContainerComponent = ({children, panelStates, classes, ...props}: MPVCo
             const panelIsMaximized = panelVisibility[idx] &&
                 panelVisibility.filter(e => e).length === 1;
 
+            let brightenerTimer: NodeJS.Timer;
             toggles = [
                 ...toggles,
                 <Tooltip title={toggleTooltip} disableFocusListener>
                     <Button variant={toggleVariant} size="small" color="primary"
                         className={classNames(classes.button)}
-                        onMouseEnter={() => setBrightenedPanel(idx)}
-                        onMouseLeave={() => setBrightenedPanel(-1)}
+                        onMouseEnter={() => {
+                            brightenerTimer = setTimeout(
+                                () => setBrightenedPanel(idx), 100);
+                        }}
+                        onMouseLeave={() => {
+                            brightenerTimer && clearTimeout(brightenerTimer);
+                            setBrightenedPanel(-1);
+                        }}
                         onClick={showFn(idx)}>
                             {panelName}
                             {toggleIcon}

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


hooks/post-receive
-- 




More information about the arvados-commits mailing list