Skip to content

Commit

Permalink
[Search] Fix connector recheck and polling related issues (#178148)
Browse files Browse the repository at this point in the history
## 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
  • Loading branch information
efegurkan authored Mar 6, 2024
1 parent 603b5f1 commit 97e1048
Show file tree
Hide file tree
Showing 6 changed files with 200 additions and 55 deletions.
Original file line number Diff line number Diff line change
@@ -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<FetchConnectorByIdApiLogicArgs, FetchConnectorByIdApiLogicResponse>['apiError'];
apiReset: Actions<FetchConnectorByIdApiLogicArgs, FetchConnectorByIdApiLogicResponse>['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<CachedFetchConnectorByIdApiLogicValues, CachedFetchConnectorByIdApiLogicActions>
>({
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);
},
],
}),
});
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import {
EuiFlexGroup,
EuiFlexItem,
EuiFormRow,
EuiLink,
EuiPanel,
EuiSpacer,
EuiText,
Expand Down Expand Up @@ -101,12 +100,6 @@ export const AttachIndexBox: React.FC<AttachIndexBoxProps> = ({ connector }) =>
/>
</EuiText>
<EuiSpacer />
<EuiLink>
{i18n.translate('xpack.enterpriseSearch.attachIndexBox.learnMoreAboutIndicesLinkLabel', {
defaultMessage: 'Learn more about indices',
})}
</EuiLink>
<EuiSpacer />
<EuiFlexGroup>
<EuiFlexItem>
<EuiFormRow
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,6 @@ import {
CONNECTORS,
getConnectorTemplate,
} from '../search_index/connector/constants';
import { IndexNameLogic } from '../search_index/index_name_logic';
import { IndexViewLogic } from '../search_index/index_view_logic';

import { AttachIndexBox } from './attach_index_box';
import { ConnectorDetailTabId } from './connector_detail';
Expand All @@ -56,18 +54,18 @@ import { NativeConnectorConfiguration } from './native_connector_configuration';

export const ConnectorConfiguration: React.FC = () => {
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) {
Expand Down Expand Up @@ -227,10 +225,11 @@ export const ConnectorConfiguration: React.FC = () => {
)}
<EuiSpacer size="s" />
<EuiButton
disabled={!index}
data-telemetry-id="entSearchContent-connector-configuration-recheckNow"
iconType="refresh"
onClick={() => recheckIndex()}
isLoading={recheckIndexLoading}
onClick={() => fetchConnector({ connectorId: connector.id })}
isLoading={isLoading}
>
{i18n.translate(
'xpack.enterpriseSearch.content.connector_detail.configurationConnector.connectorPackage.waitingForConnector.button.label',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<{
Expand Down Expand Up @@ -119,24 +119,6 @@ export const ConnectorDetail: React.FC = () => {
];

const CONNECTOR_TABS = [
{
content: <ConnectorConfiguration />,
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
? [
{
Expand Down Expand Up @@ -181,6 +163,27 @@ export const ConnectorDetail: React.FC = () => {
},
];

const CONFIG_TAB = [
{
content: <ConnectorConfiguration />,
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: <SearchIndexPipelines />,
disabled: !index,
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down
Loading

0 comments on commit 97e1048

Please sign in to comment.