Skip to content

Commit

Permalink
Merge pull request #18536 from jeclrsg/hpcc-30771-filter-cluster-mult…
Browse files Browse the repository at this point in the history
…iselect

HPCC-30771 ECL Watch v9 WUs list filter allow multiple clusters
  • Loading branch information
GordonSmith authored Apr 18, 2024
2 parents eb039c7 + bab20ed commit 35f1a67
Show file tree
Hide file tree
Showing 19 changed files with 144 additions and 79 deletions.
4 changes: 2 additions & 2 deletions esp/src/src-react/components/DataPatterns.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from "react";
import { CommandBar, ContextualMenuItemType, ICommandBarItemProps, mergeStyleSets, ScrollablePane, ScrollbarVisibility, Sticky, StickyPositionType } from "@fluentui/react";
import { CommandBar, ContextualMenuItemType, ICommandBarItemProps, IDropdownOption, mergeStyleSets, ScrollablePane, ScrollbarVisibility, Sticky, StickyPositionType } from "@fluentui/react";
import nlsHPCC from "src/nlsHPCC";
import { DPWorkunit } from "src/DataPatterns/DPWorkunit";
import { Report } from "src/DataPatterns/Report";
Expand Down Expand Up @@ -87,7 +87,7 @@ export const DataPatterns: React.FunctionComponent<DataPatternsProps> = ({
className={dpStyles.inlineDropdown}
required={true}
selectedKey={targetCluster}
onChange={(ev, row) => {
onChange={(ev, row: IDropdownOption) => {
setTargetCluster(row.key as string);
}}
/>
Expand Down
2 changes: 1 addition & 1 deletion esp/src/src-react/components/Workunits.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const FilterFields: Fields = {
"Wuid": { type: "string", label: nlsHPCC.WUID, placeholder: "W20200824-060035" },
"Owner": { type: "string", label: nlsHPCC.Owner, placeholder: nlsHPCC.jsmi },
"Jobname": { type: "string", label: nlsHPCC.JobName, placeholder: nlsHPCC.log_analysis_1 },
"Cluster": { type: "target-cluster", label: nlsHPCC.Cluster, placeholder: "" },
"Cluster": { type: "target-cluster", label: nlsHPCC.Cluster, placeholder: "", multiSelect: true },
"State": { type: "workunit-state", label: nlsHPCC.State, placeholder: "" },
"ECL": { type: "string", label: nlsHPCC.ECL, placeholder: nlsHPCC.dataset },
"LogicalFile": { type: "string", label: nlsHPCC.LogicalFile, placeholder: nlsHPCC.somefile },
Expand Down
6 changes: 3 additions & 3 deletions esp/src/src-react/components/forms/AddBinding.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from "react";
import { DefaultButton, PrimaryButton, TextField, } from "@fluentui/react";
import { DefaultButton, IDropdownOption, PrimaryButton, TextField, } from "@fluentui/react";
import { scopedLogger } from "@hpcc-js/util";
import { useForm, Controller } from "react-hook-form";
import { EsdlDefinitionsTextField, EsdlEspProcessesTextField } from "./Fields";
Expand Down Expand Up @@ -76,7 +76,7 @@ export const AddBindingForm: React.FunctionComponent<AddBindingFormProps> = ({
fieldState: { error }
}) => <EsdlEspProcessesTextField
key={fieldName}
onChange={(evt, option) => {
onChange={(evt, option: IDropdownOption) => {
onChange(option.key);
}}
required={true}
Expand Down Expand Up @@ -108,7 +108,7 @@ export const AddBindingForm: React.FunctionComponent<AddBindingFormProps> = ({
fieldState: { error }
}) => <EsdlDefinitionsTextField
key={fieldName}
onChange={(evt, option) => {
onChange={(evt, option: IDropdownOption) => {
onChange(option.key);
}}
required={true}
Expand Down
4 changes: 2 additions & 2 deletions esp/src/src-react/components/forms/AddPermission.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from "react";
import { DefaultButton, MessageBar, MessageBarType, PrimaryButton, TextField, } from "@fluentui/react";
import { DefaultButton, IDropdownOption, MessageBar, MessageBarType, PrimaryButton, TextField, } from "@fluentui/react";
import { scopedLogger } from "@hpcc-js/util";
import { useForm, Controller } from "react-hook-form";
import nlsHPCC from "src/nlsHPCC";
Expand Down Expand Up @@ -82,7 +82,7 @@ export const AddPermissionForm: React.FunctionComponent<AddPermissionFormProps>
required={true}
label={nlsHPCC.Type}
selectedKey={value}
onChange={(evt, option) => {
onChange={(evt, option: IDropdownOption) => {
onChange(option.key);
}}
errorMessage={error && error?.message}
Expand Down
4 changes: 2 additions & 2 deletions esp/src/src-react/components/forms/CopyFile.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from "react";
import { Checkbox, DefaultButton, mergeStyleSets, PrimaryButton, Stack, TextField, } from "@fluentui/react";
import { Checkbox, DefaultButton, IDropdownOption, mergeStyleSets, PrimaryButton, Stack, TextField, } from "@fluentui/react";
import { useForm, Controller } from "react-hook-form";
import nlsHPCC from "src/nlsHPCC";
import * as FileSpray from "src/FileSpray";
Expand Down Expand Up @@ -130,7 +130,7 @@ export const CopyFile: React.FunctionComponent<CopyFileProps> = ({
required={true}
selectedKey={value}
placeholder={nlsHPCC.SelectValue}
onChange={(evt, option) => {
onChange={(evt, option: IDropdownOption) => {
onChange(option.key);
}}
errorMessage={error && error.message}
Expand Down
6 changes: 3 additions & 3 deletions esp/src/src-react/components/forms/DesprayFile.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from "react";
import { Checkbox, DefaultButton, mergeStyleSets, PrimaryButton, Stack, TextField, } from "@fluentui/react";
import { Checkbox, DefaultButton, IDropdownOption, mergeStyleSets, PrimaryButton, Stack, TextField, } from "@fluentui/react";
import { useForm, Controller } from "react-hook-form";
import { FileSpray, FileSprayService } from "@hpcc-js/comms";
import { scopedLogger } from "@hpcc-js/util";
Expand Down Expand Up @@ -151,7 +151,7 @@ export const DesprayFile: React.FunctionComponent<DesprayFileProps> = ({
required={true}
selectedKey={value}
placeholder={nlsHPCC.SelectValue}
onChange={(evt, option) => {
onChange={(evt, option: IDropdownOption) => {
setDropzone(option.key as string);
setDirectory(option["path"] as string);
if (option["path"].indexOf("\\") > -1) {
Expand Down Expand Up @@ -179,7 +179,7 @@ export const DesprayFile: React.FunctionComponent<DesprayFileProps> = ({
label={nlsHPCC.IPAddress}
selectedKey={value}
placeholder={nlsHPCC.SelectValue}
onChange={(evt, option) => {
onChange={(evt, option: IDropdownOption) => {
setMachine(option.key as string);
setOs(option["OS"] as number);
onChange(option.key);
Expand Down
135 changes: 100 additions & 35 deletions esp/src/src-react/components/forms/Fields.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,9 @@ interface AsyncDropdownProps {
selectedKey?: string;
required?: boolean;
disabled?: boolean;
multiSelect?: boolean;
errorMessage?: string;
onChange?: (event: React.FormEvent<HTMLDivElement>, option?: IDropdownOption, index?: number) => void;
onChange?: (event: React.FormEvent<HTMLDivElement>, option?: IDropdownOption | IDropdownOption[], index?: number) => void;
placeholder?: string;
className?: string;
}
Expand All @@ -93,6 +94,7 @@ const AsyncDropdown: React.FunctionComponent<AsyncDropdownProps> = ({
selectedKey,
required = false,
disabled,
multiSelect = false,
errorMessage,
onChange,
placeholder,
Expand All @@ -101,43 +103,96 @@ const AsyncDropdown: React.FunctionComponent<AsyncDropdownProps> = ({

const selOptions = React.useMemo<IDropdownOption[]>(() => {
if (options !== undefined) {
return !required ? [{ key: "", text: "" }, ...options] : options;
return !required && !multiSelect ? [{ key: "", text: "" }, ...options] : options;
}
return [];
}, [options, required]);
}, [multiSelect, options, required]);
const [selectedItem, setSelectedItem] = React.useState<IDropdownOption>();
const [selectedIdx, setSelectedIdx] = React.useState<number>();

React.useEffect(() => {
let item;
if (selectedItem?.key) {
item = selOptions?.find(row => row.key === selectedItem?.key) ?? selOptions[0];
const [selectedItems, setSelectedItems] = React.useState<IDropdownOption[]>([]);

const changeSelectedItems = React.useCallback(() => {
let items = [...selectedItems];
if (selectedKey === "") return;
const keys = selectedKey.split("|");
items = keys.map(key => { return { key: key, text: key }; });
if (!items.length) return;
if (items.map(item => item.key).join("|") === selectedKey) {
// do nothing, unless
if (!selectedItems.length) {
setSelectedItems(items);
}
} else {
item = selOptions?.find(row => row.key === selectedKey) ?? selOptions[0];
setSelectedItems(items);
}
if (!item) return;
if (item.key === selectedKey) {
// do nothing, unless
if (!selectedItem) {
}, [selectedKey, selectedItems]);

React.useEffect(() => {
// only on mount, pre-populate selectedItems from url
if (multiSelect) {
changeSelectedItems();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

React.useEffect(() => {
if (multiSelect) {
if (!selectedItems.length) return;
changeSelectedItems();
} else {
let item;
if (selectedItem?.key) {
item = selOptions?.find(row => row.key === selectedItem?.key) ?? selOptions[0];
} else {
item = selOptions?.find(row => row.key === selectedKey) ?? selOptions[0];
}
if (!item) return;
if (item.key === selectedKey) {
// do nothing, unless
if (!selectedItem) {
setSelectedItem(item);
setSelectedIdx(selOptions.indexOf(item));
}
} else {
setSelectedItem(item);
setSelectedIdx(selOptions.indexOf(item));
}
} else {
setSelectedItem(item);
setSelectedIdx(selOptions.indexOf(item));
}
}, [selectedKey, selOptions, selectedItem]);
}, [changeSelectedItems, multiSelect, selectedKey, selOptions, selectedItem, selectedItems]);

React.useEffect(() => {
if (!selectedItem || selectedItem?.key === selectedKey) return;
if (selectedItem !== undefined) {
onChange(undefined, selectedItem, selectedIdx);
if (multiSelect) {
if (!selectedItems.length && selectedKey === "") return;
if (selectedItems.map(item => item.key).join("|") === selectedKey) return;
onChange(undefined, selectedItems, null);
} else {
if (!selectedItem || selectedItem?.key === selectedKey) return;
if (selectedItem !== undefined) {
onChange(undefined, selectedItem, selectedIdx);
}
}
}, [onChange, selectedItem, selectedIdx, selectedKey]);

return options === undefined ?
<DropdownBase label={label} dropdownWidth="auto" options={[]} placeholder={nlsHPCC.loadingMessage} disabled={true} /> :
<DropdownBase label={label} dropdownWidth="auto" options={selOptions} selectedKey={selectedItem?.key} onChange={(_, item: IDropdownOption) => setSelectedItem(item)} placeholder={placeholder} disabled={disabled} required={required} errorMessage={errorMessage} className={className} />;
}, [onChange, multiSelect, selectedItem, selectedIdx, selectedKey, selectedItems]);

if (multiSelect) {
return options === undefined ?
<DropdownBase label={label} multiSelect dropdownWidth="auto" options={[]} placeholder={nlsHPCC.loadingMessage} disabled={true} /> :
<DropdownBase label={label} multiSelect dropdownWidth="auto" options={selOptions} selectedKeys={selectedItems.map(item => item.key as string)} onChange={
(_, item: IDropdownOption) => {
if (item) {
let selected = selectedItems.filter(i => i.key !== item.key);
if (item.selected) {
selected = [...selectedItems, item];
}
setSelectedItems(selected);
}
}
} placeholder={placeholder} disabled={disabled} required={required} errorMessage={errorMessage} className={className} />;
} else {
return options === undefined ?
<DropdownBase label={label} dropdownWidth="auto" options={[]} placeholder={nlsHPCC.loadingMessage} disabled={true} /> :
<DropdownBase label={label} dropdownWidth="auto" options={selOptions} selectedKey={selectedItem?.key} onChange={(_, item: IDropdownOption) => setSelectedItem(item)} placeholder={placeholder} disabled={disabled} required={required} errorMessage={errorMessage} className={className} />;
}
};

interface DropdownMultiProps {
Expand Down Expand Up @@ -286,6 +341,7 @@ interface QueriesActiveStateField extends BaseField {

interface TargetClusterField extends BaseField {
type: "target-cluster";
multiSelect?: boolean;
value?: string;
}

Expand Down Expand Up @@ -965,7 +1021,9 @@ export function createInputs(fields: Fields, onChange?: (id: string, newValue: a
text: state
};
})}
onChange={(ev, row) => onChange(fieldID, row.key)}
onChange={(ev, row) => {
onChange(fieldID, row.key);
}}
placeholder={field.placeholder}
/>
});
Expand Down Expand Up @@ -1074,8 +1132,15 @@ export function createInputs(fields: Fields, onChange?: (id: string, newValue: a
label: field.label,
field: <TargetClusterTextField
key={fieldID}
multiSelect={field.multiSelect}
selectedKey={field.value}
onChange={(ev, row) => onChange(fieldID, row.key)}
onChange={(ev, row) => {
if (field.multiSelect) {
onChange(fieldID, (row as IDropdownOption[]).map(i => i.key).join("|"));
} else {
onChange(fieldID, (row as IDropdownOption).key);
}
}}
placeholder={field.placeholder}
/>
});
Expand All @@ -1088,7 +1153,7 @@ export function createInputs(fields: Fields, onChange?: (id: string, newValue: a
field: <TargetDropzoneTextField
key={fieldID}
selectedKey={field.value}
onChange={(ev, row) => {
onChange={(ev, row: IDropdownOption) => {
onChange(fieldID, row.key);
setDropzone(row.key as string);
}}
Expand All @@ -1104,7 +1169,7 @@ export function createInputs(fields: Fields, onChange?: (id: string, newValue: a
field: <TargetServerTextLinkedField
key={fieldID}
selectedKey={field.value}
onChange={(ev, row) => onChange(fieldID, row.key)}
onChange={(ev, row: IDropdownOption) => onChange(fieldID, row.key)}
placeholder={field.placeholder}
setSetDropzone={_ => setDropzone = _}
/>
Expand All @@ -1119,7 +1184,7 @@ export function createInputs(fields: Fields, onChange?: (id: string, newValue: a
key={fieldID}
required={field.required}
selectedKey={field.value}
onChange={(ev, row) => onChange(fieldID, row.key)}
onChange={(ev, row: IDropdownOption) => onChange(fieldID, row.key)}
placeholder={field.placeholder}
/>
});
Expand All @@ -1134,7 +1199,7 @@ export function createInputs(fields: Fields, onChange?: (id: string, newValue: a
username={field.username}
required={field.required}
selectedKey={field.value}
onChange={(ev, row) => onChange(fieldID, row.key)}
onChange={(ev, row: IDropdownOption) => onChange(fieldID, row.key)}
placeholder={field.placeholder}
/>
});
Expand All @@ -1149,7 +1214,7 @@ export function createInputs(fields: Fields, onChange?: (id: string, newValue: a
groupname={field.groupname}
required={field.required}
selectedKey={field.value}
onChange={(ev, row) => onChange(fieldID, row.key)}
onChange={(ev, row: IDropdownOption) => onChange(fieldID, row.key)}
placeholder={field.placeholder}
/>
});
Expand All @@ -1163,7 +1228,7 @@ export function createInputs(fields: Fields, onChange?: (id: string, newValue: a
key={fieldID}
required={field.required}
selectedKey={field.value}
onChange={(ev, row) => onChange(fieldID, row.key)}
onChange={(ev, row: IDropdownOption) => onChange(fieldID, row.key)}
placeholder={field.placeholder}
/>
});
Expand All @@ -1176,7 +1241,7 @@ export function createInputs(fields: Fields, onChange?: (id: string, newValue: a
field: <TargetDfuSprayQueueTextField
key={fieldID}
selectedKey={field.value}
onChange={(ev, row) => onChange(fieldID, row.key)}
onChange={(ev, row: IDropdownOption) => onChange(fieldID, row.key)}
placeholder={field.placeholder}
/>
});
Expand All @@ -1189,7 +1254,7 @@ export function createInputs(fields: Fields, onChange?: (id: string, newValue: a
field: <EsdlEspProcessesTextField
key={fieldID}
selectedKey={field.value}
onChange={(ev, row) => onChange(fieldID, row.key)}
onChange={(ev, row: IDropdownOption) => onChange(fieldID, row.key)}
placeholder={field.placeholder}
/>
});
Expand All @@ -1202,7 +1267,7 @@ export function createInputs(fields: Fields, onChange?: (id: string, newValue: a
field: <EsdlDefinitionsTextField
key={fieldID}
selectedKey={field.value}
onChange={(ev, row) => onChange(fieldID, row.key)}
onChange={(ev, row: IDropdownOption) => onChange(fieldID, row.key)}
placeholder={field.placeholder}
/>
});
Expand Down
4 changes: 2 additions & 2 deletions esp/src/src-react/components/forms/GroupAddUser.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from "react";
import { DefaultButton, MessageBar, MessageBarType, PrimaryButton, } from "@fluentui/react";
import { DefaultButton, IDropdownOption, MessageBar, MessageBarType, PrimaryButton, } from "@fluentui/react";
import { scopedLogger } from "@hpcc-js/util";
import { useForm, Controller } from "react-hook-form";
import nlsHPCC from "src/nlsHPCC";
Expand Down Expand Up @@ -83,7 +83,7 @@ export const GroupAddUserForm: React.FunctionComponent<GroupAddUserProps> = ({
required={true}
label={nlsHPCC.Username}
selectedKey={value}
onChange={(evt, option) => {
onChange={(evt, option: IDropdownOption) => {
onChange(option.key);
}}
errorMessage={error && error?.message}
Expand Down
Loading

0 comments on commit 35f1a67

Please sign in to comment.