Skip to content

Commit

Permalink
refactor(web): memoise defaults
Browse files Browse the repository at this point in the history
  • Loading branch information
JosephKav committed Apr 2, 2024
1 parent ac7b7fa commit 0e0e476
Show file tree
Hide file tree
Showing 63 changed files with 2,899 additions and 1,820 deletions.
36 changes: 17 additions & 19 deletions web/ui/react-app/src/components/approvals/service-info.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,22 +53,20 @@ export const ServiceInfo: FC<Props> = ({
[handleModal]
);

// If version hasn't been found or a new version has been found
const serviceWarning = useMemo(
() =>
service?.status?.deployed_version === undefined ||
service?.status?.deployed_version === "" ||
(updateAvailable && !updateSkipped),
const status = useMemo(
() => ({
// If version hasn't been found or a new version has been found (and not skipped)
warning:
(service?.status?.deployed_version ?? "") === "" ||
(updateAvailable && !updateSkipped),
// If the latest version is the same as the approved version
updateApproved:
service?.status?.latest_version !== undefined &&
service.status.latest_version === service?.status?.approved_version,
}),
[service, updateAvailable, updateSkipped]
);

const updateApproved = useMemo(
() =>
service?.status?.latest_version !== undefined &&
service.status.latest_version === service?.status?.approved_version,
[service]
);

const deployedVersionIcon = service.has_deployed_version ? (
<OverlayTrigger
key="deployed-service"
Expand Down Expand Up @@ -140,7 +138,7 @@ export const ServiceInfo: FC<Props> = ({
style={{
padding: "0px",
}}
className={serviceWarning ? "service-warning rounded-bottom" : "default"}
className={status.warning ? "service-warning rounded-bottom" : "default"}
>
<ListGroup className="list-group-flush">
{updateAvailable && !updateSkipped ? (
Expand All @@ -150,7 +148,7 @@ export const ServiceInfo: FC<Props> = ({
className={"service-item update-options service-warning"}
variant="secondary"
>
{updateApproved && (service.webhook || service.command)
{status.updateApproved && (service.webhook || service.command)
? `${service.webhook ? "WebHooks" : "Commands"} already sent:`
: "Update available:"}
</ListGroup.Item>
Expand All @@ -175,7 +173,7 @@ export const ServiceInfo: FC<Props> = ({
}`}
variant="success"
onClick={() =>
showModal(updateApproved ? "RESEND" : "SEND", service)
showModal(status.updateApproved ? "RESEND" : "SEND", service)
}
disabled={!(service.webhook || service.command)}
>
Expand All @@ -199,7 +197,7 @@ export const ServiceInfo: FC<Props> = ({
) : (
<ListGroup.Item
key="deployed_v"
variant={serviceWarning ? "warning" : "secondary"}
variant={status.warning ? "warning" : "secondary"}
className={
"service-item" +
(service.webhook || service.command ? "" : " justify-left")
Expand Down Expand Up @@ -246,15 +244,15 @@ export const ServiceInfo: FC<Props> = ({
</ListGroup>
<Card.Footer
className={
serviceWarning || !service?.status?.last_queried
status.warning || !service?.status?.last_queried
? "service-warning rounded-bottom"
: ""
}
>
<small
className={
"text-muted same-color" +
(serviceWarning ? " service-warning rounded-bottom" : "")
(status.warning ? " service-warning rounded-bottom" : "")
}
>
{service?.status?.last_queried ? (
Expand Down
39 changes: 22 additions & 17 deletions web/ui/react-app/src/components/approvals/service.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,22 +37,23 @@ const Service: FC<Props> = ({ service, editable = false }) => {
[]
);

const updateAvailable = useMemo(
(): boolean =>
(service?.status?.deployed_version ?? undefined) !==
(service?.status?.latest_version ?? undefined),
[service?.status?.latest_version, service?.status?.deployed_version]
);

const updateSkipped = useMemo(
(): boolean =>
updateAvailable &&
service?.status?.approved_version ===
`SKIP_${service?.status?.latest_version}`,
const updateStatus = useMemo(
() => ({
// Update available if latest version and deployed version are both defined and differ
available:
(service?.status?.deployed_version || undefined) !==
(service?.status?.latest_version || undefined),
// Update is available and approved version is a skip of that latest version
skipped:
(service?.status?.deployed_version || undefined) !==
(service?.status?.latest_version || undefined) &&
service?.status?.approved_version ===
`SKIP_${service?.status?.latest_version}`,
}),
[
updateAvailable,
service?.status?.approved_version,
service?.status?.latest_version,
service?.status?.deployed_version,
]
);

Expand Down Expand Up @@ -99,17 +100,21 @@ const Service: FC<Props> = ({ service, editable = false }) => {
>
<UpdateInfo
service={service}
visible={updateAvailable && showUpdateInfo && !updateSkipped}
visible={
updateStatus.available && showUpdateInfo && !updateStatus.skipped
}
/>
<ServiceImage
service={service}
visible={!(updateAvailable && showUpdateInfo && !updateSkipped)}
visible={
!(updateStatus.available && showUpdateInfo && !updateStatus.skipped)
}
/>
<ServiceInfo
service={service}
setShowUpdateInfo={setShowUpdateInfo}
updateAvailable={updateAvailable}
updateSkipped={updateSkipped}
updateAvailable={updateStatus.available}
updateSkipped={updateStatus.skipped}
/>
</Card>
</Card>
Expand Down
28 changes: 16 additions & 12 deletions web/ui/react-app/src/components/generic/form-key-val-map.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,30 +53,30 @@ const FormKeyValMap: FC<Props> = ({
const addItem = useCallback(() => {
append({ key: "", value: "" }, { shouldFocus: false });
}, []);
const removeLast = useCallback(() => {
remove(fields.length - 1);
}, [fields]);

// keep track of the array values so we can switch defaults when they're unchanged
const fieldValues: HeaderType[] = useWatch({ name: name });
// useDefaults when the fieldValues are undefined or the same as the defaults
const useDefaults = useMemo(
() => diffObjects(fieldValues, defaults),
() =>
(defaults && diffObjects(fieldValues ?? fields ?? [], defaults)) ?? false,
[fieldValues, defaults]
);
// trigger validation on change of defaults being used/not
useEffect(() => {
trigger(name);
}, [useDefaults]);

// on load, give the defaults if not overridden
useEffect(() => {
if (useDefaults) {
// Give the defaults back if the field is empty
if ((fieldValues ?? fields ?? []).length === 0)
defaults?.forEach(() => {
append({}, { shouldFocus: false });
addItem();
});
}
}, []);
}, [useDefaults]);

// remove the last item if it's not the only one or doesn't match the defaults
const removeLast = useCallback(() => {
!(useDefaults && fields.length == 1) && remove(fields.length - 1);
}, [fields.length, useDefaults]);

return (
<FormGroup>
Expand All @@ -100,6 +100,7 @@ const FormKeyValMap: FC<Props> = ({
className="btn-unchecked mb-1"
variant="danger"
style={{ float: "left" }}
disabled={fields.length === 0}
onClick={removeLast}
>
<FontAwesomeIcon icon={faMinus} />
Expand All @@ -113,7 +114,10 @@ const FormKeyValMap: FC<Props> = ({
<FormKeyVal
name={`${name}.${index}`}
defaults={useDefaults ? defaults?.[index] : undefined}
removeMe={() => remove(index)}
removeMe={
// Give the remove that's disabled if there's only one item and it matches the defaults
fieldValues?.length === 1 ? removeLast : () => remove(index)
}
keyPlaceholder={keyPlaceholder}
valuePlaceholder={valuePlaceholder}
/>
Expand Down
128 changes: 128 additions & 0 deletions web/ui/react-app/src/components/generic/form-list.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import { Button, ButtonGroup, Col, FormGroup, Row } from "react-bootstrap";
import { FC, memo, useCallback, useEffect, useMemo } from "react";
import { FormItem, FormLabel } from "components/generic/form";
import { faMinus, faPlus } from "@fortawesome/free-solid-svg-icons";
import { useFieldArray, useFormContext, useWatch } from "react-hook-form";

import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { StringFieldArray } from "types/config";
import { diffObjects } from "utils/diff-objects";

interface Props {
name: string;
label?: string;
tooltip?: string;

defaults?: StringFieldArray;
}

/**
* Returns a set of form fields for a list of strings
*
* @param name - The name of the field in the form
* @param label - The label for the field
* @param tooltip - The tooltip for the field
* @param defaults - The default values for the field
* @returns A set of form fields for a list of strings
*/
const FormList: FC<Props> = ({
name,
label = "List",
tooltip,

defaults,
}) => {
const { setValue, trigger } = useFormContext();
const { fields, append, remove } = useFieldArray({
name: name,
});
const addItem = useCallback(() => {
append({ arg: "" }, { shouldFocus: false });
}, []);

// keep track of the array values so we can switch defaults when they're unchanged
const fieldValues: StringFieldArray = useWatch({ name: name });
// useDefaults when the fieldValues are undefined or the same as the defaults
const useDefaults = useMemo(
() =>
(defaults && diffObjects(fieldValues ?? fields ?? [], defaults)) ?? false,
[fieldValues, defaults]
);
// trigger validation on change of defaults being used/not
useEffect(() => {
trigger(name);

// Give the defaults back if the field is empty
if ((fieldValues ?? fields ?? [])?.length === 0)
defaults?.forEach(() => {
addItem();
});
}, [useDefaults]);

const placeholder = useCallback(
(index: number) => (useDefaults && defaults?.[index]?.arg) || "",
[useDefaults, defaults]
);

// on load, ensure we don't have another types actions
// and give the defaults if not overridden
useEffect(() => {
for (const item of fieldValues ?? fields ?? []) {
if ((item.arg ?? "") === "") {
setValue(name, []);
break;
}
}
}, []);

// remove the last item if it's not the only one or doesn't match the defaults
const removeLast = useCallback(() => {
!(useDefaults && fields.length == 1) && remove(fields.length - 1);
}, [fields.length, useDefaults]);

return (
<FormGroup>
<Row>
<Col className="pt-1">
<FormLabel text={label} tooltip={tooltip} />
</Col>
<Col>
<ButtonGroup style={{ float: "right" }}>
<Button
aria-label={`Add new ${label}`}
className="btn-unchecked mb-1"
variant="success"
style={{ float: "right" }}
onClick={addItem}
>
<FontAwesomeIcon icon={faPlus} />
</Button>
<Button
aria-label={`Remove last ${label}`}
className="btn-unchecked mb-1"
variant="danger"
style={{ float: "left" }}
onClick={removeLast}
disabled={fields.length === 0}
>
<FontAwesomeIcon icon={faMinus} />
</Button>
</ButtonGroup>
</Col>
</Row>
<Row>
{fields.map(({ id }, index) => (
<FormItem
key={id}
name={`${name}.${index}.arg`}
required
defaultVal={placeholder(index)}
position={index % 2 === 1 ? "right" : "left"}
/>
))}
</Row>
</FormGroup>
);
};

export default memo(FormList);
2 changes: 2 additions & 0 deletions web/ui/react-app/src/components/generic/form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import FormItemWithPreview from "./form-item-with-preview";
import FormKeyVal from "./form-key-val";
import FormKeyValMap from "./form-key-val-map";
import FormLabel from "./form-label";
import FormList from "./form-list";
import FormSelect from "./form-select";
import FormTextArea from "./form-textarea";

Expand All @@ -16,6 +17,7 @@ export {
FormKeyValMap,
FormKeyVal,
FormLabel,
FormList,
FormSelect,
FormTextArea,
};
Loading

0 comments on commit 0e0e476

Please sign in to comment.