mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Logs UI] Prevent broken KIP references from breaking the Logs UI (#98532)
This fixes problems in handling broken KIP references and reduces the risk of broken references occurring the first place.
This commit is contained in:
parent
577948bea3
commit
0e948cffc9
33 changed files with 809 additions and 280 deletions
40
x-pack/plugins/infra/common/log_sources/errors.ts
Normal file
40
x-pack/plugins/infra/common/log_sources/errors.ts
Normal file
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/* eslint-disable max-classes-per-file */
|
||||
|
||||
export class ResolveLogSourceConfigurationError extends Error {
|
||||
constructor(message: string, public cause?: Error) {
|
||||
super(message);
|
||||
Object.setPrototypeOf(this, new.target.prototype);
|
||||
this.name = 'ResolveLogSourceConfigurationError';
|
||||
}
|
||||
}
|
||||
|
||||
export class FetchLogSourceConfigurationError extends Error {
|
||||
constructor(message: string, public cause?: Error) {
|
||||
super(message);
|
||||
Object.setPrototypeOf(this, new.target.prototype);
|
||||
this.name = 'FetchLogSourceConfigurationError';
|
||||
}
|
||||
}
|
||||
|
||||
export class FetchLogSourceStatusError extends Error {
|
||||
constructor(message: string, public cause?: Error) {
|
||||
super(message);
|
||||
Object.setPrototypeOf(this, new.target.prototype);
|
||||
this.name = 'FetchLogSourceStatusError';
|
||||
}
|
||||
}
|
||||
|
||||
export class PatchLogSourceConfigurationError extends Error {
|
||||
constructor(message: string, public cause?: Error) {
|
||||
super(message);
|
||||
Object.setPrototypeOf(this, new.target.prototype);
|
||||
this.name = 'PatchLogSourceConfigurationError';
|
||||
}
|
||||
}
|
|
@ -5,5 +5,6 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
export * from './errors';
|
||||
export * from './log_source_configuration';
|
||||
export * from './resolved_log_source_configuration';
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import { estypes } from '@elastic/elasticsearch';
|
||||
import { IndexPattern, IndexPatternsContract } from '../../../../../src/plugins/data/common';
|
||||
import { ObjectEntries } from '../utility_types';
|
||||
import { ResolveLogSourceConfigurationError } from './errors';
|
||||
import {
|
||||
LogSourceColumnConfiguration,
|
||||
LogSourceConfigurationProperties,
|
||||
|
@ -44,10 +45,19 @@ const resolveLegacyReference = async (
|
|||
throw new Error('This function can only resolve legacy references');
|
||||
}
|
||||
|
||||
const fields = await indexPatternsService.getFieldsForWildcard({
|
||||
pattern: sourceConfiguration.logIndices.indexName,
|
||||
allowNoIndex: true,
|
||||
});
|
||||
const indices = sourceConfiguration.logIndices.indexName;
|
||||
|
||||
const fields = await indexPatternsService
|
||||
.getFieldsForWildcard({
|
||||
pattern: indices,
|
||||
allowNoIndex: true,
|
||||
})
|
||||
.catch((error) => {
|
||||
throw new ResolveLogSourceConfigurationError(
|
||||
`Failed to fetch fields for indices "${indices}": ${error}`,
|
||||
error
|
||||
);
|
||||
});
|
||||
|
||||
return {
|
||||
indices: sourceConfiguration.logIndices.indexName,
|
||||
|
@ -70,9 +80,14 @@ const resolveKibanaIndexPatternReference = async (
|
|||
throw new Error('This function can only resolve Kibana Index Pattern references');
|
||||
}
|
||||
|
||||
const indexPattern = await indexPatternsService.get(
|
||||
sourceConfiguration.logIndices.indexPatternId
|
||||
);
|
||||
const { indexPatternId } = sourceConfiguration.logIndices;
|
||||
|
||||
const indexPattern = await indexPatternsService.get(indexPatternId).catch((error) => {
|
||||
throw new ResolveLogSourceConfigurationError(
|
||||
`Failed to fetch index pattern "${indexPatternId}": ${error}`,
|
||||
error
|
||||
);
|
||||
});
|
||||
|
||||
return {
|
||||
indices: indexPattern.title,
|
||||
|
|
|
@ -160,12 +160,6 @@ export const SavedSourceConfigurationRuntimeType = rt.intersection([
|
|||
export interface InfraSavedSourceConfiguration
|
||||
extends rt.TypeOf<typeof SavedSourceConfigurationRuntimeType> {}
|
||||
|
||||
export const pickSavedSourceConfiguration = (
|
||||
value: InfraSourceConfiguration
|
||||
): InfraSavedSourceConfiguration => {
|
||||
return value;
|
||||
};
|
||||
|
||||
/**
|
||||
* Static source configuration, the result of merging values from the config file and
|
||||
* hardcoded defaults.
|
||||
|
|
|
@ -13,10 +13,10 @@ import {
|
|||
EuiPageBody,
|
||||
EuiPageContent,
|
||||
EuiPageContentBody,
|
||||
EuiSpacer,
|
||||
} from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import React from 'react';
|
||||
|
||||
import { euiStyled } from '../../../../../src/plugins/kibana_react/common';
|
||||
import { FlexPage } from './page';
|
||||
|
||||
|
@ -45,7 +45,7 @@ export const ErrorPage: React.FC<Props> = ({ detailedMessage, retry, shortMessag
|
|||
/>
|
||||
}
|
||||
>
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexGroup alignItems="center">
|
||||
<EuiFlexItem>{shortMessage}</EuiFlexItem>
|
||||
{retry ? (
|
||||
<EuiFlexItem grow={false}>
|
||||
|
@ -58,7 +58,12 @@ export const ErrorPage: React.FC<Props> = ({ detailedMessage, retry, shortMessag
|
|||
</EuiFlexItem>
|
||||
) : null}
|
||||
</EuiFlexGroup>
|
||||
{detailedMessage ? <div>{detailedMessage}</div> : null}
|
||||
{detailedMessage ? (
|
||||
<>
|
||||
<EuiSpacer />
|
||||
<div>{detailedMessage}</div>
|
||||
</>
|
||||
) : null}
|
||||
</EuiCallOut>
|
||||
</EuiPageContentBody>
|
||||
</MinimumPageContent>
|
||||
|
|
|
@ -111,10 +111,10 @@ Read more at https://github.com/elastic/kibana/blob/master/src/plugins/kibana_re
|
|||
}
|
||||
|
||||
const {
|
||||
sourceConfiguration,
|
||||
loadSourceConfiguration,
|
||||
isLoadingSourceConfiguration,
|
||||
derivedIndexPattern,
|
||||
isLoadingSourceConfiguration,
|
||||
loadSource,
|
||||
sourceConfiguration,
|
||||
} = useLogSource({
|
||||
sourceId,
|
||||
fetch: services.http.fetch,
|
||||
|
@ -164,8 +164,8 @@ Read more at https://github.com/elastic/kibana/blob/master/src/plugins/kibana_re
|
|||
|
||||
// Component lifetime
|
||||
useEffect(() => {
|
||||
loadSourceConfiguration();
|
||||
}, [loadSourceConfiguration]);
|
||||
loadSource();
|
||||
}, [loadSource]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchEntries();
|
||||
|
|
|
@ -0,0 +1,141 @@
|
|||
/*
|
||||
* 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 {
|
||||
EuiButton,
|
||||
EuiButtonEmpty,
|
||||
EuiCallOut,
|
||||
EuiEmptyPrompt,
|
||||
EuiPageTemplate,
|
||||
EuiSpacer,
|
||||
} from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import React from 'react';
|
||||
import { SavedObjectNotFound } from '../../../../../../src/plugins/kibana_utils/common';
|
||||
import {
|
||||
FetchLogSourceConfigurationError,
|
||||
FetchLogSourceStatusError,
|
||||
ResolveLogSourceConfigurationError,
|
||||
} from '../../../common/log_sources';
|
||||
import { useLinkProps } from '../../hooks/use_link_props';
|
||||
|
||||
export const LogSourceErrorPage: React.FC<{
|
||||
errors: Error[];
|
||||
onRetry: () => void;
|
||||
}> = ({ errors, onRetry }) => {
|
||||
const settingsLinkProps = useLinkProps({ app: 'logs', pathname: '/settings' });
|
||||
|
||||
return (
|
||||
<EuiPageTemplate template="centeredBody" pageContentProps={{ paddingSize: 'none' }}>
|
||||
<EuiEmptyPrompt
|
||||
iconType="alert"
|
||||
iconColor="danger"
|
||||
title={
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.logSourceErrorPage.failedToLoadSourceTitle"
|
||||
defaultMessage="Failed to load configuration"
|
||||
/>
|
||||
</h2>
|
||||
}
|
||||
body={
|
||||
<>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.logSourceErrorPage.failedToLoadSourceMessage"
|
||||
defaultMessage="Errors occurred while attempting to load the configuration. Try again or change the configuration to fix the problem."
|
||||
/>
|
||||
</p>
|
||||
{errors.map((error) => (
|
||||
<React.Fragment key={error.name}>
|
||||
<LogSourceErrorMessage error={error} />
|
||||
<EuiSpacer />
|
||||
</React.Fragment>
|
||||
))}
|
||||
</>
|
||||
}
|
||||
actions={[
|
||||
<EuiButton onClick={onRetry} iconType="refresh" fill>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.logSourceErrorPage.tryAgainButtonLabel"
|
||||
defaultMessage="Try again"
|
||||
/>
|
||||
</EuiButton>,
|
||||
<EuiButtonEmpty iconType="gear" {...settingsLinkProps}>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.logSourceErrorPage.navigateToSettingsButtonLabel"
|
||||
defaultMessage="Change configuration"
|
||||
/>
|
||||
</EuiButtonEmpty>,
|
||||
]}
|
||||
/>
|
||||
</EuiPageTemplate>
|
||||
);
|
||||
};
|
||||
|
||||
const LogSourceErrorMessage: React.FC<{ error: Error }> = ({ error }) => {
|
||||
if (error instanceof ResolveLogSourceConfigurationError) {
|
||||
return (
|
||||
<LogSourceErrorCallout
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.infra.logSourceErrorPage.resolveLogSourceConfigurationErrorTitle"
|
||||
defaultMessage="Failed to resolve the log source configuration"
|
||||
/>
|
||||
}
|
||||
>
|
||||
{error.cause instanceof SavedObjectNotFound ? (
|
||||
// the SavedObjectNotFound error message contains broken markup
|
||||
<FormattedMessage
|
||||
id="xpack.infra.logSourceErrorPage.savedObjectNotFoundErrorMessage"
|
||||
defaultMessage="Failed to locate that {savedObjectType}: {savedObjectId}"
|
||||
values={{
|
||||
savedObjectType: error.cause.savedObjectType,
|
||||
savedObjectId: error.cause.savedObjectId,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
`${error.cause?.message ?? error.message}`
|
||||
)}
|
||||
</LogSourceErrorCallout>
|
||||
);
|
||||
} else if (error instanceof FetchLogSourceConfigurationError) {
|
||||
return (
|
||||
<LogSourceErrorCallout
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.infra.logSourceErrorPage.fetchLogSourceConfigurationErrorTitle"
|
||||
defaultMessage="Failed to load the log source configuration"
|
||||
/>
|
||||
}
|
||||
>
|
||||
{`${error.cause?.message ?? error.message}`}
|
||||
</LogSourceErrorCallout>
|
||||
);
|
||||
} else if (error instanceof FetchLogSourceStatusError) {
|
||||
return (
|
||||
<LogSourceErrorCallout
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.infra.logSourceErrorPage.fetchLogSourceStatusErrorTitle"
|
||||
defaultMessage="Failed to determine the status of the log source"
|
||||
/>
|
||||
}
|
||||
>
|
||||
{`${error.cause?.message ?? error.message}`}
|
||||
</LogSourceErrorCallout>
|
||||
);
|
||||
} else {
|
||||
return <LogSourceErrorCallout title={error.name}>{`${error.message}`}</LogSourceErrorCallout>;
|
||||
}
|
||||
};
|
||||
|
||||
const LogSourceErrorCallout: React.FC<{ title: React.ReactNode }> = ({ title, children }) => (
|
||||
<EuiCallOut className="eui-textLeft" color="danger" iconType="alert" title={title}>
|
||||
<p>{children}</p>
|
||||
</EuiCallOut>
|
||||
);
|
|
@ -10,12 +10,24 @@ import {
|
|||
getLogSourceConfigurationPath,
|
||||
getLogSourceConfigurationSuccessResponsePayloadRT,
|
||||
} from '../../../../../common/http_api/log_sources';
|
||||
import { FetchLogSourceConfigurationError } from '../../../../../common/log_sources';
|
||||
import { decodeOrThrow } from '../../../../../common/runtime_types';
|
||||
|
||||
export const callFetchLogSourceConfigurationAPI = async (sourceId: string, fetch: HttpHandler) => {
|
||||
const response = await fetch(getLogSourceConfigurationPath(sourceId), {
|
||||
method: 'GET',
|
||||
}).catch((error) => {
|
||||
throw new FetchLogSourceConfigurationError(
|
||||
`Failed to fetch log source configuration "${sourceId}": ${error}`,
|
||||
error
|
||||
);
|
||||
});
|
||||
|
||||
return decodeOrThrow(getLogSourceConfigurationSuccessResponsePayloadRT)(response);
|
||||
return decodeOrThrow(
|
||||
getLogSourceConfigurationSuccessResponsePayloadRT,
|
||||
(message: string) =>
|
||||
new FetchLogSourceConfigurationError(
|
||||
`Failed to decode log source configuration "${sourceId}": ${message}`
|
||||
)
|
||||
)(response);
|
||||
};
|
||||
|
|
|
@ -10,12 +10,24 @@ import {
|
|||
getLogSourceStatusPath,
|
||||
getLogSourceStatusSuccessResponsePayloadRT,
|
||||
} from '../../../../../common/http_api/log_sources';
|
||||
import { FetchLogSourceStatusError } from '../../../../../common/log_sources';
|
||||
import { decodeOrThrow } from '../../../../../common/runtime_types';
|
||||
|
||||
export const callFetchLogSourceStatusAPI = async (sourceId: string, fetch: HttpHandler) => {
|
||||
const response = await fetch(getLogSourceStatusPath(sourceId), {
|
||||
method: 'GET',
|
||||
}).catch((error) => {
|
||||
throw new FetchLogSourceStatusError(
|
||||
`Failed to fetch status for log source "${sourceId}": ${error}`,
|
||||
error
|
||||
);
|
||||
});
|
||||
|
||||
return decodeOrThrow(getLogSourceStatusSuccessResponsePayloadRT)(response);
|
||||
return decodeOrThrow(
|
||||
getLogSourceStatusSuccessResponsePayloadRT,
|
||||
(message: string) =>
|
||||
new FetchLogSourceStatusError(
|
||||
`Failed to decode status for log source "${sourceId}": ${message}`
|
||||
)
|
||||
)(response);
|
||||
};
|
||||
|
|
|
@ -12,6 +12,7 @@ import {
|
|||
patchLogSourceConfigurationRequestBodyRT,
|
||||
LogSourceConfigurationPropertiesPatch,
|
||||
} from '../../../../../common/http_api/log_sources';
|
||||
import { PatchLogSourceConfigurationError } from '../../../../../common/log_sources';
|
||||
import { decodeOrThrow } from '../../../../../common/runtime_types';
|
||||
|
||||
export const callPatchLogSourceConfigurationAPI = async (
|
||||
|
@ -26,7 +27,18 @@ export const callPatchLogSourceConfigurationAPI = async (
|
|||
data: patchedProperties,
|
||||
})
|
||||
),
|
||||
}).catch((error) => {
|
||||
throw new PatchLogSourceConfigurationError(
|
||||
`Failed to update log source configuration "${sourceId}": ${error}`,
|
||||
error
|
||||
);
|
||||
});
|
||||
|
||||
return decodeOrThrow(patchLogSourceConfigurationSuccessResponsePayloadRT)(response);
|
||||
return decodeOrThrow(
|
||||
patchLogSourceConfigurationSuccessResponsePayloadRT,
|
||||
(message: string) =>
|
||||
new PatchLogSourceConfigurationError(
|
||||
`Failed to decode log source configuration "${sourceId}": ${message}`
|
||||
)
|
||||
)(response);
|
||||
};
|
||||
|
|
|
@ -18,9 +18,10 @@ export const createUninitializedUseLogSourceMock: CreateUseLogSource = ({
|
|||
fields: [],
|
||||
title: 'unknown',
|
||||
},
|
||||
hasFailedLoading: false,
|
||||
hasFailedLoadingSource: false,
|
||||
hasFailedLoadingSourceStatus: false,
|
||||
hasFailedResolvingSourceConfiguration: false,
|
||||
hasFailedResolvingSource: false,
|
||||
initialize: jest.fn(),
|
||||
isLoading: false,
|
||||
isLoadingSourceConfiguration: false,
|
||||
|
@ -29,13 +30,13 @@ export const createUninitializedUseLogSourceMock: CreateUseLogSource = ({
|
|||
isUninitialized: true,
|
||||
loadSource: jest.fn(),
|
||||
loadSourceConfiguration: jest.fn(),
|
||||
loadSourceFailureMessage: undefined,
|
||||
latestLoadSourceFailures: [],
|
||||
resolveSourceFailureMessage: undefined,
|
||||
loadSourceStatus: jest.fn(),
|
||||
sourceConfiguration: undefined,
|
||||
sourceId,
|
||||
sourceStatus: undefined,
|
||||
updateSourceConfiguration: jest.fn(),
|
||||
updateSource: jest.fn(),
|
||||
resolvedSourceConfiguration: undefined,
|
||||
loadResolveLogSourceConfiguration: jest.fn(),
|
||||
});
|
||||
|
@ -83,6 +84,6 @@ export const createBasicSourceConfiguration = (sourceId: string): LogSourceConfi
|
|||
},
|
||||
});
|
||||
|
||||
export const createAvailableSourceStatus = (logIndexFields = []): LogSourceStatus => ({
|
||||
export const createAvailableSourceStatus = (): LogSourceStatus => ({
|
||||
logIndexStatus: 'available',
|
||||
});
|
||||
|
|
|
@ -7,8 +7,8 @@
|
|||
|
||||
import createContainer from 'constate';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import useMountedState from 'react-use/lib/useMountedState';
|
||||
import type { HttpHandler } from 'src/core/public';
|
||||
import { IndexPatternsContract } from '../../../../../../../src/plugins/data/common';
|
||||
import {
|
||||
LogIndexField,
|
||||
LogSourceConfigurationPropertiesPatch,
|
||||
|
@ -19,12 +19,12 @@ import {
|
|||
LogSourceConfigurationProperties,
|
||||
ResolvedLogSourceConfiguration,
|
||||
resolveLogSourceConfiguration,
|
||||
ResolveLogSourceConfigurationError,
|
||||
} from '../../../../common/log_sources';
|
||||
import { useTrackedPromise } from '../../../utils/use_tracked_promise';
|
||||
import { isRejectedPromiseState, useTrackedPromise } from '../../../utils/use_tracked_promise';
|
||||
import { callFetchLogSourceConfigurationAPI } from './api/fetch_log_source_configuration';
|
||||
import { callFetchLogSourceStatusAPI } from './api/fetch_log_source_status';
|
||||
import { callPatchLogSourceConfigurationAPI } from './api/patch_log_source_configuration';
|
||||
import { IndexPatternsContract } from '../../../../../../../src/plugins/data/common';
|
||||
|
||||
export {
|
||||
LogIndexField,
|
||||
|
@ -32,6 +32,7 @@ export {
|
|||
LogSourceConfigurationProperties,
|
||||
LogSourceConfigurationPropertiesPatch,
|
||||
LogSourceStatus,
|
||||
ResolveLogSourceConfigurationError,
|
||||
};
|
||||
|
||||
export const useLogSource = ({
|
||||
|
@ -43,7 +44,6 @@ export const useLogSource = ({
|
|||
fetch: HttpHandler;
|
||||
indexPatternsService: IndexPatternsContract;
|
||||
}) => {
|
||||
const getIsMounted = useMountedState();
|
||||
const [sourceConfiguration, setSourceConfiguration] = useState<
|
||||
LogSourceConfiguration | undefined
|
||||
>(undefined);
|
||||
|
@ -58,52 +58,34 @@ export const useLogSource = ({
|
|||
{
|
||||
cancelPreviousOn: 'resolution',
|
||||
createPromise: async () => {
|
||||
const { data: sourceConfigurationResponse } = await callFetchLogSourceConfigurationAPI(
|
||||
sourceId,
|
||||
fetch
|
||||
);
|
||||
const resolvedSourceConfigurationResponse = await resolveLogSourceConfiguration(
|
||||
sourceConfigurationResponse?.configuration,
|
||||
indexPatternsService
|
||||
);
|
||||
return { sourceConfigurationResponse, resolvedSourceConfigurationResponse };
|
||||
},
|
||||
onResolve: ({ sourceConfigurationResponse, resolvedSourceConfigurationResponse }) => {
|
||||
if (!getIsMounted()) {
|
||||
return;
|
||||
}
|
||||
|
||||
setSourceConfiguration(sourceConfigurationResponse);
|
||||
setResolvedSourceConfiguration(resolvedSourceConfigurationResponse);
|
||||
return (await callFetchLogSourceConfigurationAPI(sourceId, fetch)).data;
|
||||
},
|
||||
onResolve: setSourceConfiguration,
|
||||
},
|
||||
[sourceId, fetch, indexPatternsService]
|
||||
);
|
||||
|
||||
const [resolveSourceConfigurationRequest, resolveSourceConfiguration] = useTrackedPromise(
|
||||
{
|
||||
cancelPreviousOn: 'resolution',
|
||||
createPromise: async (unresolvedSourceConfiguration: LogSourceConfigurationProperties) => {
|
||||
return await resolveLogSourceConfiguration(
|
||||
unresolvedSourceConfiguration,
|
||||
indexPatternsService
|
||||
);
|
||||
},
|
||||
onResolve: setResolvedSourceConfiguration,
|
||||
},
|
||||
[indexPatternsService]
|
||||
);
|
||||
|
||||
const [updateSourceConfigurationRequest, updateSourceConfiguration] = useTrackedPromise(
|
||||
{
|
||||
cancelPreviousOn: 'resolution',
|
||||
createPromise: async (patchedProperties: LogSourceConfigurationPropertiesPatch) => {
|
||||
const { data: updatedSourceConfig } = await callPatchLogSourceConfigurationAPI(
|
||||
sourceId,
|
||||
patchedProperties,
|
||||
fetch
|
||||
);
|
||||
const resolvedSourceConfig = await resolveLogSourceConfiguration(
|
||||
updatedSourceConfig.configuration,
|
||||
indexPatternsService
|
||||
);
|
||||
return { updatedSourceConfig, resolvedSourceConfig };
|
||||
},
|
||||
onResolve: ({ updatedSourceConfig, resolvedSourceConfig }) => {
|
||||
if (!getIsMounted()) {
|
||||
return;
|
||||
}
|
||||
|
||||
setSourceConfiguration(updatedSourceConfig);
|
||||
setResolvedSourceConfiguration(resolvedSourceConfig);
|
||||
loadSourceStatus();
|
||||
return (await callPatchLogSourceConfigurationAPI(sourceId, patchedProperties, fetch)).data;
|
||||
},
|
||||
onResolve: setSourceConfiguration,
|
||||
},
|
||||
[sourceId, fetch, indexPatternsService]
|
||||
);
|
||||
|
@ -114,13 +96,7 @@ export const useLogSource = ({
|
|||
createPromise: async () => {
|
||||
return await callFetchLogSourceStatusAPI(sourceId, fetch);
|
||||
},
|
||||
onResolve: ({ data }) => {
|
||||
if (!getIsMounted()) {
|
||||
return;
|
||||
}
|
||||
|
||||
setSourceStatus(data);
|
||||
},
|
||||
onResolve: ({ data }) => setSourceStatus(data),
|
||||
},
|
||||
[sourceId, fetch]
|
||||
);
|
||||
|
@ -133,53 +109,67 @@ export const useLogSource = ({
|
|||
[resolvedSourceConfiguration]
|
||||
);
|
||||
|
||||
const isLoadingSourceConfiguration = useMemo(
|
||||
() => loadSourceConfigurationRequest.state === 'pending',
|
||||
[loadSourceConfigurationRequest.state]
|
||||
const isLoadingSourceConfiguration = loadSourceConfigurationRequest.state === 'pending';
|
||||
const isResolvingSourceConfiguration = resolveSourceConfigurationRequest.state === 'pending';
|
||||
const isLoadingSourceStatus = loadSourceStatusRequest.state === 'pending';
|
||||
const isUpdatingSourceConfiguration = updateSourceConfigurationRequest.state === 'pending';
|
||||
|
||||
const isLoading =
|
||||
isLoadingSourceConfiguration ||
|
||||
isResolvingSourceConfiguration ||
|
||||
isLoadingSourceStatus ||
|
||||
isUpdatingSourceConfiguration;
|
||||
|
||||
const isUninitialized =
|
||||
loadSourceConfigurationRequest.state === 'uninitialized' ||
|
||||
resolveSourceConfigurationRequest.state === 'uninitialized' ||
|
||||
loadSourceStatusRequest.state === 'uninitialized';
|
||||
|
||||
const hasFailedLoadingSource = loadSourceConfigurationRequest.state === 'rejected';
|
||||
const hasFailedResolvingSource = resolveSourceConfigurationRequest.state === 'rejected';
|
||||
const hasFailedLoadingSourceStatus = loadSourceStatusRequest.state === 'rejected';
|
||||
|
||||
const latestLoadSourceFailures = [
|
||||
loadSourceConfigurationRequest,
|
||||
resolveSourceConfigurationRequest,
|
||||
loadSourceStatusRequest,
|
||||
]
|
||||
.filter(isRejectedPromiseState)
|
||||
.map(({ value }) => (value instanceof Error ? value : new Error(`${value}`)));
|
||||
|
||||
const hasFailedLoading = latestLoadSourceFailures.length > 0;
|
||||
|
||||
const loadSource = useCallback(async () => {
|
||||
const loadSourceConfigurationPromise = loadSourceConfiguration();
|
||||
const loadSourceStatusPromise = loadSourceStatus();
|
||||
const resolveSourceConfigurationPromise = resolveSourceConfiguration(
|
||||
(await loadSourceConfigurationPromise).configuration
|
||||
);
|
||||
|
||||
return await Promise.all([
|
||||
loadSourceConfigurationPromise,
|
||||
resolveSourceConfigurationPromise,
|
||||
loadSourceStatusPromise,
|
||||
]);
|
||||
}, [loadSourceConfiguration, loadSourceStatus, resolveSourceConfiguration]);
|
||||
|
||||
const updateSource = useCallback(
|
||||
async (patchedProperties: LogSourceConfigurationPropertiesPatch) => {
|
||||
const updatedSourceConfiguration = await updateSourceConfiguration(patchedProperties);
|
||||
const resolveSourceConfigurationPromise = resolveSourceConfiguration(
|
||||
updatedSourceConfiguration.configuration
|
||||
);
|
||||
const loadSourceStatusPromise = loadSourceStatus();
|
||||
|
||||
return await Promise.all([
|
||||
updatedSourceConfiguration,
|
||||
resolveSourceConfigurationPromise,
|
||||
loadSourceStatusPromise,
|
||||
]);
|
||||
},
|
||||
[loadSourceStatus, resolveSourceConfiguration, updateSourceConfiguration]
|
||||
);
|
||||
|
||||
const isUpdatingSourceConfiguration = useMemo(
|
||||
() => updateSourceConfigurationRequest.state === 'pending',
|
||||
[updateSourceConfigurationRequest.state]
|
||||
);
|
||||
|
||||
const isLoadingSourceStatus = useMemo(() => loadSourceStatusRequest.state === 'pending', [
|
||||
loadSourceStatusRequest.state,
|
||||
]);
|
||||
|
||||
const isLoading = useMemo(
|
||||
() => isLoadingSourceConfiguration || isLoadingSourceStatus || isUpdatingSourceConfiguration,
|
||||
[isLoadingSourceConfiguration, isLoadingSourceStatus, isUpdatingSourceConfiguration]
|
||||
);
|
||||
|
||||
const isUninitialized = useMemo(
|
||||
() =>
|
||||
loadSourceConfigurationRequest.state === 'uninitialized' ||
|
||||
loadSourceStatusRequest.state === 'uninitialized',
|
||||
[loadSourceConfigurationRequest.state, loadSourceStatusRequest.state]
|
||||
);
|
||||
|
||||
const hasFailedLoadingSource = useMemo(
|
||||
() => loadSourceConfigurationRequest.state === 'rejected',
|
||||
[loadSourceConfigurationRequest.state]
|
||||
);
|
||||
|
||||
const hasFailedLoadingSourceStatus = useMemo(() => loadSourceStatusRequest.state === 'rejected', [
|
||||
loadSourceStatusRequest.state,
|
||||
]);
|
||||
|
||||
const loadSourceFailureMessage = useMemo(
|
||||
() =>
|
||||
loadSourceConfigurationRequest.state === 'rejected'
|
||||
? `${loadSourceConfigurationRequest.value}`
|
||||
: undefined,
|
||||
[loadSourceConfigurationRequest]
|
||||
);
|
||||
|
||||
const loadSource = useCallback(() => {
|
||||
return Promise.all([loadSourceConfiguration(), loadSourceStatus()]);
|
||||
}, [loadSourceConfiguration, loadSourceStatus]);
|
||||
|
||||
const initialize = useCallback(async () => {
|
||||
if (!isUninitialized) {
|
||||
return;
|
||||
|
@ -194,21 +184,23 @@ export const useLogSource = ({
|
|||
isUninitialized,
|
||||
derivedIndexPattern,
|
||||
// Failure states
|
||||
hasFailedLoading,
|
||||
hasFailedLoadingSource,
|
||||
hasFailedLoadingSourceStatus,
|
||||
loadSourceFailureMessage,
|
||||
hasFailedResolvingSource,
|
||||
latestLoadSourceFailures,
|
||||
// Loading states
|
||||
isLoading,
|
||||
isLoadingSourceConfiguration,
|
||||
isLoadingSourceStatus,
|
||||
isResolvingSourceConfiguration,
|
||||
// Source status (denotes the state of the indices, e.g. missing)
|
||||
sourceStatus,
|
||||
loadSourceStatus,
|
||||
// Source configuration (represents the raw attributes of the source configuration)
|
||||
loadSource,
|
||||
loadSourceConfiguration,
|
||||
sourceConfiguration,
|
||||
updateSourceConfiguration,
|
||||
updateSource,
|
||||
// Resolved source configuration (represents a fully resolved state, you would use this for the vast majority of "read" scenarios)
|
||||
resolvedSourceConfiguration,
|
||||
};
|
||||
|
|
|
@ -36,7 +36,7 @@ export const RedirectToNodeLogs = ({
|
|||
location,
|
||||
}: RedirectToNodeLogsType) => {
|
||||
const { services } = useKibanaContextForPlugin();
|
||||
const { isLoading, loadSourceConfiguration, sourceConfiguration } = useLogSource({
|
||||
const { isLoading, loadSource, sourceConfiguration } = useLogSource({
|
||||
fetch: services.http.fetch,
|
||||
sourceId,
|
||||
indexPatternsService: services.data.indexPatterns,
|
||||
|
@ -44,7 +44,7 @@ export const RedirectToNodeLogs = ({
|
|||
const fields = sourceConfiguration?.configuration.fields;
|
||||
|
||||
useMount(() => {
|
||||
loadSourceConfiguration();
|
||||
loadSource();
|
||||
});
|
||||
|
||||
if (isLoading) {
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import { SubscriptionSplashContent } from '../../../components/subscription_splash_content';
|
||||
import { isJobStatusWithResults } from '../../../../common/log_analysis';
|
||||
import { LoadingPage } from '../../../components/loading_page';
|
||||
import {
|
||||
|
@ -19,23 +18,13 @@ import {
|
|||
LogAnalysisSetupFlyout,
|
||||
useLogAnalysisSetupFlyoutStateContext,
|
||||
} from '../../../components/logging/log_analysis_setup/setup_flyout';
|
||||
import { SourceErrorPage } from '../../../components/source_error_page';
|
||||
import { SourceLoadingPage } from '../../../components/source_loading_page';
|
||||
import { SubscriptionSplashContent } from '../../../components/subscription_splash_content';
|
||||
import { useLogAnalysisCapabilitiesContext } from '../../../containers/logs/log_analysis';
|
||||
import { useLogEntryCategoriesModuleContext } from '../../../containers/logs/log_analysis/modules/log_entry_categories';
|
||||
import { useLogSourceContext } from '../../../containers/logs/log_source';
|
||||
import { LogEntryCategoriesResultsContent } from './page_results_content';
|
||||
import { LogEntryCategoriesSetupContent } from './page_setup_content';
|
||||
|
||||
export const LogEntryCategoriesPageContent = () => {
|
||||
const {
|
||||
hasFailedLoadingSource,
|
||||
isLoading,
|
||||
isUninitialized,
|
||||
loadSource,
|
||||
loadSourceFailureMessage,
|
||||
} = useLogSourceContext();
|
||||
|
||||
const {
|
||||
hasLogAnalysisCapabilites,
|
||||
hasLogAnalysisReadCapabilities,
|
||||
|
@ -55,11 +44,7 @@ export const LogEntryCategoriesPageContent = () => {
|
|||
}
|
||||
}, [fetchJobStatus, hasLogAnalysisReadCapabilities]);
|
||||
|
||||
if (isLoading || isUninitialized) {
|
||||
return <SourceLoadingPage />;
|
||||
} else if (hasFailedLoadingSource) {
|
||||
return <SourceErrorPage errorMessage={loadSourceFailureMessage ?? ''} retry={loadSource} />;
|
||||
} else if (!hasLogAnalysisCapabilites) {
|
||||
if (!hasLogAnalysisCapabilites) {
|
||||
return <SubscriptionSplashContent />;
|
||||
} else if (!hasLogAnalysisReadCapabilities) {
|
||||
return <MissingResultsPrivilegesPrompt />;
|
||||
|
|
|
@ -7,30 +7,46 @@
|
|||
|
||||
import React from 'react';
|
||||
import { LogAnalysisSetupFlyoutStateProvider } from '../../../components/logging/log_analysis_setup/setup_flyout';
|
||||
import { LogSourceErrorPage } from '../../../components/logging/log_source_error_page';
|
||||
import { SourceLoadingPage } from '../../../components/source_loading_page';
|
||||
import { LogEntryCategoriesModuleProvider } from '../../../containers/logs/log_analysis/modules/log_entry_categories';
|
||||
import { useLogSourceContext } from '../../../containers/logs/log_source';
|
||||
import { useActiveKibanaSpace } from '../../../hooks/use_kibana_space';
|
||||
|
||||
export const LogEntryCategoriesPageProviders: React.FunctionComponent = ({ children }) => {
|
||||
const { sourceId, resolvedSourceConfiguration } = useLogSourceContext();
|
||||
const {
|
||||
hasFailedLoading,
|
||||
isLoading,
|
||||
isUninitialized,
|
||||
latestLoadSourceFailures,
|
||||
loadSource,
|
||||
resolvedSourceConfiguration,
|
||||
sourceId,
|
||||
} = useLogSourceContext();
|
||||
const { space } = useActiveKibanaSpace();
|
||||
|
||||
// This is a rather crude way of guarding the dependent providers against
|
||||
// arguments that are only made available asynchronously. Ideally, we'd use
|
||||
// React concurrent mode and Suspense in order to handle that more gracefully.
|
||||
if (!resolvedSourceConfiguration || space == null) {
|
||||
if (space == null) {
|
||||
return null;
|
||||
} else if (hasFailedLoading) {
|
||||
return <LogSourceErrorPage errors={latestLoadSourceFailures} onRetry={loadSource} />;
|
||||
} else if (isLoading || isUninitialized) {
|
||||
return <SourceLoadingPage />;
|
||||
} else if (resolvedSourceConfiguration != null) {
|
||||
return (
|
||||
<LogEntryCategoriesModuleProvider
|
||||
indexPattern={resolvedSourceConfiguration.indices}
|
||||
sourceId={sourceId}
|
||||
spaceId={space.id}
|
||||
timestampField={resolvedSourceConfiguration.timestampField}
|
||||
runtimeMappings={resolvedSourceConfiguration.runtimeMappings}
|
||||
>
|
||||
<LogAnalysisSetupFlyoutStateProvider>{children}</LogAnalysisSetupFlyoutStateProvider>
|
||||
</LogEntryCategoriesModuleProvider>
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<LogEntryCategoriesModuleProvider
|
||||
indexPattern={resolvedSourceConfiguration.indices}
|
||||
sourceId={sourceId}
|
||||
spaceId={space.id}
|
||||
timestampField={resolvedSourceConfiguration.timestampField}
|
||||
runtimeMappings={resolvedSourceConfiguration.runtimeMappings}
|
||||
>
|
||||
<LogAnalysisSetupFlyoutStateProvider>{children}</LogAnalysisSetupFlyoutStateProvider>
|
||||
</LogEntryCategoriesModuleProvider>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -6,9 +6,8 @@
|
|||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React, { memo, useEffect, useCallback } from 'react';
|
||||
import React, { memo, useCallback, useEffect } from 'react';
|
||||
import useInterval from 'react-use/lib/useInterval';
|
||||
import { SubscriptionSplashContent } from '../../../components/subscription_splash_content';
|
||||
import { isJobStatusWithResults } from '../../../../common/log_analysis';
|
||||
import { LoadingPage } from '../../../components/loading_page';
|
||||
import {
|
||||
|
@ -20,26 +19,16 @@ import {
|
|||
LogAnalysisSetupFlyout,
|
||||
useLogAnalysisSetupFlyoutStateContext,
|
||||
} from '../../../components/logging/log_analysis_setup/setup_flyout';
|
||||
import { SourceErrorPage } from '../../../components/source_error_page';
|
||||
import { SourceLoadingPage } from '../../../components/source_loading_page';
|
||||
import { SubscriptionSplashContent } from '../../../components/subscription_splash_content';
|
||||
import { useLogAnalysisCapabilitiesContext } from '../../../containers/logs/log_analysis';
|
||||
import { useLogEntryCategoriesModuleContext } from '../../../containers/logs/log_analysis/modules/log_entry_categories';
|
||||
import { useLogEntryRateModuleContext } from '../../../containers/logs/log_analysis/modules/log_entry_rate';
|
||||
import { useLogSourceContext } from '../../../containers/logs/log_source';
|
||||
import { LogEntryRateResultsContent } from './page_results_content';
|
||||
import { LogEntryRateSetupContent } from './page_setup_content';
|
||||
|
||||
const JOB_STATUS_POLLING_INTERVAL = 30000;
|
||||
|
||||
export const LogEntryRatePageContent = memo(() => {
|
||||
const {
|
||||
hasFailedLoadingSource,
|
||||
isLoading,
|
||||
isUninitialized,
|
||||
loadSource,
|
||||
loadSourceFailureMessage,
|
||||
} = useLogSourceContext();
|
||||
|
||||
const {
|
||||
hasLogAnalysisCapabilites,
|
||||
hasLogAnalysisReadCapabilities,
|
||||
|
@ -93,11 +82,7 @@ export const LogEntryRatePageContent = memo(() => {
|
|||
}
|
||||
}, JOB_STATUS_POLLING_INTERVAL);
|
||||
|
||||
if (isLoading || isUninitialized) {
|
||||
return <SourceLoadingPage />;
|
||||
} else if (hasFailedLoadingSource) {
|
||||
return <SourceErrorPage errorMessage={loadSourceFailureMessage ?? ''} retry={loadSource} />;
|
||||
} else if (!hasLogAnalysisCapabilites) {
|
||||
if (!hasLogAnalysisCapabilites) {
|
||||
return <SubscriptionSplashContent />;
|
||||
} else if (!hasLogAnalysisReadCapabilities) {
|
||||
return <MissingResultsPrivilegesPrompt />;
|
||||
|
|
|
@ -7,42 +7,58 @@
|
|||
|
||||
import React from 'react';
|
||||
import { LogAnalysisSetupFlyoutStateProvider } from '../../../components/logging/log_analysis_setup/setup_flyout';
|
||||
import { LogSourceErrorPage } from '../../../components/logging/log_source_error_page';
|
||||
import { SourceLoadingPage } from '../../../components/source_loading_page';
|
||||
import { LogEntryCategoriesModuleProvider } from '../../../containers/logs/log_analysis/modules/log_entry_categories';
|
||||
import { LogEntryRateModuleProvider } from '../../../containers/logs/log_analysis/modules/log_entry_rate';
|
||||
import { LogFlyout } from '../../../containers/logs/log_flyout';
|
||||
import { useLogSourceContext } from '../../../containers/logs/log_source';
|
||||
import { useActiveKibanaSpace } from '../../../hooks/use_kibana_space';
|
||||
import { LogFlyout } from '../../../containers/logs/log_flyout';
|
||||
|
||||
export const LogEntryRatePageProviders: React.FunctionComponent = ({ children }) => {
|
||||
const { sourceId, resolvedSourceConfiguration } = useLogSourceContext();
|
||||
const {
|
||||
hasFailedLoading,
|
||||
isLoading,
|
||||
isUninitialized,
|
||||
latestLoadSourceFailures,
|
||||
loadSource,
|
||||
resolvedSourceConfiguration,
|
||||
sourceId,
|
||||
} = useLogSourceContext();
|
||||
const { space } = useActiveKibanaSpace();
|
||||
|
||||
// This is a rather crude way of guarding the dependent providers against
|
||||
// arguments that are only made available asynchronously. Ideally, we'd use
|
||||
// React concurrent mode and Suspense in order to handle that more gracefully.
|
||||
if (!resolvedSourceConfiguration || space == null) {
|
||||
if (space == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<LogFlyout.Provider>
|
||||
<LogEntryRateModuleProvider
|
||||
indexPattern={resolvedSourceConfiguration.indices ?? ''}
|
||||
sourceId={sourceId}
|
||||
spaceId={space.id}
|
||||
timestampField={resolvedSourceConfiguration.timestampField ?? ''}
|
||||
runtimeMappings={resolvedSourceConfiguration.runtimeMappings}
|
||||
>
|
||||
<LogEntryCategoriesModuleProvider
|
||||
indexPattern={resolvedSourceConfiguration.indices ?? ''}
|
||||
} else if (isLoading || isUninitialized) {
|
||||
return <SourceLoadingPage />;
|
||||
} else if (hasFailedLoading) {
|
||||
return <LogSourceErrorPage errors={latestLoadSourceFailures} onRetry={loadSource} />;
|
||||
} else if (resolvedSourceConfiguration != null) {
|
||||
return (
|
||||
<LogFlyout.Provider>
|
||||
<LogEntryRateModuleProvider
|
||||
indexPattern={resolvedSourceConfiguration.indices}
|
||||
sourceId={sourceId}
|
||||
spaceId={space.id}
|
||||
timestampField={resolvedSourceConfiguration.timestampField ?? ''}
|
||||
timestampField={resolvedSourceConfiguration.timestampField}
|
||||
runtimeMappings={resolvedSourceConfiguration.runtimeMappings}
|
||||
>
|
||||
<LogAnalysisSetupFlyoutStateProvider>{children}</LogAnalysisSetupFlyoutStateProvider>
|
||||
</LogEntryCategoriesModuleProvider>
|
||||
</LogEntryRateModuleProvider>
|
||||
</LogFlyout.Provider>
|
||||
);
|
||||
<LogEntryCategoriesModuleProvider
|
||||
indexPattern={resolvedSourceConfiguration.indices}
|
||||
sourceId={sourceId}
|
||||
spaceId={space.id}
|
||||
timestampField={resolvedSourceConfiguration.timestampField}
|
||||
runtimeMappings={resolvedSourceConfiguration.runtimeMappings}
|
||||
>
|
||||
<LogAnalysisSetupFlyoutStateProvider>{children}</LogAnalysisSetupFlyoutStateProvider>
|
||||
</LogEntryCategoriesModuleProvider>
|
||||
</LogEntryRateModuleProvider>
|
||||
</LogFlyout.Provider>
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -28,15 +28,30 @@ export const IndexPatternSelector: React.FC<{
|
|||
fetchIndexPatternTitles();
|
||||
}, [fetchIndexPatternTitles]);
|
||||
|
||||
const availableOptions = useMemo<IndexPatternOption[]>(
|
||||
() =>
|
||||
availableIndexPatterns.map(({ id, title }) => ({
|
||||
const availableOptions = useMemo<IndexPatternOption[]>(() => {
|
||||
const options = [
|
||||
...availableIndexPatterns.map(({ id, title }) => ({
|
||||
key: id,
|
||||
label: title,
|
||||
value: id,
|
||||
})),
|
||||
[availableIndexPatterns]
|
||||
);
|
||||
...(indexPatternId == null || availableIndexPatterns.some(({ id }) => id === indexPatternId)
|
||||
? []
|
||||
: [
|
||||
{
|
||||
key: indexPatternId,
|
||||
label: i18n.translate('xpack.infra.logSourceConfiguration.missingIndexPatternLabel', {
|
||||
defaultMessage: `Missing index pattern {indexPatternId}`,
|
||||
values: {
|
||||
indexPatternId,
|
||||
},
|
||||
}),
|
||||
value: indexPatternId,
|
||||
},
|
||||
]),
|
||||
];
|
||||
return options;
|
||||
}, [availableIndexPatterns, indexPatternId]);
|
||||
|
||||
const selectedOptions = useMemo<IndexPatternOption[]>(
|
||||
() => availableOptions.filter(({ key }) => key === indexPatternId),
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import { useMemo } from 'react';
|
||||
import { SavedObjectNotFound } from '../../../../../../../src/plugins/kibana_utils/common';
|
||||
import { useUiTracker } from '../../../../../observability/public';
|
||||
import {
|
||||
LogIndexNameReference,
|
||||
|
@ -45,9 +46,20 @@ export const useLogIndicesFormElement = (initialValue: LogIndicesFormState) => {
|
|||
return emptyStringErrors;
|
||||
}
|
||||
|
||||
const indexPatternErrors = validateIndexPattern(
|
||||
await indexPatternService.get(logIndices.indexPatternId)
|
||||
);
|
||||
const indexPatternErrors = await indexPatternService
|
||||
.get(logIndices.indexPatternId)
|
||||
.then(validateIndexPattern, (error): FormValidationError[] => {
|
||||
if (error instanceof SavedObjectNotFound) {
|
||||
return [
|
||||
{
|
||||
type: 'missing_index_pattern' as const,
|
||||
indexPatternId: logIndices.indexPatternId,
|
||||
},
|
||||
];
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
|
||||
if (indexPatternErrors.length > 0) {
|
||||
trackIndexPatternValidationError({
|
||||
|
|
|
@ -88,6 +88,16 @@ export const LogSourceConfigurationFormError: React.FC<{ error: FormValidationEr
|
|||
defaultMessage="The index pattern must not be a rollup index pattern."
|
||||
/>
|
||||
);
|
||||
} else if (error.type === 'missing_index_pattern') {
|
||||
return (
|
||||
<FormattedMessage
|
||||
id="xpack.infra.logSourceConfiguration.missingIndexPatternErrorMessage"
|
||||
defaultMessage="The index pattern {indexPatternId} must exist."
|
||||
values={{
|
||||
indexPatternId: <EuiCode>{error.indexPatternId}</EuiCode>,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -43,9 +43,10 @@ export const LogsSettingsPage = () => {
|
|||
|
||||
const {
|
||||
sourceConfiguration: source,
|
||||
hasFailedLoadingSource,
|
||||
isLoading,
|
||||
isUninitialized,
|
||||
updateSourceConfiguration,
|
||||
updateSource,
|
||||
resolvedSourceConfiguration,
|
||||
} = useLogSourceContext();
|
||||
|
||||
|
@ -65,9 +66,9 @@ export const LogsSettingsPage = () => {
|
|||
} = useLogSourceConfigurationFormState(source?.configuration);
|
||||
|
||||
const persistUpdates = useCallback(async () => {
|
||||
await updateSourceConfiguration(formState);
|
||||
await updateSource(formState);
|
||||
sourceConfigurationFormElement.resetValue();
|
||||
}, [updateSourceConfiguration, sourceConfigurationFormElement, formState]);
|
||||
}, [updateSource, sourceConfigurationFormElement, formState]);
|
||||
|
||||
const isWriteable = useMemo(() => shouldAllowEdit && source && source.origin !== 'internal', [
|
||||
shouldAllowEdit,
|
||||
|
@ -77,7 +78,7 @@ export const LogsSettingsPage = () => {
|
|||
if ((isLoading || isUninitialized) && !resolvedSourceConfiguration) {
|
||||
return <SourceLoadingPage />;
|
||||
}
|
||||
if (!source?.configuration) {
|
||||
if (hasFailedLoadingSource) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -45,6 +45,11 @@ export interface RollupIndexPatternValidationError {
|
|||
indexPatternTitle: string;
|
||||
}
|
||||
|
||||
export interface MissingIndexPatternValidationError {
|
||||
type: 'missing_index_pattern';
|
||||
indexPatternId: string;
|
||||
}
|
||||
|
||||
export type FormValidationError =
|
||||
| GenericValidationError
|
||||
| ChildFormValidationError
|
||||
|
@ -53,7 +58,8 @@ export type FormValidationError =
|
|||
| MissingTimestampFieldValidationError
|
||||
| MissingMessageFieldValidationError
|
||||
| InvalidMessageFieldTypeValidationError
|
||||
| RollupIndexPatternValidationError;
|
||||
| RollupIndexPatternValidationError
|
||||
| MissingIndexPatternValidationError;
|
||||
|
||||
export const validateStringNotEmpty = (fieldName: string, value: string): FormValidationError[] =>
|
||||
value === '' ? [{ type: 'empty_field', fieldName }] : [];
|
||||
|
|
|
@ -6,26 +6,26 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { SourceErrorPage } from '../../../components/source_error_page';
|
||||
import { LogSourceErrorPage } from '../../../components/logging/log_source_error_page';
|
||||
import { SourceLoadingPage } from '../../../components/source_loading_page';
|
||||
import { useLogSourceContext } from '../../../containers/logs/log_source';
|
||||
import { LogsPageLogsContent } from './page_logs_content';
|
||||
import { LogsPageNoIndicesContent } from './page_no_indices_content';
|
||||
import { useLogSourceContext } from '../../../containers/logs/log_source';
|
||||
|
||||
export const StreamPageContent: React.FunctionComponent = () => {
|
||||
const {
|
||||
hasFailedLoadingSource,
|
||||
hasFailedLoading,
|
||||
isLoading,
|
||||
isUninitialized,
|
||||
loadSource,
|
||||
loadSourceFailureMessage,
|
||||
latestLoadSourceFailures,
|
||||
sourceStatus,
|
||||
} = useLogSourceContext();
|
||||
|
||||
if (isLoading || isUninitialized) {
|
||||
return <SourceLoadingPage />;
|
||||
} else if (hasFailedLoadingSource) {
|
||||
return <SourceErrorPage errorMessage={loadSourceFailureMessage ?? ''} retry={loadSource} />;
|
||||
} else if (hasFailedLoading) {
|
||||
return <LogSourceErrorPage errors={latestLoadSourceFailures} onRetry={loadSource} />;
|
||||
} else if (sourceStatus?.logIndexStatus !== 'missing') {
|
||||
return <LogsPageLogsContent />;
|
||||
} else {
|
||||
|
|
|
@ -256,14 +256,18 @@ export interface RejectedPromiseState<ResolvedValue, RejectedValue> {
|
|||
value: RejectedValue;
|
||||
}
|
||||
|
||||
type SettledPromise<ResolvedValue, RejectedValue> =
|
||||
export type SettledPromiseState<ResolvedValue, RejectedValue> =
|
||||
| ResolvedPromiseState<ResolvedValue>
|
||||
| RejectedPromiseState<ResolvedValue, RejectedValue>;
|
||||
|
||||
type PromiseState<ResolvedValue, RejectedValue = unknown> =
|
||||
export type PromiseState<ResolvedValue, RejectedValue = unknown> =
|
||||
| UninitializedPromiseState
|
||||
| PendingPromiseState<ResolvedValue>
|
||||
| SettledPromise<ResolvedValue, RejectedValue>;
|
||||
| SettledPromiseState<ResolvedValue, RejectedValue>;
|
||||
|
||||
export const isRejectedPromiseState = (
|
||||
promiseState: PromiseState<any, any>
|
||||
): promiseState is RejectedPromiseState<any, any> => promiseState.state === 'rejected';
|
||||
|
||||
interface CancelablePromise<ResolvedValue> {
|
||||
// reject the promise prematurely with a CanceledPromiseError
|
||||
|
|
|
@ -46,7 +46,7 @@ export const evaluateCondition = async ({
|
|||
condition: InventoryMetricConditions;
|
||||
nodeType: InventoryItemType;
|
||||
source: InfraSource;
|
||||
logQueryFields: LogQueryFields;
|
||||
logQueryFields: LogQueryFields | undefined;
|
||||
esClient: ElasticsearchClient;
|
||||
compositeSize: number;
|
||||
filterQuery?: string;
|
||||
|
@ -115,7 +115,7 @@ const getData = async (
|
|||
metric: SnapshotMetricType,
|
||||
timerange: InfraTimerangeInput,
|
||||
source: InfraSource,
|
||||
logQueryFields: LogQueryFields,
|
||||
logQueryFields: LogQueryFields | undefined,
|
||||
compositeSize: number,
|
||||
filterQuery?: string,
|
||||
customMetric?: SnapshotCustomMetricInput
|
||||
|
@ -144,8 +144,8 @@ const getData = async (
|
|||
client,
|
||||
snapshotRequest,
|
||||
source,
|
||||
logQueryFields,
|
||||
compositeSize
|
||||
compositeSize,
|
||||
logQueryFields
|
||||
);
|
||||
|
||||
if (!nodes.length) return { [UNGROUPED_FACTORY_KEY]: null }; // No Data state
|
||||
|
|
|
@ -68,11 +68,13 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) =
|
|||
sourceId || 'default'
|
||||
);
|
||||
|
||||
const logQueryFields = await libs.getLogQueryFields(
|
||||
sourceId || 'default',
|
||||
services.savedObjectsClient,
|
||||
services.scopedClusterClient.asCurrentUser
|
||||
);
|
||||
const logQueryFields = await libs
|
||||
.getLogQueryFields(
|
||||
sourceId || 'default',
|
||||
services.savedObjectsClient,
|
||||
services.scopedClusterClient.asCurrentUser
|
||||
)
|
||||
.catch(() => undefined);
|
||||
|
||||
const compositeSize = libs.configuration.inventory.compositeSize;
|
||||
|
||||
|
|
|
@ -4,7 +4,9 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
/* eslint-disable max-classes-per-file */
|
||||
|
||||
export class NotFoundError extends Error {
|
||||
constructor(message?: string) {
|
||||
super(message);
|
||||
|
@ -18,3 +20,11 @@ export class AnomalyThresholdRangeError extends Error {
|
|||
Object.setPrototypeOf(this, new.target.prototype);
|
||||
}
|
||||
}
|
||||
|
||||
export class SavedObjectReferenceResolutionError extends Error {
|
||||
constructor(message?: string) {
|
||||
super(message);
|
||||
Object.setPrototypeOf(this, new.target.prototype);
|
||||
this.name = 'SavedObjectReferenceResolutionError';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* 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 { InfraSourceConfiguration } from '../../../common/source_configuration/source_configuration';
|
||||
import {
|
||||
extractSavedObjectReferences,
|
||||
resolveSavedObjectReferences,
|
||||
} from './saved_object_references';
|
||||
|
||||
describe('extractSavedObjectReferences function', () => {
|
||||
it('extracts log index pattern references', () => {
|
||||
const { attributes, references } = extractSavedObjectReferences(
|
||||
sourceConfigurationWithIndexPatternReference
|
||||
);
|
||||
|
||||
expect(references).toMatchObject([{ id: 'INDEX_PATTERN_ID' }]);
|
||||
expect(attributes).toHaveProperty(['logIndices', 'indexPatternId'], references[0].name);
|
||||
});
|
||||
|
||||
it('ignores log index name references', () => {
|
||||
const { attributes, references } = extractSavedObjectReferences(
|
||||
sourceConfigurationWithIndexNameReference
|
||||
);
|
||||
|
||||
expect(references).toHaveLength(0);
|
||||
expect(attributes).toHaveProperty(['logIndices', 'indexName'], 'INDEX_NAME');
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolveSavedObjectReferences function', () => {
|
||||
it('is the inverse operation of extractSavedObjectReferences', () => {
|
||||
const { attributes, references } = extractSavedObjectReferences(
|
||||
sourceConfigurationWithIndexPatternReference
|
||||
);
|
||||
|
||||
const resolvedSourceConfiguration = resolveSavedObjectReferences(attributes, references);
|
||||
|
||||
expect(resolvedSourceConfiguration).toEqual(sourceConfigurationWithIndexPatternReference);
|
||||
});
|
||||
|
||||
it('ignores additional saved object references', () => {
|
||||
const { attributes, references } = extractSavedObjectReferences(
|
||||
sourceConfigurationWithIndexPatternReference
|
||||
);
|
||||
|
||||
const resolvedSourceConfiguration = resolveSavedObjectReferences(attributes, [
|
||||
...references,
|
||||
{ name: 'log_index_pattern_1', id: 'SOME_ID', type: 'index-pattern' },
|
||||
]);
|
||||
|
||||
expect(resolvedSourceConfiguration).toEqual(sourceConfigurationWithIndexPatternReference);
|
||||
});
|
||||
|
||||
it('ignores log index name references', () => {
|
||||
const { attributes, references } = extractSavedObjectReferences(
|
||||
sourceConfigurationWithIndexNameReference
|
||||
);
|
||||
|
||||
const resolvedSourceConfiguration = resolveSavedObjectReferences(attributes, [
|
||||
...references,
|
||||
{ name: 'log_index_pattern_0', id: 'SOME_ID', type: 'index-pattern' },
|
||||
]);
|
||||
|
||||
expect(resolvedSourceConfiguration).toEqual(sourceConfigurationWithIndexNameReference);
|
||||
});
|
||||
});
|
||||
|
||||
const sourceConfigurationWithIndexPatternReference: InfraSourceConfiguration = {
|
||||
name: 'NAME',
|
||||
description: 'DESCRIPTION',
|
||||
fields: {
|
||||
container: 'CONTAINER_FIELD',
|
||||
host: 'HOST_FIELD',
|
||||
message: ['MESSAGE_FIELD'],
|
||||
pod: 'POD_FIELD',
|
||||
tiebreaker: 'TIEBREAKER_FIELD',
|
||||
timestamp: 'TIMESTAMP_FIELD',
|
||||
},
|
||||
logColumns: [],
|
||||
logIndices: {
|
||||
type: 'index_pattern',
|
||||
indexPatternId: 'INDEX_PATTERN_ID',
|
||||
},
|
||||
metricAlias: 'METRIC_ALIAS',
|
||||
anomalyThreshold: 0,
|
||||
inventoryDefaultView: 'INVENTORY_DEFAULT_VIEW',
|
||||
metricsExplorerDefaultView: 'METRICS_EXPLORER_DEFAULT_VIEW',
|
||||
};
|
||||
|
||||
const sourceConfigurationWithIndexNameReference: InfraSourceConfiguration = {
|
||||
...sourceConfigurationWithIndexPatternReference,
|
||||
logIndices: {
|
||||
type: 'index_name',
|
||||
indexName: 'INDEX_NAME',
|
||||
},
|
||||
};
|
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
* 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 { SavedObjectReference } from 'src/core/server';
|
||||
import {
|
||||
InfraSavedSourceConfiguration,
|
||||
InfraSourceConfiguration,
|
||||
} from '../../../common/source_configuration/source_configuration';
|
||||
import { SavedObjectReferenceResolutionError } from './errors';
|
||||
|
||||
const logIndexPatternReferenceName = 'log_index_pattern_0';
|
||||
|
||||
interface SavedObjectAttributesWithReferences<SavedObjectAttributes> {
|
||||
attributes: SavedObjectAttributes;
|
||||
references: SavedObjectReference[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewrites a source configuration such that well-known saved object references
|
||||
* are extracted in the `references` array and replaced by the appropriate
|
||||
* name. This is the inverse operation to `resolveSavedObjectReferences`.
|
||||
*/
|
||||
export const extractSavedObjectReferences = (
|
||||
sourceConfiguration: InfraSourceConfiguration
|
||||
): SavedObjectAttributesWithReferences<InfraSourceConfiguration> =>
|
||||
[extractLogIndicesSavedObjectReferences].reduce<
|
||||
SavedObjectAttributesWithReferences<InfraSourceConfiguration>
|
||||
>(
|
||||
({ attributes: accumulatedAttributes, references: accumulatedReferences }, extract) => {
|
||||
const { attributes, references } = extract(accumulatedAttributes);
|
||||
return {
|
||||
attributes,
|
||||
references: [...accumulatedReferences, ...references],
|
||||
};
|
||||
},
|
||||
{
|
||||
attributes: sourceConfiguration,
|
||||
references: [],
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Rewrites a source configuration such that well-known saved object references
|
||||
* are resolved from the `references` argument and replaced by the real saved
|
||||
* object ids. This is the inverse operation to `extractSavedObjectReferences`.
|
||||
*/
|
||||
export const resolveSavedObjectReferences = (
|
||||
attributes: InfraSavedSourceConfiguration,
|
||||
references: SavedObjectReference[]
|
||||
): InfraSavedSourceConfiguration =>
|
||||
[resolveLogIndicesSavedObjectReferences].reduce<InfraSavedSourceConfiguration>(
|
||||
(accumulatedAttributes, resolve) => resolve(accumulatedAttributes, references),
|
||||
attributes
|
||||
);
|
||||
|
||||
const extractLogIndicesSavedObjectReferences = (
|
||||
sourceConfiguration: InfraSourceConfiguration
|
||||
): SavedObjectAttributesWithReferences<InfraSourceConfiguration> => {
|
||||
if (sourceConfiguration.logIndices.type === 'index_pattern') {
|
||||
const logIndexPatternReference: SavedObjectReference = {
|
||||
id: sourceConfiguration.logIndices.indexPatternId,
|
||||
type: 'index-pattern',
|
||||
name: logIndexPatternReferenceName,
|
||||
};
|
||||
const attributes: InfraSourceConfiguration = {
|
||||
...sourceConfiguration,
|
||||
logIndices: {
|
||||
...sourceConfiguration.logIndices,
|
||||
indexPatternId: logIndexPatternReference.name,
|
||||
},
|
||||
};
|
||||
return {
|
||||
attributes,
|
||||
references: [logIndexPatternReference],
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
attributes: sourceConfiguration,
|
||||
references: [],
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const resolveLogIndicesSavedObjectReferences = (
|
||||
attributes: InfraSavedSourceConfiguration,
|
||||
references: SavedObjectReference[]
|
||||
): InfraSavedSourceConfiguration => {
|
||||
if (attributes.logIndices?.type === 'index_pattern') {
|
||||
const logIndexPatternReference = references.find(
|
||||
(reference) => reference.name === logIndexPatternReferenceName
|
||||
);
|
||||
|
||||
if (logIndexPatternReference == null) {
|
||||
throw new SavedObjectReferenceResolutionError(
|
||||
`Failed to resolve log index pattern reference "${logIndexPatternReferenceName}".`
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
...attributes,
|
||||
logIndices: {
|
||||
...attributes.logIndices,
|
||||
indexPatternId: logIndexPatternReference.id,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
return attributes;
|
||||
}
|
||||
};
|
|
@ -5,6 +5,8 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { SavedObject } from '../../../../../../src/core/server';
|
||||
import { infraSourceConfigurationSavedObjectName } from './saved_object_type';
|
||||
import { InfraSources } from './sources';
|
||||
|
||||
describe('the InfraSources lib', () => {
|
||||
|
@ -18,9 +20,10 @@ describe('the InfraSources lib', () => {
|
|||
id: 'TEST_ID',
|
||||
version: 'foo',
|
||||
updated_at: '2000-01-01T00:00:00.000Z',
|
||||
type: infraSourceConfigurationSavedObjectName,
|
||||
attributes: {
|
||||
metricAlias: 'METRIC_ALIAS',
|
||||
logIndices: { type: 'index_pattern', indexPatternId: 'LOG_ALIAS' },
|
||||
logIndices: { type: 'index_pattern', indexPatternId: 'log_index_pattern_0' },
|
||||
fields: {
|
||||
container: 'CONTAINER',
|
||||
host: 'HOST',
|
||||
|
@ -29,6 +32,13 @@ describe('the InfraSources lib', () => {
|
|||
timestamp: 'TIMESTAMP',
|
||||
},
|
||||
},
|
||||
references: [
|
||||
{
|
||||
id: 'LOG_INDEX_PATTERN',
|
||||
name: 'log_index_pattern_0',
|
||||
type: 'index-pattern',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(
|
||||
|
@ -39,7 +49,7 @@ describe('the InfraSources lib', () => {
|
|||
updatedAt: 946684800000,
|
||||
configuration: {
|
||||
metricAlias: 'METRIC_ALIAS',
|
||||
logIndices: { type: 'index_pattern', indexPatternId: 'LOG_ALIAS' },
|
||||
logIndices: { type: 'index_pattern', indexPatternId: 'LOG_INDEX_PATTERN' },
|
||||
fields: {
|
||||
container: 'CONTAINER',
|
||||
host: 'HOST',
|
||||
|
@ -70,12 +80,14 @@ describe('the InfraSources lib', () => {
|
|||
const request: any = createRequestContext({
|
||||
id: 'TEST_ID',
|
||||
version: 'foo',
|
||||
type: infraSourceConfigurationSavedObjectName,
|
||||
updated_at: '2000-01-01T00:00:00.000Z',
|
||||
attributes: {
|
||||
fields: {
|
||||
container: 'CONTAINER',
|
||||
},
|
||||
},
|
||||
references: [],
|
||||
});
|
||||
|
||||
expect(
|
||||
|
@ -106,8 +118,10 @@ describe('the InfraSources lib', () => {
|
|||
const request: any = createRequestContext({
|
||||
id: 'TEST_ID',
|
||||
version: 'foo',
|
||||
type: infraSourceConfigurationSavedObjectName,
|
||||
updated_at: '2000-01-01T00:00:00.000Z',
|
||||
attributes: {},
|
||||
references: [],
|
||||
});
|
||||
|
||||
expect(
|
||||
|
@ -140,7 +154,7 @@ const createMockStaticConfiguration = (sources: any) => ({
|
|||
sources,
|
||||
});
|
||||
|
||||
const createRequestContext = (savedObject?: any) => {
|
||||
const createRequestContext = (savedObject?: SavedObject<unknown>) => {
|
||||
return {
|
||||
core: {
|
||||
savedObjects: {
|
||||
|
|
|
@ -5,26 +5,29 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { failure } from 'io-ts/lib/PathReporter';
|
||||
import { identity, constant } from 'fp-ts/lib/function';
|
||||
import { fold, map } from 'fp-ts/lib/Either';
|
||||
import { constant, identity } from 'fp-ts/lib/function';
|
||||
import { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { map, fold } from 'fp-ts/lib/Either';
|
||||
import { failure } from 'io-ts/lib/PathReporter';
|
||||
import { inRange } from 'lodash';
|
||||
import { SavedObjectsClientContract } from 'src/core/server';
|
||||
import { defaultSourceConfiguration } from './defaults';
|
||||
import { AnomalyThresholdRangeError, NotFoundError } from './errors';
|
||||
import { infraSourceConfigurationSavedObjectName } from './saved_object_type';
|
||||
import { SavedObject, SavedObjectsClientContract } from 'src/core/server';
|
||||
import {
|
||||
InfraSavedSourceConfiguration,
|
||||
InfraSource,
|
||||
InfraSourceConfiguration,
|
||||
InfraStaticSourceConfiguration,
|
||||
pickSavedSourceConfiguration,
|
||||
SourceConfigurationSavedObjectRuntimeType,
|
||||
InfraSource,
|
||||
sourceConfigurationConfigFilePropertiesRT,
|
||||
SourceConfigurationConfigFileProperties,
|
||||
sourceConfigurationConfigFilePropertiesRT,
|
||||
SourceConfigurationSavedObjectRuntimeType,
|
||||
} from '../../../common/source_configuration/source_configuration';
|
||||
import { InfraConfig } from '../../../server';
|
||||
import { defaultSourceConfiguration } from './defaults';
|
||||
import { AnomalyThresholdRangeError, NotFoundError } from './errors';
|
||||
import {
|
||||
extractSavedObjectReferences,
|
||||
resolveSavedObjectReferences,
|
||||
} from './saved_object_references';
|
||||
import { infraSourceConfigurationSavedObjectName } from './saved_object_type';
|
||||
|
||||
interface Libs {
|
||||
config: InfraConfig;
|
||||
|
@ -113,13 +116,13 @@ export class InfraSources {
|
|||
staticDefaultSourceConfiguration,
|
||||
source
|
||||
);
|
||||
const { attributes, references } = extractSavedObjectReferences(newSourceConfiguration);
|
||||
|
||||
const createdSourceConfiguration = convertSavedObjectToSavedSourceConfiguration(
|
||||
await savedObjectsClient.create(
|
||||
infraSourceConfigurationSavedObjectName,
|
||||
pickSavedSourceConfiguration(newSourceConfiguration) as any,
|
||||
{ id: sourceId }
|
||||
)
|
||||
await savedObjectsClient.create(infraSourceConfigurationSavedObjectName, attributes, {
|
||||
id: sourceId,
|
||||
references,
|
||||
})
|
||||
);
|
||||
|
||||
return {
|
||||
|
@ -158,19 +161,19 @@ export class InfraSources {
|
|||
configuration,
|
||||
sourceProperties
|
||||
);
|
||||
const { attributes, references } = extractSavedObjectReferences(
|
||||
updatedSourceConfigurationAttributes
|
||||
);
|
||||
|
||||
const updatedSourceConfiguration = convertSavedObjectToSavedSourceConfiguration(
|
||||
// update() will perform a deep merge. We use create() with overwrite: true instead. mergeSourceConfiguration()
|
||||
// ensures the correct and intended merging of properties.
|
||||
await savedObjectsClient.create(
|
||||
infraSourceConfigurationSavedObjectName,
|
||||
pickSavedSourceConfiguration(updatedSourceConfigurationAttributes) as any,
|
||||
{
|
||||
id: sourceId,
|
||||
version,
|
||||
overwrite: true,
|
||||
}
|
||||
)
|
||||
await savedObjectsClient.create(infraSourceConfigurationSavedObjectName, attributes, {
|
||||
id: sourceId,
|
||||
overwrite: true,
|
||||
references,
|
||||
version,
|
||||
})
|
||||
);
|
||||
|
||||
return {
|
||||
|
@ -267,7 +270,7 @@ const mergeSourceConfiguration = (
|
|||
first
|
||||
);
|
||||
|
||||
export const convertSavedObjectToSavedSourceConfiguration = (savedObject: unknown) =>
|
||||
export const convertSavedObjectToSavedSourceConfiguration = (savedObject: SavedObject<unknown>) =>
|
||||
pipe(
|
||||
SourceConfigurationSavedObjectRuntimeType.decode(savedObject),
|
||||
map((savedSourceConfiguration) => ({
|
||||
|
@ -275,7 +278,10 @@ export const convertSavedObjectToSavedSourceConfiguration = (savedObject: unknow
|
|||
version: savedSourceConfiguration.version,
|
||||
updatedAt: savedSourceConfiguration.updated_at,
|
||||
origin: 'stored' as 'stored',
|
||||
configuration: savedSourceConfiguration.attributes,
|
||||
configuration: resolveSavedObjectReferences(
|
||||
savedSourceConfiguration.attributes,
|
||||
savedObject.references
|
||||
),
|
||||
})),
|
||||
fold((errors) => {
|
||||
throw new Error(failure(errors).join('\n'));
|
||||
|
|
|
@ -41,11 +41,13 @@ export const initSnapshotRoute = (libs: InfraBackendLibs) => {
|
|||
snapshotRequest.sourceId
|
||||
);
|
||||
const compositeSize = libs.configuration.inventory.compositeSize;
|
||||
const logQueryFields = await libs.getLogQueryFields(
|
||||
snapshotRequest.sourceId,
|
||||
requestContext.core.savedObjects.client,
|
||||
requestContext.core.elasticsearch.client.asCurrentUser
|
||||
);
|
||||
const logQueryFields = await libs
|
||||
.getLogQueryFields(
|
||||
snapshotRequest.sourceId,
|
||||
requestContext.core.savedObjects.client,
|
||||
requestContext.core.elasticsearch.client.asCurrentUser
|
||||
)
|
||||
.catch(() => undefined);
|
||||
|
||||
UsageCollector.countNode(snapshotRequest.nodeType);
|
||||
const client = createSearchClient(requestContext, framework);
|
||||
|
@ -55,8 +57,8 @@ export const initSnapshotRoute = (libs: InfraBackendLibs) => {
|
|||
client,
|
||||
snapshotRequest,
|
||||
source,
|
||||
logQueryFields,
|
||||
compositeSize
|
||||
compositeSize,
|
||||
logQueryFields
|
||||
);
|
||||
return response.ok({
|
||||
body: SnapshotNodeResponseRT.encode(snapshotResponse),
|
||||
|
|
|
@ -53,21 +53,25 @@ export const getNodes = async (
|
|||
client: ESSearchClient,
|
||||
snapshotRequest: SnapshotRequest,
|
||||
source: InfraSource,
|
||||
logQueryFields: LogQueryFields,
|
||||
compositeSize: number
|
||||
compositeSize: number,
|
||||
logQueryFields?: LogQueryFields
|
||||
) => {
|
||||
let nodes;
|
||||
|
||||
if (snapshotRequest.metrics.find((metric) => metric.type === 'logRate')) {
|
||||
// *Only* the log rate metric has been requested
|
||||
if (snapshotRequest.metrics.length === 1) {
|
||||
nodes = await transformAndQueryData({
|
||||
client,
|
||||
snapshotRequest,
|
||||
source,
|
||||
compositeSize,
|
||||
sourceOverrides: logQueryFields,
|
||||
});
|
||||
if (logQueryFields != null) {
|
||||
nodes = await transformAndQueryData({
|
||||
client,
|
||||
snapshotRequest,
|
||||
source,
|
||||
compositeSize,
|
||||
sourceOverrides: logQueryFields,
|
||||
});
|
||||
} else {
|
||||
nodes = { nodes: [], interval: '60s' };
|
||||
}
|
||||
} else {
|
||||
// A scenario whereby a single host might be shipping metrics and logs.
|
||||
const metricsWithoutLogsMetrics = snapshotRequest.metrics.filter(
|
||||
|
@ -79,13 +83,16 @@ export const getNodes = async (
|
|||
source,
|
||||
compositeSize,
|
||||
});
|
||||
const logRateNodes = await transformAndQueryData({
|
||||
client,
|
||||
snapshotRequest: { ...snapshotRequest, metrics: [{ type: 'logRate' }] },
|
||||
source,
|
||||
compositeSize,
|
||||
sourceOverrides: logQueryFields,
|
||||
});
|
||||
const logRateNodes =
|
||||
logQueryFields != null
|
||||
? await transformAndQueryData({
|
||||
client,
|
||||
snapshotRequest: { ...snapshotRequest, metrics: [{ type: 'logRate' }] },
|
||||
source,
|
||||
compositeSize,
|
||||
sourceOverrides: logQueryFields,
|
||||
})
|
||||
: { nodes: [], interval: '60s' };
|
||||
// Merge nodes where possible - e.g. a single host is shipping metrics and logs
|
||||
const mergedNodes = nodesWithoutLogsMetrics.nodes.map((node) => {
|
||||
const logRateNode = logRateNodes.nodes.find(
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue