Przeglądaj źródła

HPCC-25198 Add React Search Page

Signed-off-by: Gordon Smith <GordonJSmith@gmail.com>
Gordon Smith 4 lat temu
rodzic
commit
9f2d73453d

+ 27 - 83
esp/src/eclwatch/SearchResultsWidget.js

@@ -23,12 +23,13 @@ define([
     "src/WsDfu",
     "hpcc/DelayLoadWidget",
     "src/ESPUtil",
+    "src/ESPSearch",
     "src/Utility"
 
 ], function (declare, lang, nlsHPCCMod, arrayUtil, Memory, Observable, on, all,
     Standby, validate,
     selector,
-    GridDetailsWidget, WsWorkunits, ESPWorkunit, ESPDFUWorkunit, ESPLogicalFile, ESPQuery, FileSpray, WsDfu, DelayLoadWidget, ESPUtil, Utility) {
+    GridDetailsWidget, WsWorkunits, ESPWorkunit, ESPDFUWorkunit, ESPLogicalFile, ESPQuery, FileSpray, WsDfu, DelayLoadWidget, ESPUtil, ESPSearch, Utility) {
 
     var nlsHPCC = nlsHPCCMod.default;
     return declare("SearchResultsWidget", [GridDetailsWidget], {
@@ -398,8 +399,8 @@ define([
         },
 
         searchAll: function () {
-            var context = this;
             this.standby.show();
+
             if (validate.isNumberFormat(this.searchText, { format: ["W########-######", "W########-######-#???"] })) {
                 var tab = this.ensurePane({
                     id: this.searchText,
@@ -409,6 +410,7 @@ define([
                     _wuid: this.searchText
                 }, {});
                 this.selectChild(tab);
+
             } else if (validate.isNumberFormat(this.searchText, { format: ["D########-######", "D########-######-#???"] })) {
                 var tab = this.ensurePane({
                     id: this.searchText,
@@ -420,89 +422,31 @@ define([
                 this.selectChild(tab);
             }
 
-            var searchECL = false;
-            var searchECLText = false;
-            var searchDFU = false;
-            var searchFile = false;
-            var searchQuery = false;
-            var searchText = "";
-            if (this.searchText.indexOf("ecl:") === 0) {
-                this.selectChild(this.eclTab);
-                searchECL = true;
-                searchECLText = true;
-                searchText = this.searchText.substring(4);
-            } else if (this.searchText.indexOf("dfu:") === 0) {
-                this.selectChild(this.dfuTab);
-                searchDFU = true;
-                searchText = this.searchText.substring(4);
-            } else if (this.searchText.indexOf("file:") === 0) {
-                this.selectChild(this.fileTab);
-                searchFile = true;
-                searchText = this.searchText.substring(5);
-            } else if (this.searchText.indexOf("query:") === 0) {
-                this.selectChild(this.queryTab);
-                searchQuery = true;
-                searchText = this.searchText.substring(6);
-            } else {
-                this.selectChild(this.gridTab);
-                searchECL = true;
-                searchDFU = true;
-                searchFile = true;
-                searchQuery = true;
-                searchText = this.searchText;
-            }
-            searchText = searchText.trim();
+            const specificSearch = ESPSearch.searchAll(this.searchText,
+                (what, response) => { this.loadWUQueryResponse(what, response); },
+                (what, response) => { this.loadGetDFUWorkunitResponse(what, response); },
+                (what, response) => { this.loadGetDFUWorkunitsResponse(what, response); },
+                (what, response) => { this.loadDFUQueryResponse(what, response); },
+                (what, response) => { this.loadWUListQueriesResponse(what, response); },
+                (searchCount) => { },
+                (success) => { this.standby.hide(); });
 
-            var searchArray = [];
-            if (searchECL) {
-                searchArray.push(WsWorkunits.WUQuery({ request: { Wuid: "*" + searchText + "*" }, suppressExceptionToaster: true }).then(function (response) {
-                    context.loadWUQueryResponse(context.i18n.WUID, response);
-                }));
-                searchArray.push(WsWorkunits.WUQuery({ request: { Jobname: "*" + searchText + "*" } }).then(function (response) {
-                    context.loadWUQueryResponse(context.i18n.JobName, response);
-                }));
-                searchArray.push(WsWorkunits.WUQuery({ request: { Owner: searchText } }).then(function (response) {
-                    context.loadWUQueryResponse(context.i18n.Owner, response);
-                }));
-            }
-            if (searchECLText) {
-                searchArray.push(WsWorkunits.WUQuery({ request: { ECL: searchText } }).then(function (response) {
-                    context.loadWUQueryResponse(context.i18n.ECL, response);
-                }));
-            }
-            if (searchDFU) {
-                searchArray.push(FileSpray.GetDFUWorkunit({ request: { wuid: "*" + searchText + "*" }, suppressExceptionToaster: true }).then(function (response) {
-                    context.loadGetDFUWorkunitResponse(context.i18n.WUID, response);
-                }));
-                searchArray.push(FileSpray.GetDFUWorkunits({ request: { Jobname: "*" + searchText + "*" } }).then(function (response) {
-                    context.loadGetDFUWorkunitsResponse(context.i18n.JobName, response);
-                }));
-                searchArray.push(FileSpray.GetDFUWorkunits({ request: { Owner: searchText } }).then(function (response) {
-                    context.loadGetDFUWorkunitsResponse(context.i18n.Owner, response);
-                }));
-            }
-            if (searchFile) {
-                searchArray.push(WsDfu.DFUQuery({ request: { LogicalName: "*" + searchText + "*" } }).then(function (response) {
-                    context.loadDFUQueryResponse(context.i18n.LogicalName, response);
-                }));
-                searchArray.push(WsDfu.DFUQuery({ request: { Owner: searchText } }).then(function (response) {
-                    context.loadDFUQueryResponse(context.i18n.Owner, response);
-                }));
-            }
-            if (searchQuery) {
-                searchArray.push(WsWorkunits.WUListQueries({ request: { QueryID: "*" + searchText + "*" } }).then(function (response) {
-                    context.loadWUListQueriesResponse(context.i18n.ID, response);
-                }));
-                searchArray.push(WsWorkunits.WUListQueries({ request: { QueryName: "*" + searchText + "*" } }).then(function (response) {
-                    context.loadWUListQueriesResponse(context.i18n.Name, response);
-                }));
+            switch (specificSearch) {
+                case "ecl":
+                    this.selectChild(this.eclTab);
+                    break;
+                case "dfu":
+                    this.selectChild(this.dfuTab);
+                    break;
+                case "file":
+                    this.selectChild(this.fileTab);
+                    break;
+                case "query":
+                    this.selectChild(this.queryTab);
+                    break;
+                default:
+                    this.selectChild(this.gridTab);
             }
-
-            all(searchArray).then(function (results) {
-                context.standby.hide();
-            }, function (error) {
-                context.standby.hide();
-            });
         },
 
         refreshGrid: function (args) {

+ 1 - 2
esp/src/src-react/components/Menu.tsx

@@ -9,8 +9,7 @@ const navLinkGroups: INavLinkGroup[] = [
         links: [
             { url: "#/activities", name: nlsHPCC.Activities },
             { url: "#/clusters", name: nlsHPCC.TargetClusters },
-            { url: "#/events", name: nlsHPCC.EventScheduler },
-            { url: "#/search", name: nlsHPCC.SearchResults },
+            { url: "#/events", name: nlsHPCC.EventScheduler }
         ]
     },
     {

+ 161 - 0
esp/src/src-react/components/Search.tsx

@@ -0,0 +1,161 @@
+import * as React from "react";
+import { CommandBar, ContextualMenuItemType, ICommandBarItemProps, Pivot, PivotItem, ProgressIndicator } from "@fluentui/react";
+import { useConst } from "@fluentui/react-hooks";
+import { ESPSearch } from "src/ESPSearch";
+import * as Utility from "src/Utility";
+import nlsHPCC from "src/nlsHPCC";
+import { HolyGrail } from "../layouts/HolyGrail";
+import { ShortVerticalDivider } from "./Common";
+import { DojoGrid, selector } from "./DojoGrid";
+import { Workunits } from "./Workunits";
+import { Files } from "./Files";
+import { Queries } from "./Queries";
+import { DFUWorkunits } from "./DFUWorkunits";
+
+const defaultUIState = {
+    hasSelection: false,
+};
+
+const disabled = { disabled: true, style: { color: "grey" } };
+
+interface SearchProps {
+    searchText: string;
+}
+
+export const Search: React.FunctionComponent<SearchProps> = ({
+    searchText
+}) => {
+
+    const progress = useConst({ value: 0 });
+
+    const [grid, setGrid] = React.useState<any>(undefined);
+    const [mine, setMine] = React.useState(false);
+    const [selection, setSelection] = React.useState([]);
+    const [uiState, setUIState] = React.useState({ ...defaultUIState });
+    const [searchCount, setSearchCount] = React.useState(0);
+
+    const search = useConst(new ESPSearch(
+        searchCount => {
+            setSearchCount(searchCount);
+        },
+        () => {
+            progress.value++;
+            refreshTable();
+        }, () => {
+            setSearchCount(0);
+        }));
+
+    React.useEffect(() => {
+        if (searchText) {
+            progress.value = 0;
+            setSearchCount(0);
+            search.searchAll(searchText);
+            refreshTable();
+        }
+        // eslint-disable-next-line react-hooks/exhaustive-deps
+    }, [searchText]);
+
+    //  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 = `#/workunits/${selection[0].Wuid}`;
+                } else {
+                    for (let i = selection.length - 1; i >= 0; --i) {
+                        window.open(`#/workunits/${selection[i].Wuid}`, "_blank");
+                    }
+                }
+            }
+        },
+        { key: "divider_2", itemType: ContextualMenuItemType.Divider, onRender: () => <ShortVerticalDivider /> },
+        {
+            key: "mine", text: nlsHPCC.Mine, disabled: true, iconProps: { iconName: "Contact" }, canCheck: true, checked: mine,
+            onClick: () => {
+                setMine(!mine);
+            }
+        },
+    ];
+
+    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 gridColumns = useConst({
+        col1: selector({ width: 27, selectorType: "checkbox" }),
+        Type: {
+            label: nlsHPCC.What, width: 108, sortable: true,
+            formatter: function (type, idx) {
+                return "<a href='#' onClick='return false;' rowIndex=" + idx + " class='" + "SearchTypeClick'>" + type + "</a>";
+            }
+        },
+        Reason: { label: nlsHPCC.Where, width: 108, sortable: true },
+        Summary: {
+            label: nlsHPCC.Who, sortable: true,
+            formatter: function (summary) {
+                return "<a href='#' onClick='return false;' class='dgrid-row-url'>" + summary + "</a>";
+            }
+        }
+    });
+
+    const refreshTable = (clearSelection = false) => {
+        grid?.set("query", {});
+        if (clearSelection) {
+            grid?.clearSelection();
+        }
+    };
+
+    //  Selection  ---
+    React.useEffect(() => {
+        const state = { ...defaultUIState };
+
+        for (let i = 0; i < selection.length; ++i) {
+            state.hasSelection = true;
+        }
+        setUIState(state);
+    }, [selection]);
+
+    const [selectedKey, setSelectedKey] = React.useState("all");
+
+    return <HolyGrail
+        header={<Pivot headersOnly={true} onLinkClick={(item: PivotItem) => setSelectedKey(item.props.itemKey!)}>
+            <PivotItem itemKey="all" headerText={nlsHPCC.All} itemCount={search.store.data.length} />
+            <PivotItem itemKey="ecl" headerText={nlsHPCC.ECLWorkunit} headerButtonProps={search.eclStore.data.length === 0 ? disabled : undefined} itemCount={search.eclStore.data.length} />
+            <PivotItem itemKey="dfu" headerText={nlsHPCC.DFUWorkunit} headerButtonProps={search.dfuStore.data.length === 0 ? disabled : undefined} itemCount={search.dfuStore.data.length} />
+            <PivotItem itemKey="file" headerText={nlsHPCC.LogicalFile} headerButtonProps={search.fileStore.data.length === 0 ? disabled : undefined} itemCount={search.fileStore.data.length} />
+            <PivotItem itemKey="query" headerText={nlsHPCC.Query} headerButtonProps={search.queryStore.data.length === 0 ? disabled : undefined} itemCount={search.queryStore.data.length} />
+        </Pivot>}
+        main={selectedKey === "all" ? <HolyGrail
+            header={<>
+                <CommandBar items={buttons} overflowButtonProps={{}} farItems={rightButtons} />
+                <ProgressIndicator progressHidden={searchCount === 0} percentComplete={searchCount === 0 ? 0 : progress.value / searchCount} />
+            </>}
+            main={<DojoGrid store={search.store} columns={gridColumns} setGrid={setGrid} setSelection={setSelection} />}
+        /> : selectedKey === "ecl" ?
+                <Workunits store={search.eclStore} /> : selectedKey === "dfu" ?
+                    <DFUWorkunits store={search.dfuStore} /> : selectedKey === "file" ?
+                        <Files store={search.fileStore} /> : selectedKey === "query" ?
+                            <Queries store={search.queryStore} /> :
+                            undefined
+        }
+    />;
+};

+ 6 - 3
esp/src/src-react/components/Title.tsx

@@ -1,5 +1,7 @@
 import * as React from "react";
-import { Breadcrumb, DefaultPalette, FontSizes, IBreadcrumbItem, IBreadcrumbStyleProps, IBreadcrumbStyles, Image, IStyleFunctionOrObject, Link, Stack, Toggle } from "@fluentui/react";
+import { Breadcrumb, DefaultPalette, FontSizes, IBreadcrumbItem, IBreadcrumbStyleProps, IBreadcrumbStyles, Image, IStyleFunctionOrObject, Link, SearchBox, Stack, Toggle } from "@fluentui/react";
+
+import nlsHPCC from "src/nlsHPCC";
 
 const breadCrumbStyles: IStyleFunctionOrObject<IBreadcrumbStyleProps, IBreadcrumbStyles> = {
     itemLink: { fontSize: FontSizes.size10, lineHeight: 14, paddingLeft: 2, paddingRight: 2 },
@@ -35,9 +37,10 @@ export const DevTitle: React.FunctionComponent<DevTitleProps> = ({
                     <Stack.Item align="center" styles={{ root: { minWidth: 240 } }}>
                         <Breadcrumb items={itemsWithHref} styles={breadCrumbStyles} />
                     </Stack.Item>
+                    <Stack.Item align="center">
+                        <SearchBox onSearch={newValue => { window.location.href = `#/search/${newValue.trim()}`; }} placeholder={nlsHPCC.PlaceholderFindText} styles={{ root: { minWidth: 320 } }} />
+                    </Stack.Item>
                 </Stack>
-                <Stack.Item>
-                </Stack.Item>
                 <Stack horizontal tokens={{ childrenGap: 18 }} >
                     <Stack.Item align="center">
                         <Link href="/esp/files/stub.htm">Legacy ECL Watch</Link>

+ 1 - 1
esp/src/src-react/routes.tsx

@@ -48,7 +48,7 @@ const routes: Routes = [
             { path: "", action: () => import("./layouts/DojoAdapter").then(_ => <_.DojoAdapter widgetClassID="SearchResultsWidget" />) },
             { path: "/:Wuid(W\\d\\d\\d\\d\\d\\d\\d\\d-\\d\\d\\d\\d\\d\\d(?:-\\d+)?)", action: (ctx, params) => import("./layouts/DojoAdapter").then(_ => <_.DojoAdapter widgetClassID="WUDetailsWidget" params={params} />) },
             { path: "/:Wuid(D\\d\\d\\d\\d\\d\\d\\d\\d-\\d\\d\\d\\d\\d\\d(?:-\\d+)?)", action: (ctx, params) => import("./layouts/DojoAdapter").then(_ => <_.DojoAdapter widgetClassID="DFUWUDetailsWidget" params={params} />) },
-            { path: "/:searchText", action: (ctx, { searchText }) => import("./layouts/DojoAdapter").then(_ => <_.DojoAdapter widgetClassID="SearchResultsWidget" />) }
+            { path: "/:searchText", action: (ctx, { searchText }) => import("./components/Search").then(_ => <_.Search searchText={searchText as string} />) }
         ]
     },
     //  ECL  ---

+ 368 - 0
esp/src/src/ESPSearch.ts

@@ -0,0 +1,368 @@
+import * as Memory from "dojo/store/Memory";
+import * as Observable from "dojo/store/Observable";
+import * as ESPWorkunit from "src/ESPWorkunit";
+import * as ESPDFUWorkunit from "src/ESPDFUWorkunit";
+import * as ESPLogicalFile from "src/ESPLogicalFile";
+import * as ESPQuery from "src/ESPQuery";
+import * as WsWorkunits from "./WsWorkunits";
+import * as FileSpray from "./FileSpray";
+import * as WsDfu from "./WsDfu";
+import nlsHPCC from "./nlsHPCC";
+
+export type searchAllResponse = undefined | "ecl" | "dfu" | "file" | "query";
+
+export class ESPSearch {
+
+    protected _searchID = 0;
+    protected id = 0;
+    protected _rowID = 0;
+    protected _searchText: string;
+
+    store = Observable(new Memory());
+    eclStore = Observable(new Memory({ idProperty: "Wuid", data: [] }));
+    dfuStore = Observable(new Memory({ idProperty: "ID", data: [] }));
+    fileStore = Observable(new Memory({ idProperty: "__hpcc_id", data: [] }));
+    queryStore = Observable(new Memory({ idProperty: "__hpcc_id", data: [] }));
+
+    constructor(private begin: (searchCount: number) => void, private update: () => void, private end: (success: boolean) => void) {
+    }
+
+    searchAll(searchText: string): searchAllResponse {
+        const searchID = ++this._searchID;
+        ++this.id;
+
+        this.store.setData([]);
+        this.eclStore.setData([]);
+        this.dfuStore.setData([]);
+        this.fileStore.setData([]);
+        this.queryStore.setData([]);
+
+        let searchECL = false;
+        let searchECLText = false;
+        let searchDFU = false;
+        let searchFile = false;
+        let searchQuery = false;
+
+        let retVal: searchAllResponse = undefined;
+
+        if (searchText.indexOf("ecl:") === 0) {
+            retVal = "ecl";
+            searchECL = true;
+            searchECLText = true;
+            searchText = searchText.substring(4);
+        } else if (searchText.indexOf("dfu:") === 0) {
+            searchDFU = true;
+            searchText = searchText.substring(4);
+        } else if (searchText.indexOf("file:") === 0) {
+            searchFile = true;
+            searchText = searchText.substring(5);
+        } else if (searchText.indexOf("query:") === 0) {
+            searchQuery = true;
+            searchText = searchText.substring(6);
+        } else {
+            searchECL = true;
+            searchDFU = true;
+            searchFile = true;
+            searchQuery = true;
+        }
+        searchText = searchText.trim();
+
+        const searchArray = [];
+        if (searchECL) {
+            searchArray.push(WsWorkunits.WUQuery({ request: { Wuid: "*" + searchText + "*" }, suppressExceptionToaster: true }).then(response => {
+                if (searchID === this._searchID) {
+                    this.loadWUQueryResponse(nlsHPCC.WUID, response);
+                    this.update();
+                }
+            }));
+            searchArray.push(WsWorkunits.WUQuery({ request: { Jobname: "*" + searchText + "*" } }).then(response => {
+                if (searchID === this._searchID) {
+                    this.loadWUQueryResponse(nlsHPCC.JobName, response);
+                    this.update();
+                }
+            }));
+            searchArray.push(WsWorkunits.WUQuery({ request: { Owner: searchText } }).then(response => {
+                if (searchID === this._searchID) {
+                    this.loadWUQueryResponse(nlsHPCC.Owner, response);
+                    this.update();
+                }
+            }));
+        }
+        if (searchECLText) {
+            searchArray.push(WsWorkunits.WUQuery({ request: { ECL: searchText } }).then(response => {
+                if (searchID === this._searchID) {
+                    this.loadWUQueryResponse(nlsHPCC.ECL, response);
+                    this.update();
+                }
+            }));
+        }
+        if (searchDFU) {
+            searchArray.push(FileSpray.GetDFUWorkunit({ request: { wuid: "*" + searchText + "*" }, suppressExceptionToaster: true }).then(response => {
+                if (searchID === this._searchID) {
+                    this.loadGetDFUWorkunitResponse(nlsHPCC.WUID, response);
+                    this.update();
+                }
+            }));
+            searchArray.push(FileSpray.GetDFUWorkunits({ request: { Jobname: "*" + searchText + "*" } }).then(response => {
+                if (searchID === this._searchID) {
+                    this.loadGetDFUWorkunitsResponse(nlsHPCC.JobName, response);
+                    this.update();
+                }
+            }));
+            searchArray.push(FileSpray.GetDFUWorkunits({ request: { Owner: searchText } }).then(response => {
+                if (searchID === this._searchID) {
+                    this.loadGetDFUWorkunitsResponse(nlsHPCC.Owner, response);
+                    this.update();
+                }
+            }));
+        }
+        if (searchFile) {
+            searchArray.push(WsDfu.DFUQuery({ request: { LogicalName: "*" + searchText + "*" } }).then(response => {
+                if (searchID === this._searchID) {
+                    this.loadDFUQueryResponse(nlsHPCC.LogicalName, response);
+                    this.update();
+                }
+            }));
+            searchArray.push(WsDfu.DFUQuery({ request: { Owner: searchText } }).then(response => {
+                if (searchID === this._searchID) {
+                    this.loadDFUQueryResponse(nlsHPCC.Owner, response);
+                    this.update();
+                }
+            }));
+        }
+        if (searchQuery) {
+            searchArray.push(WsWorkunits.WUListQueries({ request: { QueryID: "*" + searchText + "*" } }).then(response => {
+                if (searchID === this._searchID) {
+                    this.loadWUListQueriesResponse(nlsHPCC.ID, response);
+                    this.update();
+                }
+            }));
+            searchArray.push(WsWorkunits.WUListQueries({ request: { QueryName: "*" + searchText + "*" } }).then(response => {
+                if (searchID === this._searchID) {
+                    this.loadWUListQueriesResponse(nlsHPCC.Name, response);
+                    this.update();
+                }
+            }));
+        }
+
+        //  Always true  ---
+        this.begin(searchArray.length);
+        Promise.all(searchArray).then(results => {
+            if (searchID === this._searchID)
+                this.end(true);
+        }).catch(e => {
+            if (searchID === this._searchID)
+                this.end(false);
+
+        });
+
+        return retVal;
+    }
+
+    loadWUQueryResponse(prefix, response) {
+        const workunits = response?.WUQueryResponse?.Workunits?.ECLWorkunit;
+        if (workunits) {
+            workunits.forEach((item, idx) => {
+                this.store.add({
+                    storeID: ++this._rowID,
+                    id: this.id + item.Wuid,
+                    Type: nlsHPCC.ECLWorkunit,
+                    Reason: prefix,
+                    Summary: item.Wuid,
+                    _type: "Wuid",
+                    _wuid: item.Wuid
+                });
+                this.eclStore.add(ESPWorkunit.Get(item.Wuid, item), { overwrite: true });
+            });
+            return workunits.length;
+        }
+        return 0;
+    }
+
+    loadGetDFUWorkunitResponse(prefix, response) {
+        const workunit = response?.GetDFUWorkunitResponse?.result;
+        if (workunit && workunit.State !== 999) {
+            this.store.add({
+                storeID: ++this._rowID,
+                id: this.id + workunit.ID,
+                Type: nlsHPCC.DFUWorkunit,
+                Reason: prefix,
+                Summary: workunit.ID,
+                _type: "DFUWuid",
+                _wuid: workunit.ID
+            });
+            this.dfuStore.add(ESPDFUWorkunit.Get(workunit.ID, workunit), { overwrite: true });
+            return 1;
+        }
+        return 0;
+    }
+
+    loadGetDFUWorkunitsResponse(prefix, response) {
+        const workunits = response?.GetDFUWorkunitsResponse?.results?.DFUWorkunit;
+        if (workunits) {
+            workunits.forEach((item, idx) => {
+                this.store.add({
+                    storeID: ++this._rowID,
+                    id: this.id + item.ID,
+                    Type: nlsHPCC.DFUWorkunit,
+                    Reason: prefix,
+                    Summary: item.ID,
+                    _type: "DFUWuid",
+                    _wuid: item.ID
+                });
+                this.dfuStore.add(ESPDFUWorkunit.Get(item.ID, item), { overwrite: true });
+            });
+            return workunits.length;
+        }
+        return 0;
+    }
+
+    loadDFUQueryResponse(prefix, response) {
+        const items = response?.DFUQueryResponse?.DFULogicalFiles?.DFULogicalFile;
+        if (items) {
+            items.forEach((item, idx) => {
+                if (item.isSuperfile) {
+                    this.store.add({
+                        storeID: ++this._rowID,
+                        id: this.id + item.Name,
+                        Type: nlsHPCC.SuperFile,
+                        Reason: prefix,
+                        Summary: item.Name,
+                        _type: "SuperFile",
+                        _name: item.Name
+                    });
+                } else {
+                    this.store.add({
+                        storeID: ++this._rowID,
+                        id: this.id + item.Name,
+                        Type: nlsHPCC.LogicalFile,
+                        Reason: prefix,
+                        Summary: item.Name + " (" + item.NodeGroup + ")",
+                        _type: "LogicalFile",
+                        _nodeGroup: item.NodeGroup,
+                        _name: item.Name
+                    });
+                }
+                this.fileStore.add(ESPLogicalFile.Get(item.NodeGroup, item.Name, item), { overwrite: true });
+            });
+            return items.length;
+        }
+        return 0;
+    }
+
+    loadWUListQueriesResponse(prefix, response) {
+        const items = response?.WUListQueriesResponse?.QuerysetQueries?.QuerySetQuery;
+        if (items) {
+            items.forEach((item, idx) => {
+                this.store.add({
+                    storeID: ++this._rowID,
+                    id: this.id + item.QuerySetId + "::" + item.Id,
+                    Type: nlsHPCC.Query,
+                    Reason: prefix,
+                    Summary: item.Name + " (" + item.QuerySetId + " - " + item.Id + ")",
+                    _type: "Query",
+                    _querySetId: item.QuerySetId,
+                    _id: item.Id
+                });
+                this.queryStore.add(ESPQuery.Get(item.QuerySetId, item.Id, item), { overwrite: true });
+            });
+            return items.length;
+        }
+        return 0;
+    }
+}
+
+export function searchAll(searchText: string,
+    loadWUQueryResponse: (what: string, response: any) => void,
+    loadGetDFUWorkunitResponse: (what: string, response: any) => void,
+    loadGetDFUWorkunitsResponse: (what: string, response: any) => void,
+    loadDFUQueryResponse: (what: string, response: any) => void,
+    loadWUListQueriesResponse: (what: string, response: any) => void,
+    start: (searchCount: number) => void,
+    done: (success: boolean) => void,
+): searchAllResponse {
+
+    let searchECL = false;
+    let searchECLText = false;
+    let searchDFU = false;
+    let searchFile = false;
+    let searchQuery = false;
+
+    let retVal: searchAllResponse = undefined;
+
+    if (searchText.indexOf("ecl:") === 0) {
+        retVal = "ecl";
+        searchECL = true;
+        searchECLText = true;
+        searchText = searchText.substring(4);
+    } else if (searchText.indexOf("dfu:") === 0) {
+        searchDFU = true;
+        searchText = searchText.substring(4);
+    } else if (searchText.indexOf("file:") === 0) {
+        searchFile = true;
+        searchText = searchText.substring(5);
+    } else if (searchText.indexOf("query:") === 0) {
+        searchQuery = true;
+        searchText = searchText.substring(6);
+    } else {
+        searchECL = true;
+        searchDFU = true;
+        searchFile = true;
+        searchQuery = true;
+    }
+    searchText = searchText.trim();
+
+    const searchArray = [];
+    if (searchECL) {
+        searchArray.push(WsWorkunits.WUQuery({ request: { Wuid: "*" + searchText + "*" }, suppressExceptionToaster: true }).then(function (response) {
+            loadWUQueryResponse(nlsHPCC.WUID, response);
+        }));
+        searchArray.push(WsWorkunits.WUQuery({ request: { Jobname: "*" + searchText + "*" } }).then(function (response) {
+            loadWUQueryResponse(nlsHPCC.JobName, response);
+        }));
+        searchArray.push(WsWorkunits.WUQuery({ request: { Owner: searchText } }).then(function (response) {
+            loadWUQueryResponse(nlsHPCC.Owner, response);
+        }));
+    }
+    if (searchECLText) {
+        searchArray.push(WsWorkunits.WUQuery({ request: { ECL: searchText } }).then(function (response) {
+            loadWUQueryResponse(nlsHPCC.ECL, response);
+        }));
+    }
+    if (searchDFU) {
+        searchArray.push(FileSpray.GetDFUWorkunit({ request: { wuid: "*" + searchText + "*" }, suppressExceptionToaster: true }).then(function (response) {
+            loadGetDFUWorkunitResponse(nlsHPCC.WUID, response);
+        }));
+        searchArray.push(FileSpray.GetDFUWorkunits({ request: { Jobname: "*" + searchText + "*" } }).then(function (response) {
+            loadGetDFUWorkunitsResponse(nlsHPCC.JobName, response);
+        }));
+        searchArray.push(FileSpray.GetDFUWorkunits({ request: { Owner: searchText } }).then(function (response) {
+            loadGetDFUWorkunitsResponse(nlsHPCC.Owner, response);
+        }));
+    }
+    if (searchFile) {
+        searchArray.push(WsDfu.DFUQuery({ request: { LogicalName: "*" + searchText + "*" } }).then(function (response) {
+            loadDFUQueryResponse(nlsHPCC.LogicalName, response);
+        }));
+        searchArray.push(WsDfu.DFUQuery({ request: { Owner: searchText } }).then(function (response) {
+            loadDFUQueryResponse(nlsHPCC.Owner, response);
+        }));
+    }
+    if (searchQuery) {
+        searchArray.push(WsWorkunits.WUListQueries({ request: { QueryID: "*" + searchText + "*" } }).then(function (response) {
+            loadWUListQueriesResponse(nlsHPCC.ID, response);
+        }));
+        searchArray.push(WsWorkunits.WUListQueries({ request: { QueryName: "*" + searchText + "*" } }).then(function (response) {
+            loadWUListQueriesResponse(nlsHPCC.Name, response);
+        }));
+    }
+
+    start(searchArray.length);
+    Promise.all(searchArray).then(function (results) {
+        done(true);
+    }, function (error) {
+        done(false);
+    });
+
+    return retVal;
+}