Skip to content

Commit

Permalink
HPCC-27635 ECL Watch v9 add syntax check to Playground
Browse files Browse the repository at this point in the history
added a syntax check button to the ECL Playground page

Signed-off-by: Jeremy Clements <[email protected]>
  • Loading branch information
jeclrsg committed Jun 19, 2024
1 parent fedef8f commit 98c533a
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 38 deletions.
129 changes: 95 additions & 34 deletions esp/src/src-react/components/ECLPlayground.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import * as React from "react";
import { ReflexContainer, ReflexElement, ReflexSplitter } from "../layouts/react-reflex";
import { PrimaryButton, IconButton, IIconProps, Link, Dropdown, IDropdownOption, TextField, useTheme } from "@fluentui/react";
import { IconButton, IIconProps, Link, Dropdown, IDropdownOption, TextField, useTheme } from "@fluentui/react";
import { Button } from "@fluentui/react-components";
import { CheckmarkCircleRegular, DismissCircleRegular, QuestionCircleRegular } from "@fluentui/react-icons";
import { scopedLogger } from "@hpcc-js/util";
import { useOnEvent } from "@fluentui/react-hooks";
import { mergeStyleSets } from "@fluentui/style-utilities";
import { ECLEditor, IPosition } from "@hpcc-js/codemirror";
import { Workunit, WUUpdate } from "@hpcc-js/comms";
import { Workunit, WUUpdate, WorkunitsService } from "@hpcc-js/comms";
import { HolyGrail } from "../layouts/HolyGrail";
import { DojoAdapter } from "../layouts/DojoAdapter";
import { pushUrl } from "../util/history";
import { debounce } from "../util/throttle";
import { darkTheme } from "../themes";
import { InfoGrid } from "./InfoGrid";
import { TabbedResults } from "./Results";
Expand Down Expand Up @@ -77,6 +80,9 @@ const playgroundStyles = mergeStyleSets({
borderRight: borderStyle
}
},
".fui-Button": {
height: "min-content"
},
".ms-Label": {
marginRight: "12px"
},
Expand Down Expand Up @@ -155,51 +161,69 @@ const warningIcon: IIconProps = { title: nlsHPCC.ErrorWarnings, ariaLabel: nlsHP
const resultsIcon: IIconProps = { title: nlsHPCC.Outputs, ariaLabel: nlsHPCC.Outputs, iconName: "Table" };
const graphIcon: IIconProps = { title: nlsHPCC.Visualizations, ariaLabel: nlsHPCC.Visualizations, iconName: "BarChartVerticalFill" };

const displayErrors = (wu, editor) => {
const displayErrors = async (wu = null, editor, errors = []) => {
if (!editor) return;
wu.fetchECLExceptions().then(errors => {
errors.forEach(err => {
const lineError = err.LineNo;
const lineErrorNum = lineError > 0 ? lineError - 1 : 0;
const startPos: IPosition = {
ch: (err.Column > 0) ? err.Column - 1 : 0,
line: lineErrorNum
};
const endPos: IPosition = {
ch: editor.getLineLength(lineErrorNum),
line: lineErrorNum
};

switch (err.Severity) {
case "Info":
editor.highlightInfo(startPos, endPos);
break;
case "Warning":
editor.highlightWarning(startPos, endPos);
break;
case "Error":
default:
editor.highlightError(startPos, endPos);
break;
}
});
if (wu) {
errors = await wu.fetchECLExceptions();
}
if (!errors.length) {
editor.removeAllHighlight();
}
errors.forEach(err => {
const lineError = err.LineNo;
const lineErrorNum = lineError > 0 ? lineError - 1 : 0;
const startPos: IPosition = {
ch: (err.Column > 0) ? err.Column - 1 : 0,
line: lineErrorNum
};
const endPos: IPosition = {
ch: editor.getLineLength(lineErrorNum),
line: lineErrorNum
};

switch (err.Severity) {
case "Info":
editor.highlightInfo(startPos, endPos);
break;
case "Warning":
editor.highlightWarning(startPos, endPos);
break;
case "Error":
default:
editor.highlightError(startPos, endPos);
break;
}
});
};

const service = new WorkunitsService({ baseUrl: "" });

enum SyntaxCheckResult {
Unknown,
Failed,
Passed
}

interface ECLEditorToolbarProps {
editor: ECLEditor;
outputMode: OutputMode;
setOutputMode: (_: OutputMode) => void;
workunit: Workunit;
setWorkunit: (_: Workunit) => void;
setSyntaxErrors: (_: any) => void;
syntaxStatusIcon: number;
setSyntaxStatusIcon: (_: number) => void;
}

const ECLEditorToolbar: React.FunctionComponent<ECLEditorToolbarProps> = ({
editor,
outputMode,
setOutputMode,
workunit,
setWorkunit
setWorkunit,
setSyntaxErrors,
syntaxStatusIcon,
setSyntaxStatusIcon
}) => {

const [cluster, setCluster] = React.useState("");
Expand Down Expand Up @@ -258,6 +282,24 @@ const ECLEditorToolbar: React.FunctionComponent<ECLEditorToolbarProps> = ({
}
}, [cluster, editor, playgroundResults, queryName, setQueryNameErrorMsg]);

const checkSyntax = React.useCallback(() => {
service.WUSyntaxCheckECL({
ECL: editor.ecl(),
Cluster: cluster
}).then(response => {
if (response.Errors) {
setSyntaxStatusIcon(SyntaxCheckResult.Failed);
setSyntaxErrors(response.Errors.ECLException);
displayErrors(null, editor, response.Errors.ECLException);
setOutputMode(OutputMode.ERRORS);
} else {
setSyntaxStatusIcon(SyntaxCheckResult.Passed);
setSyntaxErrors([]);
displayErrors(null, editor, []);
}
});
}, [cluster, editor, setOutputMode, setSyntaxErrors, setSyntaxStatusIcon]);

const handleKeyUp = React.useCallback((evt) => {
switch (evt.key) {
case "Enter":
Expand All @@ -282,10 +324,19 @@ const ECLEditorToolbar: React.FunctionComponent<ECLEditorToolbarProps> = ({
return <div className={playgroundStyles.toolBar}>
<div className={playgroundStyles.controlsWrapper}>
{showSubmitBtn ? (
<PrimaryButton text={nlsHPCC.Submit} onClick={submitWU} />
<Button appearance="primary" onClick={submitWU}>{nlsHPCC.Submit}</Button>
) : (
<PrimaryButton text={nlsHPCC.Publish} onClick={publishWU} />
<Button appearance="primary" onClick={publishWU}>{nlsHPCC.Publish}</Button>
)}
<Button style={{ marginLeft: 6 }} onClick={checkSyntax} iconPosition="after"
icon={
syntaxStatusIcon === SyntaxCheckResult.Passed ? <CheckmarkCircleRegular style={{ color: "green" }} /> :
syntaxStatusIcon === SyntaxCheckResult.Failed ? <DismissCircleRegular style={{ color: "red" }} /> :
<QuestionCircleRegular style={{ color: "initial" }} />
}
>
{nlsHPCC.Syntax}
</Button>
<TargetClusterTextField
key="target-cluster"
label={nlsHPCC.Target}
Expand Down Expand Up @@ -350,6 +401,8 @@ export const ECLPlayground: React.FunctionComponent<ECLPlaygroundProps> = (props
const [query, setQuery] = React.useState("");
const [selectedEclSample, setSelectedEclSample] = React.useState("");
const [eclContent, setEclContent] = React.useState("");
const [syntaxErrors, setSyntaxErrors] = React.useState<any[]>([]);
const [syntaxStatusIcon, setSyntaxStatusIcon] = React.useState(SyntaxCheckResult.Unknown);
const [eclSamples, setEclSamples] = React.useState<IDropdownOption[]>([]);

React.useEffect(() => {
Expand Down Expand Up @@ -417,6 +470,13 @@ export const ECLPlayground: React.FunctionComponent<ECLPlaygroundProps> = (props
}, [editor]);
useOnEvent(document, "eclwatch-theme-toggle", handleThemeToggle);

const handleEclChange = React.useMemo(() => debounce((evt) => {
if (editor.hasFocus()) {
setSyntaxStatusIcon(SyntaxCheckResult.Unknown);
}
}, 300), [editor]);
useOnEvent(window, "keyup", handleEclChange);

return <div className={playgroundStyles.root}>
<div className={playgroundStyles.titleBar}>
<h1 className={playgroundStyles.title}>{nlsHPCC.title_ECLPlayground}</h1>
Expand All @@ -437,7 +497,8 @@ export const ECLPlayground: React.FunctionComponent<ECLPlaygroundProps> = (props
main={<ECLSourceEditor text={query} setEditor={setEditor} />}
footer={
<ECLEditorToolbar
editor={editor}
editor={editor} setSyntaxErrors={setSyntaxErrors}
syntaxStatusIcon={syntaxStatusIcon} setSyntaxStatusIcon={setSyntaxStatusIcon}
workunit={workunit} setWorkunit={setWorkunit}
outputMode={outputMode} setOutputMode={setOutputMode}
/>
Expand All @@ -453,7 +514,7 @@ export const ECLPlayground: React.FunctionComponent<ECLPlaygroundProps> = (props
<ReflexSplitter />
<ReflexElement propagateDimensions={true} minSize={100}>
{outputMode === OutputMode.ERRORS ? (
<InfoGrid wuid={workunit?.Wuid} />
<InfoGrid wuid={workunit?.Wuid} syntaxErrors={syntaxErrors} />

) : outputMode === OutputMode.RESULTS ? (
<TabbedResults wuid={workunit?.Wuid} filter={filter} />
Expand Down
19 changes: 15 additions & 4 deletions esp/src/src-react/components/InfoGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,13 @@ interface FilterCounts {
}

interface InfoGridProps {
wuid: string;
wuid?: string;
syntaxErrors?: any[];
}

export const InfoGrid: React.FunctionComponent<InfoGridProps> = ({
wuid
wuid = null,
syntaxErrors = []
}) => {

const [costChecked, setCostChecked] = React.useState(true);
Expand All @@ -46,6 +48,7 @@ export const InfoGrid: React.FunctionComponent<InfoGridProps> = ({
const [otherChecked, setOtherChecked] = React.useState(true);
const [filterCounts, setFilterCounts] = React.useState<FilterCounts>({ cost: 0, penalty: 0, error: 0, warning: 0, info: 0, other: 0 });
const [exceptions] = useWorkunitExceptions(wuid);
const [errors, setErrors] = React.useState<any[]>([]);
const [data, setData] = React.useState<any[]>([]);
const {
selection, setSelection,
Expand All @@ -61,6 +64,14 @@ export const InfoGrid: React.FunctionComponent<InfoGridProps> = ({
{ key: "others", onRender: () => <Checkbox defaultChecked label={`${filterCounts.other || 0} ${nlsHPCC.Others}`} onChange={(ev, value) => setOtherChecked(value)} styles={{ root: { paddingTop: 8, paddingRight: 8 } }} /> }
], [filterCounts.cost, filterCounts.error, filterCounts.info, filterCounts.other, filterCounts.warning]);

React.useEffect(() => {
if (syntaxErrors.length) {
setErrors(syntaxErrors);
} else {
setErrors(exceptions);
}
}, [syntaxErrors, exceptions]);

// Grid ---
const columns = React.useMemo((): FluentColumns => {
return {
Expand Down Expand Up @@ -137,7 +148,7 @@ export const InfoGrid: React.FunctionComponent<InfoGridProps> = ({
info: 0,
other: 0
};
const filteredExceptions = exceptions.map((row, idx) => {
const filteredExceptions = errors?.map((row, idx) => {
if (row.Source === "Cost Optimizer") {
row.Severity = "Cost";
}
Expand Down Expand Up @@ -199,7 +210,7 @@ export const InfoGrid: React.FunctionComponent<InfoGridProps> = ({
});
setData(filteredExceptions);
setFilterCounts(filterCounts);
}, [costChecked, errorChecked, exceptions, infoChecked, otherChecked, warningChecked]);
}, [costChecked, errorChecked, errors, infoChecked, otherChecked, warningChecked]);

React.useEffect(() => {
if (data.length) {
Expand Down
1 change: 1 addition & 0 deletions esp/src/src/nls/hpcc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -922,6 +922,7 @@ export = {
Statistics: "Statistics",
SVGSource: "SVG Source",
SyncSelection: "Sync To Selection",
Syntax: "Syntax",
SystemServers: "System Servers",
tag: "tag",
Target: "Target",
Expand Down

0 comments on commit 98c533a

Please sign in to comment.