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 (
-
- );
- }
-
- handleUpdateTarget(targetIndex: number, value: string): void {
- const { onChange, data } = this.props;
- const { targets } = data;
-
- onChange(
- {
- targets: [
- ...(targets?.slice(0, targetIndex) ?? []),
- value,
- ...(targets?.slice(targetIndex + 1) ?? []),
- ],
- },
- targetIndex
- );
- }
-
- handleUpdateAloneMetrics(targetIndex: number, value: boolean): void {
- const { onChange, data } = this.props;
- let { alone_metrics: aloneMetrics } = data;
- const target = `t${targetIndex + 1}`;
-
- if (aloneMetrics == null) {
- aloneMetrics = {};
- }
-
- aloneMetrics[target] = value;
- onChange({
- alone_metrics: aloneMetrics,
- });
- }
-
- handleRemoveTarget(targetIndex: number): void {
- const { onChange, data } = this.props;
- const { targets, alone_metrics: aloneMetrics } = data;
- const aloneMetricsIndex = [];
-
- for (let i = 0; i < (targets?.length ?? 0); i += 1) {
- const target = `t${i + 1}`;
- aloneMetricsIndex[i] = aloneMetrics?.[target];
- }
-
- const newAloneMetricsIndex = [
- ...aloneMetricsIndex.slice(0, targetIndex),
- ...aloneMetricsIndex.slice(targetIndex + 1),
- ];
-
- const newAloneMetrics: {
- [key: string]: boolean;
- } = {};
-
- for (let i = 0; i < (targets?.length ?? 0); i += 1) {
- const target = `t${i + 1}`;
- const metricIndex = newAloneMetricsIndex[i];
- if (metricIndex) {
- newAloneMetrics[target] = metricIndex;
- }
- }
-
- onChange({
- targets: [
- ...(targets?.slice(0, targetIndex) ?? []),
- ...(targets?.slice(targetIndex + 1) ?? []),
- ],
- alone_metrics: newAloneMetrics,
- });
- }
-
- handleAddTarget(): void {
- const { onChange, data } = this.props;
- const { targets } = data;
-
- onChange({
- trigger_type: "expression",
- targets: [...(targets ?? []), ""],
- });
- }
-}
-
-type FormProps = {
- children: React.ReactNode;
-};
-
-function Form({ children }: FormProps): React.ReactElement {
- return {children}
;
-}
-
-type FormRowProps = {
- label?: string;
- useTopAlignForLabel?: boolean;
- singleLineControlGroup?: boolean;
- style?: {
- [key: string]: number | string;
- };
- children: React.ReactNode;
+ data-tid="Tags"
+ />
+
+
+
+ );
};
-function FormRow({
- label,
- useTopAlignForLabel,
- singleLineControlGroup,
- children,
- style,
-}: FormRowProps): React.ReactElement {
- return (
-
- {label != null && (
-
- {label}
-
- )}
-
-
- );
-}
+export default TriggerEditForm;
diff --git a/src/Components/TriggerEditForm/Validations/validations.ts b/src/Components/TriggerEditForm/Validations/validations.ts
new file mode 100644
index 000000000..473c429b5
--- /dev/null
+++ b/src/Components/TriggerEditForm/Validations/validations.ts
@@ -0,0 +1,40 @@
+import { ValidationInfo } from "@skbkontur/react-ui-validations";
+import { LOW_TRIGGER_TTL } from "../../../Domain/Trigger";
+
+export const 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;
+};
+
+export const validateTTL = (value?: number): ValidationInfo | null => {
+ if (value === undefined) {
+ return {
+ type: "submit",
+ message: "Can't be empty",
+ };
+ }
+
+ if (value <= 0) {
+ return {
+ type: "lostfocus",
+ message: "Can't be zero or negative",
+ };
+ }
+
+ if (value < LOW_TRIGGER_TTL) {
+ return {
+ type: "lostfocus",
+ level: "warning",
+ message: "Low TTL can lead to false positives",
+ };
+ }
+
+ return null;
+};
diff --git a/src/Components/TriggerInfo/TriggerInfo.tsx b/src/Components/TriggerInfo/TriggerInfo.tsx
index 56a3e58c2..d3423dc05 100644
--- a/src/Components/TriggerInfo/TriggerInfo.tsx
+++ b/src/Components/TriggerInfo/TriggerInfo.tsx
@@ -25,7 +25,7 @@ import FileExport from "../FileExport/FileExport";
import MaintenanceSelect from "../MaintenanceSelect/MaintenanceSelect";
import { CodeEditor } from "../HighlightInput/CodeEditor";
import { Gapped, Hint } from "@skbkontur/react-ui";
-import { CopyButton } from "../TriggerEditForm/CopyButton";
+import { CopyButton } from "../TriggerEditForm/Components/CopyButton";
import classNames from "classnames/bind";