Skip to content

Commit

Permalink
HPCC-32834 Persist fullscreen mode in address bar
Browse files Browse the repository at this point in the history
Add fullscreen button to WU Summary

Signed-off-by: Gordon Smith <[email protected]>
  • Loading branch information
GordonSmith committed Oct 18, 2024
1 parent 4a38af6 commit 75aebaf
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 82 deletions.
41 changes: 28 additions & 13 deletions esp/src/src-react/components/Metrics.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { HolyGrail } from "../layouts/HolyGrail";
import { AutosizeComponent, AutosizeHpccJSComponent } from "../layouts/HpccJSAdapter";
import { DockPanel, DockPanelItem, ResetableDockPanel } from "../layouts/DockPanel";
import { LayoutStatus, MetricGraph, MetricGraphWidget, isGraphvizWorkerResponse, layoutCache } from "../util/metricGraph";
import { pushUrl } from "../util/history";
import { pushUrl as _pushUrl } from "../util/history";
import { debounce } from "../util/throttle";
import { ErrorBoundary } from "../util/errorBoundary";
import { ShortVerticalDivider } from "./Common";
Expand Down Expand Up @@ -45,14 +45,16 @@ interface MetricsProps {
queryId?: string;
parentUrl?: string;
selection?: string;
fullscreen?: boolean;
}

export const Metrics: React.FunctionComponent<MetricsProps> = ({
wuid,
querySet = "",
queryId = "",
parentUrl = `/workunits/${wuid}/metrics`,
selection
selection,
fullscreen = false
}) => {
const [_uiState, _setUIState] = React.useState({ ...defaultUIState });
const [selectedMetricsSource, setSelectedMetricsSource] = React.useState<SelectedMetricsSource>("");
Expand All @@ -63,7 +65,6 @@ export const Metrics: React.FunctionComponent<MetricsProps> = ({
const [showMetricOptions, setShowMetricOptions] = React.useState(false);
const [dockpanel, setDockpanel] = React.useState<ResetableDockPanel>();
const [trackSelection, setTrackSelection] = React.useState<boolean>(true);
const [fullscreen, setFullscreen] = React.useState<boolean>(false);
const [hotspots, setHotspots] = React.useState<string>("");
const [lineage, setLineage] = React.useState<IScope[]>([]);
const [selectedLineage, setSelectedLineage] = React.useState<IScope>();
Expand Down Expand Up @@ -96,10 +97,24 @@ export const Metrics: React.FunctionComponent<MetricsProps> = ({
}).catch(err => logger.error(err));
}, [wuid]);

const pushUrl = React.useCallback((selection?: string, fullscreen?: boolean) => {
const selectionStr = selection?.length ? `/${selection}` : "";
const fullscreenStr = fullscreen ? "?fullscreen" : "";
_pushUrl(`${parentUrl}${selectionStr}${fullscreenStr}`);
}, [parentUrl]);

const pushSelectionUrl = React.useCallback((selection: string) => {
pushUrl(selection, fullscreen);
}, [fullscreen, pushUrl]);

const pushFullscreenUrl = React.useCallback((fullscreen: boolean) => {
pushUrl(selection, fullscreen);
}, [pushUrl, selection]);

const onHotspot = React.useCallback(() => {
setSelectedMetricsSource("hotspot");
pushUrl(`${parentUrl}/${selection}`);
}, [parentUrl, selection]);
pushSelectionUrl(selection);
}, [pushSelectionUrl, selection]);

// Timeline ---
const timeline = useConst(() => new WUTimelineNoFetch()
Expand All @@ -114,11 +129,11 @@ export const Metrics: React.FunctionComponent<MetricsProps> = ({
timeline.selection([]);
setSelectedMetricsSource("scopesTable");
setScopeFilter(`name:${row[7].__hpcc_id}`);
pushUrl(`${parentUrl}/${row[7].id}`);
pushSelectionUrl(row[7].id);
}
}, true)
;
}, [parentUrl, timeline]);
}, [pushSelectionUrl, timeline]);

