[arvados] created: 2.7.0-6426-ga72bfc2cc9

git repository hosting git at public.arvados.org
Tue Apr 23 14:36:32 UTC 2024


        at  a72bfc2cc90e0155ac7ea7ea9aa21390d2c18c19 (commit)


commit a72bfc2cc90e0155ac7ea7ea9aa21390d2c18c19
Author: Stephen Smith <stephen at curii.com>
Date:   Tue Apr 23 10:35:49 2024 -0400

    21642: Add unit tests for conditional tabs, add test case for main process output collection
    
    Arvados-DCO-1.1-Signed-off-by: Stephen Smith <stephen at curii.com>

diff --git a/services/workbench2/src/components/conditional-tabs/conditional-tabs.test.tsx b/services/workbench2/src/components/conditional-tabs/conditional-tabs.test.tsx
new file mode 100644
index 0000000000..3cb206a5d0
--- /dev/null
+++ b/services/workbench2/src/components/conditional-tabs/conditional-tabs.test.tsx
@@ -0,0 +1,75 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import React from "react";
+import { mount, configure } from "enzyme";
+import { ConditionalTabs, TabData } from "./conditional-tabs";
+import Adapter from 'enzyme-adapter-react-16';
+import { Tab } from "@material-ui/core";
+
+configure({ adapter: new Adapter() });
+
+describe("<ConditionalTabs />", () => {
+    let tabs: TabData[] = [];
+
+    beforeEach(() => {
+        tabs = [{
+            show: true,
+            label: "Tab1",
+            content: <div id="content">Content1</div>,
+        },{
+            show: false,
+            label: "Tab2",
+            content: <div id="content">Content2</div>,
+        },{
+            show: true,
+            label: "Tab3",
+            content: <div id="content">Content3</div>,
+        }];
+    });
+
+    it("renders visible tabs", () => {
+        // given
+        const tabContainer = mount(<ConditionalTabs
+            tabs={tabs}
+        />);
+
+        // expect 2 visible tabs
+        expect(tabContainer.find(Tab)).toHaveLength(2);
+        expect(tabContainer.find(Tab).at(0).text()).toBe("Tab1");
+        expect(tabContainer.find(Tab).at(1).text()).toBe("Tab3");
+        expect(tabContainer.find('div#content').text()).toBe("Content1");
+
+        // Show second tab
+        tabs[1].show = true;
+        tabContainer.setProps({ tabs: tabs });
+
+        // Expect 3 visible tabs
+        expect(tabContainer.find(Tab)).toHaveLength(3);
+        expect(tabContainer.find(Tab).at(0).text()).toBe("Tab1");
+        expect(tabContainer.find(Tab).at(1).text()).toBe("Tab2");
+        expect(tabContainer.find(Tab).at(2).text()).toBe("Tab3");
+        expect(tabContainer.find('div#content').text()).toBe("Content1");
+    });
+
+    it("resets selected tab on tab visibility change", () => {
+        // given
+        const tabContainer = mount(<ConditionalTabs
+            tabs={tabs}
+        />);
+
+        // Expext second tab to be Tab3
+        expect(tabContainer.find(Tab).at(1).text()).toBe("Tab3");
+        // Click on Tab3
+        tabContainer.find(Tab).at(1).simulate('click');
+        expect(tabContainer.find('div#content').text()).toBe("Content3");
+
+        // when Tab2 becomes visible
+        tabs[1].show = true;
+        tabContainer.setProps({ tabs: tabs });
+
+        // Selected tab resets
+        expect(tabContainer.find('div#content').text()).toBe("Content1");
+    });
+});
diff --git a/services/workbench2/src/components/conditional-tabs/conditional-tabs.tsx b/services/workbench2/src/components/conditional-tabs/conditional-tabs.tsx
index 74f2ecf4f9..ff8d517c4e 100644
--- a/services/workbench2/src/components/conditional-tabs/conditional-tabs.tsx
+++ b/services/workbench2/src/components/conditional-tabs/conditional-tabs.tsx
@@ -6,7 +6,7 @@ import React, { ReactNode, useEffect, useState } from "react";
 import { Tabs, Tab } from "@material-ui/core";
 import { TabsProps } from "@material-ui/core/Tabs";
 
