diff --git a/web/ui/react-app/src/components/approvals/service-info.tsx b/web/ui/react-app/src/components/approvals/service-info.tsx index f583bc1c..9ca59f51 100644 --- a/web/ui/react-app/src/components/approvals/service-info.tsx +++ b/web/ui/react-app/src/components/approvals/service-info.tsx @@ -20,6 +20,7 @@ import { import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { ModalContext } from "contexts/modal"; import { formatRelative } from "date-fns"; +import { isEmptyOrNull } from "utils"; interface Props { service: ServiceSummaryType; @@ -57,7 +58,7 @@ export const ServiceInfo: FC = ({ () => ({ // If version hasn't been found or a new version has been found (and not skipped) warning: - (service?.status?.deployed_version ?? "") === "" || + isEmptyOrNull(service?.status?.deployed_version) || (updateAvailable && !updateSkipped), // If the latest version is the same as the approved version updateApproved: diff --git a/web/ui/react-app/src/components/generic/form-item-with-preview.tsx b/web/ui/react-app/src/components/generic/form-item-with-preview.tsx index 824c4836..98edcf27 100644 --- a/web/ui/react-app/src/components/generic/form-item-with-preview.tsx +++ b/web/ui/react-app/src/components/generic/form-item-with-preview.tsx @@ -3,6 +3,7 @@ import { FC, useMemo } from "react"; import { useFormContext, useWatch } from "react-hook-form"; import FormLabel from "./form-label"; +import { isEmptyOrNull } from "utils"; import { useError } from "hooks/errors"; interface Props { @@ -70,7 +71,7 @@ const FormItemWithPreview: FC = ({ {...register(name, { validate: (value: string | undefined) => { // Allow empty values - if ((value ?? "") === "") return true; + if (isEmptyOrNull(value)) return true; // Validate that it's a URL (with prefix) try { diff --git a/web/ui/react-app/src/components/generic/form-list.tsx b/web/ui/react-app/src/components/generic/form-list.tsx index 89292cc8..4ae58a09 100644 --- a/web/ui/react-app/src/components/generic/form-list.tsx +++ b/web/ui/react-app/src/components/generic/form-list.tsx @@ -68,7 +68,8 @@ const FormList: FC = ({ // and give the defaults if not overridden useEffect(() => { for (const item of fieldValues ?? fields ?? []) { - if ((item.arg ?? "") === "") { + const keys = Object.keys(item); + if (keys.length > 1 || !keys.includes("arg")) { setValue(name, []); break; } diff --git a/web/ui/react-app/src/components/modals/service-edit/dashboard.tsx b/web/ui/react-app/src/components/modals/service-edit/dashboard.tsx index 18809c9b..1f5bdedf 100644 --- a/web/ui/react-app/src/components/modals/service-edit/dashboard.tsx +++ b/web/ui/react-app/src/components/modals/service-edit/dashboard.tsx @@ -4,7 +4,7 @@ import { FormItem, FormItemWithPreview } from "components/generic/form"; import { Accordion } from "react-bootstrap"; import { BooleanWithDefault } from "components/generic"; import { ServiceDashboardOptionsType } from "types/config"; -import { firstNonDefault } from "components/modals/service-edit/util"; +import { firstNonDefault } from "utils"; interface Props { defaults?: ServiceDashboardOptionsType; diff --git a/web/ui/react-app/src/components/modals/service-edit/latest-version-require.tsx b/web/ui/react-app/src/components/modals/service-edit/latest-version-require.tsx index 6d89349c..38d8f1e4 100644 --- a/web/ui/react-app/src/components/modals/service-edit/latest-version-require.tsx +++ b/web/ui/react-app/src/components/modals/service-edit/latest-version-require.tsx @@ -7,10 +7,10 @@ import { } from "types/config"; import { FC, memo, useEffect, useMemo } from "react"; import { FormItem, FormLabel, FormSelect } from "components/generic/form"; +import { firstNonDefault, isEmptyOrNull } from "utils"; import { useFormContext, useWatch } from "react-hook-form"; import Command from "./command"; -import { firstNonDefault } from "components/modals/service-edit/util"; const DockerRegistryOptions = [ { label: "Docker Hub", value: "hub" }, @@ -87,7 +87,7 @@ const EditServiceLatestVersionRequire: FC = ({ useEffect(() => { // Default to Docker Hub if no registry is selected and no default registry. - if ((selectedDockerRegistry ?? "") === "") + if (isEmptyOrNull(selectedDockerRegistry)) setValue("latest_version.require.docker.type", "hub"); }, []); diff --git a/web/ui/react-app/src/components/modals/service-edit/latest-version.tsx b/web/ui/react-app/src/components/modals/service-edit/latest-version.tsx index 84aea555..c009fca3 100644 --- a/web/ui/react-app/src/components/modals/service-edit/latest-version.tsx +++ b/web/ui/react-app/src/components/modals/service-edit/latest-version.tsx @@ -11,7 +11,7 @@ import EditServiceLatestVersionRequire from "./latest-version-require"; import FormURLCommands from "./latest-version-urlcommands"; import { LatestVersionLookupEditType } from "types/service-edit"; import VersionWithRefresh from "./version-with-refresh"; -import { firstNonDefault } from "components/modals/service-edit/util"; +import { firstNonDefault } from "utils"; import { useWatch } from "react-hook-form"; interface Props { diff --git a/web/ui/react-app/src/components/modals/service-edit/notify-types/bark.tsx b/web/ui/react-app/src/components/modals/service-edit/notify-types/bark.tsx index 723ab37c..47a88bec 100644 --- a/web/ui/react-app/src/components/modals/service-edit/notify-types/bark.tsx +++ b/web/ui/react-app/src/components/modals/service-edit/notify-types/bark.tsx @@ -8,7 +8,7 @@ import { useEffect, useMemo } from "react"; import { NotifyBarkType } from "types/config"; import NotifyOptions from "components/modals/service-edit/notify-types/shared"; -import { firstNonDefault } from "components/modals/service-edit/util"; +import { firstNonDefault } from "utils"; import { normaliseForSelect } from "components/modals/service-edit/util"; import { useFormContext } from "react-hook-form"; diff --git a/web/ui/react-app/src/components/modals/service-edit/notify-types/discord.tsx b/web/ui/react-app/src/components/modals/service-edit/notify-types/discord.tsx index 0e533046..cd12494a 100644 --- a/web/ui/react-app/src/components/modals/service-edit/notify-types/discord.tsx +++ b/web/ui/react-app/src/components/modals/service-edit/notify-types/discord.tsx @@ -7,7 +7,7 @@ import { import { BooleanWithDefault } from "components/generic"; import { NotifyDiscordType } from "types/config"; import NotifyOptions from "components/modals/service-edit/notify-types/shared"; -import { firstNonDefault } from "components/modals/service-edit/util"; +import { firstNonDefault } from "utils"; import { strToBool } from "utils"; import { useMemo } from "react"; diff --git a/web/ui/react-app/src/components/modals/service-edit/notify-types/extra/ntfy/actions.tsx b/web/ui/react-app/src/components/modals/service-edit/notify-types/extra/ntfy/actions.tsx index f38aace9..a1d7362c 100644 --- a/web/ui/react-app/src/components/modals/service-edit/notify-types/extra/ntfy/actions.tsx +++ b/web/ui/react-app/src/components/modals/service-edit/notify-types/extra/ntfy/actions.tsx @@ -16,6 +16,7 @@ import { NotifyNtfyAction } from "types/config"; import NtfyAction from "./action"; import { convertNtfyActionsFromString } from "components/modals/service-edit/util"; import { diffObjects } from "utils/diff-objects"; +import { isEmptyOrNull } from "utils"; interface Props { name: string; @@ -82,7 +83,7 @@ const NtfyActions: FC = ({ name, label, tooltip, defaults }) => { useEffect(() => { // ensure we don't have another types actions for (const item of fieldValues ?? fields ?? []) { - if ((item.action ?? "") === "") { + if (isEmptyOrNull(item.action)) { setValue(name, []); break; } diff --git a/web/ui/react-app/src/components/modals/service-edit/notify-types/generic.tsx b/web/ui/react-app/src/components/modals/service-edit/notify-types/generic.tsx index 343f2d3b..87e8264b 100644 --- a/web/ui/react-app/src/components/modals/service-edit/notify-types/generic.tsx +++ b/web/ui/react-app/src/components/modals/service-edit/notify-types/generic.tsx @@ -12,7 +12,7 @@ import { import { BooleanWithDefault } from "components/generic"; import NotifyOptions from "components/modals/service-edit/notify-types/shared"; -import { firstNonDefault } from "components/modals/service-edit/util"; +import { firstNonDefault } from "utils"; import { strToBool } from "utils"; import { useMemo } from "react"; import { useWatch } from "react-hook-form"; diff --git a/web/ui/react-app/src/components/modals/service-edit/notify-types/google_chat.tsx b/web/ui/react-app/src/components/modals/service-edit/notify-types/google_chat.tsx index 5ee64edc..75e17e7b 100644 --- a/web/ui/react-app/src/components/modals/service-edit/notify-types/google_chat.tsx +++ b/web/ui/react-app/src/components/modals/service-edit/notify-types/google_chat.tsx @@ -2,7 +2,7 @@ import { FormLabel, FormTextArea } from "components/generic/form"; import { NotifyGoogleChatType } from "types/config"; import NotifyOptions from "components/modals/service-edit/notify-types/shared"; -import { firstNonDefault } from "components/modals/service-edit/util"; +import { firstNonDefault } from "utils"; import { useMemo } from "react"; /** diff --git a/web/ui/react-app/src/components/modals/service-edit/notify-types/gotify.tsx b/web/ui/react-app/src/components/modals/service-edit/notify-types/gotify.tsx index 9efb1272..ef5f642e 100644 --- a/web/ui/react-app/src/components/modals/service-edit/notify-types/gotify.tsx +++ b/web/ui/react-app/src/components/modals/service-edit/notify-types/gotify.tsx @@ -3,7 +3,7 @@ import { FormItem, FormLabel } from "components/generic/form"; import { BooleanWithDefault } from "components/generic"; import { NotifyGotifyType } from "types/config"; import NotifyOptions from "components/modals/service-edit/notify-types/shared"; -import { firstNonDefault } from "components/modals/service-edit/util"; +import { firstNonDefault } from "utils"; import { strToBool } from "utils"; import { useMemo } from "react"; diff --git a/web/ui/react-app/src/components/modals/service-edit/notify-types/ifttt.tsx b/web/ui/react-app/src/components/modals/service-edit/notify-types/ifttt.tsx index db7d8480..31c45378 100644 --- a/web/ui/react-app/src/components/modals/service-edit/notify-types/ifttt.tsx +++ b/web/ui/react-app/src/components/modals/service-edit/notify-types/ifttt.tsx @@ -2,7 +2,7 @@ import { FormItem, FormLabel } from "components/generic/form"; import { NotifyIFTTTType } from "types/config"; import NotifyOptions from "components/modals/service-edit/notify-types/shared"; -import { firstNonDefault } from "components/modals/service-edit/util"; +import { firstNonDefault } from "utils"; import { useMemo } from "react"; /** diff --git a/web/ui/react-app/src/components/modals/service-edit/notify-types/join.tsx b/web/ui/react-app/src/components/modals/service-edit/notify-types/join.tsx index 3321cd02..608f82fd 100644 --- a/web/ui/react-app/src/components/modals/service-edit/notify-types/join.tsx +++ b/web/ui/react-app/src/components/modals/service-edit/notify-types/join.tsx @@ -6,7 +6,7 @@ import { import { NotifyJoinType } from "types/config"; import NotifyOptions from "components/modals/service-edit/notify-types/shared"; -import { firstNonDefault } from "components/modals/service-edit/util"; +import { firstNonDefault } from "utils"; import { useMemo } from "react"; /** diff --git a/web/ui/react-app/src/components/modals/service-edit/notify-types/matrix.tsx b/web/ui/react-app/src/components/modals/service-edit/notify-types/matrix.tsx index 994b42bb..42c37537 100644 --- a/web/ui/react-app/src/components/modals/service-edit/notify-types/matrix.tsx +++ b/web/ui/react-app/src/components/modals/service-edit/notify-types/matrix.tsx @@ -3,7 +3,7 @@ import { FormItem, FormLabel } from "components/generic/form"; import { BooleanWithDefault } from "components/generic"; import { NotifyMatrixType } from "types/config"; import NotifyOptions from "components/modals/service-edit/notify-types/shared"; -import { firstNonDefault } from "components/modals/service-edit/util"; +import { firstNonDefault } from "utils"; import { strToBool } from "utils"; import { useMemo } from "react"; diff --git a/web/ui/react-app/src/components/modals/service-edit/notify-types/mattermost.tsx b/web/ui/react-app/src/components/modals/service-edit/notify-types/mattermost.tsx index 2096d06e..375b23a9 100644 --- a/web/ui/react-app/src/components/modals/service-edit/notify-types/mattermost.tsx +++ b/web/ui/react-app/src/components/modals/service-edit/notify-types/mattermost.tsx @@ -6,7 +6,7 @@ import { import { NotifyMatterMostType } from "types/config"; import NotifyOptions from "components/modals/service-edit/notify-types/shared"; -import { firstNonDefault } from "components/modals/service-edit/util"; +import { firstNonDefault } from "utils"; import { useMemo } from "react"; /** diff --git a/web/ui/react-app/src/components/modals/service-edit/notify-types/ntfy.tsx b/web/ui/react-app/src/components/modals/service-edit/notify-types/ntfy.tsx index d2a8b627..7527b55a 100644 --- a/web/ui/react-app/src/components/modals/service-edit/notify-types/ntfy.tsx +++ b/web/ui/react-app/src/components/modals/service-edit/notify-types/ntfy.tsx @@ -14,7 +14,7 @@ import { BooleanWithDefault } from "components/generic"; import { NotifyNtfyType } from "types/config"; import NotifyOptions from "components/modals/service-edit/notify-types/shared"; import { NtfyActions } from "components/modals/service-edit/notify-types/extra"; -import { firstNonDefault } from "components/modals/service-edit/util"; +import { firstNonDefault } from "utils"; import { strToBool } from "utils"; import { useFormContext } from "react-hook-form"; diff --git a/web/ui/react-app/src/components/modals/service-edit/notify-types/opsgenie.tsx b/web/ui/react-app/src/components/modals/service-edit/notify-types/opsgenie.tsx index 782ab175..ad3736bc 100644 --- a/web/ui/react-app/src/components/modals/service-edit/notify-types/opsgenie.tsx +++ b/web/ui/react-app/src/components/modals/service-edit/notify-types/opsgenie.tsx @@ -13,7 +13,7 @@ import { import { NotifyOpsGenieType } from "types/config"; import NotifyOptions from "components/modals/service-edit/notify-types/shared"; import { OpsGenieTargets } from "components/modals/service-edit/notify-types/extra"; -import { firstNonDefault } from "components/modals/service-edit/util"; +import { firstNonDefault } from "utils"; import { useMemo } from "react"; /** diff --git a/web/ui/react-app/src/components/modals/service-edit/notify-types/pushbullet.tsx b/web/ui/react-app/src/components/modals/service-edit/notify-types/pushbullet.tsx index dc594413..370b06f0 100644 --- a/web/ui/react-app/src/components/modals/service-edit/notify-types/pushbullet.tsx +++ b/web/ui/react-app/src/components/modals/service-edit/notify-types/pushbullet.tsx @@ -2,7 +2,7 @@ import { FormItem, FormLabel } from "components/generic/form"; import NotifyOptions from "components/modals/service-edit/notify-types/shared"; import { NotifyPushbulletType } from "types/config"; -import { firstNonDefault } from "components/modals/service-edit/util"; +import { firstNonDefault } from "utils"; import { useMemo } from "react"; /** diff --git a/web/ui/react-app/src/components/modals/service-edit/notify-types/pushover.tsx b/web/ui/react-app/src/components/modals/service-edit/notify-types/pushover.tsx index 723f89e7..929a5fee 100644 --- a/web/ui/react-app/src/components/modals/service-edit/notify-types/pushover.tsx +++ b/web/ui/react-app/src/components/modals/service-edit/notify-types/pushover.tsx @@ -2,7 +2,7 @@ import { FormItem, FormLabel } from "components/generic/form"; import NotifyOptions from "components/modals/service-edit/notify-types/shared"; import { NotifyPushoverType } from "types/config"; -import { firstNonDefault } from "components/modals/service-edit/util"; +import { firstNonDefault } from "utils"; import { useMemo } from "react"; /** diff --git a/web/ui/react-app/src/components/modals/service-edit/notify-types/rocketchat.tsx b/web/ui/react-app/src/components/modals/service-edit/notify-types/rocketchat.tsx index c16cf17f..a85761bf 100644 --- a/web/ui/react-app/src/components/modals/service-edit/notify-types/rocketchat.tsx +++ b/web/ui/react-app/src/components/modals/service-edit/notify-types/rocketchat.tsx @@ -2,7 +2,7 @@ import { FormItem, FormLabel } from "components/generic/form"; import NotifyOptions from "components/modals/service-edit/notify-types/shared"; import { NotifyRocketChatType } from "types/config"; -import { firstNonDefault } from "components/modals/service-edit/util"; +import { firstNonDefault } from "utils"; import { useMemo } from "react"; /** diff --git a/web/ui/react-app/src/components/modals/service-edit/notify-types/shared.tsx b/web/ui/react-app/src/components/modals/service-edit/notify-types/shared.tsx index ad39221d..0c02fb20 100644 --- a/web/ui/react-app/src/components/modals/service-edit/notify-types/shared.tsx +++ b/web/ui/react-app/src/components/modals/service-edit/notify-types/shared.tsx @@ -2,7 +2,7 @@ import { FormItem, FormLabel, FormTextArea } from "components/generic/form"; import { memo, useMemo } from "react"; import { NotifyOptionsType } from "types/config"; -import { firstNonDefault } from "components/modals/service-edit/util"; +import { firstNonDefault } from "utils"; /** * Returns the form fields for the `notify.X.options` section diff --git a/web/ui/react-app/src/components/modals/service-edit/notify-types/slack.tsx b/web/ui/react-app/src/components/modals/service-edit/notify-types/slack.tsx index d8bc1d44..b7300eee 100644 --- a/web/ui/react-app/src/components/modals/service-edit/notify-types/slack.tsx +++ b/web/ui/react-app/src/components/modals/service-edit/notify-types/slack.tsx @@ -7,7 +7,7 @@ import { import NotifyOptions from "components/modals/service-edit/notify-types/shared"; import { NotifySlackType } from "types/config"; -import { firstNonDefault } from "components/modals/service-edit/util"; +import { firstNonDefault } from "utils"; import { useMemo } from "react"; /** diff --git a/web/ui/react-app/src/components/modals/service-edit/notify-types/smtp.tsx b/web/ui/react-app/src/components/modals/service-edit/notify-types/smtp.tsx index 7515c3c0..fab349e1 100644 --- a/web/ui/react-app/src/components/modals/service-edit/notify-types/smtp.tsx +++ b/web/ui/react-app/src/components/modals/service-edit/notify-types/smtp.tsx @@ -4,7 +4,7 @@ import { useEffect, useMemo } from "react"; import { BooleanWithDefault } from "components/generic"; import NotifyOptions from "components/modals/service-edit/notify-types/shared"; import { NotifySMTPType } from "types/config"; -import { firstNonDefault } from "components/modals/service-edit/util"; +import { firstNonDefault } from "utils"; import { normaliseForSelect } from "components/modals/service-edit/util/normalise-selects"; import { strToBool } from "utils"; import { useFormContext } from "react-hook-form"; diff --git a/web/ui/react-app/src/components/modals/service-edit/notify-types/teams.tsx b/web/ui/react-app/src/components/modals/service-edit/notify-types/teams.tsx index 55acbf82..d18a8075 100644 --- a/web/ui/react-app/src/components/modals/service-edit/notify-types/teams.tsx +++ b/web/ui/react-app/src/components/modals/service-edit/notify-types/teams.tsx @@ -2,7 +2,7 @@ import { FormItem, FormItemColour, FormLabel } from "components/generic/form"; import NotifyOptions from "components/modals/service-edit/notify-types/shared"; import { NotifyTeamsType } from "types/config"; -import { firstNonDefault } from "components/modals/service-edit/util"; +import { firstNonDefault } from "utils"; import { useMemo } from "react"; /** diff --git a/web/ui/react-app/src/components/modals/service-edit/notify-types/telegram.tsx b/web/ui/react-app/src/components/modals/service-edit/notify-types/telegram.tsx index ad14049b..6ba17dad 100644 --- a/web/ui/react-app/src/components/modals/service-edit/notify-types/telegram.tsx +++ b/web/ui/react-app/src/components/modals/service-edit/notify-types/telegram.tsx @@ -4,7 +4,7 @@ import { useEffect, useMemo } from "react"; import { BooleanWithDefault } from "components/generic"; import NotifyOptions from "components/modals/service-edit/notify-types/shared"; import { NotifyTelegramType } from "types/config"; -import { firstNonDefault } from "components/modals/service-edit/notify-types/util"; +import { firstNonDefault } from "utils"; import { normaliseForSelect } from "components/modals/service-edit/util"; import { strToBool } from "utils"; import { useFormContext } from "react-hook-form"; diff --git a/web/ui/react-app/src/components/modals/service-edit/notify-types/util.tsx b/web/ui/react-app/src/components/modals/service-edit/notify-types/util.tsx index 0fc1d108..e13f80da 100644 --- a/web/ui/react-app/src/components/modals/service-edit/notify-types/util.tsx +++ b/web/ui/react-app/src/components/modals/service-edit/notify-types/util.tsx @@ -1,3 +1,5 @@ +import { isEmptyOrNull } from "utils"; + /** * Returns the first non-empty value from a list of values * @@ -9,7 +11,7 @@ export const firstNonDefault: (...args: unknown[]) => string = ( ) => { // Iterate through all arguments and return the first non-empty one for (const arg of args) { - if ((arg ?? "") !== "") return `${arg}`; + if (!isEmptyOrNull(arg)) return `${arg}`; } // If no non-empty argument is found, return an empty string by default return ""; diff --git a/web/ui/react-app/src/components/modals/service-edit/notify-types/zulip.tsx b/web/ui/react-app/src/components/modals/service-edit/notify-types/zulip.tsx index 96ed3fe8..ff5fffae 100644 --- a/web/ui/react-app/src/components/modals/service-edit/notify-types/zulip.tsx +++ b/web/ui/react-app/src/components/modals/service-edit/notify-types/zulip.tsx @@ -2,7 +2,7 @@ import { FormItem, FormLabel } from "components/generic/form"; import NotifyOptions from "components/modals/service-edit/notify-types/shared"; import { NotifyZulipType } from "types/config"; -import { firstNonDefault } from "components/modals/service-edit/util"; +import { firstNonDefault } from "utils"; import { useMemo } from "react"; /** diff --git a/web/ui/react-app/src/components/modals/service-edit/options.tsx b/web/ui/react-app/src/components/modals/service-edit/options.tsx index f37f3620..e3ea2daa 100644 --- a/web/ui/react-app/src/components/modals/service-edit/options.tsx +++ b/web/ui/react-app/src/components/modals/service-edit/options.tsx @@ -4,7 +4,7 @@ import { FormCheck, FormItem } from "components/generic/form"; import { Accordion } from "react-bootstrap"; import { BooleanWithDefault } from "components/generic"; import { ServiceOptionsType } from "types/config"; -import { firstNonDefault } from "components/modals/service-edit/util"; +import { firstNonDefault } from "utils"; interface Props { defaults?: ServiceOptionsType; diff --git a/web/ui/react-app/src/components/modals/service-edit/util/api-ui-conversions.tsx b/web/ui/react-app/src/components/modals/service-edit/util/api-ui-conversions.tsx index 84385827..207cdcec 100644 --- a/web/ui/react-app/src/components/modals/service-edit/util/api-ui-conversions.tsx +++ b/web/ui/react-app/src/components/modals/service-edit/util/api-ui-conversions.tsx @@ -11,8 +11,8 @@ import { ServiceEditOtherData, ServiceEditType, } from "types/service-edit"; +import { firstNonDefault, firstNonEmpty, isEmptyOrNull } from "utils"; -import { firstNonDefault } from "./first-non-default"; import { urlCommandsTrimArray } from "./url-command-trim"; /** @@ -66,8 +66,9 @@ export const convertAPIServiceDataEditToUI = ( ...header, oldIndex: key, })) ?? [], - template_toggle: - (serviceData?.deployed_version?.regex_template ?? "") !== "", + template_toggle: !isEmptyOrNull( + serviceData?.deployed_version?.regex_template + ), }, command: serviceData?.command?.map((args) => ({ args: args.map((arg) => ({ arg })), @@ -83,18 +84,19 @@ export const convertAPIServiceDataEditToUI = ( ...header, oldIndex: index, })) - : otherOptionsData?.webhook?.[whName]?.custom_headers ?? - ( - otherOptionsData?.defaults?.webhook?.[whType] as - | WebHookType - | undefined - )?.custom_headers ?? - ( - otherOptionsData?.hard_defaults?.webhook?.[whType] as - | WebHookType - | undefined - )?.custom_headers ?? - []; + : firstNonEmpty( + otherOptionsData?.webhook?.[whName]?.custom_headers, + ( + otherOptionsData?.defaults?.webhook?.[whType] as + | WebHookType + | undefined + )?.custom_headers, + ( + otherOptionsData?.hard_defaults?.webhook?.[whType] as + | WebHookType + | undefined + )?.custom_headers + ).map(() => ({ key: "", value: "" })); // Return modified item return { diff --git a/web/ui/react-app/src/components/modals/service-edit/util/index.tsx b/web/ui/react-app/src/components/modals/service-edit/util/index.tsx index 1c11e7a5..e7283cea 100644 --- a/web/ui/react-app/src/components/modals/service-edit/util/index.tsx +++ b/web/ui/react-app/src/components/modals/service-edit/util/index.tsx @@ -18,7 +18,6 @@ import { } from "./url-command-trim"; import { convertValuesToString } from "./notify-string-string-map"; -import { firstNonDefault } from "./first-non-default"; import { normaliseForSelect } from "./normalise-selects"; export { @@ -32,7 +31,6 @@ export { convertNotifyToAPI, convertUIServiceDataEditToAPI, convertValuesToString, - firstNonDefault, normaliseForSelect, urlCommandsTrim, urlCommandTrim, diff --git a/web/ui/react-app/src/components/modals/service-edit/util/notify-string-string-map.tsx b/web/ui/react-app/src/components/modals/service-edit/util/notify-string-string-map.tsx index 9dc722a8..20fa0321 100644 --- a/web/ui/react-app/src/components/modals/service-edit/util/notify-string-string-map.tsx +++ b/web/ui/react-app/src/components/modals/service-edit/util/notify-string-string-map.tsx @@ -5,6 +5,8 @@ import { StringFieldArray, } from "types/config"; +import { isEmptyOrNull } from "utils"; + interface StringAnyMap { [key: string]: | string @@ -37,8 +39,8 @@ export const convertValuesToString = ( if ("responders" === key || "visibleto" === key) { // `value` empty means defaults were used. Skip. if ( - (value as NotifyOpsGenieTarget[]).find( - (item) => (item.value || "") === "" + (value as NotifyOpsGenieTarget[]).find((item) => + isEmptyOrNull(item.value) ) ) { return result; @@ -67,8 +69,8 @@ export const convertValuesToString = ( } else { // `value` empty means defaults were used. Skip. if ( - (value as NotifyOpsGenieTarget[]).find( - (item) => (item.value ?? "") === "" + (value as NotifyOpsGenieTarget[]).find((item) => + isEmptyOrNull(item.value) ) ) { return result; diff --git a/web/ui/react-app/src/components/modals/service-edit/util/ui-api-conversions.tsx b/web/ui/react-app/src/components/modals/service-edit/util/ui-api-conversions.tsx index 9f3fff58..0abfc814 100644 --- a/web/ui/react-app/src/components/modals/service-edit/util/ui-api-conversions.tsx +++ b/web/ui/react-app/src/components/modals/service-edit/util/ui-api-conversions.tsx @@ -79,12 +79,23 @@ export const convertUIServiceDataEditToAPI = ( if (data.webhook) payload.webhook = data.webhook.reduce((acc, webhook) => { webhook = removeEmptyValues(webhook); + // Defaults were being shown if key/value were empty + const removeCustomHeaders = (webhook.custom_headers ?? []).find( + (header) => header.key === "" || header.value === "" + ); acc[webhook.name as string] = { ...webhook, - desired_status_code: webhook?.desired_status_code - ? Number(webhook?.desired_status_code) - : undefined, - max_tries: webhook.max_tries ? Number(webhook.max_tries) : undefined, + custom_headers: removeCustomHeaders + ? undefined + : webhook.custom_headers, + desired_status_code: + webhook?.desired_status_code !== undefined + ? Number(webhook?.desired_status_code) + : undefined, + max_tries: + webhook.max_tries !== undefined + ? Number(webhook.max_tries) + : undefined, }; return acc; }, {} as Dict); diff --git a/web/ui/react-app/src/components/modals/service-edit/util/url-command-trim.tsx b/web/ui/react-app/src/components/modals/service-edit/util/url-command-trim.tsx index 2b3a5e8f..4843a848 100644 --- a/web/ui/react-app/src/components/modals/service-edit/util/url-command-trim.tsx +++ b/web/ui/react-app/src/components/modals/service-edit/util/url-command-trim.tsx @@ -1,4 +1,5 @@ import { URLCommandType } from "types/config"; +import { isEmptyOrNull } from "utils"; /** * Returns a `url_command` object with only the relevant keys for the type @@ -26,7 +27,7 @@ export const urlCommandTrim = ( regex: command.regex, index: command.index ? Number(command.index) : undefined, template: command.template ? command.template : undefined, - template_toggle: (command.template ?? "") !== "", + template_toggle: !isEmptyOrNull(command.template), }; // replace diff --git a/web/ui/react-app/src/components/modals/service-edit/webhook.tsx b/web/ui/react-app/src/components/modals/service-edit/webhook.tsx index 327afc5d..cb70f348 100644 --- a/web/ui/react-app/src/components/modals/service-edit/webhook.tsx +++ b/web/ui/react-app/src/components/modals/service-edit/webhook.tsx @@ -7,12 +7,12 @@ import { FormLabel, FormSelect, } from "components/generic/form"; +import { firstNonDefault, firstNonEmpty } from "utils"; import { useFormContext, useWatch } from "react-hook-form"; import { BooleanWithDefault } from "components/generic"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faTrash } from "@fortawesome/free-solid-svg-icons"; -import { firstNonDefault } from "components/modals/service-edit/util"; interface Props { name: string; @@ -80,10 +80,11 @@ const EditServiceWebHook: FC = ({ main?.allow_invalid_certs ?? defaults?.allow_invalid_certs ?? hard_defaults?.allow_invalid_certs, - custom_headers: - main?.custom_headers ?? - defaults?.custom_headers ?? - hard_defaults?.custom_headers, + custom_headers: firstNonEmpty( + main?.custom_headers, + defaults?.custom_headers, + hard_defaults?.custom_headers + ), delay: firstNonDefault( main?.delay, defaults?.delay, diff --git a/web/ui/react-app/src/utils/diff-objects.tsx b/web/ui/react-app/src/utils/diff-objects.tsx index df3752ca..59db82f1 100644 --- a/web/ui/react-app/src/utils/diff-objects.tsx +++ b/web/ui/react-app/src/utils/diff-objects.tsx @@ -1,3 +1,5 @@ +import isEmptyOrNull from "./is-empty-or-null"; + /** * Returns whether a is different from b after allowing for only values at * allowedDefined to be the value in b @@ -48,7 +50,7 @@ export function diffObjects( return true; } else if (typeof b === "string") { // a is undefined/empty - if ((a ?? "") === "") return true; + if (isEmptyOrNull(a)) return true; // a is defined, and on a key that's allowed and is the same as b if ( containsEndsWith( diff --git a/web/ui/react-app/src/components/modals/service-edit/util/first-non-default.tsx b/web/ui/react-app/src/utils/first-non-default.tsx similarity index 68% rename from web/ui/react-app/src/components/modals/service-edit/util/first-non-default.tsx rename to web/ui/react-app/src/utils/first-non-default.tsx index adb4cfaf..9085395a 100644 --- a/web/ui/react-app/src/components/modals/service-edit/util/first-non-default.tsx +++ b/web/ui/react-app/src/utils/first-non-default.tsx @@ -1,3 +1,5 @@ +import isEmptyOrNull from "./is-empty-or-null"; + /** * Returns the first non-empty from the list of arguments * @@ -6,13 +8,15 @@ * @param args - The list of arguments to check * @returns The first non-empty string */ -export const firstNonDefault: (...args: unknown[]) => string = ( +const firstNonDefault: (...args: unknown[]) => string = ( ...args: unknown[] ) => { // Iterate through all arguments and return the first non-empty one for (const arg of args) { - if ((arg ?? "") !== "") return `${arg}`; + if (!isEmptyOrNull(arg)) return `${arg}`; } // If no non-empty argument is found, return an empty string by default return ""; }; + +export default firstNonDefault; diff --git a/web/ui/react-app/src/utils/first-non-empty.tsx b/web/ui/react-app/src/utils/first-non-empty.tsx new file mode 100644 index 00000000..355481c1 --- /dev/null +++ b/web/ui/react-app/src/utils/first-non-empty.tsx @@ -0,0 +1,16 @@ +/** + * Returns the first non-zero length argument + * + * @param args - The arguments to check for the first non-zero length + * @returns The first non-zero length argument + */ +const firstNonEmpty = ( + ...args: T[] +): NonNullable => { + for (const arg of args) { + if (arg && (arg as unknown[]).length) return arg; + } + return [] as NonNullable; +}; + +export default firstNonEmpty; diff --git a/web/ui/react-app/src/utils/index.tsx b/web/ui/react-app/src/utils/index.tsx index e2fed459..2514a26e 100644 --- a/web/ui/react-app/src/utils/index.tsx +++ b/web/ui/react-app/src/utils/index.tsx @@ -7,19 +7,25 @@ import dateIsAfterNow from "./is-after-date"; import { diffObjects } from "./diff-objects"; import fetchJSON from "./fetch-json"; import fetchYAML from "./fetch-yaml"; +import firstNonDefault from "./first-non-default"; +import firstNonEmpty from "./first-non-empty"; import getBasename from "./get-basename"; +import isEmptyOrNull from "./is-empty-or-null"; import removeEmptyValues from "./remove-empty-values"; export { boolToStr, convertToQueryParams, cleanEmpty, + dateIsAfterNow, diffObjects, extractErrors, fetchJSON, fetchYAML, + firstNonDefault, + firstNonEmpty, getBasename, - dateIsAfterNow, + isEmptyOrNull, removeEmptyValues, stringifyQueryParam, strToBool, diff --git a/web/ui/react-app/src/utils/is-empty-or-null.tsx b/web/ui/react-app/src/utils/is-empty-or-null.tsx new file mode 100644 index 00000000..60604b73 --- /dev/null +++ b/web/ui/react-app/src/utils/is-empty-or-null.tsx @@ -0,0 +1,11 @@ +/** + * Returns whether the value is empty, null, or undefined + * + * @param value - The value to check + * @returns Whether the value is empty, null, or undefined + */ +const isEmptyOrNull = (value: unknown | undefined): boolean => { + return (value ?? "") == ""; +}; + +export default isEmptyOrNull; diff --git a/web/ui/react-app/src/utils/remove-empty-values.tsx b/web/ui/react-app/src/utils/remove-empty-values.tsx index bf0b7d5a..d68a442d 100644 --- a/web/ui/react-app/src/utils/remove-empty-values.tsx +++ b/web/ui/react-app/src/utils/remove-empty-values.tsx @@ -4,6 +4,9 @@ * @param obj - The object to remove empty values from * @returns The object with all empty values removed */ + +import isEmptyOrNull from "./is-empty-or-null"; + // eslint-disable-next-line @typescript-eslint/no-explicit-any const removeEmptyValues = (obj: { [x: string]: any }) => { for (const key in obj) { @@ -23,7 +26,7 @@ const removeEmptyValues = (obj: { [x: string]: any }) => { continue; } // "" Empty/undefined string - } else if ((obj[key] ?? "") === "") delete obj[key]; + } else if (isEmptyOrNull(obj[key])) delete obj[key]; } return obj; }; diff --git a/web/ui/react-app/src/utils/string-boolean.tsx b/web/ui/react-app/src/utils/string-boolean.tsx index 4d6ba895..23ba372f 100644 --- a/web/ui/react-app/src/utils/string-boolean.tsx +++ b/web/ui/react-app/src/utils/string-boolean.tsx @@ -1,3 +1,5 @@ +import isEmptyOrNull from "./is-empty-or-null"; + /** * Returns the boolean value of a string * @@ -6,8 +8,8 @@ */ export const strToBool = (str?: string | boolean): boolean | null => { if (typeof str === "boolean") return str; - if (str == null || str === "") return null; - return ["true", "yes"].includes(str.toLowerCase()); + if (isEmptyOrNull(str)) return null; + return ["true", "yes"].includes((str as string).toLowerCase()); }; export const boolToStr = (bool?: boolean) =>