Skip to content

Commit

Permalink
HPCC-31074 Refactor ECL Archive Widget to React
Browse files Browse the repository at this point in the history
Fixes resize issue with ECL Archive Widget.  Also is first step for direct
link to lines of ECL Code https://track.hpccsystems.com/browse/HPCC-30997

Signed-off-by: Gordon Smith <[email protected]>
  • Loading branch information
GordonSmith committed Jan 9, 2024
1 parent 8fa13a2 commit cb90461
Show file tree
Hide file tree
Showing 6 changed files with 774 additions and 481 deletions.
220 changes: 220 additions & 0 deletions esp/src/src-react/components/ECLArchive.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
import * as React from "react";
import { CommandBar, ContextualMenuItemType, ICommandBarItemProps } from "@fluentui/react";
import { useConst } from "@fluentui/react-hooks";
import { Table } from "@hpcc-js/dgrid";
import { DirectoryTree } from "@hpcc-js/tree";
import { ECLEditor } from "@hpcc-js/codemirror";
import { scopedLogger, xml2json } from "@hpcc-js/util";
import nlsHPCC from "src/nlsHPCC";
import { useWorkunitArchive, useWorkunitQuery } from "../hooks/workunit";
import { useWorkunitMetrics } from "../hooks/metrics";
import { useUserTheme } from "../hooks/theme";
import { HolyGrail } from "../layouts/HolyGrail";
import { DockPanel, DockPanelItems, ResetableDockPanel } from "../layouts/DockPanel";
import { AutosizeHpccJSComponent } from "../layouts/HpccJSAdapter";
import { ShortVerticalDivider } from "./Common";
import { addMarkers, buildMarkerData, recursiveSort, updateSummary, walk } from "src/ECLArchiveWidget";
import { WUDetails } from "@hpcc-js/comms";

const logger = scopedLogger("src-react/components/ECLArchive.tsx");

interface ECLArchiveProps {
wuid: string;
parentUrl?: string;
}

const scopeFilterDefault: WUDetails.RequestNS.ScopeFilter = {
MaxDepth: 999999,
ScopeTypes: ["graph"]
};

const nestedFilterDefault: WUDetails.RequestNS.NestedFilter = {
Depth: 999999,
ScopeTypes: ["activity"]
};

export const ECLArchive: React.FunctionComponent<ECLArchiveProps> = ({
wuid,
parentUrl = `/workunits/${wuid}/eclsummary`
}) => {
logger.debug({ wuid, parentUrl });

const [fullscreen, setFullscreen] = React.useState<boolean>(false);
const [query, workunit, _state, refreshQuery] = useWorkunitQuery(wuid);
const [archiveXmlStr, _workunit2, _state2, refreshArchive] = useWorkunitArchive(wuid);
const [dockpanel, setDockpanel] = React.useState<ResetableDockPanel>();
const [metrics, _columns, _activities, _properties, _measures, _scopeTypes, _fetchStatus, refreshMetrics] = useWorkunitMetrics(wuid, scopeFilterDefault, nestedFilterDefault);
const [selectedMarker, setSelectedMarker] = React.useState<number>(-1);
const { isDark } = useUserTheme();

const directoryTree = useConst(() => {
const retVal = new DirectoryTree()
.textFileIcon("fa fa-spinner fa-spin")
.iconSize(20)
.rowItemPadding(2)
.omitRoot(true)
.data({
label: "root",
children: [
{
label: ""
}
]
});
return retVal;
}
);

const editor = useConst(() =>
new ECLEditor()
.readOnly(true)
);

React.useEffect(() => {
editor?.option("theme", isDark ? "darcula" : "default")?.lazyRender();
}, [editor, isDark]);

React.useEffect(() => {
editor?.text(query).lazyRender();
}, [editor, query]);

const summaryTable = useConst(() =>
new Table()
.sortable(true)
.columns(["Name", "Cnt", "Avg", "Sum"])
);

const directoryTreeClick = React.useCallback((contentStr, markers = []) => {
editor.text(contentStr);

const fontFamily = "Verdana";
const fontSize = 12;

const maxLabelWidth = Math.max(
...markers.map(marker => {
return editor.textSize(marker.label, fontFamily, fontSize).width;
})
);

editor.gutterMarkerWidth(maxLabelWidth + 22);
try {
addMarkers(markers, { editor, summaryTable, selectedMarker, setSelectedMarker });
editor.lazyRender();
} catch (e) {
logger.error(e.message);
}
updateSummary(markers, { summaryTable });
}, [editor, selectedMarker, summaryTable]);
directoryTree.rowClick = directoryTreeClick;

React.useEffect(() => {
if (metrics && archiveXmlStr && directoryTreeClick) {
const markerData = buildMarkerData(metrics);
const archive = xml2json(archiveXmlStr);

const data = {
label: ".",
children: []
};
const queryPath = "";

walk((archive as any)._children[0], [], { queryPath, data, markerData });

recursiveSort(data, { queryPath, directoryTreeClick });

let firstTierLabels = data.children.map(n => n.label);

const librariesIdx = firstTierLabels.indexOf("Libraries");
if (librariesIdx !== -1) {
const lib = data.children[librariesIdx];
data.children.splice(librariesIdx, 1);
data.children.push(lib);
firstTierLabels = data.children.map(n => n.label);
}
const resourcesIdx = firstTierLabels.indexOf("Resources");
if (resourcesIdx !== -1) {
const res = data.children[resourcesIdx];
data.children.splice(resourcesIdx, 1);
data.children.push(res);
}
directoryTree
.data({})
.render(() => {
directoryTree
.data(data)
.iconSize(16)
.rowItemPadding(2)
.textFileIcon("fa fa-file-code-o")
.render()
;
});
}
}, [archiveXmlStr, directoryTree, directoryTreeClick, metrics]);

const items = React.useMemo<DockPanelItems>(() => {
const retVal: DockPanelItems = [{
key: "eclEditor",
title: "ECL",
component: <AutosizeHpccJSComponent widget={editor}></AutosizeHpccJSComponent>
}];

if (workunit?.HasArchiveQuery) {
retVal.push({
key: "scopesTable",
title: "Files",
component: <AutosizeHpccJSComponent widget={directoryTree}></AutosizeHpccJSComponent>,
location: "split-left",
ref: "eclEditor"
});
retVal.push({
key: "welcome",
title: "Welcome",
component: <AutosizeHpccJSComponent widget={summaryTable}></AutosizeHpccJSComponent>,
location: "split-bottom",
ref: "scopesTable"
});

setTimeout(() => {
// Should only happen once on startup ---
const layout: any = dockpanel.layout();
if (Array.isArray(layout?.main?.sizes) && layout.main.sizes.length === 2) {
layout.main.sizes = [0.3, 0.7];
dockpanel.layout(layout).lazyRender();
}
}, 0);
}
return retVal;
}, [directoryTree, dockpanel, editor, summaryTable, workunit?.HasArchiveQuery]);

// Command Bar ---
const buttons = React.useMemo((): ICommandBarItemProps[] => [
{
key: "refresh", text: nlsHPCC.Refresh, iconProps: { iconName: "Refresh" },
onClick: () => {
refreshQuery();
refreshArchive();
refreshMetrics();
}
},
{ key: "divider_1", itemType: ContextualMenuItemType.Divider, onRender: () => <ShortVerticalDivider /> },
], [refreshArchive, refreshMetrics, refreshQuery]);

const rightButtons = React.useMemo((): ICommandBarItemProps[] => [
{
key: "copy", text: nlsHPCC.CopyToClipboard, disabled: !navigator?.clipboard?.writeText, iconOnly: true, iconProps: { iconName: "Copy" },
onClick: () => {
navigator?.clipboard?.writeText(editor?.text());
}
}, {
key: "fullscreen", title: nlsHPCC.MaximizeRestore, iconProps: { iconName: fullscreen ? "ChromeRestore" : "FullScreen" },
onClick: () => setFullscreen(!fullscreen)
}
], [editor, fullscreen]);

return <HolyGrail fullscreen={fullscreen}
header={<CommandBar items={buttons} farItems={rightButtons} />}
main={
<DockPanel hideSingleTabs items={items} onDockPanelCreate={setDockpanel} />
}
/>;
};
3 changes: 2 additions & 1 deletion esp/src/src-react/components/WorkunitDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { Variables } from "./Variables";
import { Workflows } from "./Workflows";
import { WorkunitSummary } from "./WorkunitSummary";
import { TabInfo, DelayLoadedPanel, OverflowTabList } from "./controls/TabbedPanes/index";
import { ECLArchive } from "./ECLArchive";

const logger = scopedLogger("src-react/components/WorkunitDetails.tsx");

Expand Down Expand Up @@ -155,7 +156,7 @@ export const WorkunitDetails: React.FunctionComponent<WorkunitDetailsProps> = ({
<Logs wuid={wuid} filter={queryParams.logs} setLogCount={setLogCount} />
</DelayLoadedPanel>
<DelayLoadedPanel visible={tab === "eclsummary"} size={size}>
<DojoAdapter widgetClassID="ECLArchiveWidget" params={{ Wuid: wuid }} />
<ECLArchive wuid={wuid} />
</DelayLoadedPanel>
<DelayLoadedPanel visible={tab === "xml"} size={size}>
<WUXMLSourceEditor wuid={wuid} />
Expand Down
30 changes: 19 additions & 11 deletions esp/src/src-react/hooks/metrics.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as React from "react";
import { useConst, useForceUpdate } from "@fluentui/react-hooks";
import { WUDetailsMeta, WorkunitsService } from "@hpcc-js/comms";
import { WUDetails, WUDetailsMeta, WorkunitsService } from "@hpcc-js/comms";
import { userKeyValStore } from "src/KeyValStore";
import { useWorkunit } from "./workunit";
import { useCounter } from "./util";
Expand Down Expand Up @@ -101,7 +101,21 @@ export enum FetchStatus {
COMPLETE
}

export function useWorkunitMetrics(wuid: string): [any[], { [id: string]: any }, WUDetailsMeta.Activity[], WUDetailsMeta.Property[], string[], string[], FetchStatus, () => void] {
const scopeFilterDefault: WUDetails.RequestNS.ScopeFilter = {
MaxDepth: 999999,
ScopeTypes: []
};

const nestedFilterDefault: WUDetails.RequestNS.NestedFilter = {
Depth: 999999,
ScopeTypes: []
};

export function useWorkunitMetrics(
wuid: string,
scopeFilter: WUDetails.RequestNS.ScopeFilter = scopeFilterDefault,
nestedFilter: WUDetails.RequestNS.NestedFilter = nestedFilterDefault
): [any[], { [id: string]: any }, WUDetailsMeta.Activity[], WUDetailsMeta.Property[], string[], string[], FetchStatus, () => void] {

const [workunit, state] = useWorkunit(wuid);
const [data, setData] = React.useState<any[]>([]);
Expand All @@ -116,14 +130,8 @@ export function useWorkunitMetrics(wuid: string): [any[], { [id: string]: any },
React.useEffect(() => {
setStatus(FetchStatus.STARTED);
workunit?.fetchDetailsNormalized({
ScopeFilter: {
MaxDepth: 999999,
ScopeTypes: []
},
NestedFilter: {
Depth: 0,
ScopeTypes: []
},
ScopeFilter: scopeFilter,
NestedFilter: nestedFilter,
PropertiesToReturn: {
AllScopes: true,
AllAttributes: true,
Expand Down Expand Up @@ -158,7 +166,7 @@ export function useWorkunitMetrics(wuid: string): [any[], { [id: string]: any },
}).finally(() => {
setStatus(FetchStatus.COMPLETE);
});
}, [workunit, state, count]);
}, [workunit, state, count, scopeFilter, nestedFilter]);

return [data, columns, activities, properties, measures, scopeTypes, status, increment];
}
36 changes: 36 additions & 0 deletions esp/src/src-react/hooks/workunit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,42 @@ export function useWorkunitResources(wuid: string): [string[], Workunit, WUState
return [resources, workunit, state, increment];
}

export function useWorkunitQuery(wuid: string): [string, Workunit, WUStateID, () => void] {

const [workunit, state] = useWorkunit(wuid);
const [query, setQuery] = React.useState<string>("");
const [count, increment] = useCounter();

React.useEffect(() => {
if (workunit) {
const fetchQuery = singletonDebounce(workunit, "fetchQuery");
fetchQuery().then(response => {
setQuery(response?.Text ?? "");
}).catch(err => logger.error(err));
}
}, [workunit, state, count]);

return [query, workunit, state, increment];
}

export function useWorkunitArchive(wuid: string): [string, Workunit, WUStateID, () => void] {

const [workunit, state] = useWorkunit(wuid);
const [archive, setArchive] = React.useState<string>("");
const [count, increment] = useCounter();

React.useEffect(() => {
if (workunit) {
const fetchArchive = singletonDebounce(workunit, "fetchArchive");
fetchArchive().then(response => {
setArchive(response);
}).catch(err => logger.error(err));
}
}, [workunit, state, count]);

return [archive, workunit, state, increment];
}

export interface HelperRow {
id: string;
Type: string;
Expand Down
Loading

0 comments on commit cb90461

Please sign in to comment.