From bab20ed5af5ed89f154559f4e0299739c4ce44d9 Mon Sep 17 00:00:00 2001 From: Jeremy Clements <79224539+jeclrsg@users.noreply.github.com> Date: Mon, 1 Apr 2024 16:30:01 -0400 Subject: [PATCH 1/2] HPCC-30771 ECL Watch v9 WUs list filter allow multiple clusters Changed our AsyncDropdown component to accept a "multiSelect" parameter (which is already built-in to the extended FluentUI Dropdown) Signed-off-by: Jeremy Clements <79224539+jeclrsg@users.noreply.github.com> --- esp/src/src-react/components/DataPatterns.tsx | 4 +- esp/src/src-react/components/Workunits.tsx | 2 +- .../src-react/components/forms/AddBinding.tsx | 6 +- .../components/forms/AddPermission.tsx | 4 +- .../src-react/components/forms/CopyFile.tsx | 4 +- .../components/forms/DesprayFile.tsx | 6 +- esp/src/src-react/components/forms/Fields.tsx | 135 +++++++++++++----- .../components/forms/GroupAddUser.tsx | 4 +- .../src-react/components/forms/Optimize.tsx | 4 +- .../src-react/components/forms/RemoteCopy.tsx | 4 +- .../components/forms/ReplicateFile.tsx | 4 +- .../components/forms/UserAddGroup.tsx | 4 +- .../forms/landing-zone/BlobImportForm.tsx | 6 +- .../landing-zone/DelimitedImportForm.tsx | 6 +- .../forms/landing-zone/FileListForm.tsx | 6 +- .../forms/landing-zone/FixedImportForm.tsx | 6 +- .../forms/landing-zone/JsonImportForm.tsx | 6 +- .../forms/landing-zone/VariableImportForm.tsx | 6 +- .../forms/landing-zone/XmlImportForm.tsx | 6 +- 19 files changed, 144 insertions(+), 79 deletions(-) diff --git a/esp/src/src-react/components/DataPatterns.tsx b/esp/src/src-react/components/DataPatterns.tsx index 9abc1e3e5cd..703ed29891a 100644 --- a/esp/src/src-react/components/DataPatterns.tsx +++ b/esp/src/src-react/components/DataPatterns.tsx @@ -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"; @@ -87,7 +87,7 @@ export const DataPatterns: React.FunctionComponent = ({ className={dpStyles.inlineDropdown} required={true} selectedKey={targetCluster} - onChange={(ev, row) => { + onChange={(ev, row: IDropdownOption) => { setTargetCluster(row.key as string); }} /> diff --git a/esp/src/src-react/components/Workunits.tsx b/esp/src/src-react/components/Workunits.tsx index 5dc413d3664..88b3236c38a 100644 --- a/esp/src/src-react/components/Workunits.tsx +++ b/esp/src/src-react/components/Workunits.tsx @@ -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 }, diff --git a/esp/src/src-react/components/forms/AddBinding.tsx b/esp/src/src-react/components/forms/AddBinding.tsx index 0ebf60ad358..33df4fa0127 100644 --- a/esp/src/src-react/components/forms/AddBinding.tsx +++ b/esp/src/src-react/components/forms/AddBinding.tsx @@ -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"; @@ -76,7 +76,7 @@ export const AddBindingForm: React.FunctionComponent = ({ fieldState: { error } }) => { + onChange={(evt, option: IDropdownOption) => { onChange(option.key); }} required={true} @@ -108,7 +108,7 @@ export const AddBindingForm: React.FunctionComponent = ({ fieldState: { error } }) => { + onChange={(evt, option: IDropdownOption) => { onChange(option.key); }} required={true} diff --git a/esp/src/src-react/components/forms/AddPermission.tsx b/esp/src/src-react/components/forms/AddPermission.tsx index b527db7d6ca..39f7df0f866 100644 --- a/esp/src/src-react/components/forms/AddPermission.tsx +++ b/esp/src/src-react/components/forms/AddPermission.tsx @@ -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"; @@ -82,7 +82,7 @@ export const AddPermissionForm: React.FunctionComponent required={true} label={nlsHPCC.Type} selectedKey={value} - onChange={(evt, option) => { + onChange={(evt, option: IDropdownOption) => { onChange(option.key); }} errorMessage={error && error?.message} diff --git a/esp/src/src-react/components/forms/CopyFile.tsx b/esp/src/src-react/components/forms/CopyFile.tsx index c3662170c72..289f310d6c9 100644 --- a/esp/src/src-react/components/forms/CopyFile.tsx +++ b/esp/src/src-react/components/forms/CopyFile.tsx @@ -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"; @@ -130,7 +130,7 @@ export const CopyFile: React.FunctionComponent = ({ required={true} selectedKey={value} placeholder={nlsHPCC.SelectValue} - onChange={(evt, option) => { + onChange={(evt, option: IDropdownOption) => { onChange(option.key); }} errorMessage={error && error.message} diff --git a/esp/src/src-react/components/forms/DesprayFile.tsx b/esp/src/src-react/components/forms/DesprayFile.tsx index 15e297270a8..b57a171d0d1 100644 --- a/esp/src/src-react/components/forms/DesprayFile.tsx +++ b/esp/src/src-react/components/forms/DesprayFile.tsx @@ -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"; @@ -151,7 +151,7 @@ export const DesprayFile: React.FunctionComponent = ({ 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) { @@ -179,7 +179,7 @@ export const DesprayFile: React.FunctionComponent = ({ 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); diff --git a/esp/src/src-react/components/forms/Fields.tsx b/esp/src/src-react/components/forms/Fields.tsx index d9221b96775..5a72453c409 100644 --- a/esp/src/src-react/components/forms/Fields.tsx +++ b/esp/src/src-react/components/forms/Fields.tsx @@ -81,8 +81,9 @@ interface AsyncDropdownProps { selectedKey?: string; required?: boolean; disabled?: boolean; + multiSelect?: boolean; errorMessage?: string; - onChange?: (event: React.FormEvent, option?: IDropdownOption, index?: number) => void; + onChange?: (event: React.FormEvent, option?: IDropdownOption | IDropdownOption[], index?: number) => void; placeholder?: string; className?: string; } @@ -93,6 +94,7 @@ const AsyncDropdown: React.FunctionComponent = ({ selectedKey, required = false, disabled, + multiSelect = false, errorMessage, onChange, placeholder, @@ -101,43 +103,96 @@ const AsyncDropdown: React.FunctionComponent = ({ const selOptions = React.useMemo(() => { 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(); const [selectedIdx, setSelectedIdx] = React.useState(); - React.useEffect(() => { - let item; - if (selectedItem?.key) { - item = selOptions?.find(row => row.key === selectedItem?.key) ?? selOptions[0]; + const [selectedItems, setSelectedItems] = React.useState([]); + + 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 ? - : - setSelectedItem(item)} placeholder={placeholder} disabled={disabled} required={required} errorMessage={errorMessage} className={className} />; + }, [onChange, multiSelect, selectedItem, selectedIdx, selectedKey, selectedItems]); + + if (multiSelect) { + return options === undefined ? + : + 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 ? + : + setSelectedItem(item)} placeholder={placeholder} disabled={disabled} required={required} errorMessage={errorMessage} className={className} />; + } }; interface DropdownMultiProps { @@ -286,6 +341,7 @@ interface QueriesActiveStateField extends BaseField { interface TargetClusterField extends BaseField { type: "target-cluster"; + multiSelect?: boolean; value?: string; } @@ -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} /> }); @@ -1074,8 +1132,15 @@ export function createInputs(fields: Fields, onChange?: (id: string, newValue: a label: field.label, field: 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} /> }); @@ -1088,7 +1153,7 @@ export function createInputs(fields: Fields, onChange?: (id: string, newValue: a field: { + onChange={(ev, row: IDropdownOption) => { onChange(fieldID, row.key); setDropzone(row.key as string); }} @@ -1104,7 +1169,7 @@ export function createInputs(fields: Fields, onChange?: (id: string, newValue: a field: onChange(fieldID, row.key)} + onChange={(ev, row: IDropdownOption) => onChange(fieldID, row.key)} placeholder={field.placeholder} setSetDropzone={_ => setDropzone = _} /> @@ -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} /> }); @@ -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} /> }); @@ -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} /> }); @@ -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} /> }); @@ -1176,7 +1241,7 @@ export function createInputs(fields: Fields, onChange?: (id: string, newValue: a field: onChange(fieldID, row.key)} + onChange={(ev, row: IDropdownOption) => onChange(fieldID, row.key)} placeholder={field.placeholder} /> }); @@ -1189,7 +1254,7 @@ export function createInputs(fields: Fields, onChange?: (id: string, newValue: a field: onChange(fieldID, row.key)} + onChange={(ev, row: IDropdownOption) => onChange(fieldID, row.key)} placeholder={field.placeholder} /> }); @@ -1202,7 +1267,7 @@ export function createInputs(fields: Fields, onChange?: (id: string, newValue: a field: onChange(fieldID, row.key)} + onChange={(ev, row: IDropdownOption) => onChange(fieldID, row.key)} placeholder={field.placeholder} /> }); diff --git a/esp/src/src-react/components/forms/GroupAddUser.tsx b/esp/src/src-react/components/forms/GroupAddUser.tsx index 08230ac5007..c8f1cef78b9 100644 --- a/esp/src/src-react/components/forms/GroupAddUser.tsx +++ b/esp/src/src-react/components/forms/GroupAddUser.tsx @@ -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"; @@ -83,7 +83,7 @@ export const GroupAddUserForm: React.FunctionComponent = ({ required={true} label={nlsHPCC.Username} selectedKey={value} - onChange={(evt, option) => { + onChange={(evt, option: IDropdownOption) => { onChange(option.key); }} errorMessage={error && error?.message} diff --git a/esp/src/src-react/components/forms/Optimize.tsx b/esp/src/src-react/components/forms/Optimize.tsx index 722014eb03a..b3e482ace92 100644 --- a/esp/src/src-react/components/forms/Optimize.tsx +++ b/esp/src/src-react/components/forms/Optimize.tsx @@ -1,5 +1,5 @@ import * as React from "react"; -import { Checkbox, DefaultButton, PrimaryButton, TextField, } from "@fluentui/react"; +import { Checkbox, DefaultButton, IDropdownOption, PrimaryButton, TextField, } from "@fluentui/react"; import { scopedLogger } from "@hpcc-js/util"; import { useForm, Controller } from "react-hook-form"; import { DPWorkunit } from "src/DataPatterns/DPWorkunit"; @@ -70,7 +70,7 @@ export const Optimize: React.FunctionComponent = ({ placeholder={nlsHPCC.Target} selectedKey={value} required={true} - onChange={(ev, row) => { + onChange={(ev, row: IDropdownOption) => { return onChange(row.key); }} errorMessage={error && error?.message} diff --git a/esp/src/src-react/components/forms/RemoteCopy.tsx b/esp/src/src-react/components/forms/RemoteCopy.tsx index 160c29d3885..baf5fcce2c2 100644 --- a/esp/src/src-react/components/forms/RemoteCopy.tsx +++ b/esp/src/src-react/components/forms/RemoteCopy.tsx @@ -1,5 +1,5 @@ import * as React from "react"; -import { Checkbox, DefaultButton, mergeStyleSets, MessageBar, MessageBarType, PrimaryButton, Stack, TextField } from "@fluentui/react"; +import { Checkbox, DefaultButton, IDropdownOption, mergeStyleSets, MessageBar, MessageBarType, PrimaryButton, Stack, TextField } from "@fluentui/react"; import { Controller, useForm } from "react-hook-form"; import { scopedLogger } from "@hpcc-js/util"; import nlsHPCC from "src/nlsHPCC"; @@ -187,7 +187,7 @@ export const RemoteCopy: React.FunctionComponent = ({ key={fieldName} label={nlsHPCC.Group} required={true} - onChange={(evt, option) => { + onChange={(evt, option: IDropdownOption) => { setSelectedDestGroup(option.key.toString()); onChange(option.key); }} diff --git a/esp/src/src-react/components/forms/ReplicateFile.tsx b/esp/src/src-react/components/forms/ReplicateFile.tsx index 6bc5e625ba7..4a8b61034f9 100644 --- a/esp/src/src-react/components/forms/ReplicateFile.tsx +++ b/esp/src/src-react/components/forms/ReplicateFile.tsx @@ -1,5 +1,5 @@ import * as React from "react"; -import { DefaultButton, PrimaryButton, Stack, TextField, } from "@fluentui/react"; +import { DefaultButton, IDropdownOption, PrimaryButton, Stack, TextField, } from "@fluentui/react"; import { useForm, Controller } from "react-hook-form"; import nlsHPCC from "src/nlsHPCC"; import * as FileSpray from "src/FileSpray"; @@ -115,7 +115,7 @@ export const ReplicateFile: React.FunctionComponent = ({ required={true} selectedKey={value} placeholder={nlsHPCC.SelectValue} - onChange={(evt, option) => { + onChange={(evt, option: IDropdownOption) => { onChange(option.key); }} errorMessage={error && error.message} diff --git a/esp/src/src-react/components/forms/UserAddGroup.tsx b/esp/src/src-react/components/forms/UserAddGroup.tsx index 48fd13be177..b09393c30f1 100644 --- a/esp/src/src-react/components/forms/UserAddGroup.tsx +++ b/esp/src/src-react/components/forms/UserAddGroup.tsx @@ -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"; @@ -83,7 +83,7 @@ export const UserAddGroupForm: React.FunctionComponent = ({ required={true} label={nlsHPCC.GroupName} selectedKey={value} - onChange={(evt, option) => { + onChange={(evt, option: IDropdownOption) => { onChange(option.key); }} errorMessage={error && error?.message} diff --git a/esp/src/src-react/components/forms/landing-zone/BlobImportForm.tsx b/esp/src/src-react/components/forms/landing-zone/BlobImportForm.tsx index 2ce53e8f583..8c313e65725 100644 --- a/esp/src/src-react/components/forms/landing-zone/BlobImportForm.tsx +++ b/esp/src/src-react/components/forms/landing-zone/BlobImportForm.tsx @@ -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 { scopedLogger } from "@hpcc-js/util"; import { useForm, Controller } from "react-hook-form"; import * as FileSpray from "src/FileSpray"; @@ -168,7 +168,7 @@ export const BlobImportForm: React.FunctionComponent = ({ required={true} selectedKey={value} placeholder={nlsHPCC.SelectValue} - onChange={(evt, option) => { + onChange={(evt, option: IDropdownOption) => { onChange(option.key); }} errorMessage={error && error?.message} @@ -188,7 +188,7 @@ export const BlobImportForm: React.FunctionComponent = ({ required={true} selectedKey={value} placeholder={nlsHPCC.SelectValue} - onChange={(evt, option) => { + onChange={(evt, option: IDropdownOption) => { onChange(option.key); }} errorMessage={error && error?.message} diff --git a/esp/src/src-react/components/forms/landing-zone/DelimitedImportForm.tsx b/esp/src/src-react/components/forms/landing-zone/DelimitedImportForm.tsx index 6e4d8accf49..93510cf8191 100644 --- a/esp/src/src-react/components/forms/landing-zone/DelimitedImportForm.tsx +++ b/esp/src/src-react/components/forms/landing-zone/DelimitedImportForm.tsx @@ -1,5 +1,5 @@ import * as React from "react"; -import { Checkbox, DefaultButton, Dropdown, mergeStyleSets, PrimaryButton, Stack, TextField } from "@fluentui/react"; +import { Checkbox, DefaultButton, Dropdown, IDropdownOption, mergeStyleSets, PrimaryButton, Stack, TextField } from "@fluentui/react"; import { scopedLogger } from "@hpcc-js/util"; import { useForm, Controller } from "react-hook-form"; import * as FileSpray from "src/FileSpray"; @@ -190,7 +190,7 @@ export const DelimitedImportForm: React.FunctionComponent { + onChange={(evt, option: IDropdownOption) => { onChange(option.key); }} errorMessage={error && error?.message} @@ -210,7 +210,7 @@ export const DelimitedImportForm: React.FunctionComponent { + onChange={(evt, option: IDropdownOption) => { onChange(option.key); }} errorMessage={error && error?.message} diff --git a/esp/src/src-react/components/forms/landing-zone/FileListForm.tsx b/esp/src/src-react/components/forms/landing-zone/FileListForm.tsx index bda1c8bc8e3..366196f4d2d 100644 --- a/esp/src/src-react/components/forms/landing-zone/FileListForm.tsx +++ b/esp/src/src-react/components/forms/landing-zone/FileListForm.tsx @@ -1,5 +1,5 @@ import * as React from "react"; -import { Checkbox, DefaultButton, keyframes, mergeStyleSets, PrimaryButton, Stack } from "@fluentui/react"; +import { Checkbox, DefaultButton, IDropdownOption, keyframes, mergeStyleSets, PrimaryButton, Stack } from "@fluentui/react"; import { ProgressRingDotsIcon } from "@fluentui/react-icons-mdl2"; import { FileSprayService } from "@hpcc-js/comms"; import { scopedLogger } from "@hpcc-js/util"; @@ -189,7 +189,7 @@ export const FileListForm: React.FunctionComponent = ({ label={nlsHPCC.LandingZone} required={true} placeholder={nlsHPCC.SelectValue} - onChange={(evt, option) => { + onChange={(evt, option: IDropdownOption) => { setDirectory(option["path"] as string); if (option["path"].indexOf("\\") > -1) { setPathSep("\\"); @@ -214,7 +214,7 @@ export const FileListForm: React.FunctionComponent = ({ dropzone={dropzone} required={true} placeholder={nlsHPCC.SelectValue} - onChange={(evt, option) => { + onChange={(evt, option: IDropdownOption) => { if (option) { setMachine(option.key as string); setOs(option["OS"] as number); diff --git a/esp/src/src-react/components/forms/landing-zone/FixedImportForm.tsx b/esp/src/src-react/components/forms/landing-zone/FixedImportForm.tsx index 756ea9b8ad0..6b8a5cbaa02 100644 --- a/esp/src/src-react/components/forms/landing-zone/FixedImportForm.tsx +++ b/esp/src/src-react/components/forms/landing-zone/FixedImportForm.tsx @@ -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 { scopedLogger } from "@hpcc-js/util"; import { useForm, Controller } from "react-hook-form"; import * as FileSpray from "src/FileSpray"; @@ -175,7 +175,7 @@ export const FixedImportForm: React.FunctionComponent = ({ required={true} selectedKey={value} placeholder={nlsHPCC.SelectValue} - onChange={(evt, option) => { + onChange={(evt, option: IDropdownOption) => { onChange(option.key); }} errorMessage={error && error?.message} @@ -195,7 +195,7 @@ export const FixedImportForm: React.FunctionComponent = ({ required={true} selectedKey={value} placeholder={nlsHPCC.SelectValue} - onChange={(evt, option) => { + onChange={(evt, option: IDropdownOption) => { onChange(option.key); }} errorMessage={error && error?.message} diff --git a/esp/src/src-react/components/forms/landing-zone/JsonImportForm.tsx b/esp/src/src-react/components/forms/landing-zone/JsonImportForm.tsx index f97a0a64b05..7132df1e0fa 100644 --- a/esp/src/src-react/components/forms/landing-zone/JsonImportForm.tsx +++ b/esp/src/src-react/components/forms/landing-zone/JsonImportForm.tsx @@ -1,5 +1,5 @@ import * as React from "react"; -import { Checkbox, DefaultButton, Dropdown, mergeStyleSets, PrimaryButton, Stack, TextField } from "@fluentui/react"; +import { Checkbox, DefaultButton, Dropdown, IDropdownOption, mergeStyleSets, PrimaryButton, Stack, TextField } from "@fluentui/react"; import { scopedLogger } from "@hpcc-js/util"; import { useForm, Controller } from "react-hook-form"; import * as FileSpray from "src/FileSpray"; @@ -180,7 +180,7 @@ export const JsonImportForm: React.FunctionComponent = ({ required={true} selectedKey={value} placeholder={nlsHPCC.SelectValue} - onChange={(evt, option) => { + onChange={(evt, option: IDropdownOption) => { onChange(option.key); }} errorMessage={error && error?.message} @@ -200,7 +200,7 @@ export const JsonImportForm: React.FunctionComponent = ({ required={true} selectedKey={value} placeholder={nlsHPCC.SelectValue} - onChange={(evt, option) => { + onChange={(evt, option: IDropdownOption) => { onChange(option.key); }} errorMessage={error && error?.message} diff --git a/esp/src/src-react/components/forms/landing-zone/VariableImportForm.tsx b/esp/src/src-react/components/forms/landing-zone/VariableImportForm.tsx index d9042425a31..64fadc72493 100644 --- a/esp/src/src-react/components/forms/landing-zone/VariableImportForm.tsx +++ b/esp/src/src-react/components/forms/landing-zone/VariableImportForm.tsx @@ -1,5 +1,5 @@ import * as React from "react"; -import { Checkbox, DefaultButton, Dropdown, mergeStyleSets, PrimaryButton, Stack, TextField } from "@fluentui/react"; +import { Checkbox, DefaultButton, Dropdown, IDropdownOption, mergeStyleSets, PrimaryButton, Stack, TextField } from "@fluentui/react"; import { scopedLogger } from "@hpcc-js/util"; import { useForm, Controller } from "react-hook-form"; import * as FileSpray from "src/FileSpray"; @@ -170,7 +170,7 @@ export const VariableImportForm: React.FunctionComponent { + onChange={(evt, option: IDropdownOption) => { onChange(option.key); }} errorMessage={error && error?.message} @@ -190,7 +190,7 @@ export const VariableImportForm: React.FunctionComponent { + onChange={(evt, option: IDropdownOption) => { onChange(option.key); }} errorMessage={error && error?.message} diff --git a/esp/src/src-react/components/forms/landing-zone/XmlImportForm.tsx b/esp/src/src-react/components/forms/landing-zone/XmlImportForm.tsx index 93ce5f27107..b2dc3197ff3 100644 --- a/esp/src/src-react/components/forms/landing-zone/XmlImportForm.tsx +++ b/esp/src/src-react/components/forms/landing-zone/XmlImportForm.tsx @@ -1,5 +1,5 @@ import * as React from "react"; -import { Checkbox, DefaultButton, Dropdown, mergeStyleSets, PrimaryButton, Stack, TextField } from "@fluentui/react"; +import { Checkbox, DefaultButton, Dropdown, IDropdownOption, mergeStyleSets, PrimaryButton, Stack, TextField } from "@fluentui/react"; import { scopedLogger } from "@hpcc-js/util"; import { useForm, Controller } from "react-hook-form"; import * as FileSpray from "src/FileSpray"; @@ -178,7 +178,7 @@ export const XmlImportForm: React.FunctionComponent = ({ required={true} selectedKey={value} placeholder={nlsHPCC.SelectValue} - onChange={(evt, option) => { + onChange={(evt, option: IDropdownOption) => { onChange(option.key); }} errorMessage={error && error?.message} @@ -198,7 +198,7 @@ export const XmlImportForm: React.FunctionComponent = ({ required={true} selectedKey={value} placeholder={nlsHPCC.SelectValue} - onChange={(evt, option) => { + onChange={(evt, option: IDropdownOption) => { onChange(option.key); }} errorMessage={error && error?.message} From d028565b0e51b6696076cab5ffb5dc611d000d02 Mon Sep 17 00:00:00 2001 From: Gordon Smith Date: Thu, 18 Apr 2024 08:41:01 +0100 Subject: [PATCH 2/2] HPCC-31633 Issue fetching WsECL port from WsTopology When built for containerised environment, the port number needs to be fetched from WsResources rather than WsTopology. Signed-off-by: Gordon Smith --- esp/src/src/WsTopology.ts | 97 ++++++++++++++++++++++----------------- 1 file changed, 54 insertions(+), 43 deletions(-) diff --git a/esp/src/src/WsTopology.ts b/esp/src/src/WsTopology.ts index d23e4bc97f3..7a467c662a5 100644 --- a/esp/src/src/WsTopology.ts +++ b/esp/src/src/WsTopology.ts @@ -1,4 +1,7 @@ -import { Connection } from "@hpcc-js/comms"; +import { Connection, ResourcesService, Topology } from "@hpcc-js/comms"; +import { scopedLogger } from "@hpcc-js/util"; +import { containerized } from "src/BuildInfo"; +import { Memory } from "src/store/Memory"; import * as arrayUtil from "dojo/_base/array"; import * as Deferred from "dojo/_base/Deferred"; import * as lang from "dojo/_base/lang"; @@ -9,7 +12,8 @@ import * as aspect from "dojo/aspect"; import * as ESPRequest from "./ESPRequest"; import * as Utility from "./Utility"; -import { Memory } from "src/store/Memory"; + +const logger = scopedLogger("src/ESPRequest.ts"); declare const dojoConfig; @@ -119,61 +123,68 @@ export function TpClusterQuery(params) { return ESPRequest.send("WsTopology", "TpClusterQuery", params); } -export function GetESPServiceBaseURL(type) { - const deferred = new Deferred(); - this.TpServiceQuery({}).then(function (response) { +const eclqueriesPromise: { [id: string]: Promise } = {}; +export function GetESPServiceBaseURL(type: string): Promise { + if (!eclqueriesPromise[type]) { let retVal = ESPRequest.getURL({ port: window.location.protocol === "https:" ? 18002 : 8002, pathname: "" }); - if (lang.exists("TpServiceQueryResponse.ServiceList.TpEspServers.TpEspServer", response)) { - arrayUtil.forEach(response.TpServiceQueryResponse.ServiceList.TpEspServers.TpEspServer, function (item, idx) { - if (lang.exists("TpBindings.TpBinding", item)) { - arrayUtil.forEach(item.TpBindings.TpBinding, function (binding, idx) { - if (binding.Service === type && binding.Protocol + ":" === location.protocol) { - retVal = ESPRequest.getURL({ - port: binding.Port, - pathname: "" - }); - return true; - } + if (containerized) { + const resources = new ResourcesService({ baseUrl: "" }); + eclqueriesPromise[type] = resources.ServiceQuery({ Type: type }).then(response => { + const service = response?.Services?.Service?.find(s => s.Type === type); + if (service) { + retVal = ESPRequest.getURL({ + protocol: service.TLSSecure ? "https:" : "http:", + port: service.Port, + pathname: "" }); } - if (retVal !== "") - return true; + return retVal; + }).catch(e => { + logger.error(e); + return retVal; + }); + } else { + const topology = Topology.attach({ baseUrl: "" }); + eclqueriesPromise[type] = topology.fetchServices({ Type: type }).then(response => { + const service = response?.TpEspServers?.TpEspServer?.find(s => s.Type === type); + if (service) { + const binding = service.TpBindings?.TpBinding?.find(b => b.Service === type && b.Protocol + ":" === location.protocol); + if (binding) { + retVal = ESPRequest.getURL({ + port: binding.Port, + pathname: "" + }); + } + } + return retVal; + }).catch(e => { + logger.error(e); + return retVal; }); } - deferred.resolve(retVal); - }); - return deferred.promise; + } + return eclqueriesPromise[type]; } -export const WsEclURL = ""; -export function GetWsEclURL(type) { - const deferred = new Deferred(); - if (this.WsEclURL === "") { - const context = this; - this.GetESPServiceBaseURL("ws_ecl").then(function (response) { - context.WsEclURL = response + "/WsEcl/"; - deferred.resolve(context.WsEclURL + type + "/query/"); +let WsEclURL: Promise; +export function GetWsEclURL(type): Promise { + if (!WsEclURL) { + WsEclURL = GetESPServiceBaseURL(containerized ? "eclqueries" : "ws_ecl").then(response => { + return response + "/WsEcl/"; }); - } else { - deferred.resolve(this.WsEclURL + type + "/query/"); } - return deferred.promise; + return this.WsEclURL.then(response => response + type + "/query/"); } -export const WsEclIFrameURL = ""; -export function GetWsEclIFrameURL(type) { - const deferred = new Deferred(); - if (this.WsEclIFrameURL === "") { - const context = this; - this.GetESPServiceBaseURL("ws_ecl").then(function (response) { - context.WsEclIFrameURL = response + dojoConfig.urlInfo.basePath + "/stub.htm?Widget=IFrameWidget&src=" + encodeURIComponent("/WsEcl/"); - deferred.resolve(context.WsEclIFrameURL + encodeURIComponent(type + "/query/")); +let WsEclIFrameURL: Promise; +export function GetWsEclIFrameURL(type): Promise { + if (!WsEclIFrameURL) { + WsEclIFrameURL = GetESPServiceBaseURL(containerized ? "eclqueries" : "ws_ecl").then(response => { + return response + dojoConfig.urlInfo.basePath + "/stub.htm?Widget=IFrameWidget&src=" + encodeURIComponent("/WsEcl/"); }); - } else { - deferred.resolve(this.WsEclIFrameURL + encodeURIComponent(type + "/query/")); } - return deferred.promise; + return WsEclIFrameURL.then(url => url + encodeURIComponent(type + "/query/")); } export function TpTargetClusterQuery(params) { return ESPRequest.send("WsTopology", "TpTargetClusterQuery", params);