Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/candidate-9.2.x' into candidate-…
Browse files Browse the repository at this point in the history
…9.4.x
  • Loading branch information
GordonSmith committed Feb 12, 2024
2 parents 51e8a91 + 3efc2d9 commit c3c13f1
Show file tree
Hide file tree
Showing 17 changed files with 1,267 additions and 669 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build-test-eclwatch.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
build:
strategy:
matrix:
node: ["18", "16"]
node: ["20", "18", "16"]
fail-fast: false
name: "Check eclwatch and npm"
needs: pre_job
Expand Down
1,027 changes: 511 additions & 516 deletions esp/src/package-lock.json

Large diffs are not rendered by default.

26 changes: 13 additions & 13 deletions esp/src/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,20 +41,20 @@
"@fluentui/react-hooks": "8.6.36",
"@fluentui/react-icons-mdl2": "1.3.56",
"@fluentui/react-migration-v8-v9": "9.5.4",
"@hpcc-js/chart": "2.81.9",
"@hpcc-js/codemirror": "2.61.1",
"@hpcc-js/common": "2.71.14",
"@hpcc-js/comms": "2.87.0",
"@hpcc-js/chart": "2.81.10",
"@hpcc-js/codemirror": "2.61.2",
"@hpcc-js/common": "2.71.15",
"@hpcc-js/comms": "2.89.0",
"@hpcc-js/dataflow": "8.1.6",
"@hpcc-js/eclwatch": "2.73.33",
"@hpcc-js/graph": "2.85.10",
"@hpcc-js/html": "2.42.15",
"@hpcc-js/layout": "2.49.14",
"@hpcc-js/map": "2.77.13",
"@hpcc-js/other": "2.15.14",
"@hpcc-js/phosphor": "2.18.0",
"@hpcc-js/react": "2.53.11",
"@hpcc-js/tree": "2.40.14",
"@hpcc-js/eclwatch": "2.73.36",
"@hpcc-js/graph": "2.85.11",
"@hpcc-js/html": "2.42.16",
"@hpcc-js/layout": "2.49.15",
"@hpcc-js/map": "2.77.14",
"@hpcc-js/other": "2.15.15",
"@hpcc-js/phosphor": "2.18.1",
"@hpcc-js/react": "2.53.12",
"@hpcc-js/tree": "2.40.15",
"@hpcc-js/util": "2.50.6",
"@kubernetes/client-node": "0.20.0",
"clipboard": "2.0.11",
Expand Down
119 changes: 119 additions & 0 deletions esp/src/src-react/components/ECLArchive.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import * as React from "react";
import { CommandBar, ContextualMenuItemType, ICommandBarItemProps } from "@fluentui/react";
import { WUDetails, IScope } from "@hpcc-js/comms";
import nlsHPCC from "src/nlsHPCC";
import { useWorkunitArchive } from "../hooks/workunit";
import { useWorkunitMetrics } from "../hooks/metrics";
import { HolyGrail } from "../layouts/HolyGrail";
import { DockPanel, DockPanelItem, ResetableDockPanel } from "../layouts/DockPanel";
import { pushUrl } from "../util/history";
import { ShortVerticalDivider } from "./Common";
import { ECLArchiveTree } from "./ECLArchiveTree";
import { ECLArchiveEditor } from "./ECLArchiveEditor";
import { MetricsPropertiesTables } from "./MetricsPropertiesTables";

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

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

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

