From fa3df0347b7896c5bbd79d239179d172fdb65420 Mon Sep 17 00:00:00 2001 From: RachelElysia <71795832+RachelElysia@users.noreply.github.com> Date: Thu, 3 Mar 2022 08:26:03 -0500 Subject: [PATCH] Debounce, onchange, changelog (#4413) --- changes/issue-4365-sql-error-onchange | 1 + .../components/forms/LabelForm/LabelForm.tsx | 33 ++++++++++++++++- .../components/PolicyForm/PolicyForm.tsx | 35 +++++++++++++++++-- .../components/QueryForm/QueryForm.tsx | 19 +++++----- 4 files changed, 77 insertions(+), 11 deletions(-) create mode 100644 changes/issue-4365-sql-error-onchange diff --git a/changes/issue-4365-sql-error-onchange b/changes/issue-4365-sql-error-onchange new file mode 100644 index 0000000000..1e2480fa7c --- /dev/null +++ b/changes/issue-4365-sql-error-onchange @@ -0,0 +1 @@ +* Users are presented with debounced SQL errors on change for labels, queries, and policies \ No newline at end of file diff --git a/frontend/components/forms/LabelForm/LabelForm.tsx b/frontend/components/forms/LabelForm/LabelForm.tsx index 83910d8f3d..a062b9f0b1 100644 --- a/frontend/components/forms/LabelForm/LabelForm.tsx +++ b/frontend/components/forms/LabelForm/LabelForm.tsx @@ -1,6 +1,7 @@ import React, { useState, useEffect } from "react"; import { IAceEditor } from "react-ace/lib/types"; -import { noop } from "lodash"; +import { noop, size } from "lodash"; +import { useDebouncedCallback } from "use-debounce/lib"; import { ILabel, ILabelFormData } from "interfaces/label"; import Button from "components/buttons/Button"; // @ts-ignore @@ -36,6 +37,18 @@ const platformOptions = [ { label: "Centos", value: "centos" }, ]; +const validateQuerySQL = (query: string) => { + const errors: { [key: string]: any } = {}; + const { error: queryError, valid: queryValid } = validateQuery(query); + + if (!queryValid) { + errors.query = queryError; + } + + const valid = !size(errors); + return { valid, errors }; +}; + const LabelForm = ({ baseError, selectedLabel, @@ -56,10 +69,28 @@ const LabelForm = ({ selectedLabel?.platform || "" ); + const debounceSQL = useDebouncedCallback((queryString: string) => { + let valid = true; + const { valid: isValidated, errors: newErrors } = validateQuerySQL( + queryString + ); + valid = isValidated; + + if (query === "") { + setQueryError(""); + } else { + setQueryError(newErrors.query); + } + }, 500); + useEffect(() => { setNameError(backendValidators.name); }, [backendValidators]); + useEffect(() => { + debounceSQL(query); + }, [query]); + const onLoad = (editor: IAceEditor) => { editor.setOptions({ enableLinking: true, diff --git a/frontend/pages/policies/PolicyPage/components/PolicyForm/PolicyForm.tsx b/frontend/pages/policies/PolicyPage/components/PolicyForm/PolicyForm.tsx index 5920c5ff51..08021f7187 100644 --- a/frontend/pages/policies/PolicyPage/components/PolicyForm/PolicyForm.tsx +++ b/frontend/pages/policies/PolicyPage/components/PolicyForm/PolicyForm.tsx @@ -1,8 +1,9 @@ /* eslint-disable jsx-a11y/no-noninteractive-element-to-interactive-role */ /* eslint-disable jsx-a11y/interactive-supports-focus */ -import React, { useState, useContext, KeyboardEvent } from "react"; +import React, { useState, useContext, useEffect, KeyboardEvent } from "react"; import { IAceEditor } from "react-ace/lib/types"; -import { isUndefined } from "lodash"; +import { useDebouncedCallback } from "use-debounce/lib"; +import { isUndefined, size } from "lodash"; import classnames from "classnames"; import { addGravatarUrlToResource } from "fleet/helpers"; @@ -15,6 +16,8 @@ import { IQueryPlatform } from "interfaces/query"; import Avatar from "components/Avatar"; import FleetAce from "components/FleetAce"; +// @ts-ignore +import validateQuery from "components/forms/validators/validate_query"; import Button from "components/buttons/Button"; import Checkbox from "components/forms/fields/Checkbox"; import Spinner from "components/Spinner"; @@ -40,6 +43,18 @@ interface IPolicyFormProps { backendValidators: { [key: string]: string }; } +const validateQuerySQL = (query: string) => { + const errors: { [key: string]: any } = {}; + const { error: queryError, valid: queryValid } = validateQuery(query); + + if (!queryValid) { + errors.query = queryError; + } + + const valid = !size(errors); + return { valid, errors }; +}; + const PolicyForm = ({ policyIdForEdit, showOpenSchemaActionText, @@ -98,6 +113,22 @@ const PolicyForm = ({ isTeamMaintainer, } = useContext(AppContext); + policyIdForEdit = policyIdForEdit || 0; + + const debounceSQL = useDebouncedCallback((sql: string) => { + let valid = true; + const { valid: isValidated, errors: newErrors } = validateQuerySQL(sql); + valid = isValidated; + + setErrors({ + ...newErrors, + }); + }, 500); + + useEffect(() => { + debounceSQL(lastEditedQueryBody); + }, [lastEditedQueryBody]); + const isEditMode = !!policyIdForEdit && !isTeamObserver && !isGlobalObserver; const hasSavePermissions = diff --git a/frontend/pages/queries/QueryPage/components/QueryForm/QueryForm.tsx b/frontend/pages/queries/QueryPage/components/QueryForm/QueryForm.tsx index c1deb72bf6..52020bc075 100644 --- a/frontend/pages/queries/QueryPage/components/QueryForm/QueryForm.tsx +++ b/frontend/pages/queries/QueryPage/components/QueryForm/QueryForm.tsx @@ -122,6 +122,16 @@ const QueryForm = ({ { leading: true } ); + const debounceSQL = useDebouncedCallback((sql: string) => { + let valid = true; + const { valid: isValidated, errors: newErrors } = validateQuerySQL(sql); + valid = isValidated; + + setErrors({ + ...newErrors, + }); + }, 500); + queryIdForEdit = queryIdForEdit || 0; useEffect(() => { @@ -129,14 +139,7 @@ const QueryForm = ({ debounceCompatiblePlatforms(lastEditedQueryBody); } - let valid = true; - const { valid: isValidated, errors: newErrors } = validateQuerySQL( - lastEditedQueryBody - ); - valid = isValidated; - setErrors({ - ...newErrors, - }); + debounceSQL(lastEditedQueryBody); }, [lastEditedQueryBody, lastEditedQueryId]); const hasTeamMaintainerPermissions = isEditMode