refactor: improve readability of several components (#3714)

* refactor to improve readability

* refactor to improve readability

* refactor to improve readability

* refactor to improve readability

* refactor to improve readability

* refactor to improve readability

* refactor to improve readability

* refactor to improve readability

* ✅ (Simple Agent.spec.ts): update expected count of python words to 2 for accurate test results
📝 (auto-login-off.spec.ts): add a 1-second timeout before checking visibility of a text element to ensure proper rendering and improve test reliability

* 📝 (cardComponent/index.tsx): Extract handlePlaygroundClick function to improve code readability and maintainability
📝 (codeAreaComponent/index.tsx): Refactor code to use consistent naming conventions and improve code structure
📝 (rename-label.tsx): Refactor code to use consistent naming conventions and improve code structure
📝 (dictAreaModal/index.tsx): Refactor code to use consistent naming conventions and improve code structure

* 🔧 (rename-label.tsx): Refactor RenameLabel component to improve readability and maintainability by restructuring the component logic into separate functions for handling blur, change, and double click events. Split the component rendering logic into separate functions for input and span elements.

* update type

* [autofix.ci] apply automated fixes

---------

Co-authored-by: anovazzi1 <otavio2204@gmail.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
Cristhian Zanforlin Lousa 2024-09-06 15:30:20 -03:00 • committed by GitHub
commit 40798c5b5a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 737 additions and 710 deletions

View file

@ -55,6 +55,32 @@ export default function CollectionCardComponent({
const { onDragStart } = useDragStart(data); const { onDragStart } = useDragStart(data);
const handlePlaygroundClick = (e: React.MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
e.stopPropagation();
track("Playground Button Clicked", { flowId: data.id });
setLoadingPlayground(true);
const flow = getFlowById(data.id);
if (flow) {
if (!hasPlayground(flow)) {
setErrorData({
title: "Error",
list: ["This flow doesn't have a playground."],
});
setLoadingPlayground(false);
return;
}
setCurrentFlow(flow);
setOpenPlayground(true);
setLoadingPlayground(false);
} else {
setErrorData({
title: "Error",
list: ["Error getting flow data."],
});
}
};
return ( return (
<> <>
<Card <Card
@ -135,31 +161,7 @@ export default function CollectionCardComponent({
size="sm" size="sm"
className="gap-2 whitespace-nowrap bg-muted" className="gap-2 whitespace-nowrap bg-muted"
data-testid={"playground-flow-button-" + data.id} data-testid={"playground-flow-button-" + data.id}
onClick={(e) => { onClick={handlePlaygroundClick}
e.preventDefault();
e.stopPropagation();
track("Playground Button Clicked", { flowId: data.id });
setLoadingPlayground(true);
const flow = getFlowById(data.id);
if (flow) {
if (!hasPlayground(flow)) {
setErrorData({
title: "Error",
list: ["This flow doesn't have a playground."],
});
setLoadingPlayground(false);
return;
}
setCurrentFlow(flow);
setOpenPlayground(true);
setLoadingPlayground(false);
} else {
setErrorData({
title: "Error",
list: ["Error getting flow data."],
});
}
}}
> >
{!loadingPlayground ? ( {!loadingPlayground ? (
<IconComponent <IconComponent

View file

@ -1,6 +1,7 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import CodeAreaModal from "../../modals/codeAreaModal"; import CodeAreaModal from "../../modals/codeAreaModal";
import { CodeAreaComponentType } from "../../types/components"; import { CodeAreaComponentType } from "../../types/components";
import { cn } from "../../utils/utils";
import IconComponent from "../genericIconComponent"; import IconComponent from "../genericIconComponent";
@ -17,57 +18,68 @@ export default function CodeAreaComponent({
open, open,
setOpen, setOpen,
}: CodeAreaComponentType) { }: CodeAreaComponentType) {
const [myValue, setMyValue] = useState( const [componentValue, setComponentValue] = useState(
typeof value == "string" ? value : JSON.stringify(value), typeof value == "string" ? value : JSON.stringify(value),
); );
useEffect(() => { useEffect(() => {
if (disabled && myValue !== "") { if (disabled && componentValue !== "") {
setMyValue(""); setComponentValue("");
onChange("", undefined, true); onChange("", undefined, true);
} }
}, [disabled]); }, [disabled]);
useEffect(() => { useEffect(() => {
setMyValue(typeof value == "string" ? value : JSON.stringify(value)); setComponentValue(typeof value == "string" ? value : JSON.stringify(value));
}, [value]); }, [value]);
const handleValueChange = (newValue) => {
onChange(newValue);
};
const renderInputText = () => (
<span
id={id}
data-testid={id}
className={cn(
editNode
? "input-edit-node input-dialog"
: "primary-input text-muted-foreground",
disabled && !editNode && "input-disable input-ring",
)}
>
{value !== "" ? value : "Type something..."}
</span>
);
const renderExternalLinkIcon = () => {
if (editNode) return null;
return (
<IconComponent
name="ExternalLink"
className={cn(
"icons-parameters-comp shrink-0",
disabled ? "text-ring" : "hover:text-accent-foreground",
)}
/>
);
};
return ( return (
<div className={disabled ? "pointer-events-none w-full" : "w-full"}> <div className={cn("w-full", disabled && "pointer-events-none")}>
<CodeAreaModal <CodeAreaModal
open={open} open={open}
setOpen={setOpen} setOpen={setOpen}
readonly={readonly} readonly={readonly}
dynamic={dynamic} dynamic={dynamic}
value={myValue} value={value}
nodeClass={nodeClass} nodeClass={nodeClass}
setNodeClass={setNodeClass!} setNodeClass={setNodeClass!}
setValue={(value: string) => { setValue={handleValueChange}
setMyValue(value);
onChange(value);
}}
> >
<div className="flex w-full items-center gap-3"> <div className="flex w-full items-center gap-3">
<span {renderInputText()}
id={id} {renderExternalLinkIcon()}
data-testid={id}
className={
editNode
? "input-edit-node input-dialog"
: (disabled ? "input-disable input-ring " : "") +
" primary-input text-muted-foreground"
}
>
{myValue !== "" ? myValue : "Type something..."}
</span>
{!editNode && (
<IconComponent
name="ExternalLink"
className={
"icons-parameters-comp shrink-0" +
(disabled ? " text-ring" : " hover:text-accent-foreground")
}
/>
)}
</div> </div>
</CodeAreaModal> </CodeAreaModal>
</div> </div>

View file

@ -69,131 +69,125 @@ export default function Dropdown({
} }
}, [open]); }, [open]);
return ( const renderTriggerButton = () => (
<> <PopoverTrigger asChild>
{Object.keys(options ?? [])?.length > 0 || combobox ? ( <Button
<> disabled={disabled}
<Popover open={open} onOpenChange={children ? () => {} : setOpen}> variant="primary"
{children ? ( size="xs"
<PopoverAnchor>{children}</PopoverAnchor> role="combobox"
) : ( ref={refButton}
<PopoverTrigger asChild> aria-expanded={open}
<Button data-testid={id}
disabled={disabled} className={cn(
variant="primary" editNode
size="xs" ? "dropdown-component-outline input-edit-node"
role="combobox" : "dropdown-component-false-outline py-2",
ref={refButton} "w-full justify-between font-normal",
aria-expanded={open} )}
data-testid={`${id ?? ""}`} >
className={cn( <span className="truncate" data-testid={`value-dropdown-${id}`}>
editNode {value &&
? "dropdown-component-outline" value !== "" &&
: "dropdown-component-false-outline", filteredOptions.find((option) => option === value)
"w-full justify-between font-normal", ? filteredOptions.find((option) => option === value)
editNode ? "input-edit-node" : "py-2", : "Choose an option..."}
)} </span>
> <ForwardedIconComponent
<span name="ChevronsUpDown"
className="truncate" className="ml-2 h-4 w-4 shrink-0 opacity-50"
data-testid={`value-dropdown-` + id} />
> </Button>
{value && </PopoverTrigger>
value !== "" && );
filteredOptions.find((option) => option === value)
? filteredOptions.find((option) => option === value)
: "Choose an option..."}
</span>
<ForwardedIconComponent const renderSearchInput = () => (
name="ChevronsUpDown" <div className="flex items-center border-b px-3">
className="ml-2 h-4 w-4 shrink-0 opacity-50" <ForwardedIconComponent
/> name="search"
</Button> className="mr-2 h-4 w-4 shrink-0 opacity-50"
</PopoverTrigger> />
)} <input
<PopoverContentDropdown onChange={searchRoleByTerm}
side="bottom" placeholder="Search options..."
avoidCollisions={!!children} className="flex h-9 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50"
className="noflow nowheel nopan nodelete nodrag p-0" />
style={ </div>
children );
? {}
: { minWidth: refButton?.current?.clientWidth ?? "200px" } const renderOptionsList = () => (
} <CommandList>
> <CommandEmpty>No values found.</CommandEmpty>
<Command> <CommandGroup defaultChecked={false}>
<div className="flex items-center border-b px-3"> {filteredOptions?.map((option, index) => (
<ForwardedIconComponent <ShadTooltip key={index} delayDuration={700} content={option}>
name="search" <div>
className="mr-2 h-4 w-4 shrink-0 opacity-50" <CommandItem
/> value={option}
<input onSelect={(currentValue) => {
onChange={searchRoleByTerm} onSelect(currentValue);
placeholder="Search options..." setOpen(false);
className="flex h-9 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50" }}
/> className="items-center overflow-hidden truncate"
</div> data-testid={`${option}-${index}-option`}
<CommandList> >
<CommandEmpty>No values found.</CommandEmpty> {customValue === option && (
<CommandGroup defaultChecked={false}> <span className="text-muted-foreground">Text:&nbsp;</span>
{filteredOptions?.map((option, id) => ( )}
<ShadTooltip <span className="truncate">{option}</span>
delayDuration={700} <ForwardedIconComponent
key={id} name="Check"
content={option} className={cn(
> "ml-auto h-4 w-4 shrink-0 text-primary",
<div> value === option ? "opacity-100" : "opacity-0",
<CommandItem )}
key={id} />
value={option} </CommandItem>
onSelect={(currentValue) => { </div>
onSelect(currentValue); </ShadTooltip>
setOpen(false); ))}
}} </CommandGroup>
className="items-center overflow-hidden truncate" </CommandList>
data-testid={`${option}-${id ?? ""}-option`} );
>
{customValue === option ? ( const renderPopoverContent = () => (
<span className="text-muted-foreground"> <PopoverContentDropdown
Text:&nbsp; side="bottom"
</span> avoidCollisions={!!children}
) : ( className="noflow nowheel nopan nodelete nodrag p-0"
<></> style={
)} children ? {} : { minWidth: refButton?.current?.clientWidth ?? "200px" }
<span className="truncate">{option}</span> }
<ForwardedIconComponent >
name="Check" <Command>
className={cn( {renderSearchInput()}
"ml-auto h-4 w-4 shrink-0 text-primary", {renderOptionsList()}
value === option ? "opacity-100" : "opacity-0", </Command>
)} </PopoverContentDropdown>
/> );
</CommandItem>
</div> if (Object.keys(options).length === 0 && !combobox) {
</ShadTooltip> return isLoading ? (
))} <div>
</CommandGroup> <span className="text-sm italic">Loading...</span>
</CommandList> </div>
</Command> ) : (
</PopoverContentDropdown> <div>
</Popover> <span className="text-sm italic">
</> No parameters are available for display.
</span>
</div>
);
}
return (
<Popover open={open} onOpenChange={children ? () => {} : setOpen}>
{children ? (
<PopoverAnchor>{children}</PopoverAnchor>
) : ( ) : (
<> renderTriggerButton()
{(!isLoading && (
<div>
<span className="text-sm italic">
No parameters are available for display.
</span>
</div>
)) || (
<div>
<span className="text-sm italic">Loading...</span>
</div>
)}
</>
)} )}
</> {renderPopoverContent()}
</Popover>
); );
} }

View file

@ -20,6 +20,19 @@ export default function FloatComponent({
} }
}, [disabled]); }, [disabled]);
const handleInput = (event: React.ChangeEvent<HTMLInputElement>) => {
const inputValue = Number(event.target.value);
if (inputValue < min) {
event.target.value = min.toString();
} else if (inputValue > max) {
event.target.value = max.toString();
}
};
const handleChange = (event) => {
onChange(event.target.value);
};
return ( return (
<div className="w-full"> <div className="w-full">
<Input <Input
@ -28,25 +41,14 @@ export default function FloatComponent({
type="number" type="number"
step={step} step={step}
min={min} min={min}
onInput={(event: React.ChangeEvent<HTMLInputElement>) => {
if (Number(event.target.value) < min) {
event.target.value = min.toString();
}
if (Number(event.target.value) > max) {
event.target.value = max.toString();
}
}}
max={max} max={max}
value={value ?? ""} value={value ?? ""}
disabled={disabled} disabled={disabled}
className={editNode ? "input-edit-node" : ""} className={editNode ? "input-edit-node" : ""}
placeholder={`Enter a value`} placeholder="Enter a value"
onChange={(event) => { onInput={handleInput}
onChange(event.target.value); onChange={handleChange}
}} onKeyDown={(e) => handleKeyDown(e, value, "")}
onKeyDown={(e) => {
handleKeyDown(e, value, "");
}}
/> />
</div> </div>
); );

View file

@ -29,6 +29,36 @@ export default function InputListComponent({
if (!value?.length) value = [""]; if (!value?.length) value = [""];
const handleInputChange = (index, newValue) => {
const newInputList = _.cloneDeep(value);
newInputList[index] = newValue;
onChange(newInputList);
};
const addNewInput = (e) => {
e.preventDefault();
const newInputList = _.cloneDeep(value);
newInputList.push("");
onChange(newInputList);
};
const removeInput = (index, e) => {
e.preventDefault();
const newInputList = _.cloneDeep(value);
newInputList.splice(index, 1);
onChange(newInputList);
};
const getButtonClassName = () =>
classNames(
disabled || playgroundDisabled
? "cursor-not-allowed text-muted-foreground"
: "text-primary hover:text-accent-foreground",
);
const getTestId = (type, index) =>
`input-list-${type}-btn${editNode ? "-edit" : ""}_${componentName}-${index}`;
return ( return (
<div <div
className={classNames( className={classNames(
@ -36,72 +66,31 @@ export default function InputListComponent({
"flex w-full flex-col gap-3", "flex w-full flex-col gap-3",
)} )}
> >
{value.map((singleValue, idx) => { {value.map((singleValue, index) => (
return ( <div key={index} className="flex w-full gap-3">
<div key={idx} className="flex w-full gap-3"> <Input
<Input disabled={disabled || playgroundDisabled}
disabled={disabled || playgroundDisabled} type="text"
type="text" value={singleValue}
value={singleValue} className={editNode ? "input-edit-node" : ""}
className={editNode ? "input-edit-node" : ""} placeholder="Type something..."
placeholder="Type something..." onChange={(event) => handleInputChange(index, event.target.value)}
onChange={(event) => { data-testid={`${id}_${index}`}
let newInputList = _.cloneDeep(value); />
newInputList[idx] = event.target.value; <Button
onChange(newInputList); unstyled
}} className={getButtonClassName()}
data-testid={`${id}_` + idx} onClick={index === 0 ? addNewInput : (e) => removeInput(index, e)}
data-testid={getTestId(index === 0 ? "plus" : "minus", index)}
disabled={disabled || playgroundDisabled}
>
<IconComponent
name={index === 0 ? "Plus" : "X"}
className="h-4 w-4"
/> />
{idx === 0 ? ( </Button>
<Button </div>
unstyled ))}
className={cn(
disabled || playgroundDisabled
? "cursor-not-allowed text-muted-foreground"
: "text-primary hover:text-accent-foreground",
)}
onClick={(e) => {
let newInputList = _.cloneDeep(value);
newInputList.push("");
onChange(newInputList);
e.preventDefault();
}}
data-testid={
`input-list-plus-btn${
editNode ? "-edit" : ""
}_${componentName}-` + idx
}
disabled={disabled || playgroundDisabled}
>
<IconComponent name="Plus" className="h-4 w-4" />
</Button>
) : (
<Button
unstyled
className={cn(
disabled || playgroundDisabled
? "cursor-not-allowed text-muted-foreground"
: "text-primary hover:text-accent-foreground",
)}
data-testid={
`input-list-minus-btn${
editNode ? "-edit" : ""
}_${componentName}-` + idx
}
onClick={(e) => {
let newInputList = _.cloneDeep(value);
newInputList.splice(idx, 1);
onChange(newInputList);
e.preventDefault();
}}
disabled={disabled || playgroundDisabled}
>
<IconComponent name="X" className="h-4 w-4" />
</Button>
)}
</div>
);
})}
</div> </div>
); );
} }

View file

@ -38,36 +38,57 @@ export default function IntComponent({
onChange(Number(e.target.value)); onChange(Number(e.target.value));
}; };
const getStepValue = () => {
return (Number.isInteger(rangeSpec?.step) ? rangeSpec.step : 1) ?? 1;
};
const getMinValue = () => {
return rangeSpec?.min ?? min;
};
const getMaxValue = () => {
return rangeSpec?.max ?? undefined;
};
const getInputClassName = () => {
return cn(
editNode ? "input-edit-node" : "",
"nopan nodelete nodrag noflow primary-input",
);
};
const handleNumberChange = (newValue) => {
onChange(Number(newValue));
};
const handleInputChange = (event) => {
const inputValue = Number(event.target.value);
if (inputValue < getMinValue()) {
event.target.value = getMinValue().toString();
}
};
const inputRef = useRef(null);
return ( return (
<div className="w-full"> <div className="w-full">
<NumberInput <NumberInput
id={id} id={id}
step={(Number.isInteger(rangeSpec?.step) ? rangeSpec?.step : 1) ?? 1} step={getStepValue()}
min={rangeSpec?.min ?? min} min={getMinValue()}
max={rangeSpec?.max ?? undefined} max={getMaxValue()}
onChange={(value) => { onChange={handleNumberChange}
onChange(Number(value));
}}
value={value ?? ""} value={value ?? ""}
> >
<NumberInputField <NumberInputField
className={cn( className={getInputClassName()}
editNode ? "input-edit-node" : "",
"nopan nodelete nodrag noflow primary-input",
)}
onChange={handleChangeInput} onChange={handleChangeInput}
onKeyDown={(event) => { onKeyDown={(event) => handleKeyDown(event, value, "")}
handleKeyDown(event, value, ""); onInput={handleInputChange}
}}
onInput={(event: React.ChangeEvent<HTMLInputElement>) => {
if (Number(event.target.value) < min) {
event.target.value = min.toString();
}
}}
disabled={disabled} disabled={disabled}
placeholder={editNode ? "Integer number" : "Type an integer number"} placeholder={editNode ? "Integer number" : "Type an integer number"}
data-testid={id} data-testid={id}
ref={ref} ref={inputRef}
/> />
<NumberInputStepper paddingRight={10}> <NumberInputStepper paddingRight={10}>
<NumberIncrementStepper fontSize={8} marginTop={6} /> <NumberIncrementStepper fontSize={8} marginTop={6} />

View file

@ -7,7 +7,6 @@ import {
hasDuplicateKeys, hasDuplicateKeys,
} from "@/utils/reactflowUtils"; } from "@/utils/reactflowUtils";
import { cloneDeep } from "lodash"; import { cloneDeep } from "lodash";
import { classNames } from "../../utils/utils";
import IconComponent from "../genericIconComponent"; import IconComponent from "../genericIconComponent";
import { Input } from "../ui/input"; import { Input } from "../ui/input";
@ -27,7 +26,7 @@ export default function KeypairListComponent({
const [duplicateKey, setDuplicateKey] = useState(false); const [duplicateKey, setDuplicateKey] = useState(false);
const myValue = const values =
Object.keys(value || {})?.length === 0 || !value Object.keys(value || {})?.length === 0 || !value
? [{ "": "" }] ? [{ "": "" }]
: convertObjToArray(value, "dict"); : convertObjToArray(value, "dict");
@ -43,120 +42,100 @@ export default function KeypairListComponent({
}; };
const handleChangeKey = (event, idx) => { const handleChangeKey = (event, idx) => {
const oldKey = Object.keys(myValue[idx])[0]; const oldKey = Object.keys(values[idx])[0];
const updatedObj = { [event.target.value]: myValue[idx][oldKey] }; const updatedObj = { [event.target.value]: values[idx][oldKey] };
const newValue = cloneDeep(myValue); const newValue = cloneDeep(values);
newValue[idx] = updatedObj; newValue[idx] = updatedObj;
handleNewValue(newValue); handleNewValue(newValue);
}; };
const handleChangeValue = (event, idx) => { const handleChangeValue = (event, idx) => {
const key = Object.keys(myValue[idx])[0]; const key = Object.keys(values[idx])[0];
const updatedObj = { [key]: event.target.value }; const updatedObj = { [key]: event.target.value };
const newValue = cloneDeep(myValue); const newValue = cloneDeep(values);
newValue[idx] = updatedObj; newValue[idx] = updatedObj;
handleNewValue(newValue); handleNewValue(newValue);
}; };
const addNewKeyValuePair = () => {
const newValues = cloneDeep(values);
newValues.push({ "": "" });
onChange(newValues);
};
const removeKeyValuePair = (index) => {
const newValues = cloneDeep(values);
newValues.splice(index, 1);
onChange(newValues);
};
const getInputClassName = (isEditNode, isDuplicateKey) => {
return `${isEditNode ? "input-edit-node" : ""} ${isDuplicateKey ? "input-invalid" : ""}`.trim();
};
const getTestId = (prefix, index) =>
`${editNode ? "editNode" : ""}${prefix}${index}`;
return ( return (
<div <div
className={classNames( className={`flex h-full flex-col gap-3 ${values?.length > 1 && editNode ? "mx-2 my-1" : ""}`}
myValue?.length > 1 && editNode ? "mx-2 my-1" : "",
"flex h-full flex-col gap-3",
)}
> >
{myValue?.map((obj, index) => { {values?.map((obj, index) =>
return Object.keys(obj).map((key, idx) => { Object.keys(obj).map((key, idx) => (
return ( <div key={idx} className="flex w-full gap-2">
<div key={idx} className="flex w-full gap-2"> <Input
<Input data-testid={getTestId("keypair", index)}
data-testid={ id={getTestId("keypair", index)}
editNode ? "editNodekeypair" + index : "keypair" + index type="text"
} value={key.trim()}
id={editNode ? "editNodekeypair" + index : "keypair" + index} className={getInputClassName(editNode, duplicateKey)}
type="text" placeholder="Type key..."
value={key.trim()} onChange={(event) => handleChangeKey(event, index)}
className={classNames( />
editNode ? "input-edit-node" : "",
duplicateKey ? "input-invalid" : "",
)}
placeholder="Type key..."
onChange={(event) => handleChangeKey(event, index)}
/>
<Input <Input
data-testid={ data-testid={getTestId("keypair", index + 100)}
editNode id={getTestId("keypair", index + 100)}
? "editNodekeypair" + (index + 100).toString() type="text"
: "keypair" + (index + 100).toString() disabled={disabled}
} value={obj[key]}
id={ className={editNode ? "input-edit-node" : ""}
editNode placeholder="Type a value..."
? "editNodekeypair" + (index + 100).toString() onChange={(event) => handleChangeValue(event, index)}
: "keypair" + (index + 100).toString() />
}
type="text"
disabled={disabled}
value={obj[key]}
className={editNode ? "input-edit-node" : ""}
placeholder="Type a value..."
onChange={(event) => handleChangeValue(event, index)}
/>
{isList && index === myValue.length - 1 ? ( {isList &&
(index === values.length - 1 ? (
<button <button
disabled={disabled} disabled={disabled}
onClick={() => { onClick={addNewKeyValuePair}
let newInputList = cloneDeep(myValue); id={getTestId("plusbtn", index)}
newInputList.push({ "": "" });
onChange(newInputList);
}}
id={
editNode
? "editNodeplusbtn" + index.toString()
: "plusbtn" + index.toString()
}
data-testid={id} data-testid={id}
> >
<IconComponent <IconComponent
name="Plus" name="Plus"
className={"h-4 w-4 hover:text-accent-foreground"} className="h-4 w-4 hover:text-accent-foreground"
/> />
</button> </button>
) : isList ? ( ) : (
<button <button
onClick={() => { onClick={() => removeKeyValuePair(index)}
let newInputList = cloneDeep(myValue); data-testid={getTestId("minusbtn", index)}
newInputList.splice(index, 1); id={getTestId("minusbtn", index)}
onChange(newInputList);
}}
data-testid={
editNode
? "editNodeminusbtn" + index.toString()
: "minusbtn" + index.toString()
}
id={
editNode
? "editNodeminusbtn" + index.toString()
: "minusbtn" + index.toString()
}
> >
<IconComponent <IconComponent
name="X" name="X"
className="h-4 w-4 hover:text-status-red" className="h-4 w-4 hover:text-status-red"
/> />
</button> </button>
) : ( ))}
"" </div>
)} )),
</div> )}
);
});
})}
</div> </div>
); );
} }

View file

@ -92,156 +92,139 @@ export default function MultiselectComponent({
} }
}, [open]); }, [open]);
return ( const handleOptionSelect = (currentValue) => {
<> if (value.includes(currentValue)) {
{Object.keys(options ?? [])?.length > 0 || combobox ? ( onSelect(value.filter((v) => v !== currentValue));
<> } else {
<Popover open={open} onOpenChange={children ? () => {} : setOpen}> onSelect([...value, currentValue]);
{children ? ( }
<PopoverAnchor>{children}</PopoverAnchor> };
) : (
<PopoverTrigger asChild> const renderDropdownTrigger = () => (
<Button <PopoverTrigger asChild>
disabled={disabled} <Button
variant="primary" disabled={disabled}
size="xs" variant="primary"
role="combobox" size="xs"
ref={refButton} role="combobox"
aria-expanded={open} ref={refButton}
data-testid={`${id ?? ""}`} aria-expanded={open}
data-testid={id}
className={cn(
editNode
? "dropdown-component-outline input-edit-node"
: "dropdown-component-false-outline py-2",
"w-full justify-between font-normal",
)}
>
<span className="truncate" data-testid={`value-dropdown-${id}`}>
{value.length > 0 && options.find((option) => value.includes(option))
? value.join(", ")
: "Choose an option..."}
</span>
<ForwardedIconComponent
name="ChevronsUpDown"
className="ml-2 h-4 w-4 shrink-0 opacity-50"
/>
</Button>
</PopoverTrigger>
);
const renderSearchInput = () => (
<div className="flex items-center border-b px-3">
<ForwardedIconComponent
name="search"
className="mr-2 h-4 w-4 shrink-0 opacity-50"
/>
<input
onChange={(event) => {
setSearchValue(event.target.value);
}}
placeholder="Search options..."
className="flex h-9 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50"
/>
<Button
unstyled
className="ml-2"
onClick={() => setOnlySelected((old) => !old)}
>
<ForwardedIconComponent
className="h-4 w-4"
name={onlySelected ? "CheckCheck" : "Check"}
/>
</Button>
</div>
);
const renderOptionsList = () => (
<CommandList className="overflow-y-scroll">
<CommandEmpty>No values found.</CommandEmpty>
<CommandGroup>
{filteredOptions.map((option, index) => (
<ShadTooltip key={index} delayDuration={700} content={option}>
<div>
<CommandItem
value={option}
onSelect={handleOptionSelect}
className="items-center overflow-hidden truncate"
data-testid={`${option}-${id ?? ""}-option`}
>
{(customValues.includes(option) || searchValue === option) && (
<span className="text-muted-foreground">Text:&nbsp;</span>
)}
<span className="truncate">{option}</span>
<ForwardedIconComponent
name="Check"
className={cn( className={cn(
editNode "ml-auto h-4 w-4 shrink-0 text-primary",
? "dropdown-component-outline" value.includes(option) ? "opacity-100" : "opacity-0",
: "dropdown-component-false-outline",
"w-full justify-between font-normal",
editNode ? "input-edit-node" : "py-2",
)} )}
> />
<span </CommandItem>
className="truncate" </div>
data-testid={`value-dropdown-` + id} </ShadTooltip>
> ))}
{value && </CommandGroup>
value.length > 0 && </CommandList>
options.find((option) => value.includes(option)) );
? value.join(", ")
: "Choose an option..."}
</span>
<ForwardedIconComponent if (Object.keys(options).length === 0 && !combobox) {
name="ChevronsUpDown" return isLoading ? (
className="ml-2 h-4 w-4 shrink-0 opacity-50" <div>
/> <span className="text-sm italic">Loading...</span>
</Button> </div>
</PopoverTrigger> ) : (
)} <div>
<PopoverContentDropdown <span className="text-sm italic">
onOpenAutoFocus={(event) => { No parameters are available for display.
event.preventDefault(); </span>
}} </div>
side="bottom" );
avoidCollisions={!!children} }
className="noflow nowheel nopan nodelete nodrag p-0"
style={
children
? {}
: { minWidth: refButton?.current?.clientWidth ?? "200px" }
}
>
<Command>
<div className="flex items-center border-b px-3">
<ForwardedIconComponent
name="search"
className="mr-2 h-4 w-4 shrink-0 opacity-50"
/>
<input
onChange={(event) => {
setSearchValue(event.target.value);
searchRoleByTerm(event.target.value);
}}
placeholder="Search options..."
className="flex h-9 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50"
/>
<Button
unstyled
className="ml-2"
onClick={() => setOnlySelected((old) => !old)}
>
<ForwardedIconComponent
className="h-4 w-4"
name={onlySelected ? "CheckCheck" : "Check"}
/>
</Button>
</div>
<CommandList className="overflow-y-scroll"> return (
<CommandEmpty>No values found.</CommandEmpty> <Popover open={open} onOpenChange={children ? () => {} : setOpen}>
<CommandGroup defaultChecked={false}> {children ? (
{filteredOptions?.map((option, id) => ( <PopoverAnchor>{children}</PopoverAnchor>
<ShadTooltip
delayDuration={700}
key={id}
content={option}
>
<div>
<CommandItem
key={id}
value={option}
onSelect={(currentValue) => {
if (value.includes(currentValue)) {
onSelect(
value.filter((v) => v !== currentValue),
);
} else {
onSelect([...value, currentValue]);
}
}}
className="items-center overflow-hidden truncate"
data-testid={`${option}-${id ?? ""}-option`}
>
{customValues.includes(option) ||
searchValue === option ? (
<span className="text-muted-foreground">
Text:&nbsp;
</span>
) : (
<></>
)}
<span className="truncate">{option}</span>
<ForwardedIconComponent
name="Check"
className={cn(
"ml-auto h-4 w-4 shrink-0 text-primary",
value.includes(option)
? "opacity-100"
: "opacity-0",
)}
/>
</CommandItem>
</div>
</ShadTooltip>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContentDropdown>
</Popover>
</>
) : ( ) : (
<> renderDropdownTrigger()
{(!isLoading && (
<div>
<span className="text-sm italic">
No parameters are available for display.
</span>
</div>
)) || (
<div>
<span className="text-sm italic">Loading...</span>
</div>
)}
</>
)} )}
</> <PopoverContentDropdown
onOpenAutoFocus={(event) => event.preventDefault()}
side="bottom"
avoidCollisions={!!children}
className="noflow nowheel nopan nodelete nodrag p-0"
style={
children
? {}
: { minWidth: refButton?.current?.clientWidth ?? "200px" }
}
>
<Command>
{renderSearchInput()}
{renderOptionsList()}
</Command>
</PopoverContentDropdown>
</Popover>
); );
} }

View file

@ -1,6 +1,7 @@
import PromptModal from "@/modals/promptModal"; import PromptModal from "@/modals/promptModal";
import { useEffect } from "react"; import { useEffect } from "react";
import { PromptAreaComponentType } from "../../types/components"; import { PromptAreaComponentType } from "../../types/components";
import { cn } from "../../utils/utils";
import IconComponent from "../genericIconComponent"; import IconComponent from "../genericIconComponent";
import { Button } from "../ui/button"; import { Button } from "../ui/button";
@ -21,8 +22,38 @@ export default function PromptAreaComponent({
} }
}, [disabled]); }, [disabled]);
const renderPromptText = () => (
<span
id={id}
data-testid={id}
className={cn(
editNode
? "input-edit-node input-dialog"
: "primary-input text-muted-foreground",
disabled && !editNode && "input-disable text-ring",
)}
>
{value !== "" ? value : "Type your prompt here..."}
</span>
);
const renderExternalLinkIcon = () => {
if (editNode) return null;
return (
<IconComponent
id={id}
name="ExternalLink"
className={cn(
"icons-parameters-comp shrink-0",
disabled ? "text-ring" : "hover:text-accent-foreground",
)}
/>
);
};
return ( return (
<div className={disabled ? "pointer-events-none w-full" : "w-full"}> <div className={cn("w-full", disabled && "pointer-events-none")}>
<PromptModal <PromptModal
id={id} id={id}
field_name={field_name} field_name={field_name}
@ -34,28 +65,8 @@ export default function PromptAreaComponent({
> >
<Button unstyled className="w-full"> <Button unstyled className="w-full">
<div className="flex w-full items-center gap-3"> <div className="flex w-full items-center gap-3">
<span {renderPromptText()}
id={id} {renderExternalLinkIcon()}
data-testid={id}
className={
editNode
? "input-edit-node input-dialog"
: (disabled ? "input-disable text-ring " : "") +
" primary-input text-muted-foreground"
}
>
{value !== "" ? value : "Type your prompt here..."}
</span>
{!editNode && (
<IconComponent
id={id}
name="ExternalLink"
className={
"icons-parameters-comp shrink-0" +
(disabled ? " text-ring" : " hover:text-accent-foreground")
}
/>
)}
</div> </div>
</Button> </Button>
</PromptModal> </PromptModal>

View file

@ -22,107 +22,117 @@ export default function TextAreaComponent({
} }
}, [disabled]); }, [disabled]);
return ( const renderTextarea = () => (
<div className={"flex w-full items-center" + (disabled ? "" : "")}> <Textarea
<div className="flex w-full items-center gap-3" data-testid={"div-" + id}> id={id}
<Textarea data-testid={id}
id={id} value={value}
data-testid={id} disabled={disabled}
value={value} className={classNames(
disabled={disabled} "w-full resize-none",
className={classNames( password !== undefined && password && value !== ""
password !== undefined && password && value !== "" ? "text-clip password"
? "text-clip password" : "",
: "", editNode ? "input-edit-node" : "",
editNode ? "input-edit-node" : "", password !== undefined ? "pr-8" : "",
password != undefined ? "pr-8" : "", )}
"w-full", rows={1}
"resize-none", placeholder="Type something..."
)} onChange={(event) => onChange(event.target.value)}
rows={1} />
placeholder={"Type something..."} );
onChange={(event) => {
onChange(event.target.value); const renderExternalLinkButton = () => (
}} <ComponentTextModal
/> changeVisibility={updateVisibility}
<ComponentTextModal value={value}
changeVisibility={updateVisibility} setValue={(value) => onChange(value)}
value={value} disabled={disabled}
setValue={(value: string) => { password={password}
onChange(value); >
}} <div
disabled={disabled} className={classNames(
password={password} "flex items-center",
> password ? "relative left-6" : "",
<div
className={
"flex items-center" + (password ? "relative left-6" : "")
}
>
<Button unstyled>
<IconComponent
strokeWidth={1.5}
id={id}
name="ExternalLink"
className={
"icons-parameters-comp shrink-0" +
(disabled ? " text-ring" : " hover:text-accent-foreground")
}
/>
</Button>
</div>
</ComponentTextModal>
{password !== undefined && (
<Button
unstyled
tabIndex={-1}
className={classNames(
"mb-px text-muted-foreground hover:text-current",
editNode
? "side-bar-button-size absolute bottom-[1.3rem] right-[4.2rem]"
: "side-bar-button-size absolute bottom-4 right-[4.2rem]",
)}
onClick={(event) => {
event.preventDefault();
if (updateVisibility) updateVisibility();
}}
>
{password ? (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M2.036 12.322a1.012 1.012 0 010-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178z"
/>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
/>
</svg>
) : (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M3.98 8.223A10.477 10.477 0 001.934 12C3.226 16.338 7.244 19.5 12 19.5c.993 0 1.953-.138 2.863-.395M6.228 6.228A10.45 10.45 0 0112 4.5c4.756 0 8.773 3.162 10.065 7.498a10.523 10.523 0 01-4.293 5.774M6.228 6.228L3 3m3.228 3.228l3.65 3.65m7.894 7.894L21 21m-3.228-3.228l-3.65-3.65m0 0a3 3 0 10-4.243-4.243m4.242 4.242L9.88 9.88"
/>
</svg>
)}
</Button>
)} )}
>
<Button unstyled>
<IconComponent
strokeWidth={1.5}
id={id}
name="ExternalLink"
className={classNames(
"icons-parameters-comp shrink-0",
disabled ? "text-ring" : "hover:text-accent-foreground",
)}
/>
</Button>
</div>
</ComponentTextModal>
);
const renderPasswordToggle = () => {
if (password === undefined) return null;
return (
<Button
unstyled
tabIndex={-1}
className={classNames(
"side-bar-button-size absolute mb-px text-muted-foreground hover:text-current",
editNode
? "bottom-[1.3rem] right-[4.2rem]"
: "bottom-4 right-[4.2rem]",
)}
onClick={(event) => {
event.preventDefault();
if (updateVisibility) updateVisibility();
}}
>
{password ? (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M2.036 12.322a1.012 1.012 0 010-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178z"
/>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
/>
</svg>
) : (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M3.98 8.223A10.477 10.477 0 001.934 12C3.226 16.338 7.244 19.5 12 19.5c.993 0 1.953-.138 2.863-.395M6.228 6.228A10.45 10.45 0 0112 4.5c4.756 0 8.773 3.162 10.065 7.498a10.523 10.523 0 01-4.293 5.774M6.228 6.228L3 3m3.228 3.228l3.65 3.65m7.894 7.894L21 21m-3.228-3.228l-3.65-3.65m0 0a3 3 0 10-4.243-4.243m4.242 4.242L9.88 9.88"
/>
</svg>
)}
</Button>
);
};
return (
<div className={classNames("flex w-full items-center", disabled ? "" : "")}>
<div className="flex w-full items-center gap-3" data-testid={`div-${id}`}>
{renderTextarea()}
{renderExternalLinkButton()}
{renderPasswordToggle()}
</div> </div>
</div> </div>
); );

View file

@ -1,24 +1,30 @@
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import { cn } from "../../utils/utils"; import { cn } from "../../utils/utils";
export default function RenameLabel(props) { export default function RenameLabel({
value,
setValue,
className,
rename,
setRename,
}) {
const [internalState, setInternalState] = useState(false); const [internalState, setInternalState] = useState(false);
const [isRename, setIsRename] = props.rename const [componentValue, setComponentValue] = useState(value);
? [props.rename, props.setRename] const [isRename, setIsRename] = rename
? [rename, setRename]
: [internalState, setInternalState]; : [internalState, setInternalState];
useEffect(() => { useEffect(() => {
if (props.value) setMyValue(props.value); if (value) setComponentValue(value);
}, [props.value]); }, [value]);
const [myValue, setMyValue] = useState(props.value);
useEffect(() => { useEffect(() => {
if (isRename) { if (isRename) {
setMyValue(props.value); setComponentValue(value);
document.addEventListener("keydown", (event) => { document.addEventListener("keydown", (event) => {
if (event.key === "Escape") { if (event.key === "Escape") {
setIsRename(false); setIsRename(false);
props.setValue(""); setValue("");
} }
}); });
if (inputRef.current) { if (inputRef.current) {
@ -52,41 +58,47 @@ export default function RenameLabel(props) {
input.style.width = `${textWidth + 16}px`; input.style.width = `${textWidth + 16}px`;
} }
}; };
return (
<div> const handleBlur = () => {
{isRename ? ( setIsRename(false);
<input if (componentValue !== "") {
autoFocus setValue(componentValue);
ref={inputRef} }
onInput={resizeInput} };
className={cn(
"nopan nodelete nodrag noflow rounded-md bg-transparent px-2 outline-ring hover:outline focus:border-none focus:outline active:outline", const handleChange = (event) => {
props.className, setComponentValue(event.target.value);
)} };
onBlur={() => {
setIsRename(false); const handleDoubleClick = () => {
if (props.value !== "") { setIsRename(true);
props.setValue(myValue); setComponentValue(value);
} };
}}
value={myValue} const renderInput = () => (
onChange={(event) => { <input
setMyValue(event.target.value); ref={inputRef}
}} onInput={resizeInput}
/> className={cn(
) : ( "nopan nodelete nodrag noflow rounded-md bg-transparent px-2 outline-ring hover:outline focus:border-none focus:outline active:outline",
<div className="flex items-center gap-2"> className,
<span
className={cn("truncate px-2 text-left", props.className)}
onDoubleClick={() => {
setIsRename(true);
setMyValue(props.value);
}}
>
{props.value}
</span>
</div>
)} )}
onBlur={handleBlur}
value={componentValue}
onChange={handleChange}
/>
);
const renderSpan = () => (
<div className="flex items-center gap-2">
<span
className={cn("truncate px-2 text-left", className)}
onDoubleClick={handleDoubleClick}
>
{value}
</span>
</div> </div>
); );
return <div>{isRename ? renderInput() : renderSpan()}</div>;
} }

View file

@ -27,58 +27,68 @@ export default function DictAreaModal({
}): JSX.Element { }): JSX.Element {
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const isDark = useDarkStore((state) => state.dark); const isDark = useDarkStore((state) => state.dark);
const [myValue, setMyValue] = useState(value); const [componentValue, setComponentValue] = useState(value);
useEffect(() => { useEffect(() => {
setMyValue(value); setComponentValue(value);
}, [value, open]); }, [value, open]);
const handleSubmit = () => {
if (onChange) {
onChange(componentValue);
setOpen(false);
}
};
const handleJsonChange = (edit) => {
setComponentValue(edit.src);
};
const customizeCopy = (copy) => {
navigator.clipboard.writeText(JSON.stringify(copy));
};
const renderHeader = () => (
<BaseModal.Header description={onChange ? CODE_DICT_DIALOG_SUBTITLE : null}>
<span className="pr-2">
{onChange ? "Edit Dictionary" : "View Dictionary"}
</span>
<IconComponent
name="BookMarked"
className="h-6 w-6 pl-1 text-primary"
aria-hidden="true"
/>
</BaseModal.Header>
);
const renderContent = () => (
<BaseModal.Content>
<div className="flex h-full w-full flex-col transition-all">
<JsonView
theme="vscode"
dark={isDark}
className={!isDark ? "json-view-white" : "json-view-dark"}
editable={!!onChange}
enableClipboard
onChange={handleJsonChange}
src={cloneDeep(componentValue)}
customizeCopy={customizeCopy}
/>
</div>
</BaseModal.Content>
);
return ( return (
<BaseModal <BaseModal
size="medium-h-full" size="medium-h-full"
open={open} open={open}
disable={disabled} disable={disabled}
setOpen={setOpen} setOpen={setOpen}
onSubmit={ onSubmit={onChange ? handleSubmit : undefined}
onChange
? () => {
onChange(myValue);
setOpen(false);
}
: undefined
}
> >
<BaseModal.Trigger className="h-full">{children}</BaseModal.Trigger> <BaseModal.Trigger className="h-full">{children}</BaseModal.Trigger>
<BaseModal.Header {renderHeader()}
description={onChange ? CODE_DICT_DIALOG_SUBTITLE : null} {renderContent()}
>
<span className="pr-2">
{onChange ? "Edit Dictionary" : "View Dictionary"}
</span>
<IconComponent
name="BookMarked"
className="h-6 w-6 pl-1 text-primary"
aria-hidden="true"
/>
</BaseModal.Header>
<BaseModal.Content>
<div className="flex h-full w-full flex-col transition-all">
<JsonView
theme="vscode"
dark={isDark}
className={!isDark ? "json-view-white" : "json-view-dark"}
editable={!!onChange}
enableClipboard
onChange={(edit) => {
setMyValue(edit.src);
}}
src={cloneDeep(myValue)}
customizeCopy={(copy) => {
navigator.clipboard.writeText(JSON.stringify(copy));
}}
/>
</div>
</BaseModal.Content>
<BaseModal.Footer submit={onChange ? { label: "Save" } : undefined} /> <BaseModal.Footer submit={onChange ? { label: "Save" } : undefined} />
</BaseModal> </BaseModal>
); );

View file

@ -217,6 +217,8 @@ test("when auto_login is false, admin can CRUD user's and should see just your o
timeout: 30000, timeout: 30000,
}); });
await page.waitForTimeout(1000);
expect( expect(
await page.getByText(secondRandomFlowName, { exact: true }).isVisible(), await page.getByText(secondRandomFlowName, { exact: true }).isVisible(),
).toBe(true); ).toBe(true);