Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Enterprise Search] Fixes Search Index page to go blank when connection lost #144022

Merged
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
/*
* 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 { Status } from '../../../../../common/types/api';
import { ElasticsearchIndexWithIngestion } from '../../../../../common/types/indices';

import { Actions } from '../../../shared/api_logic/create_api_logic';

import {
FetchIndexApiParams,
FetchIndexApiLogic,
FetchIndexApiResponse,
} from './fetch_index_api_logic';

const FETCH_INDEX_POLLING_DURATION = 5000; // 5 seconds
const FETCH_INDEX_POLLING_DURATION_ON_FAILURE = 30000; // 30 seconds

export interface CachedFetchIndexApiLogicActions {
apiError: Actions<FetchIndexApiParams, FetchIndexApiResponse>['apiError'];
apiReset: Actions<FetchIndexApiParams, FetchIndexApiResponse>['apiReset'];
apiSuccess: Actions<FetchIndexApiParams, FetchIndexApiResponse>['apiSuccess'];
clearPollTimeout(): void;
createPollTimeout(duration: number): { duration: number };
makeRequest: Actions<FetchIndexApiParams, FetchIndexApiResponse>['makeRequest'];
setTimeoutId(id: NodeJS.Timeout): { id: NodeJS.Timeout };
startPolling(indexName: string): { indexName: string };
stopPolling(): void;
}
export interface CachedFetchIndexApiLogicValues {
fetchIndexApiData: FetchIndexApiResponse;
indexData: ElasticsearchIndexWithIngestion | null;
indexName: string;
isInitialLoading: boolean;
isLoading: boolean;
pollTimeoutId: NodeJS.Timeout | null;
status: Status;
}

export const CachedFetchIndexApiLogic = kea<
efegurkan marked this conversation as resolved.
Show resolved Hide resolved
MakeLogicType<CachedFetchIndexApiLogicValues, CachedFetchIndexApiLogicActions>
>({
actions: {
clearPollTimeout: true,
createPollTimeout: (duration) => ({ duration }),
setTimeoutId: (id) => ({ id }),
startPolling: (indexName) => ({ indexName }),
stopPolling: true,
},
connect: {
actions: [FetchIndexApiLogic, ['apiSuccess', 'apiError', 'apiReset', 'makeRequest']],
values: [FetchIndexApiLogic, ['data as fetchIndexApiData', 'status']],
},
events: ({ values }) => ({
beforeUnmount: () => {
if (values.pollTimeoutId) {
clearTimeout(values.pollTimeoutId);
}
},
}),
listeners: ({ actions, values }) => ({
apiError: () => {
if (values.pollTimeoutId) {
actions.createPollTimeout(FETCH_INDEX_POLLING_DURATION_ON_FAILURE);
}
},
apiSuccess: () => {
if (values.pollTimeoutId) {
actions.createPollTimeout(FETCH_INDEX_POLLING_DURATION);
}
},
createPollTimeout: ({ duration }) => {
if (values.pollTimeoutId) {
clearTimeout(values.pollTimeoutId);
}

const timeoutId = setTimeout(() => {
actions.makeRequest({ indexName: values.indexName });
}, duration);
actions.setTimeoutId(timeoutId);
},
startPolling: ({ indexName }) => {
// Recurring polls are created by apiSuccess and apiError, depending on pollTimeoutId
if (values.pollTimeoutId) {
if (indexName === values.indexName) return;
clearTimeout(values.pollTimeoutId);
}
actions.makeRequest({ indexName });

actions.createPollTimeout(FETCH_INDEX_POLLING_DURATION);
},
stopPolling: () => {
if (values.pollTimeoutId) {
clearTimeout(values.pollTimeoutId);
}
actions.clearPollTimeout();
},
}),
path: ['enterprise_search', 'content', 'api', 'fetch_index_api_wrapper'],
reducers: {
indexData: [
null,
{
apiReset: () => null,
apiSuccess: (currentState, newIndexData) => {
return isEqual(currentState, newIndexData) ? currentState : newIndexData;
},
},
],
indexName: [
'',
{
apiReset: () => '',
startPolling: (_, { indexName }) => indexName,
},
],
pollTimeoutId: [
null,
{
clearPollTimeout: () => null,
setTimeoutId: (_, { id }) => id,
},
],
},
selectors: ({ selectors }) => ({
isInitialLoading: [
() => [selectors.status, selectors.indexData],
(
status: CachedFetchIndexApiLogicValues['status'],
indexData: CachedFetchIndexApiLogicValues['indexData']
) => {
return status === Status.IDLE || (indexData === null && status === Status.LOADING);
},
],
}),
});
Original file line number Diff line number Diff line change
Expand Up @@ -47,18 +47,18 @@ import { NativeConnectorConfiguration } from './native_connector_configuration/n

export const ConnectorConfiguration: React.FC = () => {
const { data: apiKeyData } = useValues(GenerateConnectorApiKeyApiLogic);
const { index: indexData, recheckIndexLoading } = useValues(IndexViewLogic);
const { index, recheckIndexLoading } = useValues(IndexViewLogic);
const { indexName } = useValues(IndexNameLogic);
const { recheckIndex } = useActions(IndexViewLogic);
if (!isConnectorIndex(indexData)) {
if (!isConnectorIndex(index)) {
return <></>;
}

if (indexData.connector.is_native && indexData.connector.service_type) {
if (index.connector.is_native && index.connector.service_type) {
return <NativeConnectorConfiguration />;
}

const hasApiKey = !!(indexData.connector.api_key_id ?? apiKeyData);
const hasApiKey = !!(index.connector.api_key_id ?? apiKeyData);

return (
<>
Expand All @@ -70,10 +70,7 @@ export const ConnectorConfiguration: React.FC = () => {
steps={[
{
children: (
<ApiKeyConfig
indexName={indexName}
hasApiKey={!!indexData.connector.api_key_id}
/>
<ApiKeyConfig indexName={indexName} hasApiKey={!!index.connector.api_key_id} />
),
status: hasApiKey ? 'complete' : 'incomplete',
title: i18n.translate(
Expand All @@ -86,7 +83,7 @@ export const ConnectorConfiguration: React.FC = () => {
},
{
children: <ConnectorNameAndDescription />,
status: indexData.connector.description ? 'complete' : 'incomplete',
status: index.connector.description ? 'complete' : 'incomplete',
title: i18n.translate(
'xpack.enterpriseSearch.content.indices.configurationConnector.steps.nameAndDescriptionTitle',
{
Expand Down Expand Up @@ -165,7 +162,7 @@ export const ConnectorConfiguration: React.FC = () => {
api_key: "${apiKeyData?.encoded}"
`
: ''
}connector_id: "${indexData.connector.id}"
}connector_id: "${index.connector.id}"
`}
</EuiCodeBlock>
<EuiSpacer />
Expand All @@ -181,8 +178,7 @@ export const ConnectorConfiguration: React.FC = () => {
</>
),
status:
!indexData.connector.status ||
indexData.connector.status === ConnectorStatus.CREATED
!index.connector.status || index.connector.status === ConnectorStatus.CREATED
? 'incomplete'
: 'complete',
title: i18n.translate(
Expand All @@ -196,8 +192,8 @@ export const ConnectorConfiguration: React.FC = () => {
{
children: (
<ConnectorConfigurationConfig>
{!indexData.connector.status ||
indexData.connector.status === ConnectorStatus.CREATED ? (
{!index.connector.status ||
index.connector.status === ConnectorStatus.CREATED ? (
<EuiCallOut
title={i18n.translate(
'xpack.enterpriseSearch.content.indices.configurationConnector.connectorPackage.waitingForConnectorTitle',
Expand Down Expand Up @@ -238,15 +234,15 @@ export const ConnectorConfiguration: React.FC = () => {
{
defaultMessage:
'Your connector {name} has connected to Enterprise Search successfully.',
values: { name: indexData.connector.name },
values: { name: index.connector.name },
}
)}
/>
)}
</ConnectorConfigurationConfig>
),
status:
indexData.connector.status === ConnectorStatus.CONNECTED
index.connector.status === ConnectorStatus.CONNECTED
? 'complete'
: 'incomplete',
title: i18n.translate(
Expand Down Expand Up @@ -293,7 +289,7 @@ export const ConnectorConfiguration: React.FC = () => {
</EuiFlexItem>
</EuiFlexGroup>
),
status: indexData.connector.scheduling.enabled ? 'complete' : 'incomplete',
status: index.connector.scheduling.enabled ? 'complete' : 'incomplete',
title: i18n.translate(
'xpack.enterpriseSearch.content.indices.configurationConnector.steps.schedule.title',
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { LogicMounter, mockFlashMessageHelpers } from '../../../../__mocks__/kea
import { connectorIndex } from '../../../__mocks__/view_index.mock';

import { ConnectorConfigurationApiLogic } from '../../../api/connector/update_connector_configuration_api_logic';
import { FetchIndexApiLogic } from '../../../api/index/fetch_index_api_logic';
import { CachedFetchIndexApiLogic } from '../../../api/index/cached_fetch_index_api_logic';

import { IndexNameLogic } from '../index_name_logic';
import { IndexViewLogic } from '../index_view_logic';
Expand All @@ -19,7 +19,7 @@ import { ConnectorConfigurationLogic } from './connector_configuration_logic';
const DEFAULT_VALUES = {
configState: {},
configView: [],
index: undefined,
index: null,
isEditing: false,
localConfigState: {},
localConfigView: [],
Expand All @@ -28,14 +28,14 @@ const DEFAULT_VALUES = {
describe('ConnectorConfigurationLogic', () => {
const { mount } = new LogicMounter(ConnectorConfigurationLogic);
const { mount: mountIndexNameLogic } = new LogicMounter(IndexNameLogic);
const { mount: mountFetchIndexApiLogic } = new LogicMounter(FetchIndexApiLogic);
const { mount: mountFetchIndexApiWrapperLogic } = new LogicMounter(CachedFetchIndexApiLogic);
const { mount: mountIndexViewLogic } = new LogicMounter(IndexViewLogic);
const { clearFlashMessages, flashAPIErrors, flashSuccessToast } = mockFlashMessageHelpers;

beforeEach(() => {
jest.clearAllMocks();
mountIndexNameLogic({ indexName: 'index-name' }, { indexName: 'index-name' });
mountFetchIndexApiLogic();
mountFetchIndexApiWrapperLogic();
mountIndexViewLogic({ index: 'index' });
mount();
});
Expand Down Expand Up @@ -110,16 +110,16 @@ describe('ConnectorConfigurationLogic', () => {
expect(ConnectorConfigurationLogic.values).toEqual({
...DEFAULT_VALUES,
configState: {
foo: { label: 'thirdBar', value: 'fourthBar' },
bar: { label: 'foo', value: 'foofoo' },
foo: { label: 'thirdBar', value: 'fourthBar' },
},
configView: [
{ key: 'bar', label: 'foo', value: 'foofoo' },
{ key: 'foo', label: 'thirdBar', value: 'fourthBar' },
],
localConfigState: {
foo: { label: 'thirdBar', value: 'fourthBar' },
bar: { label: 'foo', value: 'fafa' },
foo: { label: 'thirdBar', value: 'fourthBar' },
},
localConfigView: [
{ key: 'bar', label: 'foo', value: 'fafa' },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,17 @@ import {
PostConnectorConfigurationResponse,
} from '../../../api/connector/update_connector_configuration_api_logic';
import {
FetchIndexApiLogic,
FetchIndexApiParams,
FetchIndexApiResponse,
} from '../../../api/index/fetch_index_api_logic';
CachedFetchIndexApiLogic,
CachedFetchIndexApiLogicActions,
} from '../../../api/index/cached_fetch_index_api_logic';
import { FetchIndexApiResponse } from '../../../api/index/fetch_index_api_logic';
import { isConnectorIndex } from '../../../utils/indices';

type ConnectorConfigurationActions = Pick<
Actions<PostConnectorConfigurationArgs, PostConnectorConfigurationResponse>,
'apiError' | 'apiSuccess' | 'makeRequest'
> & {
fetchIndexApiSuccess: Actions<FetchIndexApiParams, FetchIndexApiResponse>['apiSuccess'];
fetchIndexApiSuccess: CachedFetchIndexApiLogicActions['apiSuccess'];
saveConfig: () => void;
setConfigState(configState: ConnectorConfiguration): {
configState: ConnectorConfiguration;
Expand Down Expand Up @@ -76,10 +76,10 @@ export const ConnectorConfigurationLogic = kea<
actions: [
ConnectorConfigurationApiLogic,
['apiError', 'apiSuccess', 'makeRequest'],
FetchIndexApiLogic,
CachedFetchIndexApiLogic,
['apiSuccess as fetchIndexApiSuccess'],
],
values: [FetchIndexApiLogic, ['data as index']],
values: [CachedFetchIndexApiLogic, ['indexData as index']],
},
events: ({ actions, values }) => ({
afterMount: () =>
Expand All @@ -96,7 +96,7 @@ export const ConnectorConfigurationLogic = kea<
{ defaultMessage: 'Configuration successfully updated' }
)
);
FetchIndexApiLogic.actions.makeRequest({ indexName });
CachedFetchIndexApiLogic.actions.makeRequest({ indexName });
},
fetchIndexApiSuccess: (index) => {
if (!values.isEditing && isConnectorIndex(index)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,14 @@ import { ConnectorNameAndDescriptionForm } from './connector_name_and_descriptio
import { ConnectorNameAndDescriptionLogic } from './connector_name_and_description_logic';

export const ConnectorNameAndDescription: React.FC = () => {
const { index: indexData } = useValues(IndexViewLogic);
const { index } = useValues(IndexViewLogic);
const {
isEditing,
nameAndDescription: { name, description },
} = useValues(ConnectorNameAndDescriptionLogic);
const { setIsEditing } = useActions(ConnectorNameAndDescriptionLogic);

if (!isConnectorIndex(indexData)) {
if (!isConnectorIndex(index)) {
return <></>;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import { IndexViewLogic } from '../../index_view_logic';
import { ConnectorNameAndDescriptionLogic } from './connector_name_and_description_logic';

export const ConnectorNameAndDescriptionForm: React.FC = () => {
const { index: indexData } = useValues(IndexViewLogic);
const { index } = useValues(IndexViewLogic);
const { status } = useValues(ConnectorNameAndDescriptionApiLogic);
const {
localNameAndDescription: { name, description },
Expand All @@ -43,7 +43,7 @@ export const ConnectorNameAndDescriptionForm: React.FC = () => {
ConnectorNameAndDescriptionLogic
);

if (!isConnectorIndex(indexData)) {
if (!isConnectorIndex(index)) {
return <></>;
}

Expand Down
Loading