Skip to content

Commit

Permalink
Make log view resolution more robust
Browse files Browse the repository at this point in the history
  • Loading branch information
weltenwort committed Feb 11, 2025
1 parent d140b6d commit d6b9539
Show file tree
Hide file tree
Showing 10 changed files with 202 additions and 53 deletions.
1 change: 1 addition & 0 deletions x-pack/platform/plugins/shared/logs_shared/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export {
FetchLogViewError,
FetchLogViewStatusError,
ResolveLogViewError,
isNoSuchRemoteClusterError,
} from './log_views/errors';

export type {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,6 @@ export class PutLogViewError extends Error {
this.name = 'PutLogViewError';
}
}

export const isNoSuchRemoteClusterError = (err: Error) =>
err?.message?.includes('no_such_remote_cluster_exception');
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,16 @@ const resolveLegacyReference = async (
}

const indices = logViewAttributes.logIndices.indexName;
const dataViewId = `log-view-${logViewId}`;

// If we didn't remove the item from the cache here the subsequent call to
// create would not have any effect
dataViewsService.clearInstanceCache(dataViewId);

const dataViewReference = await dataViewsService
.create(
{
id: `log-view-${logViewId}`,
id: dataViewId,
name: logViewAttributes.name,
title: indices,
timeFieldName: TIMESTAMP_FIELD,
Expand Down Expand Up @@ -134,11 +139,16 @@ const resolveKibanaAdvancedSettingReference = async (
const indices = (await logSourcesService.getLogSources())
.map((logSource) => logSource.indexPattern)
.join(',');
const dataViewId = `log-view-${logViewId}`;

// If we didn't remove the item from the cache here the subsequent call to
// create would not have any effect
dataViewsService.clearInstanceCache(dataViewId);

const dataViewReference = await dataViewsService
.create(
{
id: `log-view-${logViewId}`,
id: dataViewId,
name: logViewAttributes.name,
title: indices,
timeFieldName: TIMESTAMP_FIELD,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,17 +106,25 @@ export const logViewRT = rt.exact(
);
export type LogView = rt.TypeOf<typeof logViewRT>;

export const logViewIndexStatusRT = rt.keyof({
available: null,
empty: null,
missing: null,
unknown: null,
});
export type LogViewIndexStatus = rt.TypeOf<typeof logViewIndexStatusRT>;

export const logViewStatusRT = rt.strict({
index: logViewIndexStatusRT,
});
export const logViewStatusRT = rt.union([
rt.strict({
index: rt.literal('available'),
}),
rt.strict({
index: rt.literal('empty'),
}),
rt.strict({
index: rt.literal('missing'),
reason: rt.keyof({
noIndicesFound: null,
noShardsFound: null,
remoteClusterNotFound: null,
}),
}),
rt.strict({
index: rt.literal('unknown'),
}),
]);
export type LogViewStatus = rt.TypeOf<typeof logViewStatusRT>;

export const persistedLogViewReferenceRT = rt.type({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,12 @@ import { useInterpret, useSelector } from '@xstate/react';
import createContainer from 'constate';
import { useCallback, useState } from 'react';
import { waitFor } from 'xstate/lib/waitFor';
import { DEFAULT_LOG_VIEW, LogViewAttributes, LogViewReference } from '../../common/log_views';
import {
DEFAULT_LOG_VIEW,
LogViewAttributes,
LogViewReference,
LogViewStatus,
} from '../../common/log_views';
import {
InitializeFromUrl,
UpdateContextInUrl,
Expand Down Expand Up @@ -73,7 +78,10 @@ export const useLogView = ({

const logView = useSelector(logViewStateService, (state) =>
state.matches('resolving') ||
state.matches('updating') ||
state.matches('checkingStatus') ||
state.matches('resolutionFailed') ||
state.matches('checkingStatusFailed') ||
state.matches('resolvedPersistedLogView') ||
state.matches('resolvedInlineLogView')
? state.context.logView
Expand All @@ -82,16 +90,19 @@ export const useLogView = ({

const resolvedLogView = useSelector(logViewStateService, (state) =>
state.matches('checkingStatus') ||
state.matches('checkingStatusFailed') ||
state.matches('resolvedPersistedLogView') ||
state.matches('resolvedInlineLogView')
? state.context.resolvedLogView
: undefined
);

const logViewStatus = useSelector(logViewStateService, (state) =>
state.matches('resolvedPersistedLogView') || state.matches('resolvedInlineLogView')
const logViewStatus: LogViewStatus = useSelector(logViewStateService, (state) =>
state.matches('resolvedPersistedLogView') ||
state.matches('resolvedInlineLogView') ||
state.matches('resolutionFailed')
? state.context.status
: undefined
: { index: 'unknown' }
);

const isLoadingLogView = useSelector(logViewStateService, (state) => state.matches('loading'));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import { catchError, from, map, of, throwError } from 'rxjs';
import { createMachine, actions, assign } from 'xstate';
import { isNoSuchRemoteClusterError } from '../../../../common';
import { ILogViewsClient } from '../../../services/log_views';
import { NotificationChannel } from '../../xstate_helpers';
import { LogViewNotificationEvent, logViewNotificationEventSelectors } from './notifications';
Expand Down Expand Up @@ -75,7 +76,7 @@ export const createPureLogViewStateMachine = (initialContext: LogViewContextWith
on: {
RESOLUTION_FAILED: {
target: 'resolutionFailed',
actions: 'storeError',
actions: ['storeError', 'storeStatusAfterError'],
},
RESOLUTION_SUCCEEDED: {
target: 'checkingStatus',
Expand Down Expand Up @@ -250,6 +251,20 @@ export const createPureLogViewStateMachine = (initialContext: LogViewContextWith
} as LogViewContextWithStatus)
: {}
),
storeStatusAfterError: assign((context, event) =>
'error' in event
? ({
status: isNoSuchRemoteClusterError(event.error)
? {
index: 'missing',
reason: 'remoteClusterNotFound',
}
: {
index: 'unknown',
},
} as LogViewContextWithStatus)
: {}
),
storeError: assign((context, event) =>
'error' in event
? ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export type LogViewTypestate =
}
| {
value: 'updating';
context: LogViewContextWithReference;
context: LogViewContextWithReference & LogViewContextWithLogView;
}
| {
value: 'loadingFailed';
Expand All @@ -83,11 +83,17 @@ export type LogViewTypestate =
}
| {
value: 'resolutionFailed';
context: LogViewContextWithReference & LogViewContextWithLogView & LogViewContextWithError;
context: LogViewContextWithReference &
LogViewContextWithLogView &
LogViewContextWithStatus &
LogViewContextWithError;
}
| {
value: 'checkingStatusFailed';
context: LogViewContextWithReference & LogViewContextWithLogView & LogViewContextWithError;
context: LogViewContextWithReference &
LogViewContextWithLogView &
LogViewContextWithResolvedLogView &
LogViewContextWithError;
};

export type LogViewContext = LogViewTypestate['context'];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
PutLogViewError,
ResolvedLogView,
resolveLogView,
isNoSuchRemoteClusterError,
} from '../../../common/log_views';
import { decodeOrThrow } from '../../../common/runtime_types';
import { ILogViewsClient } from './types';
Expand Down Expand Up @@ -69,7 +70,7 @@ export class LogViewsClient implements ILogViewsClient {
}

public async getResolvedLogViewStatus(resolvedLogView: ResolvedLogView): Promise<LogViewStatus> {
const indexStatus = await lastValueFrom(
return await lastValueFrom(
this.search({
params: {
ignore_unavailable: true,
Expand All @@ -81,31 +82,43 @@ export class LogViewsClient implements ILogViewsClient {
},
})
).then(
({ rawResponse }) => {
({ rawResponse }): LogViewStatus => {
if (rawResponse._shards.total <= 0) {
return 'missing' as const;
return {
index: 'missing',
reason: 'noShardsFound',
};
}

const totalHits = decodeTotalHits(rawResponse.hits.total);
if (typeof totalHits === 'number' ? totalHits > 0 : totalHits.value > 0) {
return 'available' as const;
return {
index: 'available',
};
}

return 'empty' as const;
return {
index: 'empty',
};
},
(err) => {
(err): LogViewStatus => {
if (err.status === 404) {
return 'missing' as const;
return {
index: 'missing',
reason: 'noIndicesFound',
};
} else if (err != null && isNoSuchRemoteClusterError(err)) {
return {
index: 'missing',
reason: 'remoteClusterNotFound',
};
}

throw new FetchLogViewStatusError(
`Failed to check status of log indices of "${resolvedLogView.indices}": ${err}`
);
}
);

return {
index: indexStatus,
};
}

public async putLogView(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ import { EuiCheckableCard, EuiFormFieldset, EuiSpacer, EuiTitle } from '@elastic
import { FormattedMessage } from '@kbn/i18n-react';
import React, { useCallback, useEffect, useState } from 'react';
import { useUiTracker } from '@kbn/observability-shared-plugin/public';
import type { LogDataViewReference, LogIndexReference } from '@kbn/logs-shared-plugin/common';
import type {
LogDataViewReference,
LogIndexReference,
LogViewStatus,
} from '@kbn/logs-shared-plugin/common';
import {
logIndexNameReferenceRT,
logDataViewReferenceRT,
Expand All @@ -35,7 +39,8 @@ export const IndicesConfigurationPanel = React.memo<{
isLoading: boolean;
isReadOnly: boolean;
indicesFormElement: FormElement<LogIndexReference | undefined, FormValidationError>;
}>(({ isLoading, isReadOnly, indicesFormElement }) => {
logViewStatus: LogViewStatus;
}>(({ isLoading, isReadOnly, indicesFormElement, logViewStatus }) => {
const {
services: {
http,
Expand Down Expand Up @@ -200,6 +205,7 @@ export const IndicesConfigurationPanel = React.memo<{
/>
)}
</EuiCheckableCard>
<LogViewStatusWarning logViewStatus={logViewStatus} />
{numberOfLogsRules > 0 && indicesFormElement.isDirty && (
<>
<EuiSpacer size="s" />
Expand Down Expand Up @@ -234,6 +240,36 @@ export const IndicesConfigurationPanel = React.memo<{
);
});

const LogViewStatusWarning: React.FC<{ logViewStatus: LogViewStatus }> = ({ logViewStatus }) => {
if (logViewStatus.index === 'missing') {
return (
<>
<EuiSpacer size="s" />
<EuiCallOut title={logIndicesMissingTitle} color="warning" iconType="warning">
{logViewStatus.reason === 'noShardsFound' ? (
<FormattedMessage
id="xpack.infra.sourceConfiguration.logIndicesMissingMessage.noShardsFound"
defaultMessage="No shards found for the specified indices."
/>
) : logViewStatus.reason === 'noIndicesFound' ? (
<FormattedMessage
id="xpack.infra.sourceConfiguration.logIndicesMissingMessage.noIndicesFound"
defaultMessage="No indices found for the specified pattern."
/>
) : logViewStatus.reason === 'remoteClusterNotFound' ? (
<FormattedMessage
id="xpack.infra.sourceConfiguration.logIndicesMissingMessage.remoteClusterNotFound"
defaultMessage="At least one remote cluster was not found."
/>
) : null}
</EuiCallOut>
</>
);
} else {
return null;
}
};

const isDataViewFormElement = isFormElementForType(
(value): value is LogDataViewReference | undefined =>
value == null || logDataViewReferenceRT.is(value)
Expand All @@ -244,3 +280,10 @@ const isIndexNamesFormElement = isFormElementForType(logIndexNameReferenceRT.is)
const isKibanaAdvancedSettingFormElement = isFormElementForType(
logSourcesKibanaAdvancedSettingRT.is
);

const logIndicesMissingTitle = i18n.translate(
'xpack.infra.sourceConfiguration.logIndicesMissingTitle',
{
defaultMessage: 'Log indices missing',
}
);
Loading

0 comments on commit d6b9539

Please sign in to comment.