Skip to content

Commit

Permalink
🪟🔧 Remove top level state for unfinished flows in connector form (#20135
Browse files Browse the repository at this point in the history
)

* refactor loading state
  • Loading branch information
Joe Reuter committed Dec 13, 2022
1 parent 9dae098 commit 75dcf82
Show file tree
Hide file tree
Showing 7 changed files with 125 additions and 150 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import styled from "styled-components";

import { Button } from "components/ui/Button";

import { useConnectorForm } from "../connectorFormContext";
import styles from "./EditControls.module.scss";
import { TestingConnectionError } from "./TestingConnectionError";
import { TestingConnectionSpinner } from "./TestingConnectionSpinner";
Expand Down Expand Up @@ -42,8 +41,6 @@ const EditControls: React.FC<IProps> = ({
errorMessage,
onCancelTesting,
}) => {
const { unfinishedFlows } = useConnectorForm();

if (isSubmitting) {
return <TestingConnectionSpinner isCancellable={isTestConnectionInProgress} onCancelTesting={onCancelTesting} />;
}
Expand All @@ -63,7 +60,7 @@ const EditControls: React.FC<IProps> = ({
{renderStatusMessage()}
<Controls>
<div className={styles.buttonsContainer}>
<Button type="submit" disabled={isSubmitting || !dirty || Object.keys(unfinishedFlows).length > 0}>
<Button type="submit" disabled={isSubmitting || !dirty}>
<FormattedMessage id="form.saveChangesAndTest" />
</Button>
<Button
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -5,35 +5,23 @@ import { DatePicker } from "components/ui/DatePicker";
import { DropDown } from "components/ui/DropDown";
import { Input } from "components/ui/Input";
import { Multiselect } from "components/ui/Multiselect";
import { SecretTextArea } from "components/ui/SecretTextArea";
import { TagInput } from "components/ui/TagInput/TagInput";
import { TextArea } from "components/ui/TextArea";

import { FormBaseItem } from "core/form/types";
import { useExperiment } from "hooks/services/Experiment";
import { isDefined } from "utils/common";

import ConfirmationControl from "./ConfirmationControl";
import SecretConfirmationControl from "./SecretConfirmationControl";

interface ControlProps {
property: FormBaseItem;
name: string;
unfinishedFlows: Record<string, { startValue: string }>;
addUnfinishedFlow: (key: string, info?: Record<string, unknown>) => void;
removeUnfinishedFlow: (key: string) => void;
disabled?: boolean;
error?: boolean;
}

export const Control: React.FC<ControlProps> = ({
property,
name,
addUnfinishedFlow,
removeUnfinishedFlow,
unfinishedFlows,
disabled,
error,
}) => {
export const Control: React.FC<ControlProps> = ({ property, name, disabled, error }) => {
const [field, meta, helpers] = useField(name);
const useDatepickerExperiment = useExperiment("connector.form.useDatepicker", true);

Expand Down Expand Up @@ -107,46 +95,14 @@ export const Control: React.FC<ControlProps> = ({
} else if (property.multiline && !property.isSecret) {
return <TextArea {...field} autoComplete="off" value={value ?? ""} rows={3} disabled={disabled} error={error} />;
} else if (property.isSecret) {
const unfinishedSecret = unfinishedFlows[name];
const isEditInProgress = !!unfinishedSecret;
const isFormInEditMode = isDefined(meta.initialValue);
return (
<ConfirmationControl
component={
property.multiline && (isEditInProgress || !isFormInEditMode) ? (
<SecretTextArea
{...field}
autoComplete="off"
value={value ?? ""}
rows={3}
disabled={disabled}
error={error}
/>
) : (
<Input
{...field}
autoComplete="off"
value={value ?? ""}
type="password"
disabled={disabled}
error={error}
/>
)
}
<SecretConfirmationControl
name={name}
multiline={Boolean(property.multiline)}
showButtons={isFormInEditMode}
isEditInProgress={isEditInProgress}
onDone={() => removeUnfinishedFlow(name)}
onStart={() => {
addUnfinishedFlow(name, { startValue: field.value });
helpers.setValue("");
}}
onCancel={() => {
removeUnfinishedFlow(name);
if (unfinishedSecret && unfinishedSecret.hasOwnProperty("startValue")) {
helpers.setValue(unfinishedSecret.startValue);
}
}}
disabled={disabled}
error={error}
/>
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
@use "scss/variables";

.container {
display: flex;
flex-direction: row;
align-items: top;
gap: variables.$spacing-sm;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { useField, useFormikContext } from "formik";
import React, { useEffect, useRef, useState } from "react";
import { FormattedMessage } from "react-intl";

import { Button } from "components/ui/Button";
import { Input } from "components/ui/Input";
import { SecretTextArea } from "components/ui/SecretTextArea";

import styles from "./SecretConfirmationControl.module.scss";

interface SecretConfirmationControlProps {
showButtons?: boolean;
name: string;
multiline: boolean;
disabled?: boolean;
error?: boolean;
}

const SecretConfirmationControl: React.FC<SecretConfirmationControlProps> = ({
showButtons,
disabled,
multiline,
name,
error,
}) => {
const [field, , helpers] = useField(name);
const [previousValue, setPreviousValue] = useState<unknown>(undefined);
const isEditInProgress = Boolean(previousValue);
const controlRef = useRef<HTMLInputElement>(null);

const { dirty, touched } = useFormikContext();

const component =
multiline && (isEditInProgress || !showButtons) ? (
<SecretTextArea
{...field}
autoComplete="off"
value={field.value ?? ""}
rows={3}
error={error}
// eslint-disable-next-line jsx-a11y/no-autofocus
autoFocus={showButtons && isEditInProgress}
disabled={(showButtons && !isEditInProgress) || disabled}
/>
) : (
<Input
{...field}
autoComplete="off"
value={field.value ?? ""}
type="password"
error={error}
ref={controlRef}
disabled={(showButtons && !isEditInProgress) || disabled}
/>
);

useEffect(() => {
if (!dirty && !touched && previousValue) {
setPreviousValue(undefined);
}
}, [dirty, helpers, previousValue, touched]);

if (!showButtons) {
return <>{component}</>;
}

const handleStartEdit = () => {
if (controlRef && controlRef.current) {
controlRef.current?.removeAttribute?.("disabled");
controlRef.current?.focus?.();
}
setPreviousValue(field.value);
helpers.setValue("");
};

const onDone = () => {
setPreviousValue(undefined);
};

const onCancel = () => {
if (previousValue) {
helpers.setValue(previousValue);
}
setPreviousValue(undefined);
};

return (
<div className={styles.container}>
{component}
{isEditInProgress ? (
<>
<Button size="xs" onClick={onDone} type="button" disabled={disabled}>
<FormattedMessage id="form.done" />
</Button>
<Button size="xs" onClick={onCancel} type="button" variant="secondary" disabled={disabled}>
<FormattedMessage id="form.cancel" />
</Button>
</>
) : (
<Button size="xs" onClick={handleStartEdit} type="button" disabled={disabled}>
<FormattedMessage id="form.edit" />
</Button>
)}
</div>
);
};

export default SecretConfirmationControl;
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const PropertySection: React.FC<PropertySectionProps> = ({ property, path, disab
const propertyPath = path ?? property.path;
const formikBag = useField(propertyPath);
const [field, meta] = formikBag;
const { addUnfinishedFlow, removeUnfinishedFlow, unfinishedFlows, widgetsInfo } = useConnectorForm();
const { widgetsInfo } = useConnectorForm();

const overriddenComponent = widgetsInfo[propertyPath]?.component;
if (overriddenComponent) {
Expand Down Expand Up @@ -59,15 +59,7 @@ const PropertySection: React.FC<PropertySectionProps> = ({ property, path, disab

return (
<PropertyLabel className={styles.defaultLabel} property={property} label={labelText}>
<Control
property={property}
name={propertyPath}
addUnfinishedFlow={addUnfinishedFlow}
removeUnfinishedFlow={removeUnfinishedFlow}
unfinishedFlows={unfinishedFlows}
disabled={disabled}
error={hasError}
/>
<Control property={property} name={propertyPath} disabled={disabled} error={hasError} />
{hasError && <PropertyError>{errorMessage}</PropertyError>}
</PropertyLabel>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,6 @@ interface ConnectorFormContext {
getValues: <T = unknown>(values: ConnectorFormValues<T>) => ConnectorFormValues<T>;
widgetsInfo: WidgetConfigMap;
setUiWidgetsInfo: (path: string, value: Record<string, unknown>) => void;
unfinishedFlows: Record<string, { startValue: string; id: number | string }>;
addUnfinishedFlow: (key: string, info?: Record<string, unknown>) => void;
removeUnfinishedFlow: (key: string) => void;
resetConnectorForm: () => void;
selectedConnectorDefinition: ConnectorDefinition;
selectedConnectorDefinitionSpecification: ConnectorDefinitionSpecification;
Expand Down Expand Up @@ -62,7 +59,6 @@ export const ConnectorFormContextProvider: React.FC<React.PropsWithChildren<Conn
const { resetForm } = useFormikContext<ConnectorFormValues>();

const ctx = useMemo<ConnectorFormContext>(() => {
const unfinishedFlows = widgetsInfo["_common.unfinishedFlows"] ?? {};
const context: ConnectorFormContext = {
widgetsInfo,
getValues,
Expand All @@ -73,17 +69,6 @@ export const ConnectorFormContextProvider: React.FC<React.PropsWithChildren<Conn
validationSchema,
isEditMode,
connectorId,
unfinishedFlows,
addUnfinishedFlow: (path, info) =>
setUiWidgetsInfo("_common.unfinishedFlows", {
...unfinishedFlows,
[path]: info ?? {},
}),
removeUnfinishedFlow: (path: string) =>
setUiWidgetsInfo(
"_common.unfinishedFlows",
Object.fromEntries(Object.entries(unfinishedFlows).filter(([key]) => key !== path))
),
resetConnectorForm: () => {
resetForm();
resetUiWidgetsInfo();
Expand Down

0 comments on commit 75dcf82

Please sign in to comment.