React.useEffect(() => {
if (view.showTimeline) {
Expand All @@ -142,10 +157,10 @@ export const Metrics: React.FunctionComponent<MetricsProps> = ({
.on("selectionChanged", () => {
const selection = metricGraphWidget.selection().filter(id => metricGraph.item(id)).map(id => metricGraph.item(id).id);
setSelectedMetricsSource("metricGraphWidget");
pushUrl(`${parentUrl}/${selection.join(",")}`);
pushSelectionUrl(selection.join(","));
}, true)
;
}, [metricGraph, metricGraphWidget, parentUrl]);
}, [metricGraph, metricGraphWidget, pushSelectionUrl]);

React.useEffect(() => {
metricGraph.load(metrics);
Expand Down Expand Up @@ -301,8 +316,8 @@ export const Metrics: React.FunctionComponent<MetricsProps> = ({

const scopesSelectionChanged = React.useCallback((source: SelectedMetricsSource, selection: IScope[]) => {
setSelectedMetricsSource(source);
pushUrl(`${parentUrl}/${selection.map(row => row.__lparam?.id ?? row.id).join(",")}`);
}, [parentUrl]);
pushSelectionUrl(selection.map(row => row.__lparam?.id ?? row.id).join(","));
}, [pushSelectionUrl]);

const scopesTable = useConst(() => new ScopesTable()
.multiSelect(true)
Expand Down Expand Up @@ -520,9 +535,9 @@ export const Metrics: React.FunctionComponent<MetricsProps> = ({
{ key: "divider_2", itemType: ContextualMenuItemType.Divider, onRender: () => <ShortVerticalDivider /> },
{
key: "fullscreen", title: nlsHPCC.MaximizeRestore, iconProps: { iconName: fullscreen ? "ChromeRestore" : "FullScreen" },
onClick: () => setFullscreen(!fullscreen)
onClick: () => pushFullscreenUrl(!fullscreen)
}
], [dot, formatColumns, fullscreen, metrics, wuid]);
], [dot, formatColumns, fullscreen, metrics, pushFullscreenUrl, wuid]);

const setShowMetricOptionsHook = React.useCallback((show: boolean) => {
setShowMetricOptions(show);
Expand Down
6 changes: 3 additions & 3 deletions esp/src/src-react/components/WorkunitDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ interface WorkunitDetailsProps {
parentUrl?: string;
tab?: string;
state?: { outputs?: string, metrics?: string, resources?: string, helpers?: string, eclsummary?: string };
queryParams?: { outputs?: StringStringMap, inputs?: StringStringMap, resources?: StringStringMap, helpers?: StringStringMap, logs?: StringStringMap };
queryParams?: { summary?: StringStringMap, outputs?: StringStringMap, inputs?: StringStringMap, metrics?: StringStringMap, resources?: StringStringMap, helpers?: StringStringMap, logs?: StringStringMap };
}

export const WorkunitDetails: React.FunctionComponent<WorkunitDetailsProps> = ({
Expand Down Expand Up @@ -180,7 +180,7 @@ export const WorkunitDetails: React.FunctionComponent<WorkunitDetailsProps> = ({
<div style={{ height: "100%" }}>
<OverflowTabList tabs={tabs} selected={tab} onTabSelect={onTabSelect} size="medium" />
<DelayLoadedPanel visible={tab === "summary"} size={size}>
<WorkunitSummary wuid={wuid} />
<WorkunitSummary wuid={wuid} fullscreen={queryParams.summary?.fullscreen !== undefined} />
</DelayLoadedPanel>
<DelayLoadedPanel visible={tab === "variables"} size={size}>
<Variables wuid={wuid} />
Expand All @@ -205,7 +205,7 @@ export const WorkunitDetails: React.FunctionComponent<WorkunitDetailsProps> = ({
<Shimmer />
</>
}>
<Metrics wuid={wuid} parentUrl={`${parentUrl}/${wuid}/metrics`} selection={state?.metrics} />
<Metrics wuid={wuid} parentUrl={`${parentUrl}/${wuid}/metrics`} selection={state?.metrics} fullscreen={queryParams.metrics?.fullscreen !== undefined} />
</React.Suspense>
</DelayLoadedPanel>
<DelayLoadedPanel visible={tab === "workflows"} size={size}>
Expand Down
142 changes: 77 additions & 65 deletions esp/src/src-react/components/WorkunitSummary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { useConfirm } from "../hooks/confirm";
import { useWorkunit, useWorkunitExceptions } from "../hooks/workunit";
import { ReflexContainer, ReflexElement, ReflexSplitter } from "../layouts/react-reflex";
import { pushUrl, replaceUrl } from "../util/history";
import { HolyGrail } from "../layouts/HolyGrail";
import { ShortVerticalDivider } from "./Common";
import { TableGroup } from "./forms/Groups";
import { PublishQueryForm } from "./forms/PublishQuery";
Expand All @@ -26,10 +27,12 @@ interface MessageBarContent {

interface WorkunitSummaryProps {
wuid: string;
fullscreen?: boolean;
}

export const WorkunitSummary: React.FunctionComponent<WorkunitSummaryProps> = ({
wuid
wuid,
fullscreen = false
}) => {

const [workunit, , , , refresh] = useWorkunit(wuid, true);
Expand Down Expand Up @@ -171,6 +174,13 @@ export const WorkunitSummary: React.FunctionComponent<WorkunitSummaryProps> = ({
},
], [_protected, canDelete, canDeschedule, canReschedule, canSave, description, jobname, refresh, refreshSavings, setShowDeleteConfirm, showMessageBar, workunit, wuid]);

const rightButtons = React.useMemo((): ICommandBarItemProps[] => [
{
key: "fullscreen", title: nlsHPCC.MaximizeRestore, iconProps: { iconName: fullscreen ? "ChromeRestore" : "FullScreen" },
onClick: () => pushUrl(`/workunits/${wuid}${fullscreen ? "" : "?fullscreen"}`)
}
], [fullscreen, wuid]);

const serviceNames = React.useMemo(() => {
return workunit?.ServiceNames?.Item?.join("\n") || "";
}, [workunit?.ServiceNames?.Item]);
Expand All @@ -190,68 +200,70 @@ export const WorkunitSummary: React.FunctionComponent<WorkunitSummaryProps> = ({
}, 0) || 0;
}, [exceptions]);

return <>
<ReflexContainer orientation="horizontal">
<ReflexElement>
<div className="pane-content">
<ScrollablePane scrollbarVisibility={ScrollbarVisibility.auto}>
<Sticky stickyPosition={StickyPositionType.Header}>
<CommandBar items={buttons} />
{messageBarContent &&
<MessageBar messageBarType={messageBarContent.type} dismissButtonAriaLabel={nlsHPCC.Close} onDismiss={dismissMessageBar} >
{messageBarContent.message}
</MessageBar>
}
</Sticky>
<Sticky stickyPosition={StickyPositionType.Header}>
<WorkunitPersona wuid={wuid} />
<div style={{ width: "512px", height: "64px", float: "right" }}>
<WUStatus wuid={wuid}></WUStatus>
</div>
</Sticky>
<TableGroup fields={{
"wuid": { label: nlsHPCC.WUID, type: "string", value: wuid, readonly: true },
"action": { label: nlsHPCC.Action, type: "string", value: workunit?.ActionEx, readonly: true },
"state": { label: nlsHPCC.State, type: "string", value: workunit?.State + (workunit?.StateEx ? ` (${workunit.StateEx})` : ""), readonly: true },
"owner": { label: nlsHPCC.Owner, type: "string", value: workunit?.Owner, readonly: true },
"jobname": { label: nlsHPCC.JobName, type: "string", value: jobname },
"description": { label: nlsHPCC.Description, type: "string", value: description },
"potentialSavings": { label: nlsHPCC.PotentialSavings, type: "string", value: `${formatCost(potentialSavings)} (${totalCosts > 0 ? Math.round((potentialSavings / totalCosts) * 10000) / 100 : 0}%)`, readonly: true },
"compileCost": { label: nlsHPCC.CompileCost, type: "string", value: `${formatCost(workunit?.CompileCost)}`, readonly: true },
"executeCost": { label: nlsHPCC.ExecuteCost, type: "string", value: `${formatCost(workunit?.ExecuteCost)}`, readonly: true },
"fileAccessCost": { label: nlsHPCC.FileAccessCost, type: "string", value: `${formatCost(workunit?.FileAccessCost)}`, readonly: true },
"protected": { label: nlsHPCC.Protected, type: "checkbox", value: _protected },
"cluster": { label: nlsHPCC.Cluster, type: "string", value: workunit?.Cluster, readonly: true },
"totalClusterTime": { label: nlsHPCC.TotalClusterTime, type: "string", value: workunit?.TotalClusterTime ? workunit?.TotalClusterTime : "0.00", readonly: true },
"abortedBy": { label: nlsHPCC.AbortedBy, type: "string", value: workunit?.AbortBy, readonly: true },
"abortedTime": { label: nlsHPCC.AbortedTime, type: "string", value: workunit?.AbortTime, readonly: true },
"ServiceNamesCustom": { label: nlsHPCC.Services, type: "string", value: serviceNames, readonly: true, multiline: true },
}} onChange={(id, value) => {
switch (id) {
case "jobname":
setJobname(value);
break;
case "description":
setDescription(value);
break;
case "protected":
setProtected(value);
break;
default:
logger.debug(`${id}: ${value}`);
}
}} />
</ScrollablePane>
</div>
</ReflexElement>
<ReflexSplitter />
<ReflexElement>
<InfoGrid wuid={wuid}></InfoGrid>
</ReflexElement>
</ReflexContainer>
<PublishQueryForm wuid={wuid} showForm={showPublishForm} setShowForm={setShowPublishForm} />
<ZAPDialog wuid={wuid} showForm={showZapForm} setShowForm={setShowZapForm} />
<SlaveLogs wuid={wuid} showForm={showThorSlaveLogs} setShowForm={setShowThorSlaveLogs} />
<DeleteConfirm />
</>;
return <HolyGrail fullscreen={fullscreen}
main={<>
<ReflexContainer orientation="horizontal">
<ReflexElement>
<div className="pane-content">
<ScrollablePane scrollbarVisibility={ScrollbarVisibility.auto}>
<Sticky stickyPosition={StickyPositionType.Header}>
<CommandBar items={buttons} farItems={rightButtons} />
{messageBarContent &&
<MessageBar messageBarType={messageBarContent.type} dismissButtonAriaLabel={nlsHPCC.Close} onDismiss={dismissMessageBar} >
{messageBarContent.message}
</MessageBar>
}
</Sticky>
<Sticky stickyPosition={StickyPositionType.Header}>
<WorkunitPersona wuid={wuid} />
<div style={{ width: "512px", height: "64px", float: "right" }}>
<WUStatus wuid={wuid}></WUStatus>
</div>
</Sticky>
<TableGroup fields={{
"wuid": { label: nlsHPCC.WUID, type: "string", value: wuid, readonly: true },
"action": { label: nlsHPCC.Action, type: "string", value: workunit?.ActionEx, readonly: true },
"state": { label: nlsHPCC.State, type: "string", value: workunit?.State + (workunit?.StateEx ? ` (${workunit.StateEx})` : ""), readonly: true },
"owner": { label: nlsHPCC.Owner, type: "string", value: workunit?.Owner, readonly: true },
"jobname": { label: nlsHPCC.JobName, type: "string", value: jobname },
"description": { label: nlsHPCC.Description, type: "string", value: description },
"potentialSavings": { label: nlsHPCC.PotentialSavings, type: "string", value: `${formatCost(potentialSavings)} (${totalCosts > 0 ? Math.round((potentialSavings / totalCosts) * 10000) / 100 : 0}%)`, readonly: true },
"compileCost": { label: nlsHPCC.CompileCost, type: "string", value: `${formatCost(workunit?.CompileCost)}`, readonly: true },
"executeCost": { label: nlsHPCC.ExecuteCost, type: "string", value: `${formatCost(workunit?.ExecuteCost)}`, readonly: true },
"fileAccessCost": { label: nlsHPCC.FileAccessCost, type: "string", value: `${formatCost(workunit?.FileAccessCost)}`, readonly: true },
"protected": { label: nlsHPCC.Protected, type: "checkbox", value: _protected },
"cluster": { label: nlsHPCC.Cluster, type: "string", value: workunit?.Cluster, readonly: true },
"totalClusterTime": { label: nlsHPCC.TotalClusterTime, type: "string", value: workunit?.TotalClusterTime ? workunit?.TotalClusterTime : "0.00", readonly: true },
"abortedBy": { label: nlsHPCC.AbortedBy, type: "string", value: workunit?.AbortBy, readonly: true },
"abortedTime": { label: nlsHPCC.AbortedTime, type: "string", value: workunit?.AbortTime, readonly: true },
"ServiceNamesCustom": { label: nlsHPCC.Services, type: "string", value: serviceNames, readonly: true, multiline: true },
}} onChange={(id, value) => {
switch (id) {
case "jobname":
setJobname(value);
break;
case "description":
setDescription(value);
break;
case "protected":
setProtected(value);
break;
default:
logger.debug(`${id}: ${value}`);
}
}} />
</ScrollablePane>
</div>
</ReflexElement>
<ReflexSplitter />
<ReflexElement>
<InfoGrid wuid={wuid}></InfoGrid>
</ReflexElement>
</ReflexContainer>
<PublishQueryForm wuid={wuid} showForm={showPublishForm} setShowForm={setShowPublishForm} />
<ZAPDialog wuid={wuid} showForm={showZapForm} setShowForm={setShowZapForm} />
<SlaveLogs wuid={wuid} showForm={showThorSlaveLogs} setShowForm={setShowThorSlaveLogs} />
<DeleteConfirm />
</>}
/>;
};
2 changes: 1 addition & 1 deletion esp/src/src-react/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const workunitsChildren: Route[] = [
},
{
path: "/:Wuid", action: (ctx, params) => import("./components/WorkunitDetails").then(_ => {
return <_.WorkunitDetails wuid={params.Wuid as string} parentUrl={params.parentUrl as string} />;
return <_.WorkunitDetails wuid={params.Wuid as string} parentUrl={params.parentUrl as string} queryParams={{ summary: parseSearch(ctx.search) as any }} />;
})
},
{
Expand Down

0 comments on commit 75aebaf

Please sign in to comment.