export const ECLArchive: React.FunctionComponent<ECLArchiveProps> = ({
wuid,
parentUrl = `/workunits/${wuid}/eclsummary`,
selection
}) => {
const [fullscreen, setFullscreen] = React.useState<boolean>(false);
const [dockpanel, setDockpanel] = React.useState<ResetableDockPanel>();
const [_archiveXmlStr, _workunit2, _state2, archive, refreshArchive] = useWorkunitArchive(wuid);
const [metrics, _columns, _activities, _properties, _measures, _scopeTypes, _fetchStatus, refreshMetrics] = useWorkunitMetrics(wuid, scopeFilterDefault, nestedFilterDefault);
const [markers, setMarkers] = React.useState<{ lineNum: number, label: string }[]>([]);
const [selectionText, setSelectionText] = React.useState<string>("");
const [selectedMetrics, setSelectedMetrics] = React.useState<IScope[]>([]);

selection = selection ?? archive?.queryId();

React.useEffect(() => {
if (archive) {
archive?.updateMetrics(metrics);
}
}, [archive, metrics]);

React.useEffect(() => {
if (metrics.length) {
setSelectionText(archive?.content(selection) ?? "");
setMarkers(archive?.markers(selection) ?? []);
setSelectedMetrics(archive?.metrics(selection) ?? []);
}
}, [archive, metrics.length, selection]);

const setSelectedItem = React.useCallback((selId: string) => {
pushUrl(`${parentUrl}/${selId}`);
}, [parentUrl]);

React.useEffect(() => {
if (dockpanel) {
// 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();
}
}
}, [dockpanel]);

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

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

return <HolyGrail fullscreen={fullscreen}
header={<CommandBar items={buttons} farItems={rightButtons} />}
main={
<DockPanel hideSingleTabs onDockPanelCreate={setDockpanel}>
<DockPanelItem key="scopesTable" title="Files" >
{ // Only render after archive is loaded (to ensure it "defaults to open") ---
archive?.modAttrs.length &&
<ECLArchiveTree archive={archive} selectedAttrIDs={[selection]} setSelectedItem={setSelectedItem} />
}
</DockPanelItem>
<DockPanelItem key="eclEditor" title="ECL" padding={4} location="split-right" relativeTo="scopesTable">
<ECLArchiveEditor ecl={selectionText} markers={markers}></ECLArchiveEditor>
</DockPanelItem>
<DockPanelItem key="properties" title="Properties" location="split-bottom" relativeTo="scopesTable" >
<MetricsPropertiesTables scopes={selectedMetrics}></MetricsPropertiesTables>
</DockPanelItem>
</DockPanel>
}
/>;
};
54 changes: 54 additions & 0 deletions esp/src/src-react/components/ECLArchiveEditor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import * as React from "react";
import { useConst } from "@fluentui/react-hooks";
import { Palette } from "@hpcc-js/common";
import { ECLEditor } from "@hpcc-js/codemirror";
import { useUserTheme } from "../hooks/theme";
import { AutosizeHpccJSComponent } from "../layouts/HpccJSAdapter";

const palette = Palette.rainbow("YlOrRd");

interface ECLArchiveProps {
ecl?: string;
readonly?: boolean;
markers?: { lineNum: number, label: string }[];
}

export const ECLArchiveEditor: React.FunctionComponent<ECLArchiveProps> = ({
ecl = "",
readonly = true,
markers = []
}) => {
const { isDark } = useUserTheme();

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

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

React.useEffect(() => {
const fontFamily = "Verdana";
const fontSize = 12;
const maxLabelWidth = Math.max(
...markers.map(marker => {
const color = palette(+marker.label, 0, 100);
editor?.addGutterMarker(+marker.lineNum - 1, marker.label, color, fontFamily, `${fontSize}px`);
return editor?.textSize(marker.label, fontFamily, fontSize)?.width ?? 0;
})
);
editor
?.gutterMarkerWidth(maxLabelWidth + 6)
?.lazyRender()
;
}, [editor, markers]);

return <AutosizeHpccJSComponent widget={editor}></AutosizeHpccJSComponent>;
};
96 changes: 96 additions & 0 deletions esp/src/src-react/components/ECLArchiveTree.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import * as React from "react";
import { FlatTree, useHeadlessFlatTree_unstable, HeadlessFlatTreeItemProps, TreeItem, TreeItemLayout, CounterBadge } from "@fluentui/react-components";
import { FluentIconsProps, FolderOpen20Regular, Folder20Regular, FolderOpen20Filled, Folder20Filled, Document20Regular, Document20Filled, Important16Regular } from "@fluentui/react-icons";
import { Archive, isAttribute } from "../util/metricArchive";

type FlatItem = HeadlessFlatTreeItemProps & { fileTimePct?: number, content: string };

const iconStyleProps: FluentIconsProps = {
primaryFill: "red",
};

const AsideContent = ({
isImportant,
messageCount,
}: {
isImportant?: boolean;
messageCount?: number;
}) => {
const color = messageCount < 50 ? "brand" :
messageCount < 70 ? "informative" :
messageCount < 90 ? "important" :
"danger";
return <>
{isImportant && <Important16Regular {...iconStyleProps} />}
{!isNaN(messageCount) && messageCount > 0 && (
<CounterBadge count={messageCount} color={color} size="small" />
)}
</>;
};

interface ECLArchiveTreeProps {
archive?: Archive;
selectedAttrIDs: string[];
setSelectedItem: (eclId: string, scopeID: string[]) => void;
}

export const ECLArchiveTree: React.FunctionComponent<ECLArchiveTreeProps> = ({
archive,
selectedAttrIDs = [],
setSelectedItem
}) => {

const defaultOpenItems = React.useMemo(() => {
return (archive?.modAttrs.filter(modAttr => modAttr.type === "Module") ?? []).map(modAttr => modAttr.id) ?? [];
}, [archive?.modAttrs]);

const [flatTreeItems, setFlatTreeItems] = React.useState<FlatItem[]>([]);
const flatTree = useHeadlessFlatTree_unstable(flatTreeItems, { defaultOpenItems });

React.useEffect(() => {
const flatTreeItems: FlatItem[] = [];
archive?.modAttrs.forEach(modAttr => {
flatTreeItems.push({
value: modAttr.id,
parentValue: modAttr.parentId ? modAttr.parentId : undefined,
content: modAttr.name,
fileTimePct: isAttribute(modAttr) && Math.round((archive?.sourcePathTime(modAttr.sourcePath) / archive?.timeTotalExecute) * 100),
});
});
setFlatTreeItems(flatTreeItems);
}, [archive, archive?.modAttrs, archive?.timeTotalExecute]);

const onClick = React.useCallback(evt => {
const attrId = evt.currentTarget?.dataset?.fuiTreeItemValue;
const modAttr = archive?.modAttrs.find(modAttr => modAttr.id === attrId);
if (modAttr?.type === "Attribute") {
setSelectedItem(attrId, archive.metricIDs(attrId));
}
}, [archive, setSelectedItem]);

const { ...treeProps } = flatTree.getTreeProps();
return <FlatTree {...treeProps} size="small">
{
Array.from(flatTree.items(), flatTreeItem => {
console.log(flatTreeItem.getTreeItemProps());
const { fileTimePct, content, ...treeItemProps } = flatTreeItem.getTreeItemProps();
return <TreeItem {...treeItemProps} onClick={onClick}>
<TreeItemLayout
iconBefore={
flatTreeItem.itemType === "branch" ?
(treeProps.openItems.has(flatTreeItem.value) ?
selectedAttrIDs.some(attrId => attrId.startsWith(content)) ? <FolderOpen20Filled /> : <FolderOpen20Regular /> :
selectedAttrIDs.some(attrId => attrId.startsWith(content)) ? <Folder20Filled /> : <Folder20Regular />) :
selectedAttrIDs.some(attrId => attrId === flatTreeItem.value) ?
<Document20Filled /> :
<Document20Regular />
}
aside={<AsideContent isImportant={false} messageCount={fileTimePct} />}
>
{content}
</TreeItemLayout>
</TreeItem>;
})
}
</FlatTree >;
};
Loading

0 comments on commit c3c13f1

Please sign in to comment.