From 97e10487101145e9e7ccc6023c4ab1a02cd4e38b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Efe=20G=C3=BCrkan=20YALAMAN?= Date: Wed, 6 Mar 2024 19:31:07 +0100 Subject: [PATCH] [Search] Fix connector recheck and polling related issues (#178148) ## Summary Updates data fetching to rely on purely connector polling rather than index polling. This fixes a bunch of bugs related to data not being up to date. It also fixes two small UI issues. ### Checklist Delete any items that are not applicable to this PR. - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed --- .../cached_fetch_connector_by_id_api_logic.ts | 152 ++++++++++++++++++ .../connector_detail/attach_index_box.tsx | 7 - .../connector_configuration.tsx | 13 +- .../connector_detail/connector_detail.tsx | 44 ++--- .../connector_detail_router.tsx | 8 - .../connector_detail/connector_view_logic.ts | 31 ++-- 6 files changed, 200 insertions(+), 55 deletions(-) create mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/cached_fetch_connector_by_id_api_logic.ts diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/cached_fetch_connector_by_id_api_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/cached_fetch_connector_by_id_api_logic.ts new file mode 100644 index 0000000000000..05475ba553995 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/cached_fetch_connector_by_id_api_logic.ts @@ -0,0 +1,152 @@ +/* + * 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. + */ + +import { kea, MakeLogicType } from 'kea'; + +import { isEqual } from 'lodash'; + +import { Connector } from '@kbn/search-connectors'; + +import { Status } from '../../../../../common/types/api'; + +import { Actions } from '../../../shared/api_logic/create_api_logic'; + +import { + FetchConnectorByIdApiLogic, + FetchConnectorByIdApiLogicArgs, + FetchConnectorByIdApiLogicResponse, +} from './fetch_connector_by_id_logic'; + +const FETCH_CONNECTOR_POLLING_DURATION = 5000; // 5 seconds +const FETCH_CONNECTOR_POLLING_DURATION_ON_FAILURE = 30000; // 30 seconds + +export interface CachedFetchConnectorByIdApiLogicActions { + apiError: Actions['apiError']; + apiReset: Actions['apiReset']; + apiSuccess: Actions< + FetchConnectorByIdApiLogicArgs, + FetchConnectorByIdApiLogicResponse + >['apiSuccess']; + clearPollTimeout(): void; + createPollTimeout(duration: number): { duration: number }; + makeRequest: Actions< + FetchConnectorByIdApiLogicArgs, + FetchConnectorByIdApiLogicResponse + >['makeRequest']; + setTimeoutId(id: NodeJS.Timeout): { id: NodeJS.Timeout }; + startPolling(connectorId: string): { connectorId: string }; + stopPolling(): void; +} +export interface CachedFetchConnectorByIdApiLogicValues { + connectorData: Connector | null; + connectorId: string; + fetchConnectorByIdApiData: FetchConnectorByIdApiLogicResponse; + isInitialLoading: boolean; + isLoading: boolean; + pollTimeoutId: NodeJS.Timeout | null; + status: Status; +} + +export const CachedFetchConnectorByIdApiLogic = kea< + MakeLogicType +>({ + actions: { + clearPollTimeout: true, + createPollTimeout: (duration) => ({ duration }), + setTimeoutId: (id) => ({ id }), + startPolling: (connectorId) => ({ connectorId }), + stopPolling: true, + }, + connect: { + actions: [FetchConnectorByIdApiLogic, ['apiSuccess', 'apiError', 'apiReset', 'makeRequest']], + values: [FetchConnectorByIdApiLogic, ['data as fetchConnectorByIdApiData', 'status']], + }, + events: ({ values }) => ({ + beforeUnmount: () => { + if (values.pollTimeoutId) { + clearTimeout(values.pollTimeoutId); + } + }, + }), + listeners: ({ actions, values }) => ({ + apiError: () => { + if (values.pollTimeoutId) { + actions.createPollTimeout(FETCH_CONNECTOR_POLLING_DURATION_ON_FAILURE); + } + }, + apiSuccess: () => { + if (values.pollTimeoutId) { + actions.createPollTimeout(FETCH_CONNECTOR_POLLING_DURATION); + } + }, + createPollTimeout: ({ duration }) => { + if (values.pollTimeoutId) { + clearTimeout(values.pollTimeoutId); + } + + const timeoutId = setTimeout(() => { + actions.makeRequest({ connectorId: values.connectorId }); + }, duration); + actions.setTimeoutId(timeoutId); + }, + startPolling: ({ connectorId }) => { + // Recurring polls are created by apiSuccess and apiError, depending on pollTimeoutId + if (values.pollTimeoutId) { + if (connectorId === values.connectorId) return; + clearTimeout(values.pollTimeoutId); + } + actions.makeRequest({ connectorId }); + + actions.createPollTimeout(FETCH_CONNECTOR_POLLING_DURATION); + }, + stopPolling: () => { + if (values.pollTimeoutId) { + clearTimeout(values.pollTimeoutId); + } + actions.clearPollTimeout(); + }, + }), + path: ['enterprise_search', 'content', 'api', 'fetch_connector_by_id_api_wrapper'], + reducers: { + connectorData: [ + null, + { + apiReset: () => null, + apiSuccess: (currentState, newConnectorData) => { + return isEqual(currentState, newConnectorData.connector) + ? currentState + : newConnectorData.connector ?? null; + }, + }, + ], + connectorId: [ + '', + { + apiReset: () => '', + startPolling: (_, { connectorId }) => connectorId, + }, + ], + pollTimeoutId: [ + null, + { + clearPollTimeout: () => null, + setTimeoutId: (_, { id }) => id, + }, + ], + }, + selectors: ({ selectors }) => ({ + isInitialLoading: [ + () => [selectors.status, selectors.connectorData], + ( + status: CachedFetchConnectorByIdApiLogicValues['status'], + connectorData: CachedFetchConnectorByIdApiLogicValues['connectorData'] + ) => { + return status === Status.IDLE || (connectorData === null && status === Status.LOADING); + }, + ], + }), +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/attach_index_box.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/attach_index_box.tsx index 1310484a1d4dc..b65356d683968 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/attach_index_box.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/attach_index_box.tsx @@ -15,7 +15,6 @@ import { EuiFlexGroup, EuiFlexItem, EuiFormRow, - EuiLink, EuiPanel, EuiSpacer, EuiText, @@ -101,12 +100,6 @@ export const AttachIndexBox: React.FC = ({ connector }) => /> - - {i18n.translate('xpack.enterpriseSearch.attachIndexBox.learnMoreAboutIndicesLinkLabel', { - defaultMessage: 'Learn more about indices', - })} - - { const { data: apiKeyData } = useValues(GenerateConnectorApiKeyApiLogic); - const { index, recheckIndexLoading, connector } = useValues(ConnectorViewLogic); - const { indexName } = useValues(IndexNameLogic); - const { recheckIndex } = useActions(IndexViewLogic); + const { index, isLoading, connector } = useValues(ConnectorViewLogic); const cloudContext = useCloudDetails(); const { hasPlatinumLicense } = useValues(LicensingLogic); const { status } = useValues(ConnectorConfigurationApiLogic); const { makeRequest } = useActions(ConnectorConfigurationApiLogic); const { http } = useValues(HttpLogic); + const { fetchConnector } = useActions(ConnectorViewLogic); if (!connector) { return <>; } + const indexName = connector.index_name ?? ''; // TODO make it work without index if possible if (connector.is_native && connector.service_type) { @@ -227,10 +225,11 @@ export const ConnectorConfiguration: React.FC = () => { )} recheckIndex()} - isLoading={recheckIndexLoading} + onClick={() => fetchConnector({ connectorId: connector.id })} + isLoading={isLoading} > {i18n.translate( 'xpack.enterpriseSearch.content.connector_detail.configurationConnector.connectorPackage.waitingForConnector.button.label', diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/connector_detail.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/connector_detail.tsx index 381af9a3d0b74..7e161253af076 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/connector_detail.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/connector_detail.tsx @@ -45,9 +45,9 @@ export enum ConnectorDetailTabId { export const ConnectorDetail: React.FC = () => { const connectorId = decodeURIComponent(useParams<{ connectorId: string }>().connectorId); const { hasFilteringFeature, isLoading, index, connector } = useValues(ConnectorViewLogic); - const { fetchConnector } = useActions(ConnectorViewLogic); + const { startConnectorPoll } = useActions(ConnectorViewLogic); useEffect(() => { - fetchConnector({ connectorId }); + startConnectorPoll(connectorId); }, []); const { tabId = ConnectorDetailTabId.OVERVIEW } = useParams<{ @@ -119,24 +119,6 @@ export const ConnectorDetail: React.FC = () => { ]; const CONNECTOR_TABS = [ - { - content: , - id: ConnectorDetailTabId.CONFIGURATION, - isSelected: tabId === ConnectorDetailTabId.CONFIGURATION, - label: i18n.translate( - 'xpack.enterpriseSearch.content.connectors.connectorDetail.configurationTabLabel', - { - defaultMessage: 'Configuration', - } - ), - onClick: () => - KibanaLogic.values.navigateToUrl( - generateEncodedPath(CONNECTOR_DETAIL_TAB_PATH, { - connectorId, - tabId: ConnectorDetailTabId.CONFIGURATION, - }) - ), - }, ...(hasFilteringFeature ? [ { @@ -181,6 +163,27 @@ export const ConnectorDetail: React.FC = () => { }, ]; + const CONFIG_TAB = [ + { + content: , + id: ConnectorDetailTabId.CONFIGURATION, + isSelected: tabId === ConnectorDetailTabId.CONFIGURATION, + label: i18n.translate( + 'xpack.enterpriseSearch.content.connectors.connectorDetail.configurationTabLabel', + { + defaultMessage: 'Configuration', + } + ), + onClick: () => + KibanaLogic.values.navigateToUrl( + generateEncodedPath(CONNECTOR_DETAIL_TAB_PATH, { + connectorId, + tabId: ConnectorDetailTabId.CONFIGURATION, + }) + ), + }, + ]; + const PIPELINES_TAB = { content: , disabled: !index, @@ -216,6 +219,7 @@ export const ConnectorDetail: React.FC = () => { ...ALL_INDICES_TABS, ...CONNECTOR_TABS, ...(hasDefaultIngestPipeline ? [PIPELINES_TAB] : []), + ...CONFIG_TAB, ]; const selectedTab = tabs.find((tab) => tab.id === tabId); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/connector_detail_router.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/connector_detail_router.tsx index 539d549f2bd51..a2932f93a8973 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/connector_detail_router.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/connector_detail_router.tsx @@ -7,30 +7,22 @@ import React, { useEffect } from 'react'; -import { useActions } from 'kea'; - import { Routes, Route } from '@kbn/shared-ux-router'; import { CONNECTOR_DETAIL_PATH, CONNECTOR_DETAIL_TAB_PATH } from '../../routes'; import { IndexNameLogic } from '../search_index/index_name_logic'; -import { IndexViewLogic } from '../search_index/index_view_logic'; - import { ConnectorDetail } from './connector_detail'; import { ConnectorViewLogic } from './connector_view_logic'; export const ConnectorDetailRouter: React.FC = () => { - const { stopFetchIndexPoll } = useActions(IndexViewLogic); useEffect(() => { const unmountName = IndexNameLogic.mount(); const unmountView = ConnectorViewLogic.mount(); - const unmountIndexView = IndexViewLogic.mount(); return () => { - stopFetchIndexPoll(); unmountName(); unmountView(); - unmountIndexView(); }; }, []); return ( diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/connector_view_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/connector_view_logic.ts index 27bf20dd56d40..d54eec09605e6 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/connector_view_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/connector_view_logic.ts @@ -18,19 +18,22 @@ import { import { Status } from '../../../../../common/types/api'; import { - FetchConnectorByIdApiLogic, - FetchConnectorByIdApiLogicActions, -} from '../../api/connector/fetch_connector_by_id_logic'; + CachedFetchConnectorByIdApiLogic, + CachedFetchConnectorByIdApiLogicActions, + CachedFetchConnectorByIdApiLogicValues, +} from '../../api/connector/cached_fetch_connector_by_id_api_logic'; import { FetchIndexActions, FetchIndexApiLogic } from '../../api/index/fetch_index_api_logic'; import { ElasticsearchViewIndex } from '../../types'; import { IndexNameActions, IndexNameLogic } from '../search_index/index_name_logic'; export interface ConnectorViewActions { - fetchConnector: FetchConnectorByIdApiLogicActions['makeRequest']; - fetchConnectorApiError: FetchConnectorByIdApiLogicActions['apiError']; - fetchConnectorApiReset: FetchConnectorByIdApiLogicActions['apiReset']; - fetchConnectorApiSuccess: FetchConnectorByIdApiLogicActions['apiSuccess']; + fetchConnector: CachedFetchConnectorByIdApiLogicActions['makeRequest']; + fetchConnectorApiError: CachedFetchConnectorByIdApiLogicActions['apiError']; + fetchConnectorApiReset: CachedFetchConnectorByIdApiLogicActions['apiReset']; + fetchConnectorApiSuccess: CachedFetchConnectorByIdApiLogicActions['apiSuccess']; + startConnectorPoll: CachedFetchConnectorByIdApiLogicActions['startPolling']; + stopConnectorPoll: CachedFetchConnectorByIdApiLogicActions['stopPolling']; fetchIndex: FetchIndexActions['makeRequest']; fetchIndexApiError: FetchIndexActions['apiError']; fetchIndexApiReset: FetchIndexActions['apiReset']; @@ -40,7 +43,7 @@ export interface ConnectorViewActions { export interface ConnectorViewValues { connector: Connector | undefined; - connectorData: typeof FetchConnectorByIdApiLogic.values.data; + connectorData: CachedFetchConnectorByIdApiLogicValues['connectorData']; connectorError: string | undefined; connectorId: string | null; connectorName: string | null; @@ -74,12 +77,14 @@ export const ConnectorViewLogic = kea { actions.fetchConnectorApiReset(); actions.fetchIndexApiReset(); + actions.stopConnectorPoll(); }, }), listeners: ({ actions, values }) => ({ fetchConnectorApiSuccess: () => { if (values.indexName) { actions.fetchIndex({ indexName: values.indexName }); - actions.setIndexName(values.indexName); } }, }), @@ -119,7 +124,7 @@ export const ConnectorViewLogic = kea [selectors.connectorData], (connectorData) => { - return connectorData?.connector; + return connectorData; }, ], connectorError: [