Explorar el Código

Merge pull request #14577 from GordonSmith/HPCC-25350-SourceFiles

HPCC-25350 React WU SourceFiles

Reviewed-By: Jaman Brundage <jaman.brundage@lexisnexisrisk.com>
Reviewed-By: Richard Chapman <rchapman@hpccsystems.com>
Richard Chapman hace 4 años
padre
commit
5c93784ea5

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 1075 - 756
esp/src/package-lock.json


+ 2 - 3
esp/src/package.json

@@ -37,13 +37,12 @@
     "@fluentui/react-button": "^1.0.0-beta.18",
     "@fluentui/react-cards": "^1.0.0-beta.34",
     "@fluentui/react-hooks": "^8.0.0-beta.0",
-    "@fluentui/react-theme-provider": "^1.0.0-beta.14",
     "@hpcc-js/chart": "2.55.0",
     "@hpcc-js/codemirror": "2.36.0",
     "@hpcc-js/common": "2.45.0",
-    "@hpcc-js/comms": "2.36.0",
+    "@hpcc-js/comms": "2.38.0",
     "@hpcc-js/dataflow": "3.0.1",
-    "@hpcc-js/eclwatch": "2.26.0",
+    "@hpcc-js/eclwatch": "2.28.0",
     "@hpcc-js/graph": "2.47.0",
     "@hpcc-js/html": "2.20.0",
     "@hpcc-js/layout": "2.22.0",

+ 1 - 1
esp/src/src-react/components/Frame.tsx

@@ -1,5 +1,5 @@
 import * as React from "react";
-import { ThemeProvider } from "@fluentui/react-theme-provider";
+import { ThemeProvider } from "@fluentui/react";
 import { HolyGrail } from "../layouts/HolyGrail";
 import { hashHistory } from "../util/history";
 import { router } from "../routes";

+ 134 - 0
esp/src/src-react/components/SourceFiles.tsx

@@ -0,0 +1,134 @@
+import * as React from "react";
+import { CommandBar, ContextualMenuItemType, ICommandBarItemProps } from "@fluentui/react";
+import { useConst } from "@fluentui/react-hooks";
+import * as Observable from "dojo/store/Observable";
+import * as domClass from "dojo/dom-class";
+import { AlphaNumSortMemory } from "src/Memory";
+import * as Utility from "src/Utility";
+import nlsHPCC from "src/nlsHPCC";
+import { useWorkunitSourceFiles } from "../hooks/Workunit";
+import { HolyGrail } from "../layouts/HolyGrail";
+import { ShortVerticalDivider } from "./Common";
+import { DojoGrid, selector, tree } from "./DojoGrid";
+
+const defaultUIState = {
+    hasSelection: false
+};
+
+interface SourceFilesProps {
+    wuid: string;
+}
+
+class TreeStore extends AlphaNumSortMemory {
+
+    mayHaveChildren(item) {
+        return item.IsSuperFile;
+    }
+
+    getChildren(parent, options) {
+        return this.query({ __hpcc_parentName: parent.Name }, options);
+    }
+}
+
+export const SourceFiles: React.FunctionComponent<SourceFilesProps> = ({
+    wuid
+}) => {
+
+    const [grid, setGrid] = React.useState<any>(undefined);
+    const [selection, setSelection] = React.useState([]);
+    const [uiState, setUIState] = React.useState({ ...defaultUIState });
+    const [variables] = useWorkunitSourceFiles(wuid);
+
+    //  Command Bar  ---
+    const buttons: ICommandBarItemProps[] = [
+        {
+            key: "refresh", text: nlsHPCC.Refresh, iconProps: { iconName: "Refresh" },
+            onClick: () => refreshTable()
+        },
+        { key: "divider_1", itemType: ContextualMenuItemType.Divider, onRender: () => <ShortVerticalDivider /> },
+        {
+            key: "open", text: nlsHPCC.Open, disabled: !uiState.hasSelection, iconProps: { iconName: "WindowEdit" },
+            onClick: () => {
+                if (selection.length === 1) {
+                    window.location.href = `#/files/${selection[0].Name}`;
+                } else {
+                    for (let i = selection.length - 1; i >= 0; --i) {
+                        window.open(`#/files/${selection[i].Name}`, "_blank");
+                    }
+                }
+            }
+        },
+    ];
+
+    const rightButtons: ICommandBarItemProps[] = [
+        {
+            key: "copy", text: nlsHPCC.CopyWUIDs, disabled: !uiState.hasSelection || !navigator?.clipboard?.writeText, iconOnly: true, iconProps: { iconName: "Copy" },
+            onClick: () => {
+                const wuids = selection.map(s => s.Wuid);
+                navigator?.clipboard?.writeText(wuids.join("\n"));
+            }
+        },
+        {
+            key: "download", text: nlsHPCC.DownloadToCSV, disabled: !uiState.hasSelection, iconOnly: true, iconProps: { iconName: "Download" },
+            onClick: () => {
+                Utility.downloadToCSV(grid, selection.map(row => ([row.Protected, row.Wuid, row.Owner, row.Jobname, row.Cluster, row.RoxieCluster, row.State, row.TotalClusterTime])), "workunits.csv");
+            }
+        }
+    ];
+
+    //  Grid ---
+    const gridStore = useConst(new Observable(new TreeStore("Name", { Name: true, Value: true })));
+    const gridSort = useConst([{ attribute: "Name", "descending": false }]);
+    const gridQuery = useConst({ __hpcc_parentName: "" });
+    const gridColumns = useConst({
+        col1: selector({
+            width: 27,
+            selectorType: "checkbox"
+        }),
+        Name: tree({
+            label: "Name", sortable: true,
+            formatter: function (Name, row) {
+                return Utility.getImageHTML(row.IsSuperFile ? "folder_table.png" : "file.png") + "&nbsp;<a href='#' onClick='return false;' class='dgrid-row-url'>" + Name + "</a>";
+            }
+        }),
+        FileCluster: { label: nlsHPCC.FileCluster, width: 300, sortable: false },
+        Count: {
+            label: nlsHPCC.Usage, width: 72, sortable: true,
+            renderCell: function (object, value, node, options) {
+                domClass.add(node, "justify-right");
+                node.innerText = Utility.valueCleanUp(value);
+            },
+        }
+    });
+
+    const refreshTable = (clearSelection = false) => {
+        grid?.set("query", gridQuery);
+        if (clearSelection) {
+            grid?.clearSelection();
+        }
+    };
+
+    //  Selection  ---
+    React.useEffect(() => {
+        const state = { ...defaultUIState };
+
+        for (let i = 0; i < selection.length; ++i) {
+            state.hasSelection = true;
+            break;
+        }
+        setUIState(state);
+    }, [selection]);
+
+    React.useEffect(() => {
+        gridStore.setData(variables);
+        refreshTable();
+        // eslint-disable-next-line react-hooks/exhaustive-deps
+    }, [gridStore, variables]);
+
+    return <HolyGrail
+        header={<CommandBar items={buttons} overflowButtonProps={{}} farItems={rightButtons} />}
+        main={
+            <DojoGrid store={gridStore} query={gridQuery} sort={gridSort} columns={gridColumns} setGrid={setGrid} setSelection={setSelection} />
+        }
+    />;
+};

