Skip to content

Commit 8b77d24

Browse files
committed
HPCC-25201 Add React Activities
Signed-off-by Gordon Smith <GordonJSmith@gmail.com>
1 parent c3edc6d commit 8b77d24

24 files changed

Lines changed: 1953 additions & 158 deletions

esp/src/.github/instructions/react-coding.instructions.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ Apply the [general coding guidelines](./general-coding.instructions.md) to all c
1717
## React Guidelines
1818
- Use functional components with hooks (avoid class components).
1919
- Follow the React hooks rules (no conditional or nested hooks).
20-
- Use `React.FC` type for components with children, and define prop types explicitly.
20+
- Use `React.FunctionComponent` type for components with children, and define prop types explicitly.
21+
- Prefer `useStyles` over inline styles for consistency and performance.
2122
- Keep components small, focused, and reusable; follow single-responsibility principle.
2223
- Use CSS modules or CSS-in-JS for component-level styling; avoid global styles.
2324
- Use `useCallback`, `useMemo`, and `React.memo` for performance optimization.

esp/src/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,6 @@ types/
1111
tmp/
1212
.vscode/*
1313
!.vscode/tasks.json
14+
tests/test-wus.json
1415
lws.target.txt
1516
tmp.*

esp/src/src-dojo/nls/hpcc.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ export = {
2121
ActivePackageMap: "Active Package Map",
2222
ActiveWorkunit: "Active Workunit",
2323
Activities: "Activities",
24+
ActivitiesLegacy: "Activities (L)",
25+
ActivitiesPreview: "Activities (P)",
2426
Activity: "Activity",
2527
ActivityLabel: "Activity Label",
2628
ActivityMap: "Activity Map",
@@ -379,6 +381,7 @@ export = {
379381
FirstNRows: "First N Rows",
380382
Fixed: "Fixed",
381383
Folder: "Folder",
384+
Folders: "Folders",
382385
Form: "Form",
383386
Format: "Format",
384387
Forums: "Forums",
@@ -422,6 +425,7 @@ export = {
422425
HideSpills: "Hide Spills",
423426
High: "High",
424427
History: "History",
428+
Home: "Home",
425429
Homepage: "Homepage",
426430
Hotspots: "Hot spots",
427431
Hour: "Hour",
@@ -571,6 +575,7 @@ export = {
571575
MatchCase: "Match Case",
572576
MatchWholeWord: "Match Whole Word",
573577
Max: "Max",
578+
Maximize: "Maximize",
574579
MaxConnections: "Max Connections",
575580
MaxNode: "Max Node",
576581
MaxSize: "Max Size",
@@ -592,6 +597,7 @@ export = {
592597
MetricsGraph: "Metrics/Graph",
593598
MetricsSQL: "Metrics (SQL)",
594599
Min: "Min",
600+
Minimize: "Minimize",
595601
MinimumCompileCost: "Minimum Compile Cost",
596602
MinimumExecuteCost: "Minimum Execute Cost",
597603
MinimumFileAccessCost: "Minimum File Access Cost",
@@ -617,6 +623,11 @@ export = {
617623
Month: "Month",
618624
More: "more",
619625
Move: "Move",
626+
MoveBottomHint: "Move Bottom (ctrl+click-down)",
627+
MoveDown: "Move Down",
628+
MoveDownHint: "Move Down (ctrl+click move to bottom)",
629+
MoveUp: "Move Up",
630+
MoveUpHint: "Move Up (ctrl+click move to top)",
620631
MustContainUppercaseAndSymbol: "Must contain uppercase and symbol",
621632
NA: "N/A",
622633
Name: "Name",

esp/src/src-dojo/nls/zh/hpcc.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@
7272
AutoRefreshIncrement: "自动刷新增量",
7373
Back: "返回",
7474
BackupDFUWorkunit: "备份DFU工作单元",
75-
BackupECLWorkunit: "备份ECL工作单元",
75+
BackupECLWorkunit: "备份ECL工作单元",
7676
BannerColor: "标语的颜色",
7777
BannerColorTooltip: "修改上方显示框的背景颜色",
7878
BannerMessage: "标语的文字",
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import * as React from "react";
2+
import { Toolbar, ToolbarButton, Divider } from "@fluentui/react-components";
3+
import { ArrowClockwise20Regular } from "@fluentui/react-icons";
4+
import nlsHPCC from "src/nlsHPCC";
5+
import { QueueCards } from "./cards/QueueCard";
6+
import { HolyGrail } from "../layouts/HolyGrail";
7+
import { SizeMe } from "../layouts/SizeMe";
8+
import { useBuildInfo } from "../hooks/platform";
9+
import { DiskUsageCards } from "./cards/DiskUsageCard";
10+
11+
interface ActivitiesProps {
12+
}
13+
14+
export const Activities: React.FunctionComponent<ActivitiesProps> = ({
15+
}) => {
16+
const [, { isContainer }] = useBuildInfo();
17+
const [refreshToken, setRefreshToken] = React.useState(0);
18+
19+
return <HolyGrail
20+
header={
21+
<Toolbar>
22+
<ToolbarButton appearance="subtle" icon={<ArrowClockwise20Regular />} aria-label={nlsHPCC.Refresh} onClick={() => setRefreshToken(t => t + 1)}>
23+
{nlsHPCC.Refresh}
24+
</ToolbarButton>
25+
</Toolbar>
26+
}
27+
main={
28+
<SizeMe>{({ size }) => {
29+
return <div style={{ position: "relative", width: "100%", height: "100%" }}>
30+
<div style={{ position: "absolute", width: "100%", height: `${size.height}px`, overflowY: "auto" }}>
31+
{
32+
!isContainer ?
33+
<>
34+
<Divider>{nlsHPCC.DiskUsage}</Divider>
35+
<DiskUsageCards refreshToken={refreshToken} />
36+
</> :
37+
<>
38+
</>
39+
}
40+
<>
41+
<Divider>{nlsHPCC.Activities}</Divider>
42+
<QueueCards refreshToken={refreshToken} />
43+
</>
44+
</div>
45+
</div>;
46+
}}</SizeMe>
47+
}
48+
/>;
49+
};
Lines changed: 50 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,17 @@
11
import * as React from "react";
22
import { Link } from "@fluentui/react";
3-
import { MachineService } from "@hpcc-js/comms";
4-
import { scopedLogger } from "@hpcc-js/util";
3+
import { Divider, Toolbar, ToolbarButton } from "@fluentui/react-components";
4+
import { ArrowClockwise20Regular } from "@fluentui/react-icons";
55
import { ComponentDetails as ComponentDetailsWidget, Summary as SummaryWidget } from "src/DiskUsage";
66
import nlsHPCC from "src/nlsHPCC";
77
import * as Utility from "src/Utility";
88
import { AutosizeHpccJSComponent } from "../layouts/HpccJSAdapter";
9-
import { ReflexContainer, ReflexElement, ReflexSplitter, classNames, styles } from "../layouts/react-reflex";
9+
import { HolyGrail } from "../layouts/HolyGrail";
10+
import { SizeMe } from "../layouts/SizeMe";
1011
import { pushUrl } from "../util/history";
1112
import { FluentGrid, useFluentStoreState } from "./controls/Grid";
12-
13-
const logger = scopedLogger("src-react/components/DiskUsage.tsx");
14-
15-
const machineService = new MachineService({ baseUrl: "" });
13+
import { FolderUsageCards } from "./cards/DiskUsageCard";
14+
import { useTargetClusterUsageEx } from "../hooks/diskUsage";
1615

1716
interface SummaryProps {
1817
cluster?: string;
@@ -34,15 +33,17 @@ export const Summary: React.FunctionComponent<SummaryProps> = ({
3433
return <AutosizeHpccJSComponent widget={summary}></AutosizeHpccJSComponent >;
3534
};
3635

37-
interface DetailsProps {
36+
interface ClusterUsageProps {
3837
cluster: string;
3938
}
4039

41-
export const Details: React.FunctionComponent<DetailsProps> = ({
40+
export const ClusterUsage: React.FunctionComponent<ClusterUsageProps> = ({
4241
cluster
4342
}) => {
4443

4544
const { refreshTable } = useFluentStoreState({});
45+
const [refreshToken, setRefreshToken] = React.useState(0);
46+
const { data: usage, refresh } = useTargetClusterUsageEx(cluster);
4647

4748
// Grid ---
4849
const columns = React.useMemo(() => {
@@ -72,51 +73,48 @@ export const Details: React.FunctionComponent<DetailsProps> = ({
7273

7374
type Columns = typeof columns;
7475
type Row = { __hpcc_id: string } & { [K in keyof Columns]: string | number };
75-
const [data, setData] = React.useState<Row[]>([]);
76-
77-
const refreshData = React.useCallback(() => {
78-
machineService.GetTargetClusterUsageEx([cluster])
79-
.then(response => {
80-
const _data: Row[] = [];
81-
if (response) {
82-
response.forEach(component => {
83-
component.ComponentUsages.forEach(cu => {
84-
cu.MachineUsages.forEach(mu => {
85-
mu.DiskUsages.forEach((du, i) => {
86-
_data.push({
87-
__hpcc_id: `__usage_${i}`,
88-
PercentUsed: Math.round((du.InUse / du.Total) * 100),
89-
Component: cu.Name,
90-
IPAddress: mu.Name,
91-
Type: du.Name,
92-
Path: du.Path,
93-
InUse: Utility.convertedSize(du.InUse),
94-
Total: Utility.convertedSize(du.Total)
95-
});
96-
});
97-
});
76+
const data = React.useMemo<Row[]>(() => {
77+
const rows: Row[] = [];
78+
(usage ?? []).forEach(component => {
79+
component.ComponentUsages.forEach(cu => {
80+
cu.MachineUsages.forEach(mu => {
81+
mu.DiskUsages.forEach((du, i) => {
82+
rows.push({
83+
__hpcc_id: `__usage_${i}`,
84+
PercentUsed: Math.round((du.InUse / du.Total) * 100),
85+
Component: cu.Name,
86+
IPAddress: mu.Name,
87+
Type: du.Name,
88+
Path: du.Path,
89+
InUse: Utility.convertedSize(du.InUse),
90+
Total: Utility.convertedSize(du.Total)
9891
});
9992
});
100-
}
101-
setData(_data);
102-
})
103-
.catch(err => logger.error(err))
104-
;
105-
}, [cluster]);
106-
107-
React.useEffect(() => {
108-
refreshData();
109-
}, [refreshData]);
110-
111-
return <FluentGrid
112-
data={data}
113-
primaryID={"__hpcc_id"}
114-
sort={{ attribute: "__hpcc_id", descending: false }}
115-
columns={columns}
116-
setSelection={() => null}
117-
setTotal={() => null}
118-
refresh={refreshTable}
119-
></FluentGrid>;
93+
});
94+
});
95+
});
96+
return rows;
97+
}, [usage]);
98+
99+
return <HolyGrail
100+
header={
101+
<Toolbar>
102+
<ToolbarButton appearance="subtle" icon={<ArrowClockwise20Regular />} aria-label={nlsHPCC.Refresh} onClick={() => { refresh(); setRefreshToken(t => t + 1); }}>
103+
{nlsHPCC.Refresh}
104+
</ToolbarButton>
105+
</Toolbar>
106+
}
107+
main={<SizeMe>{({ size }) => {
108+
return <div style={{ position: "relative", width: "100%", height: "100%" }}>
109+
<div style={{ position: "absolute", width: "100%", height: `${size.height}px`, overflowY: "auto" }}>
110+
<Divider>{nlsHPCC.Category}</Divider>
111+
<FolderUsageCards cluster={cluster} refreshToken={refreshToken} />
112+
<Divider>{nlsHPCC.Folders}</Divider>
113+
<FluentGrid data={data} primaryID="__hpcc_id" sort={{ attribute: "__hpcc_id", descending: false }} columns={columns} setSelection={() => null} setTotal={() => null} refresh={refreshTable} />
114+
</div>
115+
</div>;
116+
}}</SizeMe>}
117+
/>;
120118
};
121119

122120
interface MachineUsageProps {
@@ -135,23 +133,3 @@ export const MachineUsage: React.FunctionComponent<MachineUsageProps> = ({
135133

136134
return <AutosizeHpccJSComponent widget={summary}></AutosizeHpccJSComponent >;
137135
};
138-
139-
interface ClusterUsageProps {
140-
cluster: string;
141-
}
142-
143-
export const ClusterUsage: React.FunctionComponent<ClusterUsageProps> = ({
144-
cluster
145-
}) => {
146-
return <ReflexContainer orientation="horizontal">
147-
<ReflexElement minSize={100} size={100} style={{ overflow: "hidden" }}>
148-
<Summary cluster={cluster} />
149-
</ReflexElement>
150-
<ReflexSplitter style={styles.reflexSplitter}>
151-
<div className={classNames.reflexSplitterDiv}></div>
152-
</ReflexSplitter>
153-
<ReflexElement style={{ overflow: "hidden" }}>
154-
<Details cluster={cluster} />
155-
</ReflexElement>
156-
</ReflexContainer >;
157-
};

esp/src/src-react/components/Menu.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@ type SubMenuItems = { [nav: string]: SubMenu[] };
179179
const subMenuItems: SubMenuItems = {
180180
"activities": [
181181
{ headerText: nlsHPCC.Activities, itemKey: "/activities" },
182+
{ headerText: nlsHPCC.ActivitiesPreview, itemKey: "/activities-preview" },
182183
{ headerText: nlsHPCC.EventScheduler, itemKey: "/events" }
183184
],
184185
"workunits": [
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import * as React from "react";
2+
import { makeStyles, mergeClasses, tokens } from "@fluentui/react-components";
3+
4+
const useStyles = makeStyles({
5+
root: {
6+
display: "grid",
7+
alignItems: "start",
8+
alignContent: "start",
9+
justifyContent: "start",
10+
placeContent: "start",
11+
width: "100%",
12+
boxSizing: "border-box",
13+
}
14+
});
15+
16+
export interface CardGroupProps {
17+
children?: React.ReactNode;
18+
minColumnWidth?: number | string;
19+
autoRows?: number | string;
20+
columnGap?: string;
21+
rowGap?: string;
22+
paddingInline?: string;
23+
paddingBlock?: string;
24+
scrollY?: boolean;
25+
className?: string;
26+
style?: React.CSSProperties;
27+
}
28+
29+
export const CardGroup: React.FunctionComponent<CardGroupProps> = ({
30+
children,
31+
minColumnWidth = 280,
32+
autoRows = 320,
33+
columnGap = tokens.spacingHorizontalM,
34+
rowGap = tokens.spacingHorizontalM,
35+
paddingInline = tokens.spacingHorizontalM,
36+
paddingBlock = tokens.spacingVerticalM,
37+
scrollY = false,
38+
className,
39+
style
40+
}) => {
41+
const styles = useStyles();
42+
43+
const toCssLen = (v: number | string) => typeof v === "number" ? `${v}px` : v;
44+
45+
const computedStyle: React.CSSProperties = {
46+
gridTemplateColumns: `repeat(auto-fill, minmax(${toCssLen(minColumnWidth)}, 1fr))`,
47+
columnGap,
48+
rowGap,
49+
paddingInline,
50+
paddingBlock,
51+
gridAutoRows: toCssLen(autoRows),
52+
overflowY: scrollY ? "auto" : undefined,
53+
minHeight: scrollY ? 0 : undefined,
54+
...style
55+
};
56+
57+
return <div className={mergeClasses(styles.root, className)} style={computedStyle}>
58+
{children}
59+
</div>;
60+
};

0 commit comments

Comments
 (0)