Fix flow settings validation and save button (#2360)
This pull request fixes the validation and save button behavior in the flow settings modal. Previously, the save button was not disabled when it should have been, and the endpoint name validation was not working correctly. This PR updates the validation logic and ensures that the save button is disabled when necessary.
This commit is contained in:
commit
a471c671d7
6 changed files with 35 additions and 21 deletions
|
|
@ -1,5 +1,5 @@
|
||||||
from datetime import datetime, timezone
|
|
||||||
import re
|
import re
|
||||||
|
from datetime import datetime, timezone
|
||||||
from typing import List
|
from typing import List
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
|
|
@ -211,8 +211,7 @@ def update_flow(
|
||||||
if settings_service.settings.remove_api_keys:
|
if settings_service.settings.remove_api_keys:
|
||||||
flow_data = remove_api_keys(flow_data)
|
flow_data = remove_api_keys(flow_data)
|
||||||
for key, value in flow_data.items():
|
for key, value in flow_data.items():
|
||||||
if value is not None:
|
setattr(db_flow, key, value)
|
||||||
setattr(db_flow, key, value)
|
|
||||||
webhook_component = get_webhook_component_in_flow(db_flow.data)
|
webhook_component = get_webhook_component_in_flow(db_flow.data)
|
||||||
db_flow.webhook = webhook_component is not None
|
db_flow.webhook = webhook_component is not None
|
||||||
db_flow.updated_at = datetime.now(timezone.utc)
|
db_flow.updated_at = datetime.now(timezone.utc)
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import { Label } from "../../components/ui/label";
|
||||||
import { Textarea } from "../../components/ui/textarea";
|
import { Textarea } from "../../components/ui/textarea";
|
||||||
import useFlowsManagerStore from "../../stores/flowsManagerStore";
|
import useFlowsManagerStore from "../../stores/flowsManagerStore";
|
||||||
import { InputProps } from "../../types/components";
|
import { InputProps } from "../../types/components";
|
||||||
import { cn } from "../../utils/utils";
|
import { cn, isEndpointNameValid } from "../../utils/utils";
|
||||||
|
|
||||||
export const EditFlowSettings: React.FC<InputProps> = ({
|
export const EditFlowSettings: React.FC<InputProps> = ({
|
||||||
name,
|
name,
|
||||||
|
|
@ -17,7 +17,7 @@ export const EditFlowSettings: React.FC<InputProps> = ({
|
||||||
setEndpointName,
|
setEndpointName,
|
||||||
}: InputProps): JSX.Element => {
|
}: InputProps): JSX.Element => {
|
||||||
const [isMaxLength, setIsMaxLength] = useState(false);
|
const [isMaxLength, setIsMaxLength] = useState(false);
|
||||||
const [isEndpointNameValid, setIsEndpointNameValid] = useState(true);
|
const [validEndpointName, setValidEndpointName] = useState(true);
|
||||||
const [isInvalidName, setIsInvalidName] = useState(false);
|
const [isInvalidName, setIsInvalidName] = useState(false);
|
||||||
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
|
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
|
||||||
|
|
||||||
|
|
@ -34,10 +34,6 @@ export const EditFlowSettings: React.FC<InputProps> = ({
|
||||||
invalid = true;
|
invalid = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (value === currentFlow?.name) {
|
|
||||||
invalid = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
invalid = false;
|
invalid = false;
|
||||||
}
|
}
|
||||||
setIsInvalidName(invalid);
|
setIsInvalidName(invalid);
|
||||||
|
|
@ -51,12 +47,8 @@ export const EditFlowSettings: React.FC<InputProps> = ({
|
||||||
const handleEndpointNameChange = (event: ChangeEvent<HTMLInputElement>) => {
|
const handleEndpointNameChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||||
// Validate the endpoint name
|
// Validate the endpoint name
|
||||||
// use this regex r'^[a-zA-Z0-9_-]+$'
|
// use this regex r'^[a-zA-Z0-9_-]+$'
|
||||||
const isValid =
|
const isValid = isEndpointNameValid(event.target.value, maxLength);
|
||||||
(/^[a-zA-Z0-9_-]+$/.test(event.target.value) &&
|
setValidEndpointName(isValid);
|
||||||
event.target.value.length <= maxLength) ||
|
|
||||||
// empty is also valid
|
|
||||||
event.target.value.length === 0;
|
|
||||||
setIsEndpointNameValid(isValid);
|
|
||||||
setEndpointName!(event.target.value);
|
setEndpointName!(event.target.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -129,7 +121,7 @@ export const EditFlowSettings: React.FC<InputProps> = ({
|
||||||
<Label>
|
<Label>
|
||||||
<div className="edit-flow-arrangement mt-3">
|
<div className="edit-flow-arrangement mt-3">
|
||||||
<span className="font-medium">Endpoint Name</span>
|
<span className="font-medium">Endpoint Name</span>
|
||||||
{!isEndpointNameValid && (
|
{!validEndpointName && (
|
||||||
<span className="edit-flow-span">
|
<span className="edit-flow-span">
|
||||||
Invalid endpoint name. Use only letters, numbers, hyphens, and
|
Invalid endpoint name. Use only letters, numbers, hyphens, and
|
||||||
underscores ({maxLength} characters max).
|
underscores ({maxLength} characters max).
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import useAlertStore from "../../stores/alertStore";
|
||||||
import useFlowsManagerStore from "../../stores/flowsManagerStore";
|
import useFlowsManagerStore from "../../stores/flowsManagerStore";
|
||||||
import { FlowSettingsPropsType } from "../../types/components";
|
import { FlowSettingsPropsType } from "../../types/components";
|
||||||
import { FlowType } from "../../types/flow";
|
import { FlowType } from "../../types/flow";
|
||||||
|
import { isEndpointNameValid } from "../../utils/utils";
|
||||||
import BaseModal from "../baseModal";
|
import BaseModal from "../baseModal";
|
||||||
|
|
||||||
export default function FlowSettingsModal({
|
export default function FlowSettingsModal({
|
||||||
|
|
@ -22,13 +23,15 @@ export default function FlowSettingsModal({
|
||||||
|
|
||||||
const [name, setName] = useState(currentFlow!.name);
|
const [name, setName] = useState(currentFlow!.name);
|
||||||
const [description, setDescription] = useState(currentFlow!.description);
|
const [description, setDescription] = useState(currentFlow!.description);
|
||||||
const [endpoint_name, setEndpointName] = useState(currentFlow!.endpoint_name);
|
const [endpoint_name, setEndpointName] = useState(currentFlow!.endpoint_name??"");
|
||||||
const [isSaving, setIsSaving] = useState(false);
|
const [isSaving, setIsSaving] = useState(false);
|
||||||
|
const [disableSave, setDisableSave] = useState(true);
|
||||||
function handleClick(): void {
|
function handleClick(): void {
|
||||||
setIsSaving(true);
|
setIsSaving(true);
|
||||||
currentFlow!.name = name;
|
currentFlow!.name = name;
|
||||||
currentFlow!.description = description;
|
currentFlow!.description = description;
|
||||||
currentFlow!.endpoint_name = endpoint_name;
|
currentFlow!.endpoint_name =
|
||||||
|
endpoint_name && endpoint_name.length > 0 ? endpoint_name : null;
|
||||||
saveFlow(currentFlow!)
|
saveFlow(currentFlow!)
|
||||||
?.then(() => {
|
?.then(() => {
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
|
|
@ -54,6 +57,18 @@ export default function FlowSettingsModal({
|
||||||
setNameList(tempNameList.filter((name) => name !== currentFlow!.name));
|
setNameList(tempNameList.filter((name) => name !== currentFlow!.name));
|
||||||
}, [flows]);
|
}, [flows]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
(!nameLists.includes(name) && currentFlow?.name !== name) ||
|
||||||
|
currentFlow?.description !== description ||
|
||||||
|
((currentFlow?.endpoint_name??"") !== endpoint_name &&
|
||||||
|
isEndpointNameValid(endpoint_name ?? "", 50))
|
||||||
|
) {
|
||||||
|
setDisableSave(false);
|
||||||
|
} else {
|
||||||
|
setDisableSave(true);
|
||||||
|
}
|
||||||
|
}, [nameLists, currentFlow, description, endpoint_name, name]);
|
||||||
return (
|
return (
|
||||||
<BaseModal
|
<BaseModal
|
||||||
open={open}
|
open={open}
|
||||||
|
|
@ -80,8 +95,8 @@ export default function FlowSettingsModal({
|
||||||
<BaseModal.Footer
|
<BaseModal.Footer
|
||||||
submit={{
|
submit={{
|
||||||
label: "Save",
|
label: "Save",
|
||||||
disabled: nameLists.includes(name) || name === currentFlow!.name,
|
|
||||||
dataTestId: "save-flow-settings",
|
dataTestId: "save-flow-settings",
|
||||||
|
disabled: disableSave,
|
||||||
loading: isSaving,
|
loading: isSaving,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -303,7 +303,7 @@ export type IconComponentProps = {
|
||||||
export type InputProps = {
|
export type InputProps = {
|
||||||
name: string | null;
|
name: string | null;
|
||||||
description: string | null;
|
description: string | null;
|
||||||
endpointName?: string;
|
endpointName?: string|null;
|
||||||
maxLength?: number;
|
maxLength?: number;
|
||||||
setName?: (name: string) => void;
|
setName?: (name: string) => void;
|
||||||
setDescription?: (description: string) => void;
|
setDescription?: (description: string) => void;
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ export type FlowType = {
|
||||||
id: string;
|
id: string;
|
||||||
data: ReactFlowJsonObject | null;
|
data: ReactFlowJsonObject | null;
|
||||||
description: string;
|
description: string;
|
||||||
endpoint_name?: string;
|
endpoint_name?: string|null;
|
||||||
style?: FlowStyleType;
|
style?: FlowStyleType;
|
||||||
is_component?: boolean;
|
is_component?: boolean;
|
||||||
last_tested_version?: string;
|
last_tested_version?: string;
|
||||||
|
|
|
||||||
|
|
@ -463,3 +463,11 @@ export const logTypeIsError = (
|
||||||
return isErrorLog(outputs);
|
return isErrorLog(outputs);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function isEndpointNameValid(name: string, maxLength: number): boolean {
|
||||||
|
return (
|
||||||
|
(/^[a-zA-Z0-9_-]+$/.test(name) && name.length <= maxLength) ||
|
||||||
|
// empty is also valid
|
||||||
|
name.length === 0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue