Skip to content

Commit 31d99f6

Browse files
committed
Merge remote-tracking branch 'origin/candidate-9.4.x' into candidate-9.6.x
Signed-off-by: Gordon Smith <[email protected]> # Conflicts: # helm/hpcc/Chart.yaml # helm/hpcc/templates/_helpers.tpl # version.cmake
2 parents 7d3194a + 13a0135 commit 31d99f6

File tree

10 files changed

+293
-180
lines changed

10 files changed

+293
-180
lines changed

esp/src/package-lock.json

+4-5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

esp/src/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
"@fluentui/react-icons-mdl2": "1.3.82",
4646
"@fluentui/react-migration-v8-v9": "9.6.48",
4747
"@hpcc-js/chart": "2.86.0",
48-
"@hpcc-js/codemirror": "2.64.0",
48+
"@hpcc-js/codemirror": "2.65.0",
4949
"@hpcc-js/common": "2.73.0",
5050
"@hpcc-js/comms": "2.99.0",
5151
"@hpcc-js/dataflow": "8.1.7",
+149-122
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,17 @@
11
import * as React from "react";
2-
import { CommandBar, ContextualMenuItemType, ICommandBarItemProps, Link } from "@fluentui/react";
2+
import { CommandBar, ContextualMenuItemType, ICommandBarItemProps } from "@fluentui/react";
3+
import { TreeItemValue } from "@fluentui/react-components";
34
import * as ESPRequest from "src/ESPRequest";
45
import nlsHPCC from "src/nlsHPCC";
56
import { HelperRow, useWorkunitHelpers } from "../hooks/workunit";
67
import { HolyGrail } from "../layouts/HolyGrail";
7-
import { FluentGrid, useCopyButtons, useFluentStoreState, FluentColumns } from "./controls/Grid";
8+
import { DockPanel, DockPanelItem, ResetableDockPanel } from "../layouts/DockPanel";
9+
import { pushUrl } from "../util/history";
810
import { ShortVerticalDivider } from "./Common";
9-
import { SearchParams } from "../util/hashUrl";
10-
import { hashHistory } from "../util/history";
11+
import { FlatItem, HelpersTree } from "./HelpersTree";
12+
import { FetchEditor } from "./SourceEditor";
1113

