From df38c6fc468392fc1db3011f2af8881b63603745 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patryk=20Kopyci=C5=84ski?= Date: Wed, 18 May 2022 11:52:01 +0200 Subject: [PATCH] [Osquery] Fix ECS mapping editor issues (#132307) --- x-pack/plugins/osquery/cypress/cypress.json | 3 +- .../integration/all/live_query.spec.ts | 27 +++++++ .../cypress/integration/all/packs.spec.ts | 33 +++++---- .../osquery/cypress/tasks/live_query.ts | 3 + .../public/agent_policies/use_agent_policy.ts | 2 - .../public/live_queries/form/index.tsx | 64 ++++------------- .../public/live_queries/form/schema.ts | 49 +++++++++++++ .../queries/ecs_mapping_editor_field.tsx | 70 +++++++++++++------ .../osquery/public/packs/queries/schema.tsx | 1 - .../saved_queries/form/playground_flyout.tsx | 4 +- .../saved_queries/saved_queries_dropdown.tsx | 25 ++++--- 11 files changed, 180 insertions(+), 101 deletions(-) create mode 100644 x-pack/plugins/osquery/public/live_queries/form/schema.ts diff --git a/x-pack/plugins/osquery/cypress/cypress.json b/x-pack/plugins/osquery/cypress/cypress.json index a793470e578ee..5df26a922d7c3 100644 --- a/x-pack/plugins/osquery/cypress/cypress.json +++ b/x-pack/plugins/osquery/cypress/cypress.json @@ -4,7 +4,8 @@ "execTimeout": 120000, "pageLoadTimeout": 12000, "retries": { - "runMode": 0 + "runMode": 1, + "openMode": 0 }, "screenshotsFolder": "../../../target/kibana-osquery/cypress/screenshots", "trashAssetsBeforeRuns": false, diff --git a/x-pack/plugins/osquery/cypress/integration/all/live_query.spec.ts b/x-pack/plugins/osquery/cypress/integration/all/live_query.spec.ts index 7cdc55c014505..930d1e29c2ebf 100644 --- a/x-pack/plugins/osquery/cypress/integration/all/live_query.spec.ts +++ b/x-pack/plugins/osquery/cypress/integration/all/live_query.spec.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { ArchiverMethod, runKbnArchiverScript } from '../../tasks/archiver'; import { login } from '../../tasks/login'; import { navigateTo } from '../../tasks/navigation'; import { @@ -24,11 +25,19 @@ import { getAdvancedButton } from '../../screens/integrations'; import { ROLES } from '../../test'; describe('ALL - Live Query', () => { + before(() => { + runKbnArchiverScript(ArchiverMethod.LOAD, 'ecs_mapping_1'); + }); + beforeEach(() => { login(ROLES.soc_manager); navigateTo('/app/osquery'); }); + after(() => { + runKbnArchiverScript(ArchiverMethod.UNLOAD, 'ecs_mapping_1'); + }); + it('should run query and enable ecs mapping', () => { const cmd = Cypress.platform === 'darwin' ? '{meta}{enter}' : '{ctrl}{enter}'; cy.contains('New live query').click(); @@ -65,4 +74,22 @@ describe('ALL - Live Query', () => { .react('EuiIconTip', { props: { type: 'indexMapping' } }) .should('exist'); }); + + it('should run customized saved query', () => { + cy.contains('New live query').click(); + selectAllAgents(); + cy.react('SavedQueriesDropdown').type('NOMAPPING{downArrow}{enter}'); + cy.getReact('SavedQueriesDropdown').getCurrentState().should('have.length', 1); + inputQuery('{selectall}{backspace}{selectall}{backspace}select * from users'); + cy.wait(1000); + submitQuery(); + checkResults(); + navigateTo('/app/osquery'); + cy.react('EuiButtonIcon', { props: { iconType: 'play' } }) + .eq(0) + .should('be.visible') + .click(); + + cy.react('ReactAce', { props: { value: 'select * from users' } }).should('exist'); + }); }); diff --git a/x-pack/plugins/osquery/cypress/integration/all/packs.spec.ts b/x-pack/plugins/osquery/cypress/integration/all/packs.spec.ts index 917147fe88457..b765a9d16ef7e 100644 --- a/x-pack/plugins/osquery/cypress/integration/all/packs.spec.ts +++ b/x-pack/plugins/osquery/cypress/integration/all/packs.spec.ts @@ -110,7 +110,7 @@ describe('ALL - Packs', () => { `pack_${PACK_NAME}_${SAVED_QUERY_ID}` ); }); - it('by clicking in Lens button', () => { + it.skip('by clicking in Lens button', () => { let lensUrl = ''; cy.window().then((win) => { cy.stub(win, 'open') @@ -160,8 +160,8 @@ describe('ALL - Packs', () => { .contains(/^Save and deploy changes$/) .click(); cy.contains(`${PACK_NAME}`).click(); - cy.contains(`${PACK_NAME} details`); - cy.contains(/^No items found/); + cy.contains(`${PACK_NAME} details`).should('exist'); + cy.contains(/^No items found/).should('exist'); }); it('enable changing saved queries and ecs_mappings', () => { @@ -171,12 +171,13 @@ describe('ALL - Packs', () => { findAndClickButton('Add query'); getSavedQueriesDropdown().type('Multiple {downArrow} {enter}'); - cy.contains('Custom key/value pairs'); - cy.contains('Days of uptime'); - cy.contains('List of keywords used to tag each'); - cy.contains('Seconds of uptime'); - cy.contains('Client network address.'); - cy.contains('Total uptime seconds'); + cy.contains('Custom key/value pairs').should('exist'); + cy.contains('Days of uptime').should('exist'); + cy.contains('List of keywords used to tag each').should('exist'); + cy.contains('Seconds of uptime').should('exist'); + cy.contains('Client network address.').should('exist'); + cy.contains('Total uptime seconds').should('exist'); + cy.getBySel('ECSMappingEditorForm').should('have.length', 4); getSavedQueriesDropdown().type('NOMAPPING {downArrow} {enter}'); cy.contains('Custom key/value pairs').should('not.exist'); @@ -185,17 +186,19 @@ describe('ALL - Packs', () => { cy.contains('Seconds of uptime').should('not.exist'); cy.contains('Client network address.').should('not.exist'); cy.contains('Total uptime seconds').should('not.exist'); + cy.getBySel('ECSMappingEditorForm').should('have.length', 1); getSavedQueriesDropdown().type('ONE_MAPPING {downArrow} {enter}'); - cy.contains('Name of the continent'); - cy.contains('Seconds of uptime'); + cy.contains('Name of the continent').should('exist'); + cy.contains('Seconds of uptime').should('exist'); + cy.getBySel('ECSMappingEditorForm').should('have.length', 2); findAndClickButton('Save'); cy.react('CustomItemAction', { props: { index: 0, item: { id: 'ONE_MAPPING_CHANGED' } }, }).click(); - cy.contains('Name of the continent'); - cy.contains('Seconds of uptime'); + cy.contains('Name of the continent').should('exist'); + cy.contains('Seconds of uptime').should('exist'); }); it('to click delete button', () => { @@ -231,7 +234,7 @@ describe('ALL - Packs', () => { cy.getBySel('toastCloseButton').click(); cy.contains(REMOVING_PACK).click(); - cy.contains(`${REMOVING_PACK} details`); + cy.contains(`${REMOVING_PACK} details`).should('exist'); findAndClickButton('Edit'); cy.react('EuiComboBoxInput', { props: { value: AGENT_NAME } }).should('exist'); @@ -246,7 +249,7 @@ describe('ALL - Packs', () => { closeModalIfVisible(); navigateTo('app/osquery/packs'); cy.contains(REMOVING_PACK).click(); - cy.contains(`${REMOVING_PACK} details`); + cy.contains(`${REMOVING_PACK} details`).should('exist'); cy.wait(1000); findAndClickButton('Edit'); cy.react('EuiComboBoxInput', { props: { value: '' } }).should('exist'); diff --git a/x-pack/plugins/osquery/cypress/tasks/live_query.ts b/x-pack/plugins/osquery/cypress/tasks/live_query.ts index ca232d8507d2a..d43516be2bc35 100644 --- a/x-pack/plugins/osquery/cypress/tasks/live_query.ts +++ b/x-pack/plugins/osquery/cypress/tasks/live_query.ts @@ -20,6 +20,9 @@ export const selectAllAgents = () => { cy.contains('1 agent selected.'); }; +export const clearInputQuery = () => + cy.get(LIVE_QUERY_EDITOR).click().type(`{selectall}{backspace}`); + export const inputQuery = (query: string) => cy.get(LIVE_QUERY_EDITOR).type(query); export const submitQuery = () => cy.contains('Submit').click(); diff --git a/x-pack/plugins/osquery/public/agent_policies/use_agent_policy.ts b/x-pack/plugins/osquery/public/agent_policies/use_agent_policy.ts index 3e211bd683a9e..608357bd72912 100644 --- a/x-pack/plugins/osquery/public/agent_policies/use_agent_policy.ts +++ b/x-pack/plugins/osquery/public/agent_policies/use_agent_policy.ts @@ -37,8 +37,6 @@ export const useAgentPolicy = ({ policyId, skip, silent }: UseAgentPolicy) => { defaultMessage: 'Error while fetching agent policy details', }), }), - refetchOnReconnect: false, - refetchOnWindowFocus: false, } ); }; diff --git a/x-pack/plugins/osquery/public/live_queries/form/index.tsx b/x-pack/plugins/osquery/public/live_queries/form/index.tsx index 6a72a0b59979f..bba443be9569a 100644 --- a/x-pack/plugins/osquery/public/live_queries/form/index.tsx +++ b/x-pack/plugins/osquery/public/live_queries/form/index.tsx @@ -14,7 +14,6 @@ import { EuiAccordion, EuiAccordionProps, } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useMutation } from 'react-query'; @@ -23,17 +22,16 @@ import styled from 'styled-components'; import { pickBy, isEmpty, map } from 'lodash'; import { convertECSMappingToObject } from '../../../common/schemas/common/utils'; -import { UseField, Form, FormData, useForm, useFormData, FIELD_TYPES } from '../../shared_imports'; +import { UseField, Form, FormData, useForm, useFormData } from '../../shared_imports'; import { AgentsTableField } from './agents_table_field'; import { LiveQueryQueryField } from './live_query_query_field'; import { useKibana } from '../../common/lib/kibana'; import { ResultTabs } from '../../routes/saved_queries/edit/tabs'; -import { queryFieldValidation } from '../../common/validations'; -import { fieldValidators } from '../../shared_imports'; import { SavedQueryFlyout } from '../../saved_queries'; import { useErrorToast } from '../../common/hooks/use_error_toast'; import { ECSMappingEditorField } from '../../packs/queries/lazy_ecs_mapping_editor_field'; import { SavedQueriesDropdown } from '../../saved_queries/saved_queries_dropdown'; +import { liveQueryFormSchema } from './schema'; const FORM_ID = 'liveQueryForm'; @@ -44,8 +42,6 @@ const StyledEuiAccordion = styled(EuiAccordion)` } `; -export const MAX_QUERY_LENGTH = 2000; - const GhostFormField = () => <>; type FormType = 'simple' | 'steps'; @@ -100,46 +96,9 @@ const LiveQueryFormComponent: React.FC = ({ } ); - const formSchema = { - agentSelection: { - defaultValue: { - agents: [], - allAgentsSelected: false, - platformsSelected: [], - policiesSelected: [], - }, - type: FIELD_TYPES.JSON, - validations: [], - }, - savedQueryId: { - type: FIELD_TYPES.TEXT, - validations: [], - }, - query: { - type: FIELD_TYPES.TEXT, - validations: [ - { - validator: fieldValidators.maxLengthField({ - length: MAX_QUERY_LENGTH, - message: i18n.translate('xpack.osquery.liveQuery.queryForm.largeQueryError', { - defaultMessage: 'Query is too large (max {maxLength} characters)', - values: { maxLength: MAX_QUERY_LENGTH }, - }), - }), - }, - { validator: queryFieldValidation }, - ], - }, - ecs_mapping: { - defaultValue: [], - type: FIELD_TYPES.JSON, - validations: [], - }, - }; - const { form } = useForm({ id: FORM_ID, - schema: formSchema, + schema: liveQueryFormSchema, onSubmit: async (formData, isValid) => { if (isValid) { try { @@ -181,11 +140,14 @@ const LiveQueryFormComponent: React.FC = ({ const actionId = useMemo(() => data?.actions[0].action_id, [data?.actions]); const agentIds = useMemo(() => data?.actions[0].agents, [data?.actions]); - // eslint-disable-next-line @typescript-eslint/naming-convention - const [{ agentSelection, ecs_mapping, query, savedQueryId }] = useFormData({ - form, - watch: ['agentSelection', 'ecs_mapping', 'query', 'savedQueryId'], - }); + const [{ agentSelection, ecs_mapping: ecsMapping, query, savedQueryId }, formDataSerializer] = + useFormData({ + form, + }); + + /* recalculate the form data when ecs_mapping changes */ + // eslint-disable-next-line react-hooks/exhaustive-deps + const serializedFormData = useMemo(() => formDataSerializer(), [ecsMapping, formDataSerializer]); const agentSelected = useMemo( () => @@ -260,8 +222,8 @@ const LiveQueryFormComponent: React.FC = ({ ); const flyoutFormDefaultValue = useMemo( - () => ({ savedQueryId, query, ecs_mapping }), - [savedQueryId, ecs_mapping, query] + () => ({ savedQueryId, query, ecs_mapping: serializedFormData.ecs_mapping }), + [savedQueryId, serializedFormData.ecs_mapping, query] ); const handleToggle = useCallback((isOpen) => { diff --git a/x-pack/plugins/osquery/public/live_queries/form/schema.ts b/x-pack/plugins/osquery/public/live_queries/form/schema.ts new file mode 100644 index 0000000000000..8aa61bbdb1268 --- /dev/null +++ b/x-pack/plugins/osquery/public/live_queries/form/schema.ts @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const MAX_QUERY_LENGTH = 2000; + +import { i18n } from '@kbn/i18n'; +import { FIELD_TYPES } from '../../shared_imports'; +import { queryFieldValidation } from '../../common/validations'; +import { fieldValidators } from '../../shared_imports'; + +export const liveQueryFormSchema = { + agentSelection: { + defaultValue: { + agents: [], + allAgentsSelected: false, + platformsSelected: [], + policiesSelected: [], + }, + type: FIELD_TYPES.JSON, + validations: [], + }, + savedQueryId: { + type: FIELD_TYPES.TEXT, + validations: [], + }, + query: { + type: FIELD_TYPES.TEXT, + validations: [ + { + validator: fieldValidators.maxLengthField({ + length: MAX_QUERY_LENGTH, + message: i18n.translate('xpack.osquery.liveQuery.queryForm.largeQueryError', { + defaultMessage: 'Query is too large (max {maxLength} characters)', + values: { maxLength: MAX_QUERY_LENGTH }, + }), + }), + }, + { validator: queryFieldValidation }, + ], + }, + ecs_mapping: { + defaultValue: [], + type: FIELD_TYPES.JSON, + }, +}; diff --git a/x-pack/plugins/osquery/public/packs/queries/ecs_mapping_editor_field.tsx b/x-pack/plugins/osquery/public/packs/queries/ecs_mapping_editor_field.tsx index a9c663051e273..a70013ad5d107 100644 --- a/x-pack/plugins/osquery/public/packs/queries/ecs_mapping_editor_field.tsx +++ b/x-pack/plugins/osquery/public/packs/queries/ecs_mapping_editor_field.tsx @@ -136,7 +136,7 @@ const ECSFieldWrapper = styled(EuiFlexItem)` max-width: 100%; `; -const singleSelection = { asPlainText: true }; +const SINGLE_SELECTION = { asPlainText: true }; const ECSSchemaOptions = ECSSchema.map((ecs) => ({ label: ecs.field, @@ -162,6 +162,7 @@ const ECSComboboxFieldComponent: React.FC = ({ ); const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage(field); const describedByIds = useMemo(() => (idAria ? [idAria] : []), [idAria]); + const [formData] = useFormData(); const handleChange = useCallback( (newSelectedOptions) => { @@ -229,6 +230,12 @@ const ECSComboboxFieldComponent: React.FC = ({ return text; }, [selectedOptions]); + const availableECSSchemaOptions = useMemo(() => { + const currentFormECSFieldValues = map(formData.ecs_mapping, 'key'); + + return ECSSchemaOptions.filter(({ label }) => !currentFormECSFieldValues.includes(label)); + }, [formData.ecs_mapping]); + useEffect(() => { // @ts-expect-error update types setSelected(() => { @@ -236,7 +243,16 @@ const ECSComboboxFieldComponent: React.FC = ({ const selectedOption = find(ECSSchemaOptions, ['label', field.value]); - return selectedOption ? [selectedOption] : []; + return selectedOption + ? [selectedOption] + : [ + { + label: field.value, + value: { + value: field.value, + }, + }, + ]; }); }, [field.value]); @@ -253,9 +269,9 @@ const ECSComboboxFieldComponent: React.FC = ({ ; resultValue: FieldHook; euiFieldProps: EuiComboBoxProps; + item: ArrayItem; idAria?: string; } @@ -325,6 +342,7 @@ const OsqueryColumnFieldComponent: React.FC = ({ resultValue, euiFieldProps = {}, idAria, + item, }) => { const inputRef = useRef(); const { setValue } = resultValue; @@ -334,6 +352,7 @@ const OsqueryColumnFieldComponent: React.FC = ({ const [selectedOptions, setSelected] = useState< Array> >([]); + const [formData] = useFormData(); const renderOsqueryOption = useCallback( (option, searchValue, contentClassName) => ( @@ -370,14 +389,25 @@ const OsqueryColumnFieldComponent: React.FC = ({ [setValue, setSelected] ); + const isSingleSelection = useMemo(() => { + const ecsKey = get(formData, item.path)?.key; + if (ecsKey?.length && typeValue === 'value') { + const ecsKeySchemaOption = find(ECSSchemaOptions, ['label', ecsKey]); + + return ecsKeySchemaOption?.value?.normalization !== 'array'; + } + + return true; + }, [typeValue, formData, item.path]); + const onTypeChange = useCallback( (newType) => { if (newType !== typeValue) { setType(newType); - setValue(newType === 'value' && euiFieldProps.singleSelection === false ? [] : ''); + setValue(newType === 'value' && isSingleSelection === false ? [] : ''); } }, - [typeValue, setType, setValue, euiFieldProps.singleSelection] + [typeValue, setType, setValue, isSingleSelection] ); const handleCreateOption = useCallback( @@ -386,7 +416,7 @@ const OsqueryColumnFieldComponent: React.FC = ({ if (!trimmedNewOption.length) return; - if (euiFieldProps.singleSelection === false) { + if (isSingleSelection === false) { setValue([trimmedNewOption]); if (resultValue.value.length) { setValue([...castArray(resultValue.value), trimmedNewOption]); @@ -399,7 +429,7 @@ const OsqueryColumnFieldComponent: React.FC = ({ setValue(trimmedNewOption); } }, - [euiFieldProps.singleSelection, resultValue.value, setValue] + [isSingleSelection, resultValue.value, setValue] ); const Prepend = useMemo( @@ -421,14 +451,14 @@ const OsqueryColumnFieldComponent: React.FC = ({ ); useEffect(() => { - if (euiFieldProps?.singleSelection && isArray(resultValue.value)) { + if (isSingleSelection && isArray(resultValue.value)) { setValue(resultValue.value.join(' ')); } - if (!euiFieldProps?.singleSelection && !isArray(resultValue.value)) { + if (!isSingleSelection && !isArray(resultValue.value)) { setValue(resultValue.value.length ? [resultValue.value] : []); } - }, [euiFieldProps?.singleSelection, resultValue.value, setValue]); + }, [isSingleSelection, resultValue.value, setValue]); useEffect(() => { setSelected(() => { @@ -471,6 +501,7 @@ const OsqueryColumnFieldComponent: React.FC = ({ rowHeight={32} isClearable {...euiFieldProps} + singleSelection={isSingleSelection ? SINGLE_SELECTION : false} options={(typeValue === 'field' && euiFieldProps.options) || EMPTY_ARRAY} /> @@ -566,7 +597,6 @@ const osqueryResultFieldValidator = async ( }, } ), - __isBlocking__: false, } : undefined; }; @@ -586,8 +616,6 @@ export const ECSMappingEditorForm: React.FC = ({ isLastItem, onDelete, }) => { - const multipleValuesField = useRef(false); - const MultiFields = useMemo( () => ( = ({ {(fields) => ( )} ), - [item.path, osquerySchemaOptions, isLastItem, isDisabled] + [item, osquerySchemaOptions, isLastItem, isDisabled] ); const ecsComboBoxEuiFieldProps = useMemo(() => ({ isDisabled }), [isDisabled]); @@ -738,7 +765,7 @@ export const ECSMappingEditorField = React.memo( ({ euiFieldProps }: ECSMappingEditorFieldProps) => { const lastItemPath = useRef(); const onAdd = useRef(); - const osquerySchemaOptions = useRef([]); + const [osquerySchemaOptions, setOsquerySchemaOptions] = useState([]); const [{ query, ...formData }, formDataSerializer, isMounted] = useFormData(); useEffect(() => { @@ -917,10 +944,13 @@ export const ECSMappingEditorField = React.memo( .flat(); // Remove column duplicates by keeping the column from the table that appears last in the query - osquerySchemaOptions.current = sortedUniqBy( + const newOptions = sortedUniqBy( orderBy(suggestions, ['value.suggestion_label', 'value.tableOrder'], ['asc', 'desc']), 'label' ); + setOsquerySchemaOptions((prevValue) => + !deepEqual(prevValue, newOptions) ? newOptions : prevValue + ); }, [query]); useLayoutEffect(() => { @@ -999,7 +1029,7 @@ export const ECSMappingEditorField = React.memo( {items.map((item, index) => ( ) => ({ ecs_mapping: { defaultValue: [], type: FIELD_TYPES.JSON, - validations: [], }, }); diff --git a/x-pack/plugins/osquery/public/saved_queries/form/playground_flyout.tsx b/x-pack/plugins/osquery/public/saved_queries/form/playground_flyout.tsx index b5af2652fd110..a253fada318a0 100644 --- a/x-pack/plugins/osquery/public/saved_queries/form/playground_flyout.tsx +++ b/x-pack/plugins/osquery/public/saved_queries/form/playground_flyout.tsx @@ -26,9 +26,7 @@ interface PlaygroundFlyoutProps { } const PlaygroundFlyoutComponent: React.FC = ({ enabled, onClose }) => { - const [{ query, ecs_mapping: ecsMapping, id }, formDataSerializer] = useFormData({ - watch: ['query', 'ecs_mapping', 'savedQueryId'], - }); + const [{ query, ecs_mapping: ecsMapping, id }, formDataSerializer] = useFormData(); /* recalculate the form data when ecs_mapping changes */ // eslint-disable-next-line react-hooks/exhaustive-deps diff --git a/x-pack/plugins/osquery/public/saved_queries/saved_queries_dropdown.tsx b/x-pack/plugins/osquery/public/saved_queries/saved_queries_dropdown.tsx index eec949fbe312b..784a2375ad1a6 100644 --- a/x-pack/plugins/osquery/public/saved_queries/saved_queries_dropdown.tsx +++ b/x-pack/plugins/osquery/public/saved_queries/saved_queries_dropdown.tsx @@ -40,11 +40,22 @@ interface SavedQueriesDropdownProps { ) => void; } +interface SelectedOption { + label: string; + value: { + savedQueryId: string; + id: string; + description: string; + query: string; + ecs_mapping: Record; + }; +} + const SavedQueriesDropdownComponent: React.FC = ({ disabled, onChange, }) => { - const [selectedOptions, setSelectedOptions] = useState([]); + const [selectedOptions, setSelectedOptions] = useState([]); const [{ savedQueryId }] = useFormData(); @@ -109,17 +120,13 @@ const SavedQueriesDropdownComponent: React.FC = ({ const savedQueryOption = find(['value.savedQueryId', savedQueryId], queryOptions); if (savedQueryOption) { - handleSavedQueryChange([savedQueryOption]); + setSelectedOptions([savedQueryOption]); } } - }, [savedQueryId, handleSavedQueryChange, queryOptions]); + }, [savedQueryId, queryOptions]); useEffect(() => { - if ( - selectedOptions.length && - // @ts-expect-error update types - selectedOptions[0].value.savedQueryId !== savedQueryId - ) { + if (selectedOptions.length && selectedOptions[0].value.savedQueryId !== savedQueryId) { setSelectedOptions([]); } }, [savedQueryId, selectedOptions]); @@ -152,4 +159,6 @@ const SavedQueriesDropdownComponent: React.FC = ({ ); }; +SavedQueriesDropdownComponent.displayName = 'SavedQueriesDropdown'; + export const SavedQueriesDropdown = React.memo(SavedQueriesDropdownComponent);