diff --git a/src/Components/TriggerEditForm/CopyButton.tsx b/src/Components/TriggerEditForm/Components/CopyButton.tsx similarity index 100% rename from src/Components/TriggerEditForm/CopyButton.tsx rename to src/Components/TriggerEditForm/Components/CopyButton.tsx diff --git a/src/Components/TriggerEditForm/EditDescritionHelp.tsx b/src/Components/TriggerEditForm/Components/EditDescritionHelp.tsx similarity index 94% rename from src/Components/TriggerEditForm/EditDescritionHelp.tsx rename to src/Components/TriggerEditForm/Components/EditDescritionHelp.tsx index 56df1f300..6e3483baa 100644 --- a/src/Components/TriggerEditForm/EditDescritionHelp.tsx +++ b/src/Components/TriggerEditForm/Components/EditDescritionHelp.tsx @@ -1,10 +1,10 @@ import React from "react"; import { Link } from "@skbkontur/react-ui/components/Link"; -import HelpTooltip from "../HelpTooltip/HelpTooltip"; -import CodeRef from "../CodeRef/CodeRef"; +import HelpTooltip from "../../HelpTooltip/HelpTooltip"; +import CodeRef from "../../CodeRef/CodeRef"; import classNames from "classnames/bind"; -import styles from "./TriggerEditForm.less"; +import styles from "../TriggerEditForm.less"; const cn = classNames.bind(styles); diff --git a/src/Components/TriggerEditForm/Components/Form.less b/src/Components/TriggerEditForm/Components/Form.less new file mode 100644 index 000000000..67be75ba4 --- /dev/null +++ b/src/Components/TriggerEditForm/Components/Form.less @@ -0,0 +1,37 @@ +.form { + max-width: 800px; +} + +.label { + flex-shrink: 0; + width: 150px; +} + +.label-for-group { + align-self: flex-start; + padding-top: 7px; +} + +.row { + display: flex; + align-items: baseline; + margin-bottom: 15px; + + textarea { + vertical-align: top; + } +} + +.control { + margin-left: auto; + width: calc(~'100% - 150px'); +} + +.group { + display: flex; + align-items: center; + + & > * { + margin-right: 5px; + } +} \ No newline at end of file diff --git a/src/Components/TriggerEditForm/Components/Form.tsx b/src/Components/TriggerEditForm/Components/Form.tsx new file mode 100644 index 000000000..5ab46d0dc --- /dev/null +++ b/src/Components/TriggerEditForm/Components/Form.tsx @@ -0,0 +1,45 @@ +import React from "react"; +import classNames from "classnames/bind"; + +import styles from "./Form.less"; + +const cn = classNames.bind(styles); + +interface IFormProps { + children: React.ReactNode; +} + +export function Form({ children }: IFormProps): React.ReactElement { + return
{children}
; +} + +interface IFormRowProps { + label?: string; + useTopAlignForLabel?: boolean; + singleLineControlGroup?: boolean; + style?: { + [key: string]: number | string; + }; + children: React.ReactNode; +} + +export function FormRow({ + label, + useTopAlignForLabel, + singleLineControlGroup, + children, + style, +}: IFormRowProps): React.ReactElement { + const labelElement = label && ( +
{label}
+ ); + + return ( +
+ {labelElement} +
+ {children} +
+
+ ); +} diff --git a/src/Components/TriggerEditForm/MetricSourceSelect.tsx b/src/Components/TriggerEditForm/Components/MetricSourceSelect.tsx similarity index 95% rename from src/Components/TriggerEditForm/MetricSourceSelect.tsx rename to src/Components/TriggerEditForm/Components/MetricSourceSelect.tsx index eca18184f..c3d3b7996 100644 --- a/src/Components/TriggerEditForm/MetricSourceSelect.tsx +++ b/src/Components/TriggerEditForm/Components/MetricSourceSelect.tsx @@ -1,6 +1,6 @@ import React from "react"; import { Gapped, Radio, RadioGroup } from "@skbkontur/react-ui"; -import TriggerSource from "../../Domain/Trigger"; +import TriggerSource from "../../../Domain/Trigger"; import { Link } from "@skbkontur/react-ui/components/Link"; interface Props { diff --git a/src/Components/TriggerEditForm/TriggerEditForm.less b/src/Components/TriggerEditForm/TriggerEditForm.less index 6f18a6441..cebf7d3e5 100644 --- a/src/Components/TriggerEditForm/TriggerEditForm.less +++ b/src/Components/TriggerEditForm/TriggerEditForm.less @@ -7,35 +7,6 @@ top:3px } -.form { - max-width: 800px; -} - -.row { - display: flex; - align-items: baseline; - margin-bottom: 15px; - - textarea { - vertical-align: top; - } -} - -.label { - flex-shrink: 0; - width: 150px; -} - -.control { - margin-left: auto; - width: calc(~'100% - 150px'); -} - -.label-for-group { - align-self: flex-start; - padding-top: 7px; -} - .target { top: 4px; position: relative; @@ -49,15 +20,6 @@ padding-right: 5px; } -.group { - display: flex; - align-items: center; - - & > * { - margin-right: 5px; - } -} - .days, .new-metrics { margin-bottom: 15px; display: flex; diff --git a/src/Components/TriggerEditForm/TriggerEditForm.tsx b/src/Components/TriggerEditForm/TriggerEditForm.tsx index 2cb691ab9..226516a70 100644 --- a/src/Components/TriggerEditForm/TriggerEditForm.tsx +++ b/src/Components/TriggerEditForm/TriggerEditForm.tsx @@ -1,5 +1,6 @@ -import * as React from "react"; -import { ValidationWrapperV1, tooltip, ValidationInfo } from "@skbkontur/react-ui-validations"; +import React, { useState, FC } from "react"; +import { ValidationWrapperV1, tooltip } from "@skbkontur/react-ui-validations"; +import { validateRequiredString, validateTTL } from "./Validations/validations"; import { Remarkable } from "remarkable"; import { sanitize } from "dompurify"; import RemoveIcon from "@skbkontur/react-icons/Remove"; @@ -9,7 +10,6 @@ import { RowStack, Fill, Fit } from "@skbkontur/react-stack-layout"; import { DEFAULT_TRIGGER_TTL, DEFAULT_TRIGGER_TYPE, - LOW_TRIGGER_TTL, Trigger, TriggerSource, ValidateTriggerResult, @@ -25,9 +25,10 @@ import { Status, StatusesList } from "../../Domain/Status"; import CodeRef from "../CodeRef/CodeRef"; import HighlightInput from "../HighlightInput/HighlightInput"; import HelpTooltip from "../HelpTooltip/HelpTooltip"; -import EditDescriptionHelp from "./EditDescritionHelp"; -import { MetricSourceSelect } from "./MetricSourceSelect"; -import { CopyButton } from "./CopyButton"; +import EditDescriptionHelp from "./Components/EditDescritionHelp"; +import { MetricSourceSelect } from "./Components/MetricSourceSelect"; +import { CopyButton } from "./Components/CopyButton"; +import { Form, FormRow } from "./Components/Form"; import classNames from "classnames/bind"; import styles from "./TriggerEditForm.less"; @@ -36,408 +37,260 @@ const cn = classNames.bind(styles); const md = new Remarkable({ breaks: true }); -type Props = { +interface IProps { data: Partial; tags: Array; remoteAllowed?: boolean | null; onChange: (trigger: Partial, targetIndex?: number) => void; validationResult?: ValidateTriggerResult; -}; - -type State = { - descriptionMode: "edit" | "preview"; - sourceChanged: boolean; -}; - -export default class TriggerEditForm extends React.Component { - public state: State = { - descriptionMode: "edit", - sourceChanged: false, - }; - - static validateRequiredString( - value: string, - message?: string - ): ValidationInfo | null | undefined { - return value.trim().length === 0 - ? { - type: value.trim().length === 0 ? "submit" : "lostfocus", - message: message || "Can't be empty", - } - : null; - } +} - static validateRequiredNumber(value?: number | null): ValidationInfo | null { - return typeof value !== "number" - ? { - type: "submit", - message: "Can't be empty", - } - : null; +const TriggerEditForm: FC = ({ + data, + tags: allTags, + remoteAllowed, + onChange, + validationResult, +}) => { + const [descriptionMode, setDescriptionMode] = useState<"edit" | "preview">("edit"); + + const { + name, + desc, + targets, + tags, + expression, + ttl, + ttl_state: ttlState, + sched, + trigger_source: triggerSource, + trigger_type: triggerType, + mute_new_metrics: muteNewMetrics, + alone_metrics: aloneMetrics, + } = data; + + if (sched == null) { + throw new Error("InvalidProgramState"); } - static validateTTL(value?: number): ValidationInfo | null { - if (value === undefined) { - return { - type: "submit", - message: "Can't be empty", - }; - } + const handleUpdateTarget = (targetIndex: number, value: string): void => { + const newTargets = [...(data.targets ?? [])]; + newTargets[targetIndex] = value; + onChange({ targets: newTargets }, targetIndex); + }; - if (value <= 0) { - return { - type: "lostfocus", - message: "Can't be zero or negative", - }; - } + const handleUpdateAloneMetrics = (targetIndex: number, value: boolean): void => { + const newAloneMetrics = { ...(data.alone_metrics ?? {}) }; + newAloneMetrics[`t${targetIndex + 1}`] = value; + onChange({ alone_metrics: newAloneMetrics }); + }; - if (value < LOW_TRIGGER_TTL) { - return { - type: "lostfocus", - level: "warning", - message: "Low TTL can lead to false positives", - }; + const handleRemoveTarget = (targetIndex: number): void => { + const newTargets = (data.targets ?? []).filter((_, index) => index !== targetIndex); + const newAloneMetrics: { [key: string]: boolean } = {}; + for (let i = 0; i < newTargets.length; i++) { + newAloneMetrics[`t${i + 1}`] = data.alone_metrics?.[`t${i + 2}`] ?? false; } + onChange({ targets: newTargets, alone_metrics: newAloneMetrics }); + }; - return null; - } - - render(): React.ReactElement { - const { descriptionMode } = this.state; - const { data, onChange, tags: allTags, remoteAllowed, validationResult } = this.props; - const { - name, - desc, - targets, - tags, - expression, - ttl, - ttl_state: ttlState, - sched, - trigger_source: triggerSource, - trigger_type: triggerType, - mute_new_metrics: muteNewMetrics, - alone_metrics: aloneMetrics, - } = data; - if (sched == null) { - throw new Error("InvalidProgramState"); - } + const handleAddTarget = (): void => { + const newTargets = [...(data.targets ?? []), ""]; + onChange({ trigger_type: "expression", targets: newTargets }); + }; - const triggerModeEditorValue: ValueType = { - error_value: data.error_value ?? null, - warn_value: data.warn_value ?? null, - }; + const triggerModeEditorValue: ValueType = { + error_value: data.error_value ?? null, + warn_value: data.warn_value ?? null, + }; - return ( -
- - + + + onChange({ name: value })} + data-tid="Name" + /> + + + +
+ setDescriptionMode(value)} > - + Edit + + + Preview + + +
+ {descriptionMode === "edit" ? ( + <> +