+ 89 - 4
esp/src/src-react/hooks/Workunit.ts

@@ -1,7 +1,8 @@
 import * as React from "react";
-import { Workunit, Result, WUStateID } from "@hpcc-js/comms";
+import { Workunit, Result, WUStateID, WUInfo } from "@hpcc-js/comms";
+import nlsHPCC from "src/nlsHPCC";
 
-export function useWorkunit(wuid: string): [Workunit, WUStateID] {
+export function useWorkunit(wuid: string, full: boolean = false): [Workunit, WUStateID] {
 
     const [workunit, setWorkunit] = React.useState<Workunit>();
     const [state, setState] = React.useState<WUStateID>();
@@ -9,13 +10,19 @@ export function useWorkunit(wuid: string): [Workunit, WUStateID] {
     React.useEffect(() => {
         const wu = Workunit.attach({ baseUrl: "" }, wuid);
         const handle = wu.watch(() => {
-            setState(wu.StateID);
+            if (full) {
+                wu.refresh(true).then(() => {
+                setState(wu.StateID);
+                });
+            } else {
+                setState(wu.StateID);
+            }
         });
         setWorkunit(wu);
         return () => {
             handle.release();
         };
-    }, [wuid]);
+    }, [wuid, full]);
 
     return [workunit, state];
 }
@@ -33,3 +40,81 @@ export function useWorkunitResults(wuid: string): [Result[], Workunit, WUStateID
 
     return [results, workunit, state];
 }
+
+export interface Variable {
+    Type: string;
+    Name: string;
+    Value: string;
+}
+
+export function useWorkunitVariables(wuid: string): [Variable[], Workunit, WUStateID] {
+
+    const [workunit, state] = useWorkunit(wuid);
+    const [variables, setVariables] = React.useState<Variable[]>([]);
+
+    React.useEffect(() => {
+        workunit?.fetchInfo({
+            IncludeVariables: true,
+            IncludeApplicationValues: true,
+            IncludeDebugValues: true
+        }).then(response => {
+            const vars: Variable[] = response?.Workunit?.Variables?.ECLResult?.map(row => {
+                return {
+                    Type: nlsHPCC.ECL,
+                    Name: row.Name,
+                    Value: row.Value
+                };
+            }) || [];
+            const appData: Variable[] = response?.Workunit?.ApplicationValues?.ApplicationValue.map(row => {
+                return {
+                    Type: row.Application,
+                    Name: row.Name,
+                    Value: row.Value
+                };
+            }) || [];
+            const debugData: Variable[] = response?.Workunit?.DebugValues?.DebugValue.map(row => {
+                return {
+                    Type: nlsHPCC.Debug,
+                    Name: row.Name,
+                    Value: row.Value
+                };
+            }) || [];
+            setVariables([...vars, ...appData, ...debugData]);
+        });
+    }, [workunit, state]);
+
+    return [variables, workunit, state];
+}
+
+export interface SourceFile extends WUInfo.ECLSourceFile {
+    __hpcc_parentName: string;
+}
+
+export function useWorkunitSourceFiles(wuid: string): [SourceFile[], Workunit, WUStateID] {
+
+    const [workunit, state] = useWorkunit(wuid);
+    const [sourceFiles, setSourceFiles] = React.useState<SourceFile[]>([]);
+
+    React.useEffect(() => {
+        workunit?.fetchInfo({
+            IncludeSourceFiles: true
+        }).then(response => {
+            const sourceFiles: SourceFile[] = [];
+            response?.Workunit?.SourceFiles?.ECLSourceFile.forEach(sourceFile => {
+                sourceFiles.push({
+                    __hpcc_parentName: "",
+                    ...sourceFile
+                });
+                sourceFile?.ECLSourceFiles?.ECLSourceFile.forEach(childSourceFile => {
+                    sourceFiles.push({
+                        __hpcc_parentName: sourceFile.Name,
+                        ...childSourceFile
+                    });
+                });
+            });
+            setSourceFiles(sourceFiles);
+        });
+    }, [workunit, state]);
+
+    return [sourceFiles, workunit, state];
+}