-type TabData = {
+export type TabData = {
     show: boolean;
     label: string;
     content: ReactNode;
@@ -37,7 +37,7 @@ export const ConditionalTabs = (props: Omit<TabsProps, 'value' | 'onChange'> & C
             {...props}
             value={tabState}
             onChange={handleTabChange} >
-            {visibleTabs.map(tab => <Tab label={tab.label} />)}
+            {visibleTabs.map(tab => <Tab key={tab.label} label={tab.label} />)}
         </Tabs>
         {activeTab && activeTab.content}
     </>;
diff --git a/services/workbench2/src/views/process-panel/process-io-card.test.tsx b/services/workbench2/src/views/process-panel/process-io-card.test.tsx
index c0feead398..38061e3f06 100644
--- a/services/workbench2/src/views/process-panel/process-io-card.test.tsx
+++ b/services/workbench2/src/views/process-panel/process-io-card.test.tsx
@@ -138,6 +138,36 @@ describe('renderers', () => {
             expect(panel.find(TableBody).text()).toContain('someValue');
         });
 
+        it('shows main process with output collection', () => {
+            // when
+            const outputCollection = '987654321';
+            const parameters = [{id: 'someId', label: 'someLabel', value: {display: 'someValue'}}];
+            let panel = mount(
+                <Provider store={store}>
+                    <MuiThemeProvider theme={CustomTheme}>
+                        <ProcessIOCard
+                            label={ProcessIOCardType.OUTPUT}
+                            process={false} // Treat as a main process, no requestingContainerUuid
+                            outputUuid={outputCollection}
+                            params={parameters}
+                            raw={{}}
+                        />
+                    </MuiThemeProvider>
+                </Provider>
+                );
+
+            // then
+            expect(panel.find(CircularProgress).exists()).toBeFalsy();
+            expect(panel.find(Tab).length).toBe(3); // Empty raw is shown if parameters are present
+            expect(panel.find(TableBody).text()).toContain('someId');
+            expect(panel.find(TableBody).text()).toContain('someLabel');
+            expect(panel.find(TableBody).text()).toContain('someValue');
+
+            // Visit output tab
+            panel.find(Tab).at(2).simulate('click');
+            expect(panel.find(ProcessOutputCollectionFiles).prop('currentItemUuid')).toBe(outputCollection);
+        });
+
         // Subprocess
 
         it('shows subprocess loading', () => {

commit 9e778ff22ec24e21f1a777f05efb81569a549f6b
Author: Stephen Smith <stephen at curii.com>
Date:   Thu Apr 18 16:43:53 2024 -0400

    21642: Remove connect from main component and instead connect component that uses navigateTo
    
    Arvados-DCO-1.1-Signed-off-by: Stephen Smith <stephen at curii.com>

diff --git a/services/workbench2/src/views/process-panel/process-io-card.tsx b/services/workbench2/src/views/process-panel/process-io-card.tsx
index beae24c92a..76d4c52d04 100644
--- a/services/workbench2/src/views/process-panel/process-io-card.tsx
+++ b/services/workbench2/src/views/process-panel/process-io-card.tsx
@@ -267,220 +267,206 @@ export interface ProcessIOCardDataProps {
     forceShowParams?: boolean;
 }
 
-export interface ProcessIOCardActionProps {
-    navigateTo: (uuid: string) => void;
-}
-
-const mapDispatchToProps = (dispatch: Dispatch): ProcessIOCardActionProps => ({
-    navigateTo: uuid => dispatch<any>(navigateTo(uuid)),
-});
-
-type ProcessIOCardProps = ProcessIOCardDataProps & ProcessIOCardActionProps & WithStyles<CssRules> & MPVPanelProps;
+type ProcessIOCardProps = ProcessIOCardDataProps & WithStyles<CssRules> & MPVPanelProps;
 
 export const ProcessIOCard = withStyles(styles)(
-    connect(
-        null,
-        mapDispatchToProps
-    )(
-        ({
-            classes,
-            label,
-            params,
-            raw,
-            mounts,
-            outputUuid,
-            doHidePanel,
-            doMaximizePanel,
-            doUnMaximizePanel,
-            panelMaximized,
-            panelName,
-            process,
-            navigateTo,
-            forceShowParams,
-        }: ProcessIOCardProps) => {
-            const PanelIcon = label === ProcessIOCardType.INPUT ? InputIcon : OutputIcon;
-            const mainProcess = !(process && process!.containerRequest.requestingContainerUuid);
-            const showParamTable = mainProcess || forceShowParams;
-
-            const loading = raw === null || raw === undefined || params === null;
-
-            const hasRaw = !!(raw && Object.keys(raw).length > 0);
-            const hasParams = !!(params && params.length > 0);
-            // isRawLoaded allows subprocess panel to display raw even if it's {}
-            const isRawLoaded = !!(raw && Object.keys(raw).length >= 0);
-
-            // Subprocess
-            const hasInputMounts = !!(label === ProcessIOCardType.INPUT && mounts && mounts.length);
-            const hasOutputCollecton = !!(label === ProcessIOCardType.OUTPUT && outputUuid);
-            // Subprocess should not show loading if hasOutputCollection or hasInputMounts
-            const subProcessLoading = loading && !hasOutputCollecton && !hasInputMounts;
-
-            return (
-                <Card
-                    className={classes.card}
-                    data-cy="process-io-card"
-                >
-                    <CardHeader
-                        className={classes.header}
-                        classes={{
-                            content: classes.title,
-                            avatar: classes.avatar,
-                        }}
-                        avatar={<PanelIcon className={classes.iconHeader} />}
-                        title={
-                            <Typography
-                                noWrap
-                                variant="h6"
-                                color="inherit"
-                            >
-                                {label}
-                            </Typography>
-                        }
-                        action={
-                            <div>
-                                {doUnMaximizePanel && panelMaximized && (
-                                    <Tooltip
-                                        title={`Unmaximize ${panelName || "panel"}`}
-                                        disableFocusListener
-                                    >
-                                        <IconButton onClick={doUnMaximizePanel}>
-                                            <UnMaximizeIcon />
-                                        </IconButton>
-                                    </Tooltip>
-                                )}
-                                {doMaximizePanel && !panelMaximized && (
-                                    <Tooltip
-                                        title={`Maximize ${panelName || "panel"}`}
-                                        disableFocusListener
-                                    >
-                                        <IconButton onClick={doMaximizePanel}>
-                                            <MaximizeIcon />
-                                        </IconButton>
-                                    </Tooltip>
-                                )}
-                                {doHidePanel && (
-                                    <Tooltip
-                                        title={`Close ${panelName || "panel"}`}
-                                        disableFocusListener
-                                    >
-                                        <IconButton
-                                            disabled={panelMaximized}
-                                            onClick={doHidePanel}
-                                        >
-                                            <CloseIcon />
-                                        </IconButton>
-                                    </Tooltip>
-                                )}
-                            </div>
-                        }
-                    />
-                    <CardContent className={classes.content}>
-                        {showParamTable ? (
-                            <>
-                                {/* raw is undefined until params are loaded */}
-                                {loading && (
-                                    <Grid
-                                        container
-                                        item
-                                        alignItems="center"
-                                        justify="center"
-                                    >
-                                        <CircularProgress />
-                                    </Grid>
-                                )}
-                                {/* Once loaded, either raw or params may still be empty
-                                  *   Raw when all params are empty
-                                  *   Params when raw is provided by containerRequest properties but workflow mount is absent for preview
-                                  */}
-                                {!loading && (hasRaw || hasParams) && (
-                                    <ConditionalTabs
-                                        variant="fullWidth"
-                                        className={classes.symmetricTabs}
-                                        tabs={[
-                                            {
-                                                // params will be empty on processes without workflow definitions in mounts, so we only show raw
-                                                show: hasParams,
-                                                label: "Parameters",
-                                                content: <ProcessIOPreview
-                                                        data={params || []}
-                                                        valueLabel={forceShowParams ? "Default value" : "Value"}
-                                                />,
-                                            },
-                                            {
-                                                show: !forceShowParams,
-                                                label: "JSON",
-                                                content: <ProcessIORaw data={raw} />,
-                                            },
-                                            {
-                                                show: hasOutputCollecton,
-                                                label: "Collection",
-                                                content: <ProcessOutputCollection outputUuid={outputUuid} />,
-                                            },
-                                        ]}
-                                    />
-                                )}
-                                {!loading && !hasRaw && !hasParams && (
-                                    <Grid
-                                        container
-                                        item
-                                        alignItems="center"
-                                        justify="center"
-                                    >
-                                        <DefaultView messages={["No parameters found"]} />
-                                    </Grid>
-                                )}
-                            </>
-                        ) : (
-                            // Subprocess
-                            <>
-                                {subProcessLoading ? (
-                                    <Grid
-                                        container
-                                        item
-                                        alignItems="center"
-                                        justify="center"
-                                    >
-                                        <CircularProgress />
-                                    </Grid>
-                                ) : !subProcessLoading && (hasInputMounts || hasOutputCollecton || isRawLoaded) ? (
-                                    <ConditionalTabs
-                                        variant="fullWidth"
-                                        className={classes.symmetricTabs}
-                                        tabs={[
-                                            {
-                                                show: hasInputMounts,
-                                                label: "Collections",
-                                                content: <ProcessInputMounts mounts={mounts || []} />,
-                                            },
-                                            {
-                                                show: hasOutputCollecton,
-                                                label: "Collection",
-                                                content: <ProcessOutputCollection outputUuid={outputUuid} />,
-                                            },
-                                            {
-                                                show: isRawLoaded,
-                                                label: "JSON",
-                                                content: <ProcessIORaw data={raw} />,
-                                            },
-                                        ]}
-                                    />
-                                ) : (
-                                    <Grid
-                                        container
-                                        item
-                                        alignItems="center"
-                                        justify="center"
+    ({
+        classes,
+        label,
+        params,
+        raw,
+        mounts,
+        outputUuid,
+        doHidePanel,
+        doMaximizePanel,
+        doUnMaximizePanel,
+        panelMaximized,
+        panelName,
+        process,
+        forceShowParams,
+    }: ProcessIOCardProps) => {
+        const PanelIcon = label === ProcessIOCardType.INPUT ? InputIcon : OutputIcon;
+        const mainProcess = !(process && process!.containerRequest.requestingContainerUuid);
+        const showParamTable = mainProcess || forceShowParams;
+
+        const loading = raw === null || raw === undefined || params === null;
+
+        const hasRaw = !!(raw && Object.keys(raw).length > 0);
+        const hasParams = !!(params && params.length > 0);
+        // isRawLoaded allows subprocess panel to display raw even if it's {}
+        const isRawLoaded = !!(raw && Object.keys(raw).length >= 0);
+
+        // Subprocess
+        const hasInputMounts = !!(label === ProcessIOCardType.INPUT && mounts && mounts.length);
+        const hasOutputCollecton = !!(label === ProcessIOCardType.OUTPUT && outputUuid);
+        // Subprocess should not show loading if hasOutputCollection or hasInputMounts
+        const subProcessLoading = loading && !hasOutputCollecton && !hasInputMounts;
+
+        return (
+            <Card
+                className={classes.card}
+                data-cy="process-io-card"
+            >
+                <CardHeader
+                    className={classes.header}
+                    classes={{
+                        content: classes.title,
+                        avatar: classes.avatar,
+                    }}
+                    avatar={<PanelIcon className={classes.iconHeader} />}
+                    title={
+                        <Typography
+                            noWrap
+                            variant="h6"
+                            color="inherit"
+                        >
+                            {label}
+                        </Typography>
+                    }
+                    action={
+                        <div>
+                            {doUnMaximizePanel && panelMaximized && (
+                                <Tooltip
+                                    title={`Unmaximize ${panelName || "panel"}`}
+                                    disableFocusListener
+                                >
+                                    <IconButton onClick={doUnMaximizePanel}>
+                                        <UnMaximizeIcon />
+                                    </IconButton>
+                                </Tooltip>
+                            )}
+                            {doMaximizePanel && !panelMaximized && (
+                                <Tooltip
+                                    title={`Maximize ${panelName || "panel"}`}
+                                    disableFocusListener
+                                >
+                                    <IconButton onClick={doMaximizePanel}>
+                                        <MaximizeIcon />
+                                    </IconButton>
+                                </Tooltip>
+                            )}
+                            {doHidePanel && (
+                                <Tooltip
+                                    title={`Close ${panelName || "panel"}`}
+                                    disableFocusListener
+                                >
+                                    <IconButton
+                                        disabled={panelMaximized}
+                                        onClick={doHidePanel}
                                     >
-                                        <DefaultView messages={["No data to display"]} />
-                                    </Grid>
-                                )}
-                            </>
-                        )}
-                    </CardContent>
-                </Card>
-            );
-        }
-    )
+                                        <CloseIcon />
+                                    </IconButton>
+                                </Tooltip>
+                            )}
+                        </div>
+                    }
+                />
+                <CardContent className={classes.content}>
+                    {showParamTable ? (
+                        <>
+                            {/* raw is undefined until params are loaded */}
+                            {loading && (
+                                <Grid
+                                    container
+                                    item
+                                    alignItems="center"
+                                    justify="center"
+                                >
+                                    <CircularProgress />
+                                </Grid>
+                            )}
+                            {/* Once loaded, either raw or params may still be empty
+                                *   Raw when all params are empty
+                                *   Params when raw is provided by containerRequest properties but workflow mount is absent for preview
+                                */}
+                            {!loading && (hasRaw || hasParams) && (
+                                <ConditionalTabs
+                                    variant="fullWidth"
+                                    className={classes.symmetricTabs}
+                                    tabs={[
+                                        {
+                                            // params will be empty on processes without workflow definitions in mounts, so we only show raw
+                                            show: hasParams,
+                                            label: "Parameters",
+                                            content: <ProcessIOPreview
+                                                    data={params || []}
+                                                    valueLabel={forceShowParams ? "Default value" : "Value"}
+                                            />,
+                                        },
+                                        {
+                                            show: !forceShowParams,
+                                            label: "JSON",
+                                            content: <ProcessIORaw data={raw} />,
+                                        },
+                                        {
+                                            show: hasOutputCollecton,
+                                            label: "Collection",
+                                            content: <ProcessOutputCollection outputUuid={outputUuid} />,
+                                        },
+                                    ]}
+                                />
+                            )}
+                            {!loading && !hasRaw && !hasParams && (
+                                <Grid
+                                    container
+                                    item
+                                    alignItems="center"
+                                    justify="center"
+                                >
+                                    <DefaultView messages={["No parameters found"]} />
+                                </Grid>
+                            )}
+                        </>
+                    ) : (
+                        // Subprocess
+                        <>
+                            {subProcessLoading ? (
+                                <Grid
+                                    container
+                                    item
+                                    alignItems="center"
+                                    justify="center"
+                                >
+                                    <CircularProgress />
+                                </Grid>
+                            ) : !subProcessLoading && (hasInputMounts || hasOutputCollecton || isRawLoaded) ? (
+                                <ConditionalTabs
+                                    variant="fullWidth"
+                                    className={classes.symmetricTabs}
+                                    tabs={[
+                                        {
+                                            show: hasInputMounts,
+                                            label: "Collections",
+                                            content: <ProcessInputMounts mounts={mounts || []} />,
+                                        },
+                                        {
+                                            show: hasOutputCollecton,
+                                            label: "Collection",
+                                            content: <ProcessOutputCollection outputUuid={outputUuid} />,
+                                        },
+                                        {
+                                            show: isRawLoaded,
+                                            label: "JSON",
+                                            content: <ProcessIORaw data={raw} />,
+                                        },
+                                    ]}
+                                />
+                            ) : (
+                                <Grid
+                                    container
+                                    item
+                                    alignItems="center"
+                                    justify="center"
+                                >
+                                    <DefaultView messages={["No data to display"]} />
+                                </Grid>
+                            )}
+                        </>
+                    )}
+                </CardContent>
+            </Card>
+        );
+    }
 );
 
 export type ProcessIOValue = {
@@ -658,9 +644,17 @@ const ProcessInputMounts = withStyles(styles)(
     ))
 );
 
-type ProcessOutputCollectionProps = {outputUuid: string | undefined} &  WithStyles<CssRules>;
+export interface ProcessOutputCollectionActionProps {
+    navigateTo: (uuid: string) => void;
+}
 
-const ProcessOutputCollection = withStyles(styles)(({ outputUuid, classes }: ProcessOutputCollectionProps) => (
+const mapNavigateToProps = (dispatch: Dispatch): ProcessOutputCollectionActionProps => ({
+    navigateTo: uuid => dispatch<any>(navigateTo(uuid)),
+});
+
+type ProcessOutputCollectionProps = {outputUuid: string | undefined} & ProcessOutputCollectionActionProps &  WithStyles<CssRules>;
+
+const ProcessOutputCollection = withStyles(styles)(connect(null, mapNavigateToProps)(({ outputUuid, navigateTo, classes }: ProcessOutputCollectionProps) => (
     <div className={classes.tableWrapper}>
         <>
             {outputUuid && (
@@ -682,7 +676,7 @@ const ProcessOutputCollection = withStyles(styles)(({ outputUuid, classes }: Pro
             />
         </>
     </div>
-));
+)));
 
 type FileWithSecondaryFiles = {
     secondaryFiles: File[];

commit ceb3005f274842bfdb5a7eb66d19ba8ef0150c9b
Author: Stephen Smith <stephen at curii.com>
Date:   Thu Apr 18 16:39:06 2024 -0400

    21642: Deduplicate wrapper elements into helper components
    
    Arvados-DCO-1.1-Signed-off-by: Stephen Smith <stephen at curii.com>

diff --git a/services/workbench2/src/views/process-panel/process-io-card.tsx b/services/workbench2/src/views/process-panel/process-io-card.tsx
index e05c383b1c..beae24c92a 100644
--- a/services/workbench2/src/views/process-panel/process-io-card.tsx
+++ b/services/workbench2/src/views/process-panel/process-io-card.tsx
@@ -401,19 +401,15 @@ export const ProcessIOCard = withStyles(styles)(
                                                 // params will be empty on processes without workflow definitions in mounts, so we only show raw
                                                 show: hasParams,
                                                 label: "Parameters",
-                                                content: <div className={classes.tableWrapper}>
-                                                    <ProcessIOPreview
+                                                content: <ProcessIOPreview
                                                         data={params || []}
                                                         valueLabel={forceShowParams ? "Default value" : "Value"}
-                                                    />
-                                                </div>,
+                                                />,
                                             },
                                             {
                                                 show: !forceShowParams,
                                                 label: "JSON",
-                                                content: <div className={classes.jsonWrapper}>
-                                                    <ProcessIORaw data={raw} />
-                                                </div>,
+                                                content: <ProcessIORaw data={raw} />,
                                             },
                                             {
                                                 show: hasOutputCollecton,
@@ -464,9 +460,7 @@ export const ProcessIOCard = withStyles(styles)(
                                             {
                                                 show: isRawLoaded,
                                                 label: "JSON",
-                                                content: <div className={classes.jsonWrapper}>
-                                                    <ProcessIORaw data={raw} />
-                                                </div>,
+                                                content: <ProcessIORaw data={raw} />,
                                             },
                                         ]}
                                     />
@@ -510,7 +504,7 @@ interface ProcessIOPreviewDataProps {
 type ProcessIOPreviewProps = ProcessIOPreviewDataProps & WithStyles<CssRules>;
 
 const ProcessIOPreview = memo(
-    withStyles(styles)(({ classes, data, valueLabel }: ProcessIOPreviewProps) => {
+    withStyles(styles)(({ data, valueLabel, classes }: ProcessIOPreviewProps) => {
         const showLabel = data.some((param: ProcessIOParameter) => param.label);
 
         const hasMoreValues = (index: number) => (
@@ -566,7 +560,7 @@ const ProcessIOPreview = memo(
             </TableRow>;
         };
 
-        return (
+        return <div className={classes.tableWrapper}>
             <Table
                 className={classes.paramTableRoot}
                 aria-label="Process IO Preview"
@@ -594,7 +588,7 @@ const ProcessIOPreview = memo(
                     </AutoSizer>
                 </TableBody>
             </Table>
-        );
+        </div>;
     })
 );
 
@@ -612,13 +606,15 @@ interface ProcessIORawDataProps {
     data: ProcessIOParameter[];
 }
 
-const ProcessIORaw = withStyles(styles)(({ data }: ProcessIORawDataProps) => (
-    <Paper elevation={0} style={{minWidth: "100%", height: "100%"}}>
-        <DefaultVirtualCodeSnippet
-            lines={JSON.stringify(data, null, 2).split('\n')}
-            linked
-        />
-    </Paper>
+const ProcessIORaw = withStyles(styles)(({ data, classes }: ProcessIORawDataProps & WithStyles<CssRules>) => (
+    <div className={classes.jsonWrapper}>
+        <Paper elevation={0} style={{minWidth: "100%", height: "100%"}}>
+            <DefaultVirtualCodeSnippet
+                lines={JSON.stringify(data, null, 2).split('\n')}
+                linked
+            />
+        </Paper>
+    </div>
 ));
 
 interface ProcessInputMountsDataProps {

commit fda8272ebf1a38844760bca03aef3472795b6a9e
Author: Stephen Smith <stephen at curii.com>
Date:   Thu Apr 18 16:27:01 2024 -0400

    21642: Fix tab state misalignment by oursourcing tab state management
    
    ConditionalTabs component ensures no state misalignments by displaying tab
    content by indexing into the same array of tabs that are given to the tab bar
    
    By grouping tab and contents, we can simply remove inactive tabs before trying
    to figure out which contents to show, making the logic much easier.
    
    Arvados-DCO-1.1-Signed-off-by: Stephen Smith <stephen at curii.com>

diff --git a/services/workbench2/src/components/conditional-tabs/conditional-tabs.tsx b/services/workbench2/src/components/conditional-tabs/conditional-tabs.tsx
new file mode 100644
index 0000000000..74f2ecf4f9
--- /dev/null
+++ b/services/workbench2/src/components/conditional-tabs/conditional-tabs.tsx
@@ -0,0 +1,44 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import React, { ReactNode, useEffect, useState } from "react";
+import { Tabs, Tab } from "@material-ui/core";
+import { TabsProps } from "@material-ui/core/Tabs";
+
+type TabData = {
+    show: boolean;
+    label: string;
+    content: ReactNode;
+};
+
+type ConditionalTabsProps = {
+    tabs: TabData[];
+};
+
+export const ConditionalTabs = (props: Omit<TabsProps, 'value' | 'onChange'> & ConditionalTabsProps) => {
+    const [tabState, setTabState] = useState(0);
+    const visibleTabs = props.tabs.filter(tab => tab.show);
+    const activeTab = visibleTabs[tabState];
+    const visibleTabNames = visibleTabs.map(tab => tab.label).join();
+
+    const handleTabChange = (event: React.MouseEvent<HTMLElement>, value: number) => {
+        setTabState(value);
+    };
+
+    // Reset tab to 0 when tab visibility changes
+    // (or if tab set change causes visible set to change)
+    useEffect(() => {
+        setTabState(0);
+    }, [visibleTabNames]);
+
+    return <>
+        <Tabs
+            {...props}
+            value={tabState}
+            onChange={handleTabChange} >
+            {visibleTabs.map(tab => <Tab label={tab.label} />)}
+        </Tabs>
+        {activeTab && activeTab.content}
+    </>;
+};
diff --git a/services/workbench2/src/views/process-panel/process-io-card.tsx b/services/workbench2/src/views/process-panel/process-io-card.tsx
index 9fce7e83d4..e05c383b1c 100644
--- a/services/workbench2/src/views/process-panel/process-io-card.tsx
+++ b/services/workbench2/src/views/process-panel/process-io-card.tsx
@@ -2,7 +2,7 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import React, { ReactElement, memo, useState } from "react";
+import React, { ReactElement, memo } from "react";
 import { Dispatch } from "redux";
 import {
     StyleRulesCallback,
@@ -14,8 +14,6 @@ import {
     CardContent,
     Tooltip,
     Typography,
-    Tabs,
-    Tab,
     Table,
     TableHead,
     TableBody,
@@ -70,6 +68,7 @@ import { KEEP_URL_REGEX } from "models/resource";
 import { FixedSizeList } from 'react-window';
 import AutoSizer from "react-virtualized-auto-sizer";
 import { LinkProps } from "@material-ui/core/Link";
+import { ConditionalTabs } from "components/conditional-tabs/conditional-tabs";
 
 type CssRules =
     | "card"
@@ -299,15 +298,6 @@ export const ProcessIOCard = withStyles(styles)(
             navigateTo,
             forceShowParams,
         }: ProcessIOCardProps) => {
-            const [mainProcTabState, setMainProcTabState] = useState(0);
-            const [subProcTabState, setSubProcTabState] = useState(0);
-            const handleMainProcTabChange = (event: React.MouseEvent<HTMLElement>, value: number) => {
-                setMainProcTabState(value);
-            };
-            const handleSubProcTabChange = (event: React.MouseEvent<HTMLElement>, value: number) => {
-                setSubProcTabState(value);
-            };
-
             const PanelIcon = label === ProcessIOCardType.INPUT ? InputIcon : OutputIcon;
             const mainProcess = !(process && process!.containerRequest.requestingContainerUuid);
             const showParamTable = mainProcess || forceShowParams;
@@ -403,54 +393,35 @@ export const ProcessIOCard = withStyles(styles)(
                                   *   Params when raw is provided by containerRequest properties but workflow mount is absent for preview
                                   */}
                                 {!loading && (hasRaw || hasParams) && (
-                                    <>
-                                        <Tabs
-                                            value={mainProcTabState}
-                                            onChange={handleMainProcTabChange}
-                                            variant="fullWidth"
-                                            className={classes.symmetricTabs}
-                                        >
-                                            {/* params will be empty on processes without workflow definitions in mounts, so we only show raw */}
-                                            {hasParams && <Tab label="Parameters" />}
-                                            {!forceShowParams && <Tab label="JSON" />}
-                                            {hasOutputCollecton && <Tab label="Collection" />}
-                                        </Tabs>
-                                        {mainProcTabState === 0 && params && hasParams && (
-                                            <div className={classes.tableWrapper}>
-                                                <ProcessIOPreview
-                                                    data={params}
-                                                    valueLabel={forceShowParams ? "Default value" : "Value"}
-                                                />
-                                            </div>
-                                        )}
-                                        {(mainProcTabState === 1 || !hasParams) && (
-                                            <div className={classes.jsonWrapper}>
-                                                <ProcessIORaw data={raw} />
-                                            </div>
-                                        )}
-                                        {mainProcTabState === 2 && hasOutputCollecton && (
-                                            <>
-                                                {outputUuid && (
-                                                    <Typography className={classes.collectionLink}>
-                                                        Output Collection:{" "}
-                                                        <MuiLink
-                                                            className={classes.keepLink}
-                                                            onClick={() => {
-                                                                navigateTo(outputUuid || "");
-                                                            }}
-                                                        >
-                                                            {outputUuid}
-                                                        </MuiLink>
-                                                    </Typography>
-                                                )}
-                                                <ProcessOutputCollectionFiles
-                                                    isWritable={false}
-                                                    currentItemUuid={outputUuid}
-                                                />
-                                            </>
-                                        )}
-
-                                    </>
+                                    <ConditionalTabs
+                                        variant="fullWidth"
+                                        className={classes.symmetricTabs}
+                                        tabs={[
+                                            {
+                                                // params will be empty on processes without workflow definitions in mounts, so we only show raw
+                                                show: hasParams,
+                                                label: "Parameters",
+                                                content: <div className={classes.tableWrapper}>
+                                                    <ProcessIOPreview
+                                                        data={params || []}
+                                                        valueLabel={forceShowParams ? "Default value" : "Value"}
+                                                    />
+                                                </div>,
+                                            },
+                                            {
+                                                show: !forceShowParams,
+                                                label: "JSON",
+                                                content: <div className={classes.jsonWrapper}>
+                                                    <ProcessIORaw data={raw} />
+                                                </div>,
+                                            },
+                                            {
+                                                show: hasOutputCollecton,
+                                                label: "Collection",
+                                                content: <ProcessOutputCollection outputUuid={outputUuid} />,
+                                            },
+                                        ]}
+                                    />
                                 )}
                                 {!loading && !hasRaw && !hasParams && (
                                     <Grid
@@ -476,47 +447,29 @@ export const ProcessIOCard = withStyles(styles)(
                                         <CircularProgress />
                                     </Grid>
                                 ) : !subProcessLoading && (hasInputMounts || hasOutputCollecton || isRawLoaded) ? (
-                                    <>
-                                        <Tabs
-                                            value={subProcTabState}
-                                            onChange={handleSubProcTabChange}
-                                            variant="fullWidth"
-                                            className={classes.symmetricTabs}
-                                        >
-                                            {hasInputMounts && <Tab label="Collections" />}
-                                            {hasOutputCollecton && <Tab label="Collection" />}
-                                            {isRawLoaded && <Tab label="JSON" />}
-                                        </Tabs>
-                                        {subProcTabState === 0 && hasInputMounts && <ProcessInputMounts mounts={mounts || []} />}
-                                        {subProcTabState === 0 && hasOutputCollecton && (
-                                            <div className={classes.tableWrapper}>
-                                                <>
-                                                    {outputUuid && (
-                                                        <Typography className={classes.collectionLink}>
-                                                            Output Collection:{" "}
-                                                            <MuiLink
-                                                                className={classes.keepLink}
-                                                                onClick={() => {
-                                                                    navigateTo(outputUuid || "");
-                                                                }}
-                                                            >
-                                                                {outputUuid}
-                                                            </MuiLink>
-                                                        </Typography>
-                                                    )}
-                                                    <ProcessOutputCollectionFiles
-                                                        isWritable={false}
-                                                        currentItemUuid={outputUuid}
-                                                    />
-                                                </>
-                                            </div>
-                                        )}
-                                        {isRawLoaded && (subProcTabState === 1 || (!hasInputMounts && !hasOutputCollecton)) && (
-                                            <div className={classes.jsonWrapper}>
-                                                <ProcessIORaw data={raw} />
-                                            </div>
-                                        )}
-                                    </>
+                                    <ConditionalTabs
+                                        variant="fullWidth"
+                                        className={classes.symmetricTabs}
+                                        tabs={[
+                                            {
+                                                show: hasInputMounts,
+                                                label: "Collections",
+                                                content: <ProcessInputMounts mounts={mounts || []} />,
+                                            },
+                                            {
+                                                show: hasOutputCollecton,
+                                                label: "Collection",
+                                                content: <ProcessOutputCollection outputUuid={outputUuid} />,
+                                            },
+                                            {
+                                                show: isRawLoaded,
+                                                label: "JSON",
+                                                content: <div className={classes.jsonWrapper}>
+                                                    <ProcessIORaw data={raw} />
+                                                </div>,
+                                            },
+                                        ]}
+                                    />
                                 ) : (
                                     <Grid
                                         container
@@ -709,6 +662,32 @@ const ProcessInputMounts = withStyles(styles)(
     ))
 );
 
+type ProcessOutputCollectionProps = {outputUuid: string | undefined} &  WithStyles<CssRules>;
+
+const ProcessOutputCollection = withStyles(styles)(({ outputUuid, classes }: ProcessOutputCollectionProps) => (
+    <div className={classes.tableWrapper}>
+        <>
+            {outputUuid && (
+                <Typography className={classes.collectionLink}>
+                    Output Collection:{" "}
+                    <MuiLink
+                        className={classes.keepLink}
+                        onClick={() => {
+                            navigateTo(outputUuid || "");
+                        }}
+                    >
+                        {outputUuid}
+                    </MuiLink>
+                </Typography>
+            )}
+            <ProcessOutputCollectionFiles
+                isWritable={false}
+                currentItemUuid={outputUuid}
+            />
+        </>
+    </div>
+));
+
 type FileWithSecondaryFiles = {
     secondaryFiles: File[];
 };

commit 299bde122ec3174db566a19a82862f3f138215fe
Author: Stephen Smith <stephen at curii.com>
Date:   Thu Apr 18 16:24:24 2024 -0400

    21642: Remove unused import
    
    Arvados-DCO-1.1-Signed-off-by: Stephen Smith <stephen at curii.com>

diff --git a/services/workbench2/src/views-components/sharing-dialog/sharing-invitation-form-component.tsx b/services/workbench2/src/views-components/sharing-dialog/sharing-invitation-form-component.tsx
index 19fcf58849..cf463fa76a 100644
--- a/services/workbench2/src/views-components/sharing-dialog/sharing-invitation-form-component.tsx
+++ b/services/workbench2/src/views-components/sharing-dialog/sharing-invitation-form-component.tsx
@@ -4,7 +4,7 @@
 
 import React from 'react';
 import { Field, WrappedFieldProps, FieldArray, WrappedFieldArrayProps } from 'redux-form';
-import { Grid, FormControl, InputLabel, StyleRulesCallback, Divider } from '@material-ui/core';
+import { Grid, FormControl, InputLabel, StyleRulesCallback } from '@material-ui/core';
 import { PermissionSelect, parsePermissionLevel, formatPermissionLevel } from './permission-select';
 import { ParticipantSelect, Participant } from './participant-select';
 import { WithStyles } from '@material-ui/core/styles';

commit 4f6ff6b6106becc0a8a1f766b439395320adb5be
Author: Stephen Smith <stephen at curii.com>
Date:   Tue Apr 16 10:29:23 2024 -0400

    21642: Update caniuse
    
    Arvados-DCO-1.1-Signed-off-by: Stephen Smith <stephen at curii.com>

diff --git a/services/workbench2/package.json b/services/workbench2/package.json
index 71dc4d7cee..139b58f3d6 100644
--- a/services/workbench2/package.json
+++ b/services/workbench2/package.json
@@ -30,7 +30,7 @@
     "@types/shell-escape": "^0.2.0",
     "axios": "^0.28.1",
     "bootstrap": "^5.3.2",
-    "caniuse-lite": "1.0.30001606",
+    "caniuse-lite": "1.0.30001610",
     "classnames": "2.2.6",
     "cwlts": "1.15.29",
     "date-fns": "^2.28.0",
diff --git a/services/workbench2/yarn.lock b/services/workbench2/yarn.lock
index de1da9a858..985a6fa973 100644
--- a/services/workbench2/yarn.lock
+++ b/services/workbench2/yarn.lock
@@ -4165,7 +4165,7 @@ __metadata:
     axios: ^0.28.1
     axios-mock-adapter: 1.17.0
     bootstrap: ^5.3.2
-    caniuse-lite: 1.0.30001606
+    caniuse-lite: 1.0.30001610
     classnames: 2.2.6
     cwlts: 1.15.29
     cypress: ^13.6.6
@@ -5373,10 +5373,10 @@ __metadata:
   languageName: node
   linkType: hard
 
-"caniuse-lite at npm:1.0.30001606, caniuse-lite at npm:^1.0.0, caniuse-lite at npm:^1.0.30000981, caniuse-lite at npm:^1.0.30001035, caniuse-lite at npm:^1.0.30001109, caniuse-lite at npm:^1.0.30001541, caniuse-lite at npm:^1.0.30001587":
-  version: 1.0.30001606
-  resolution: "caniuse-lite at npm:1.0.30001606"
-  checksum: fcf2d799d8cb159f4f5b44cd9d2171b18df4bcfdf2770cc8a79c4bb0bc5fd19ed089854223865ced32eacffb60a0a9257c8a1d0ef239e9dc3909f587727e9bb5
+"caniuse-lite at npm:1.0.30001610, caniuse-lite at npm:^1.0.0, caniuse-lite at npm:^1.0.30000981, caniuse-lite at npm:^1.0.30001035, caniuse-lite at npm:^1.0.30001109, caniuse-lite at npm:^1.0.30001541, caniuse-lite at npm:^1.0.30001587":
+  version: 1.0.30001610
+  resolution: "caniuse-lite at npm:1.0.30001610"
+  checksum: 580c7367aafd7e524f4e3f0e8b22ac08d081a4d44ceece211f1758e214df9a87961750fb1e1ee28a2cd2830f0daf3edafe5e1d87bf1eefbbe7c6cf3d00e2979d
   languageName: node
   linkType: hard
 

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


hooks/post-receive
-- 




More information about the arvados-commits mailing list