12-
function canShowContent(type: string) {
13-
switch (type) {
14-
case "dll":
15-
return false;
16-
}
17-
return true;
18-
}
19-
20-
function getURL(item: HelperRow, option) {
14+
function getURL(item: HelperRow, option?) {
2115
let params = "";
2216

2317
const uriEncodedParams: { [key: string]: any } = {
@@ -82,165 +76,198 @@ function getURL(item: HelperRow, option) {
8276
return ESPRequest.getBaseURL() + params + (option ? `&Option=${encodeURIComponent(option)}` : "&Option=1");
8377
}
8478

85-
function getTarget(id, row: HelperRow) {
86-
if (canShowContent(row.Type)) {
87-
let sourceMode = "text";
88-
switch (row.Type) {
89-
case "ECL":
90-
sourceMode = "ecl";
91-
break;
92-
case "Workunit XML":
93-
case "Archive Query":
94-
case "xml":
95-
sourceMode = "xml";
96-
break;
97-
}
98-
return {
99-
sourceMode,
100-
url: getURL(row, id)
101-
};
102-
}
103-
return null;
104-
}
105-
106-
const defaultUIState = {
107-
hasSelection: false,
108-
canShowContent: false
109-
};
110-
11179
interface HelpersProps {
11280
wuid: string;
81+
mode?: "ecl" | "xml" | "text" | "yaml";
82+
url?: string;
83+
parentUrl?: string;
11384
}
11485

11586
export const Helpers: React.FunctionComponent<HelpersProps> = ({
116-
wuid
87+
wuid,
88+
mode,
89+
url,
90+
parentUrl = `/workunits/${wuid}/helpers`
11791
}) => {
11892

119-
const [uiState, setUIState] = React.useState({ ...defaultUIState });
93+
const [fullscreen, setFullscreen] = React.useState<boolean>(false);
94+
const [dockpanel, setDockpanel] = React.useState<ResetableDockPanel>();
12095
const [helpers, refreshData] = useWorkunitHelpers(wuid);
121-
const [data, setData] = React.useState<any[]>([]);
122-
const {
123-
selection, setSelection,
124-
setTotal,
125-
refreshTable } = useFluentStoreState({});
126-
127-
// Grid ---
128-
const columns = React.useMemo((): FluentColumns => {
129-
return {
130-
sel: {
131-
width: 27,
132-
selectorType: "checkbox"
133-
},
134-
Type: {
135-
label: nlsHPCC.Type,
136-
width: 160,
137-
formatter: (Type, row) => {
138-
const target = getTarget(row.id, row);
139-
if (target) {
140-
const searchParams = new SearchParams(hashHistory.location.search);
141-
searchParams.param("mode", encodeURIComponent(target.sourceMode));
142-
searchParams.param("src", encodeURIComponent(target.url));
143-
const linkText = Type.replace("Slave", "Worker") + (row?.Description ? " (" + row.Description + ")" : "");
144-
return <Link href={`#/workunits/${row?.workunit?.Wuid}/helpers/${row.Type}?${searchParams.serialize()}`}>{linkText}</Link>;
96+
const [noDataMsg, setNoDataMsg] = React.useState("");
97+
const [checkedRows, setCheckedRows] = React.useState([]);
98+
const [checkedItems, setCheckedItems] = React.useState([]);
99+
const [selection, setSelection] = React.useState<HelperRow>();
100+
const [treeItems, setTreeItems] = React.useState<FlatItem[]>([]);
101+
const [openItems, setOpenItems] = React.useState<Iterable<TreeItemValue>>([]);
102+
103+
React.useEffect(() => {
104+
helpers.forEach(helper => {
105+
helper.Orig = {
106+
url: getURL(helper),
107+
...helper.Orig
108+
};
109+
});
110+
}, [helpers]);
111+
112+
React.useEffect(() => {
113+
setSelection(helpers.filter(item => url === getURL(item))[0]);
114+
}, [helpers, url]);
115+
116+
React.useEffect(() => {
117+
if (helpers.length && selection !== undefined) {
118+
setNoDataMsg(selection?.Type === "dll" ? nlsHPCC.CannotDisplayBinaryData : "");
119+
}
120+
}, [helpers, selection]);
121+
122+
React.useEffect(() => {
123+
const rows = [];
124+
checkedItems.forEach(value => {
125+
const filtered = helpers.filter(row => row.id === value || row?.Name?.indexOf(value) > -1)[0];
126+
if (treeItems.filter(item => item.value === value)[0].url && filtered) {
127+
rows.push(filtered);
128+
}
129+
});
130+
setCheckedRows(rows);
131+
}, [checkedItems, helpers, treeItems]);
132+
133+
React.useEffect(() => {
134+
const flatTreeItems: FlatItem[] = [];
135+
helpers.forEach(helper => {
136+
let parentValue;
137+
const helperPath = helper.Path?.replace("/var/lib/HPCCSystems/", "");
138+
const fileName = helper.Name?.split("/").pop();
139+
if (helperPath) {
140+
const pathDirs = helperPath.split("/");
141+
let parentFolder;
142+
let folderName;
143+
for (let i = 0; i < pathDirs.length; i++) {
144+
folderName = parentFolder ? parentFolder + "/" + pathDirs[i] : pathDirs[i];
145+
if (flatTreeItems.filter(item => item.value === folderName).length === 0) {
146+
flatTreeItems.push({
147+
value: folderName,
148+
content: pathDirs[i],
149+
parentValue: parentFolder,
150+
url: ""
151+
});
152+
}
153+
if (!parentFolder) {
154+
parentFolder = pathDirs[i];
155+
} else {
156+
parentFolder += "/" + pathDirs[i];
145157
}
146-
return Type;
147158
}
148-
},
149-
Description: {
150-
label: nlsHPCC.Description
151-
},
152-
FileSize: {
153-
label: nlsHPCC.FileSize,
154-
width: 90,
155-
justify: "right"
159+
flatTreeItems.push({
160+
value: helperPath + "/" + fileName,
161+
content: fileName,
162+
fileSize: helper.FileSize,
163+
parentValue: parentFolder,
164+
url: helper.Orig.url
165+
});
166+
} else {
167+
flatTreeItems.push({
168+
value: helper.id,
169+
parentValue: parentValue ?? undefined,
170+
content: helper.Description ?? helper.Name ?? helper.Type,
171+
fileSize: helper.FileSize,
172+
url: helper.Orig.url
173+
});
156174
}
157-
};
158-
}, []);
175+
});
176+
setTreeItems(flatTreeItems.sort((a, b) => {
177+
if (a.parentValue === undefined && b.parentValue === undefined) return 0;
178+
if (a.parentValue === undefined) return -1;
179+
if (b.parentValue === undefined) return 1;
180+
return a.parentValue?.toString().localeCompare(b.parentValue?.toString(), undefined, { ignorePunctuation: false });
181+
}));
182+
setOpenItems(flatTreeItems.map(item => item.value));
183+
}, [helpers]);
184+
185+
const treeItemLeafNodes = React.useMemo(() => {
186+
return treeItems.filter(item => item.url !== "");
187+
}, [treeItems]);
159188

160189
// Command Bar ---
161190
const buttons = React.useMemo((): ICommandBarItemProps[] => [
162191
{
163192
key: "refresh", text: nlsHPCC.Refresh, iconProps: { iconName: "Refresh" },
164-
onClick: () => refreshData()
193+
onClick: () => {
194+
refreshData();
195+
pushUrl(`${parentUrl}`);
196+
}
165197
},
166198
{ key: "divider_1", itemType: ContextualMenuItemType.Divider, onRender: () => <ShortVerticalDivider /> },
167199
{
168-
key: "open", text: nlsHPCC.Open, disabled: !uiState.canShowContent, iconProps: { iconName: "WindowEdit" },
200+
key: "selectAll", text: checkedItems.length === treeItemLeafNodes.length ? nlsHPCC.DeselectAll : nlsHPCC.SelectAll, iconProps: { iconName: checkedItems.length === treeItemLeafNodes.length ? "Checkbox" : "CheckboxComposite" },
169201
onClick: () => {
170-
if (selection.length === 1) {
171-
const target = getTarget(selection[0].id, selection[0]);
172-
if (target) {
173-
window.location.href = `#/text?mode=${target.sourceMode}&src=${encodeURIComponent(target.url)}`;
174-
}
202+
if (checkedItems.length < treeItemLeafNodes.length) {
203+
setCheckedItems(treeItemLeafNodes.map(i => i.value));
175204
} else {
176-
for (let i = 0; i < selection.length; ++i) {
177-
const target = getTarget(selection[i].id, selection[i]);
178-
if (target) {
179-
window.open(`#/text?mode=${target.sourceMode}&src=${encodeURIComponent(target.url)}`, "_blank");
180-
}
181-
}
205+
setCheckedItems([]);
182206
}
183207
}
184208
},
185-
{ key: "divider_2", itemType: ContextualMenuItemType.Divider, onRender: () => <ShortVerticalDivider /> },
186209
{
187-
key: "file", text: nlsHPCC.File, disabled: !uiState.hasSelection, iconProps: { iconName: "Download" },
210+
key: "file", text: nlsHPCC.File, disabled: checkedRows.filter(item => item.url !== "").length === 0, iconProps: { iconName: "Download" },
188211
onClick: () => {
189-
selection.forEach(item => {
212+
checkedRows.forEach(item => {
190213
window.open(getURL(item, 1));
191214
});
192215
}
193216
},
194217
{
195-
key: "zip", text: nlsHPCC.Zip, disabled: !uiState.hasSelection, iconProps: { iconName: "Download" },
218+
key: "zip", text: nlsHPCC.Zip, disabled: checkedRows.filter(item => item.url !== "").length === 0, iconProps: { iconName: "Download" },
196219
onClick: () => {
197-
selection.forEach(item => {
220+
checkedRows.forEach(item => {
198221
window.open(getURL(item, 2));
199222
});
200223
}
201224
},
202225
{
203-
key: "gzip", text: nlsHPCC.GZip, disabled: !uiState.hasSelection, iconProps: { iconName: "Download" },
226+
key: "gzip", text: nlsHPCC.GZip, disabled: checkedRows.filter(item => item.url !== "").length === 0, iconProps: { iconName: "Download" },
204227
onClick: () => {
205-
selection.forEach(item => {
228+
checkedRows.forEach(item => {
206229
window.open(getURL(item, 3));
207230
});
208231
}
209232
}
233+
], [checkedItems, checkedRows, parentUrl, refreshData, treeItemLeafNodes]);
210234

211-
], [refreshData, selection, uiState.canShowContent, uiState.hasSelection]);
235+
const rightButtons = React.useMemo((): ICommandBarItemProps[] => [
236+
{
237+
key: "fullscreen", title: nlsHPCC.MaximizeRestore, iconProps: { iconName: fullscreen ? "ChromeRestore" : "FullScreen" },
238+
onClick: () => setFullscreen(!fullscreen)
239+
}
240+
], [fullscreen]);
212241

213-
const copyButtons = useCopyButtons(columns, selection, "helpers");
242+
const setSelectedItem = React.useCallback((selId: string) => {
243+
pushUrl(`${parentUrl}/${selId}`);
244+
}, [parentUrl]);
214245

215-
// Selection ---
216246
React.useEffect(() => {
217-
const state = { ...defaultUIState };
218-
219-
selection.forEach(row => {
220-
state.hasSelection = true;
221-
if (canShowContent(row.Type)) {
222-
state.canShowContent = true;
247+
if (dockpanel) {
248+
// Should only happen once on startup ---
249+
const layout: any = dockpanel.layout();
250+
if (Array.isArray(layout?.main?.sizes) && layout.main.sizes.length === 2) {
251+
layout.main.sizes = [0.3, 0.7];
252+
dockpanel.layout(layout).lazyRender();
223253
}
224-
});
225-
setUIState(state);
226-
}, [selection]);
227-
228-
React.useEffect(() => {
229-
setData(helpers);
230-
}, [helpers]);
254+
}
255+
}, [dockpanel]);
231256

232257
return <HolyGrail
233-
header={<CommandBar items={buttons} farItems={copyButtons} />}
258+
header={<CommandBar items={buttons} farItems={rightButtons} />}
234259
main={
235-
<FluentGrid
236-
data={data}
237-
primaryID={"id"}
238-
alphaNumColumns={{ Value: true }}
239-
columns={columns}
240-
setSelection={setSelection}
241-
setTotal={setTotal}
242-
refresh={refreshTable}
243-
></FluentGrid>
260+
<DockPanel hideSingleTabs onCreate={setDockpanel}>
261+
<DockPanelItem key="helpersTable" title="Helpers">
262+
{ // Only render after archive is loaded (to ensure it "defaults to open") ---
263+
helpers?.length &&
264+
<HelpersTree treeItems={treeItems} openItems={openItems} setOpenItems={setOpenItems} checkedItems={checkedItems} setCheckedItems={setCheckedItems} setSelectedItem={setSelectedItem} selectedUrl={url} />
265+
}
266+
</DockPanelItem>
267+
<DockPanelItem key="helperEditor" title="Helper" padding={4} location="split-right" relativeTo="helpersTable">
268+
<FetchEditor url={helpers && selection?.Type && selection?.Type !== "dll" ? url : null} mode={mode} noDataMsg={noDataMsg}></FetchEditor>
269+
</DockPanelItem>
270+
</DockPanel>
244271
}
245272
/>;
246273
};

0 commit comments

Comments
 (0)