mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 01:13:23 -04:00
[Logs UI] Support inline Log Views in the UI (#152933)
## Summary This closes #142840. It is the UI portion of support for inline Log Views. ## Visible changes to the UI ### ML warning  ### Alert dropdown warning  ### Settings page warning  ## Reviewer hints / notes - The ACs on the issue are quite extensive and should provide a good number of things to test. - Make use of the "playground" page (see below) to make this easier - The `AlertDropdown` has been made lazy as the page load bundle increased by 100kb otherwise. - Our `link-to` functionality is scoped to persisted Log Views only at the moment as historically they've only accepted a path segment, not full query parameters. We can look to extend this in the future once we have concrete linking needs. ## Questions - I have allowed the Log View client to handle both the inline and persisted Log Views. I wonder if the function names become confusing? (e.g. are the RESTful prefixes misleading now?). - The ML warning splash page links to settings to revert to a persisted Log View. It could also be done in place on the page. I went back and forth over whether we should keep the reverting in one place? ## Testing There is now a "state machine playground" available at the following URL: `/app/logs/state-machine-playground`, it is enabled in developer mode only. It's not fancy or pretty it just serves to make testing things easier. There are various buttons, e.g. `Switch to inline Log View`, to facilitate easier testing whilst a Log View switcher is not in the UI itself. You can utilise these buttons, and then head to other pages to ensure things are working correctly, e.g. warning callouts and disabled buttons etc. If you'd like to play with the options used, e.g. for `update`, amend the code within `state_machine_playground.tsx`. It's also useful just to sit on this page, try various things, and verify what happens in the developer tools (does the context update correctly etc). ## Known issues - When saving on the settings page we actually revert to a "Loading data sources" state. This is also the case on `main`. The reason for this is the check within settings looks like so: ```ts if ((isLoading || isUninitialized) && !resolvedLogView) { return <SourceLoadingPage />; } ``` but the `resolvedLogView` state matching looks like so: ```ts const resolvedLogView = useSelector(logViewStateService, (state) => state.matches('checkingStatus') || state.matches('resolvedPersistedLogView') || state.matches('resolvedInlineLogView') ? state.context.resolvedLogView : undefined ); ``` so even though we have resolved a Log View previously the state matching overrides this. I'd prefer to follow this up in a separate issue as I'd like to think through the ramifications a bit more. It's not a bug, but it is jarring UX. --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
e13f1c5fb2
commit
4c586a71d4
81 changed files with 1098 additions and 416 deletions
|
@ -5,7 +5,6 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
export const DEFAULT_SOURCE_ID = 'default';
|
||||
export const METRICS_INDEX_PATTERN = 'metrics-*,metricbeat-*';
|
||||
export const LOGS_INDEX_PATTERN = 'logs-*,filebeat-*,kibana_sample_data_logs*';
|
||||
export const METRICS_APP = 'metrics';
|
||||
|
|
|
@ -16,11 +16,11 @@ export const partitionField = 'event.dataset';
|
|||
export const getJobIdPrefix = (spaceId: string, sourceId: string) =>
|
||||
`kibana-logs-ui-${spaceId}-${sourceId}-`;
|
||||
|
||||
export const getJobId = (spaceId: string, sourceId: string, jobType: string) =>
|
||||
`${getJobIdPrefix(spaceId, sourceId)}${jobType}`;
|
||||
export const getJobId = (spaceId: string, logViewId: string, jobType: string) =>
|
||||
`${getJobIdPrefix(spaceId, logViewId)}${jobType}`;
|
||||
|
||||
export const getDatafeedId = (spaceId: string, sourceId: string, jobType: string) =>
|
||||
`datafeed-${getJobId(spaceId, sourceId, jobType)}`;
|
||||
export const getDatafeedId = (spaceId: string, logViewId: string, jobType: string) =>
|
||||
`datafeed-${getJobId(spaceId, logViewId, jobType)}`;
|
||||
|
||||
export const datasetFilterRT = rt.union([
|
||||
rt.strict({
|
||||
|
|
|
@ -14,6 +14,7 @@ export interface LogViewsStaticConfig {
|
|||
export const logViewOriginRT = rt.keyof({
|
||||
stored: null,
|
||||
internal: null,
|
||||
inline: null,
|
||||
'infra-source-stored': null,
|
||||
'infra-source-internal': null,
|
||||
'infra-source-fallback': null,
|
||||
|
|
|
@ -42,7 +42,10 @@ const AlertDetailsAppSection = ({ rule, alert }: AlertDetailsAppSectionProps) =>
|
|||
<CriterionPreview
|
||||
key={chartCriterion.field}
|
||||
ruleParams={rule.params}
|
||||
sourceId={rule.params.logView.logViewId}
|
||||
logViewReference={{
|
||||
type: 'log-view-reference',
|
||||
logViewId: rule.params.logView.logViewId,
|
||||
}}
|
||||
chartCriterion={chartCriterion}
|
||||
showThreshold={true}
|
||||
executionTimeRange={{ gte: rangeFrom, lte: rangeTo }}
|
||||
|
|
|
@ -9,6 +9,7 @@ import React, { useState, useCallback, useMemo } from 'react';
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiPopover, EuiContextMenuItem, EuiContextMenuPanel, EuiHeaderLink } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { useLogViewContext } from '../../../hooks/use_log_view';
|
||||
import { AlertFlyout } from './alert_flyout';
|
||||
import { useKibanaContextForPlugin } from '../../../hooks/use_kibana';
|
||||
|
||||
|
@ -26,6 +27,20 @@ const readOnlyUserTooltipTitle = i18n.translate(
|
|||
}
|
||||
);
|
||||
|
||||
const inlineLogViewTooltipTitle = i18n.translate(
|
||||
'xpack.infra.logs.alertDropdown.inlineLogViewCreateAlertTitle',
|
||||
{
|
||||
defaultMessage: 'Inline Log View',
|
||||
}
|
||||
);
|
||||
|
||||
const inlineLogViewTooltipContent = i18n.translate(
|
||||
'xpack.infra.logs.alertDropdown.inlineLogViewCreateAlertContent',
|
||||
{
|
||||
defaultMessage: 'Creating alerts is not supported with inline Log Views',
|
||||
}
|
||||
);
|
||||
|
||||
export const AlertDropdown = () => {
|
||||
const {
|
||||
services: {
|
||||
|
@ -33,7 +48,9 @@ export const AlertDropdown = () => {
|
|||
observability,
|
||||
},
|
||||
} = useKibanaContextForPlugin();
|
||||
const canCreateAlerts = capabilities?.logs?.save ?? false;
|
||||
const { isPersistedLogView } = useLogViewContext();
|
||||
const readOnly = !capabilities?.logs?.save;
|
||||
const canCreateAlerts = (!readOnly && isPersistedLogView) ?? false;
|
||||
const [popoverOpen, setPopoverOpen] = useState(false);
|
||||
const [flyoutVisible, setFlyoutVisible] = useState(false);
|
||||
|
||||
|
@ -61,8 +78,20 @@ export const AlertDropdown = () => {
|
|||
icon="bell"
|
||||
key="createLink"
|
||||
onClick={openFlyout}
|
||||
toolTipContent={!canCreateAlerts ? readOnlyUserTooltipContent : undefined}
|
||||
toolTipTitle={!canCreateAlerts ? readOnlyUserTooltipTitle : undefined}
|
||||
toolTipContent={
|
||||
!canCreateAlerts
|
||||
? readOnly
|
||||
? readOnlyUserTooltipContent
|
||||
: inlineLogViewTooltipContent
|
||||
: undefined
|
||||
}
|
||||
toolTipTitle={
|
||||
!canCreateAlerts
|
||||
? readOnly
|
||||
? readOnlyUserTooltipTitle
|
||||
: inlineLogViewTooltipTitle
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.alerting.logs.createAlertButton"
|
||||
|
@ -76,7 +105,7 @@ export const AlertDropdown = () => {
|
|||
/>
|
||||
</EuiContextMenuItem>,
|
||||
];
|
||||
}, [manageRulesLinkProps, canCreateAlerts, openFlyout]);
|
||||
}, [canCreateAlerts, openFlyout, readOnly, manageRulesLinkProps]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -104,3 +133,7 @@ export const AlertDropdown = () => {
|
|||
</>
|
||||
);
|
||||
};
|
||||
|
||||
// Allow for lazy loading
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default AlertDropdown;
|
||||
|
|
|
@ -9,7 +9,10 @@ import React, { useCallback } from 'react';
|
|||
import { EuiFlexItem, EuiFlexGroup, EuiButtonEmpty, EuiAccordion, EuiSpacer } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import type { ResolvedLogViewField } from '../../../../../common/log_views';
|
||||
import type {
|
||||
PersistedLogViewReference,
|
||||
ResolvedLogViewField,
|
||||
} from '../../../../../common/log_views';
|
||||
import { Criterion } from './criterion';
|
||||
import {
|
||||
PartialRuleParams,
|
||||
|
@ -39,7 +42,7 @@ interface SharedProps {
|
|||
defaultCriterion: PartialCriterionType;
|
||||
errors: Errors['criteria'];
|
||||
ruleParams: PartialRuleParams;
|
||||
sourceId: string;
|
||||
logViewReference: PersistedLogViewReference;
|
||||
updateCriteria: (criteria: PartialCriteriaType) => void;
|
||||
}
|
||||
|
||||
|
@ -64,7 +67,7 @@ interface CriteriaWrapperProps {
|
|||
addCriterion: () => void;
|
||||
criteria: PartialCountCriteriaType;
|
||||
errors: CriterionErrors;
|
||||
sourceId: SharedProps['sourceId'];
|
||||
logViewReference: SharedProps['logViewReference'];
|
||||
isRatio?: boolean;
|
||||
}
|
||||
|
||||
|
@ -77,7 +80,7 @@ const CriteriaWrapper: React.FC<CriteriaWrapperProps> = (props) => {
|
|||
fields,
|
||||
errors,
|
||||
ruleParams,
|
||||
sourceId,
|
||||
logViewReference,
|
||||
isRatio = false,
|
||||
} = props;
|
||||
|
||||
|
@ -105,7 +108,7 @@ const CriteriaWrapper: React.FC<CriteriaWrapperProps> = (props) => {
|
|||
<CriterionPreview
|
||||
ruleParams={ruleParams}
|
||||
chartCriterion={criterion}
|
||||
sourceId={sourceId}
|
||||
logViewReference={logViewReference}
|
||||
showThreshold={!isRatio}
|
||||
/>
|
||||
</EuiAccordion>
|
||||
|
|
|
@ -21,6 +21,7 @@ import {
|
|||
import { EuiText } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import { PersistedLogViewReference } from '../../../../../common/log_views';
|
||||
import { ExecutionTimeRange } from '../../../../types';
|
||||
import {
|
||||
ChartContainer,
|
||||
|
@ -55,7 +56,7 @@ const GROUP_LIMIT = 5;
|
|||
interface Props {
|
||||
ruleParams: PartialRuleParams;
|
||||
chartCriterion: Partial<Criterion>;
|
||||
sourceId: string;
|
||||
logViewReference: PersistedLogViewReference;
|
||||
showThreshold: boolean;
|
||||
executionTimeRange?: ExecutionTimeRange;
|
||||
}
|
||||
|
@ -63,7 +64,7 @@ interface Props {
|
|||
export const CriterionPreview: React.FC<Props> = ({
|
||||
ruleParams,
|
||||
chartCriterion,
|
||||
sourceId,
|
||||
logViewReference,
|
||||
showThreshold,
|
||||
executionTimeRange,
|
||||
}) => {
|
||||
|
@ -105,7 +106,7 @@ export const CriterionPreview: React.FC<Props> = ({
|
|||
? NUM_BUCKETS
|
||||
: NUM_BUCKETS / 4
|
||||
} // Display less data for groups due to space limitations
|
||||
sourceId={sourceId}
|
||||
logViewReference={logViewReference}
|
||||
threshold={ruleParams.count}
|
||||
chartAlertParams={chartAlertParams}
|
||||
showThreshold={showThreshold}
|
||||
|
@ -116,7 +117,7 @@ export const CriterionPreview: React.FC<Props> = ({
|
|||
|
||||
interface ChartProps {
|
||||
buckets: number;
|
||||
sourceId: string;
|
||||
logViewReference: PersistedLogViewReference;
|
||||
threshold?: Threshold;
|
||||
chartAlertParams: GetLogAlertsChartPreviewDataAlertParamsSubset;
|
||||
showThreshold: boolean;
|
||||
|
@ -125,7 +126,7 @@ interface ChartProps {
|
|||
|
||||
const CriterionPreviewChart: React.FC<ChartProps> = ({
|
||||
buckets,
|
||||
sourceId,
|
||||
logViewReference,
|
||||
threshold,
|
||||
chartAlertParams,
|
||||
showThreshold,
|
||||
|
@ -141,7 +142,7 @@ const CriterionPreviewChart: React.FC<ChartProps> = ({
|
|||
hasError,
|
||||
chartPreviewData: series,
|
||||
} = useChartPreviewData({
|
||||
sourceId,
|
||||
logViewReference,
|
||||
ruleParams: chartAlertParams,
|
||||
buckets,
|
||||
executionTimeRange,
|
||||
|
|
|
@ -27,7 +27,6 @@ import {
|
|||
} from '../../../../../common/alerting/logs/log_threshold/types';
|
||||
import { decodeOrThrow } from '../../../../../common/runtime_types';
|
||||
import { ObjectEntries } from '../../../../../common/utility_types';
|
||||
import { useSourceId } from '../../../../containers/source_id';
|
||||
import { useKibanaContextForPlugin } from '../../../../hooks/use_kibana';
|
||||
import { LogViewProvider, useLogViewContext } from '../../../../hooks/use_log_view';
|
||||
import { GroupByExpression } from '../../../common/group_by_expression/group_by_expression';
|
||||
|
@ -54,11 +53,6 @@ const DEFAULT_BASE_EXPRESSION = {
|
|||
|
||||
const DEFAULT_FIELD = 'log.level';
|
||||
|
||||
const createLogViewReference = (logViewId: string): PersistedLogViewReference => ({
|
||||
logViewId,
|
||||
type: 'log-view-reference',
|
||||
});
|
||||
|
||||
const createDefaultCriterion = (
|
||||
availableFields: ResolvedLogViewField[],
|
||||
value: ExpressionCriteria['value']
|
||||
|
@ -100,7 +94,6 @@ export const ExpressionEditor: React.FC<
|
|||
RuleTypeParamsExpressionProps<PartialRuleParams, LogsContextMeta>
|
||||
> = (props) => {
|
||||
const isInternal = props.metadata?.isInternal ?? false;
|
||||
const [logViewId] = useSourceId();
|
||||
const {
|
||||
services: { logViews },
|
||||
} = useKibanaContextForPlugin(); // injected during alert registration
|
||||
|
@ -112,7 +105,7 @@ export const ExpressionEditor: React.FC<
|
|||
<Editor {...props} />
|
||||
</SourceStatusWrapper>
|
||||
) : (
|
||||
<LogViewProvider logViewId={logViewId} logViews={logViews.client}>
|
||||
<LogViewProvider logViews={logViews.client}>
|
||||
<SourceStatusWrapper {...props}>
|
||||
<Editor {...props} />
|
||||
</SourceStatusWrapper>
|
||||
|
@ -163,7 +156,11 @@ export const Editor: React.FC<RuleTypeParamsExpressionProps<PartialRuleParams, L
|
|||
) => {
|
||||
const { setRuleParams, ruleParams, errors } = props;
|
||||
const [hasSetDefaults, setHasSetDefaults] = useState<boolean>(false);
|
||||
const { logViewId, resolvedLogView } = useLogViewContext();
|
||||
const { logViewReference, resolvedLogView } = useLogViewContext();
|
||||
|
||||
if (logViewReference.type !== 'log-view-reference') {
|
||||
throw new Error('The Log Threshold rule type only supports persisted Log Views');
|
||||
}
|
||||
|
||||
const {
|
||||
criteria: criteriaErrors,
|
||||
|
@ -230,8 +227,6 @@ export const Editor: React.FC<RuleTypeParamsExpressionProps<PartialRuleParams, L
|
|||
[setRuleParams]
|
||||
);
|
||||
|
||||
const logViewReference = useMemo(() => createLogViewReference(logViewId), [logViewId]);
|
||||
|
||||
const defaultCountAlertParams = useMemo(
|
||||
() => createDefaultCountRuleParams(supportedFields, logViewReference),
|
||||
[supportedFields, logViewReference]
|
||||
|
@ -279,7 +274,7 @@ export const Editor: React.FC<RuleTypeParamsExpressionProps<PartialRuleParams, L
|
|||
defaultCriterion={defaultCountAlertParams.criteria[0]}
|
||||
errors={criteriaErrors}
|
||||
ruleParams={ruleParams}
|
||||
sourceId={logViewId}
|
||||
logViewReference={logViewReference}
|
||||
updateCriteria={updateCriteria}
|
||||
/>
|
||||
) : null;
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import { useState, useMemo } from 'react';
|
||||
import { HttpHandler } from '@kbn/core/public';
|
||||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import { PersistedLogViewReference } from '../../../../../../common/log_views';
|
||||
import { ExecutionTimeRange } from '../../../../../types';
|
||||
import { useTrackedPromise } from '../../../../../utils/use_tracked_promise';
|
||||
import {
|
||||
|
@ -20,14 +21,14 @@ import { decodeOrThrow } from '../../../../../../common/runtime_types';
|
|||
import { GetLogAlertsChartPreviewDataAlertParamsSubset } from '../../../../../../common/http_api/log_alerts';
|
||||
|
||||
interface Options {
|
||||
sourceId: string;
|
||||
logViewReference: PersistedLogViewReference;
|
||||
ruleParams: GetLogAlertsChartPreviewDataAlertParamsSubset;
|
||||
buckets: number;
|
||||
executionTimeRange?: ExecutionTimeRange;
|
||||
}
|
||||
|
||||
export const useChartPreviewData = ({
|
||||
sourceId,
|
||||
logViewReference,
|
||||
ruleParams,
|
||||
buckets,
|
||||
executionTimeRange,
|
||||
|
@ -43,7 +44,7 @@ export const useChartPreviewData = ({
|
|||
createPromise: async () => {
|
||||
setHasError(false);
|
||||
return await callGetChartPreviewDataAPI(
|
||||
sourceId,
|
||||
logViewReference,
|
||||
http!.fetch,
|
||||
ruleParams,
|
||||
buckets,
|
||||
|
@ -58,7 +59,7 @@ export const useChartPreviewData = ({
|
|||
setHasError(true);
|
||||
},
|
||||
},
|
||||
[sourceId, http, ruleParams, buckets]
|
||||
[logViewReference, http, ruleParams, buckets]
|
||||
);
|
||||
|
||||
const isLoading = useMemo(
|
||||
|
@ -75,7 +76,7 @@ export const useChartPreviewData = ({
|
|||
};
|
||||
|
||||
export const callGetChartPreviewDataAPI = async (
|
||||
sourceId: string,
|
||||
logViewReference: PersistedLogViewReference,
|
||||
fetch: HttpHandler,
|
||||
alertParams: GetLogAlertsChartPreviewDataAlertParamsSubset,
|
||||
buckets: number,
|
||||
|
@ -86,7 +87,7 @@ export const callGetChartPreviewDataAPI = async (
|
|||
body: JSON.stringify(
|
||||
getLogAlertsChartPreviewDataRequestPayloadRT.encode({
|
||||
data: {
|
||||
logView: { type: 'log-view-reference', logViewId: sourceId },
|
||||
logView: logViewReference,
|
||||
alertParams,
|
||||
buckets,
|
||||
executionTimeRange,
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* 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 React from 'react';
|
||||
|
||||
const LazyAlertDropdown = React.lazy(() => import('./alert_dropdown'));
|
||||
|
||||
export const LazyAlertDropdownWrapper = () => (
|
||||
<React.Suspense fallback={<div />}>
|
||||
<LazyAlertDropdown />
|
||||
</React.Suspense>
|
||||
);
|
|
@ -6,4 +6,4 @@
|
|||
*/
|
||||
|
||||
export * from './log_threshold_rule_type';
|
||||
export { AlertDropdown } from './components/alert_dropdown';
|
||||
export { LazyAlertDropdownWrapper } from './components/lazy_alert_dropdown';
|
||||
|
|
|
@ -13,7 +13,7 @@ import { euiStyled } from '@kbn/kibana-react-plugin/common';
|
|||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import { noop } from 'lodash';
|
||||
import { LogEntryCursor } from '../../../common/log_entry';
|
||||
import { defaultLogViewsStaticConfig } from '../../../common/log_views';
|
||||
import { defaultLogViewsStaticConfig, LogViewReference } from '../../../common/log_views';
|
||||
import { BuiltEsQuery, useLogStream } from '../../containers/logs/log_stream';
|
||||
import { useLogView } from '../../hooks/use_log_view';
|
||||
import { LogViewsClient } from '../../services/log_views';
|
||||
|
@ -63,14 +63,8 @@ type LogColumnDefinition =
|
|||
export interface LogStreamProps extends LogStreamContentProps {
|
||||
height?: string | number;
|
||||
}
|
||||
|
||||
interface LogView {
|
||||
type: 'log-view-reference';
|
||||
logViewId: string;
|
||||
}
|
||||
|
||||
interface LogStreamContentProps {
|
||||
logView: LogView;
|
||||
logView: LogViewReference;
|
||||
startTimestamp: number;
|
||||
endTimestamp: number;
|
||||
query?: string | Query | BuiltEsQuery;
|
||||
|
@ -120,7 +114,7 @@ Read more at https://github.com/elastic/kibana/blob/main/src/plugins/kibana_reac
|
|||
);
|
||||
}
|
||||
|
||||
const { openLogEntryFlyout } = useLogEntryFlyout(logView.logViewId);
|
||||
const { openLogEntryFlyout } = useLogEntryFlyout(logView);
|
||||
|
||||
const kibanaQuerySettings = useKibanaQuerySettings();
|
||||
|
||||
|
@ -135,7 +129,7 @@ Read more at https://github.com/elastic/kibana/blob/main/src/plugins/kibana_reac
|
|||
load: loadLogView,
|
||||
resolvedLogView,
|
||||
} = useLogView({
|
||||
logViewId: logView.logViewId,
|
||||
initialLogViewReference: logView,
|
||||
logViews,
|
||||
});
|
||||
|
||||
|
@ -166,7 +160,7 @@ Read more at https://github.com/elastic/kibana/blob/main/src/plugins/kibana_reac
|
|||
isLoadingMore,
|
||||
isReloading: isLoadingEntries,
|
||||
} = useLogStream({
|
||||
sourceId: logView.logViewId,
|
||||
logViewReference: logView,
|
||||
startTimestamp,
|
||||
endTimestamp,
|
||||
query: parsedQuery,
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* 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 React from 'react';
|
||||
import { EuiButton } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { LazyObservabilityPageTemplateProps } from '@kbn/observability-plugin/public';
|
||||
import { EuiEmptyPrompt } from '@elastic/eui';
|
||||
import { EuiText } from '@elastic/eui';
|
||||
import { PageTemplate } from '../page_template';
|
||||
|
||||
type InlineLogViewSplashPageProps = {
|
||||
revertToDefaultLogView: () => void;
|
||||
} & LazyObservabilityPageTemplateProps;
|
||||
|
||||
export const InlineLogViewSplashPage: React.FC<InlineLogViewSplashPageProps> = (props) => {
|
||||
const { revertToDefaultLogView, ...templateProps } = props;
|
||||
return (
|
||||
<PageTemplate {...templateProps} isEmptyState={true}>
|
||||
<InlineLogViewSplashPrompt revertToDefaultLogView={revertToDefaultLogView} />
|
||||
</PageTemplate>
|
||||
);
|
||||
};
|
||||
|
||||
export const InlineLogViewSplashPrompt: React.FC<{
|
||||
revertToDefaultLogView: InlineLogViewSplashPageProps['revertToDefaultLogView'];
|
||||
}> = ({ revertToDefaultLogView }) => {
|
||||
const title = (
|
||||
<FormattedMessage
|
||||
id="xpack.infra.ml.splash.inlineLogView.title"
|
||||
defaultMessage="Switch to a persisted Log View"
|
||||
/>
|
||||
);
|
||||
|
||||
const ctaButton = (
|
||||
<EuiButton
|
||||
data-test-subj="infraInlineLogViewSplashPromptRevertToDefaultPersistedLogViewButton"
|
||||
fullWidth={false}
|
||||
fill
|
||||
onClick={revertToDefaultLogView}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.ml.splash.inlineLogView.buttonText"
|
||||
defaultMessage="Revert to default (persisted) Log View"
|
||||
/>
|
||||
</EuiButton>
|
||||
);
|
||||
|
||||
const description = (
|
||||
<FormattedMessage
|
||||
id="xpack.infra.ml.splash.inlineLogView.description"
|
||||
defaultMessage="This feature does not support inline Log Views"
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiEmptyPrompt
|
||||
iconType={'visLine'}
|
||||
title={<h2>{title}</h2>}
|
||||
body={
|
||||
<EuiText>
|
||||
<p>{description}</p>
|
||||
</EuiText>
|
||||
}
|
||||
actions={ctaButton}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -23,6 +23,7 @@ import { createKibanaReactContext } from '@kbn/kibana-react-plugin/public';
|
|||
import { OverlayRef } from '@kbn/core/public';
|
||||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import { DataPublicPluginStart } from '@kbn/data-plugin/public';
|
||||
import { LogViewReference } from '../../../../common/log_views';
|
||||
import { TimeKey } from '../../../../common/time';
|
||||
import { useLogEntry } from '../../../containers/logs/log_entry';
|
||||
import { CenteredEuiFlyoutBody } from '../../centered_flyout_body';
|
||||
|
@ -35,10 +36,10 @@ export interface LogEntryFlyoutProps {
|
|||
logEntryId: string | null | undefined;
|
||||
onCloseFlyout: () => void;
|
||||
onSetFieldFilter?: (filter: Query, logEntryId: string, timeKey?: TimeKey) => void;
|
||||
sourceId: string | null | undefined;
|
||||
logViewReference: LogViewReference | null | undefined;
|
||||
}
|
||||
|
||||
export const useLogEntryFlyout = (sourceId: string) => {
|
||||
export const useLogEntryFlyout = (logViewReference: LogViewReference) => {
|
||||
const flyoutRef = useRef<OverlayRef>();
|
||||
const {
|
||||
services: { http, data, uiSettings, application },
|
||||
|
@ -63,12 +64,12 @@ export const useLogEntryFlyout = (sourceId: string) => {
|
|||
<LogEntryFlyout
|
||||
logEntryId={logEntryId}
|
||||
onCloseFlyout={closeLogEntryFlyout}
|
||||
sourceId={sourceId}
|
||||
logViewReference={logViewReference}
|
||||
/>
|
||||
</KibanaReactContextProvider>
|
||||
);
|
||||
},
|
||||
[http, data, uiSettings, application, openFlyout, sourceId, closeLogEntryFlyout]
|
||||
[http, data, uiSettings, application, openFlyout, logViewReference, closeLogEntryFlyout]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -87,7 +88,7 @@ export const LogEntryFlyout = ({
|
|||
logEntryId,
|
||||
onCloseFlyout,
|
||||
onSetFieldFilter,
|
||||
sourceId,
|
||||
logViewReference,
|
||||
}: LogEntryFlyoutProps) => {
|
||||
const {
|
||||
cancelRequest: cancelLogEntryRequest,
|
||||
|
@ -98,15 +99,15 @@ export const LogEntryFlyout = ({
|
|||
logEntry,
|
||||
total: logEntryRequestTotal,
|
||||
} = useLogEntry({
|
||||
sourceId,
|
||||
logViewReference,
|
||||
logEntryId,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (sourceId && logEntryId) {
|
||||
if (logViewReference && logEntryId) {
|
||||
fetchLogEntry();
|
||||
}
|
||||
}, [fetchLogEntry, sourceId, logEntryId]);
|
||||
}, [fetchLogEntry, logViewReference, logEntryId]);
|
||||
|
||||
return (
|
||||
<EuiFlyout onClose={onCloseFlyout} size="m">
|
||||
|
|
|
@ -13,7 +13,7 @@ import { decodeOrThrow } from '../../../../../common/runtime_types';
|
|||
|
||||
interface DeleteJobsRequestArgs<JobType extends string> {
|
||||
spaceId: string;
|
||||
sourceId: string;
|
||||
logViewId: string;
|
||||
jobTypes: JobType[];
|
||||
}
|
||||
|
||||
|
@ -21,14 +21,14 @@ export const callDeleteJobs = async <JobType extends string>(
|
|||
requestArgs: DeleteJobsRequestArgs<JobType>,
|
||||
fetch: HttpHandler
|
||||
) => {
|
||||
const { spaceId, sourceId, jobTypes } = requestArgs;
|
||||
const { spaceId, logViewId, jobTypes } = requestArgs;
|
||||
|
||||
// NOTE: Deleting the jobs via this API will delete the datafeeds at the same time
|
||||
const deleteJobsResponse = await fetch('/api/ml/jobs/delete_jobs', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(
|
||||
deleteJobsRequestPayloadRT.encode({
|
||||
jobIds: jobTypes.map((jobType) => getJobId(spaceId, sourceId, jobType)),
|
||||
jobIds: jobTypes.map((jobType) => getJobId(spaceId, logViewId, jobType)),
|
||||
})
|
||||
),
|
||||
});
|
||||
|
@ -44,7 +44,7 @@ export const callGetJobDeletionTasks = async (fetch: HttpHandler) => {
|
|||
|
||||
interface StopDatafeedsRequestArgs<JobType extends string> {
|
||||
spaceId: string;
|
||||
sourceId: string;
|
||||
logViewId: string;
|
||||
jobTypes: JobType[];
|
||||
}
|
||||
|
||||
|
@ -52,14 +52,14 @@ export const callStopDatafeeds = async <JobType extends string>(
|
|||
requestArgs: StopDatafeedsRequestArgs<JobType>,
|
||||
fetch: HttpHandler
|
||||
) => {
|
||||
const { spaceId, sourceId, jobTypes } = requestArgs;
|
||||
const { spaceId, logViewId, jobTypes } = requestArgs;
|
||||
|
||||
// Stop datafeed due to https://github.com/elastic/kibana/issues/44652
|
||||
const stopDatafeedResponse = await fetch('/api/ml/jobs/stop_datafeeds', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(
|
||||
stopDatafeedsRequestPayloadRT.encode({
|
||||
datafeedIds: jobTypes.map((jobType) => getDatafeedId(spaceId, sourceId, jobType)),
|
||||
datafeedIds: jobTypes.map((jobType) => getDatafeedId(spaceId, logViewId, jobType)),
|
||||
})
|
||||
),
|
||||
});
|
||||
|
|
|
@ -13,7 +13,7 @@ import { decodeOrThrow } from '../../../../../common/runtime_types';
|
|||
|
||||
interface RequestArgs<JobType extends string> {
|
||||
spaceId: string;
|
||||
sourceId: string;
|
||||
logViewId: string;
|
||||
jobTypes: JobType[];
|
||||
}
|
||||
|
||||
|
@ -21,12 +21,12 @@ export const callJobsSummaryAPI = async <JobType extends string>(
|
|||
requestArgs: RequestArgs<JobType>,
|
||||
fetch: HttpHandler
|
||||
) => {
|
||||
const { spaceId, sourceId, jobTypes } = requestArgs;
|
||||
const { spaceId, logViewId, jobTypes } = requestArgs;
|
||||
const response = await fetch('/api/ml/jobs/jobs_summary', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(
|
||||
fetchJobStatusRequestPayloadRT.encode({
|
||||
jobIds: jobTypes.map((jobType) => getJobId(spaceId, sourceId, jobType)),
|
||||
jobIds: jobTypes.map((jobType) => getJobId(spaceId, logViewId, jobType)),
|
||||
})
|
||||
),
|
||||
});
|
||||
|
|
|
@ -11,12 +11,12 @@ import { callDeleteJobs, callGetJobDeletionTasks, callStopDatafeeds } from './ap
|
|||
|
||||
export const cleanUpJobsAndDatafeeds = async <JobType extends string>(
|
||||
spaceId: string,
|
||||
sourceId: string,
|
||||
logViewId: string,
|
||||
jobTypes: JobType[],
|
||||
fetch: HttpHandler
|
||||
) => {
|
||||
try {
|
||||
await callStopDatafeeds({ spaceId, sourceId, jobTypes }, fetch);
|
||||
await callStopDatafeeds({ spaceId, logViewId, jobTypes }, fetch);
|
||||
} catch (err) {
|
||||
// Proceed only if datafeed has been deleted or didn't exist in the first place
|
||||
if (err?.response?.status !== 404) {
|
||||
|
@ -24,27 +24,27 @@ export const cleanUpJobsAndDatafeeds = async <JobType extends string>(
|
|||
}
|
||||
}
|
||||
|
||||
return await deleteJobs(spaceId, sourceId, jobTypes, fetch);
|
||||
return await deleteJobs(spaceId, logViewId, jobTypes, fetch);
|
||||
};
|
||||
|
||||
const deleteJobs = async <JobType extends string>(
|
||||
spaceId: string,
|
||||
sourceId: string,
|
||||
logViewId: string,
|
||||
jobTypes: JobType[],
|
||||
fetch: HttpHandler
|
||||
) => {
|
||||
const deleteJobsResponse = await callDeleteJobs({ spaceId, sourceId, jobTypes }, fetch);
|
||||
await waitUntilJobsAreDeleted(spaceId, sourceId, jobTypes, fetch);
|
||||
const deleteJobsResponse = await callDeleteJobs({ spaceId, logViewId, jobTypes }, fetch);
|
||||
await waitUntilJobsAreDeleted(spaceId, logViewId, jobTypes, fetch);
|
||||
return deleteJobsResponse;
|
||||
};
|
||||
|
||||
const waitUntilJobsAreDeleted = async <JobType extends string>(
|
||||
spaceId: string,
|
||||
sourceId: string,
|
||||
logViewId: string,
|
||||
jobTypes: JobType[],
|
||||
fetch: HttpHandler
|
||||
) => {
|
||||
const moduleJobIds = jobTypes.map((jobType) => getJobId(spaceId, sourceId, jobType));
|
||||
const moduleJobIds = jobTypes.map((jobType) => getJobId(spaceId, logViewId, jobType));
|
||||
while (true) {
|
||||
const { jobIds: jobIdsBeingDeleted } = await callGetJobDeletionTasks(fetch);
|
||||
const needToWait = jobIdsBeingDeleted.some((jobId) => moduleJobIds.includes(jobId));
|
||||
|
|
|
@ -21,7 +21,7 @@ export const useLogAnalysisModule = <JobType extends string>({
|
|||
moduleDescriptor: ModuleDescriptor<JobType>;
|
||||
}) => {
|
||||
const { services } = useKibanaContextForPlugin();
|
||||
const { spaceId, sourceId, timestampField, runtimeMappings } = sourceConfiguration;
|
||||
const { spaceId, sourceId: logViewId, timestampField, runtimeMappings } = sourceConfiguration;
|
||||
const [moduleStatus, dispatchModuleStatus] = useModuleStatus(moduleDescriptor.jobTypes);
|
||||
|
||||
const trackMetric = useUiTracker({ app: 'infra_logs' });
|
||||
|
@ -31,21 +31,21 @@ export const useLogAnalysisModule = <JobType extends string>({
|
|||
cancelPreviousOn: 'resolution',
|
||||
createPromise: async () => {
|
||||
dispatchModuleStatus({ type: 'fetchingJobStatuses' });
|
||||
return await moduleDescriptor.getJobSummary(spaceId, sourceId, services.http.fetch);
|
||||
return await moduleDescriptor.getJobSummary(spaceId, logViewId, services.http.fetch);
|
||||
},
|
||||
onResolve: (jobResponse) => {
|
||||
dispatchModuleStatus({
|
||||
type: 'fetchedJobStatuses',
|
||||
payload: jobResponse,
|
||||
spaceId,
|
||||
sourceId,
|
||||
logViewId,
|
||||
});
|
||||
},
|
||||
onReject: () => {
|
||||
dispatchModuleStatus({ type: 'failedFetchingJobStatuses' });
|
||||
},
|
||||
},
|
||||
[spaceId, sourceId]
|
||||
[spaceId, logViewId]
|
||||
);
|
||||
|
||||
const [, setUpModule] = useTrackedPromise(
|
||||
|
@ -64,7 +64,7 @@ export const useLogAnalysisModule = <JobType extends string>({
|
|||
datasetFilter,
|
||||
{
|
||||
indices: selectedIndices,
|
||||
sourceId,
|
||||
sourceId: logViewId,
|
||||
spaceId,
|
||||
timestampField,
|
||||
runtimeMappings,
|
||||
|
@ -73,7 +73,7 @@ export const useLogAnalysisModule = <JobType extends string>({
|
|||
);
|
||||
const jobSummaries = await moduleDescriptor.getJobSummary(
|
||||
spaceId,
|
||||
sourceId,
|
||||
logViewId,
|
||||
services.http.fetch
|
||||
);
|
||||
return { setupResult, jobSummaries };
|
||||
|
@ -104,7 +104,7 @@ export const useLogAnalysisModule = <JobType extends string>({
|
|||
jobSetupResults: jobs,
|
||||
jobSummaries,
|
||||
spaceId,
|
||||
sourceId,
|
||||
logViewId,
|
||||
});
|
||||
},
|
||||
onReject: (e: any) => {
|
||||
|
@ -114,17 +114,17 @@ export const useLogAnalysisModule = <JobType extends string>({
|
|||
}
|
||||
},
|
||||
},
|
||||
[moduleDescriptor.setUpModule, spaceId, sourceId, timestampField]
|
||||
[moduleDescriptor.setUpModule, spaceId, logViewId, timestampField]
|
||||
);
|
||||
|
||||
const [cleanUpModuleRequest, cleanUpModule] = useTrackedPromise(
|
||||
{
|
||||
cancelPreviousOn: 'resolution',
|
||||
createPromise: async () => {
|
||||
return await moduleDescriptor.cleanUpModule(spaceId, sourceId, services.http.fetch);
|
||||
return await moduleDescriptor.cleanUpModule(spaceId, logViewId, services.http.fetch);
|
||||
},
|
||||
},
|
||||
[spaceId, sourceId]
|
||||
[spaceId, logViewId]
|
||||
);
|
||||
|
||||
const isCleaningUp = useMemo(
|
||||
|
@ -156,8 +156,8 @@ export const useLogAnalysisModule = <JobType extends string>({
|
|||
}, [dispatchModuleStatus]);
|
||||
|
||||
const jobIds = useMemo(
|
||||
() => moduleDescriptor.getJobIds(spaceId, sourceId),
|
||||
[moduleDescriptor, spaceId, sourceId]
|
||||
() => moduleDescriptor.getJobIds(spaceId, logViewId),
|
||||
[moduleDescriptor, spaceId, logViewId]
|
||||
);
|
||||
|
||||
return {
|
||||
|
|
|
@ -29,7 +29,7 @@ type StatusReducerAction =
|
|||
| { type: 'startedSetup' }
|
||||
| {
|
||||
type: 'finishedSetup';
|
||||
sourceId: string;
|
||||
logViewId: string;
|
||||
spaceId: string;
|
||||
jobSetupResults: SetupMlModuleResponsePayload['jobs'];
|
||||
jobSummaries: FetchJobStatusResponsePayload;
|
||||
|
@ -40,7 +40,7 @@ type StatusReducerAction =
|
|||
| {
|
||||
type: 'fetchedJobStatuses';
|
||||
spaceId: string;
|
||||
sourceId: string;
|
||||
logViewId: string;
|
||||
payload: FetchJobStatusResponsePayload;
|
||||
}
|
||||
| { type: 'failedFetchingJobStatuses' }
|
||||
|
@ -84,13 +84,13 @@ const createStatusReducer =
|
|||
};
|
||||
}
|
||||
case 'finishedSetup': {
|
||||
const { datafeedSetupResults, jobSetupResults, jobSummaries, spaceId, sourceId } = action;
|
||||
const { datafeedSetupResults, jobSetupResults, jobSummaries, spaceId, logViewId } = action;
|
||||
const nextJobStatus = jobTypes.reduce(
|
||||
(accumulatedJobStatus, jobType) => ({
|
||||
...accumulatedJobStatus,
|
||||
[jobType]:
|
||||
hasSuccessfullyCreatedJob(getJobId(spaceId, sourceId, jobType))(jobSetupResults) &&
|
||||
hasSuccessfullyStartedDatafeed(getDatafeedId(spaceId, sourceId, jobType))(
|
||||
hasSuccessfullyCreatedJob(getJobId(spaceId, logViewId, jobType))(jobSetupResults) &&
|
||||
hasSuccessfullyStartedDatafeed(getDatafeedId(spaceId, logViewId, jobType))(
|
||||
datafeedSetupResults
|
||||
)
|
||||
? 'started'
|
||||
|
@ -142,13 +142,13 @@ const createStatusReducer =
|
|||
};
|
||||
}
|
||||
case 'fetchedJobStatuses': {
|
||||
const { payload: jobSummaries, spaceId, sourceId } = action;
|
||||
const { payload: jobSummaries, spaceId, logViewId } = action;
|
||||
const { setupStatus } = state;
|
||||
|
||||
const nextJobStatus = jobTypes.reduce(
|
||||
(accumulatedJobStatus, jobType) => ({
|
||||
...accumulatedJobStatus,
|
||||
[jobType]: getJobStatus(getJobId(spaceId, sourceId, jobType))(jobSummaries),
|
||||
[jobType]: getJobStatus(getJobId(spaceId, logViewId, jobType))(jobSummaries),
|
||||
}),
|
||||
{} as Record<JobType, JobStatus>
|
||||
);
|
||||
|
|
|
@ -25,10 +25,10 @@ export interface ModuleDescriptor<JobType extends string> {
|
|||
moduleDescription: string;
|
||||
jobTypes: JobType[];
|
||||
bucketSpan: number;
|
||||
getJobIds: (spaceId: string, sourceId: string) => Record<JobType, string>;
|
||||
getJobIds: (spaceId: string, logViewId: string) => Record<JobType, string>;
|
||||
getJobSummary: (
|
||||
spaceId: string,
|
||||
sourceId: string,
|
||||
logViewId: string,
|
||||
fetch: HttpHandler
|
||||
) => Promise<FetchJobStatusResponsePayload>;
|
||||
getModuleDefinition: (fetch: HttpHandler) => Promise<GetMlModuleResponsePayload>;
|
||||
|
@ -41,7 +41,7 @@ export interface ModuleDescriptor<JobType extends string> {
|
|||
) => Promise<SetupMlModuleResponsePayload>;
|
||||
cleanUpModule: (
|
||||
spaceId: string,
|
||||
sourceId: string,
|
||||
logViewId: string,
|
||||
fetch: HttpHandler
|
||||
) => Promise<DeleteJobsResponsePayload>;
|
||||
validateSetupIndices: (
|
||||
|
|
|
@ -36,21 +36,21 @@ const moduleDescription = i18n.translate(
|
|||
}
|
||||
);
|
||||
|
||||
const getJobIds = (spaceId: string, sourceId: string) =>
|
||||
const getJobIds = (spaceId: string, logViewId: string) =>
|
||||
logEntryCategoriesJobTypes.reduce(
|
||||
(accumulatedJobIds, jobType) => ({
|
||||
...accumulatedJobIds,
|
||||
[jobType]: getJobId(spaceId, sourceId, jobType),
|
||||
[jobType]: getJobId(spaceId, logViewId, jobType),
|
||||
}),
|
||||
{} as Record<LogEntryCategoriesJobType, string>
|
||||
);
|
||||
|
||||
const getJobSummary = async (spaceId: string, sourceId: string, fetch: HttpHandler) => {
|
||||
const getJobSummary = async (spaceId: string, logViewId: string, fetch: HttpHandler) => {
|
||||
const response = await callJobsSummaryAPI(
|
||||
{ spaceId, sourceId, jobTypes: logEntryCategoriesJobTypes },
|
||||
{ spaceId, logViewId, jobTypes: logEntryCategoriesJobTypes },
|
||||
fetch
|
||||
);
|
||||
const jobIds = Object.values(getJobIds(spaceId, sourceId));
|
||||
const jobIds = Object.values(getJobIds(spaceId, logViewId));
|
||||
|
||||
return response.filter((jobSummary) => jobIds.includes(jobSummary.id));
|
||||
};
|
||||
|
@ -130,8 +130,8 @@ const setUpModule = async (
|
|||
);
|
||||
};
|
||||
|
||||
const cleanUpModule = async (spaceId: string, sourceId: string, fetch: HttpHandler) => {
|
||||
return await cleanUpJobsAndDatafeeds(spaceId, sourceId, logEntryCategoriesJobTypes, fetch);
|
||||
const cleanUpModule = async (spaceId: string, logViewId: string, fetch: HttpHandler) => {
|
||||
return await cleanUpJobsAndDatafeeds(spaceId, logViewId, logEntryCategoriesJobTypes, fetch);
|
||||
};
|
||||
|
||||
const validateSetupIndices = async (
|
||||
|
|
|
@ -17,13 +17,13 @@ import { useLogEntryCategoriesQuality } from './use_log_entry_categories_quality
|
|||
|
||||
export const useLogEntryCategoriesModule = ({
|
||||
indexPattern,
|
||||
sourceId,
|
||||
logViewId,
|
||||
spaceId,
|
||||
timestampField,
|
||||
runtimeMappings,
|
||||
}: {
|
||||
indexPattern: string;
|
||||
sourceId: string;
|
||||
logViewId: string;
|
||||
spaceId: string;
|
||||
timestampField: string;
|
||||
runtimeMappings: estypes.MappingRuntimeFields;
|
||||
|
@ -31,12 +31,12 @@ export const useLogEntryCategoriesModule = ({
|
|||
const sourceConfiguration: ModuleSourceConfiguration = useMemo(
|
||||
() => ({
|
||||
indices: indexPattern.split(','),
|
||||
sourceId,
|
||||
sourceId: logViewId,
|
||||
spaceId,
|
||||
timestampField,
|
||||
runtimeMappings,
|
||||
}),
|
||||
[indexPattern, sourceId, spaceId, timestampField, runtimeMappings]
|
||||
[indexPattern, logViewId, spaceId, timestampField, runtimeMappings]
|
||||
);
|
||||
|
||||
const logAnalysisModule = useLogAnalysisModule({
|
||||
|
|
|
@ -35,21 +35,21 @@ const moduleDescription = i18n.translate(
|
|||
}
|
||||
);
|
||||
|
||||
const getJobIds = (spaceId: string, sourceId: string) =>
|
||||
const getJobIds = (spaceId: string, logViewId: string) =>
|
||||
logEntryRateJobTypes.reduce(
|
||||
(accumulatedJobIds, jobType) => ({
|
||||
...accumulatedJobIds,
|
||||
[jobType]: getJobId(spaceId, sourceId, jobType),
|
||||
[jobType]: getJobId(spaceId, logViewId, jobType),
|
||||
}),
|
||||
{} as Record<LogEntryRateJobType, string>
|
||||
);
|
||||
|
||||
const getJobSummary = async (spaceId: string, sourceId: string, fetch: HttpHandler) => {
|
||||
const getJobSummary = async (spaceId: string, logViewId: string, fetch: HttpHandler) => {
|
||||
const response = await callJobsSummaryAPI(
|
||||
{ spaceId, sourceId, jobTypes: logEntryRateJobTypes },
|
||||
{ spaceId, logViewId, jobTypes: logEntryRateJobTypes },
|
||||
fetch
|
||||
);
|
||||
const jobIds = Object.values(getJobIds(spaceId, sourceId));
|
||||
const jobIds = Object.values(getJobIds(spaceId, logViewId));
|
||||
|
||||
return response.filter((jobSummary) => jobIds.includes(jobSummary.id));
|
||||
};
|
||||
|
@ -122,8 +122,8 @@ const setUpModule = async (
|
|||
);
|
||||
};
|
||||
|
||||
const cleanUpModule = async (spaceId: string, sourceId: string, fetch: HttpHandler) => {
|
||||
return await cleanUpJobsAndDatafeeds(spaceId, sourceId, logEntryRateJobTypes, fetch);
|
||||
const cleanUpModule = async (spaceId: string, logViewId: string, fetch: HttpHandler) => {
|
||||
return await cleanUpJobsAndDatafeeds(spaceId, logViewId, logEntryRateJobTypes, fetch);
|
||||
};
|
||||
|
||||
const validateSetupIndices = async (
|
||||
|
|
|
@ -16,13 +16,13 @@ import { logEntryRateModule } from './module_descriptor';
|
|||
|
||||
export const useLogEntryRateModule = ({
|
||||
indexPattern,
|
||||
sourceId,
|
||||
logViewId,
|
||||
spaceId,
|
||||
timestampField,
|
||||
runtimeMappings,
|
||||
}: {
|
||||
indexPattern: string;
|
||||
sourceId: string;
|
||||
logViewId: string;
|
||||
spaceId: string;
|
||||
timestampField: string;
|
||||
runtimeMappings: estypes.MappingRuntimeFields;
|
||||
|
@ -30,12 +30,12 @@ export const useLogEntryRateModule = ({
|
|||
const sourceConfiguration: ModuleSourceConfiguration = useMemo(
|
||||
() => ({
|
||||
indices: indexPattern.split(','),
|
||||
sourceId,
|
||||
sourceId: logViewId,
|
||||
spaceId,
|
||||
timestampField,
|
||||
runtimeMappings,
|
||||
}),
|
||||
[indexPattern, sourceId, spaceId, timestampField, runtimeMappings]
|
||||
[indexPattern, logViewId, spaceId, timestampField, runtimeMappings]
|
||||
);
|
||||
|
||||
const logAnalysisModule = useLogAnalysisModule({
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import { useCallback } from 'react';
|
||||
import { LogViewReference } from '../../../common/log_views';
|
||||
import { decodeOrThrow } from '../../../common/runtime_types';
|
||||
import {
|
||||
logEntrySearchRequestParamsRT,
|
||||
|
@ -19,26 +20,26 @@ import {
|
|||
} from '../../utils/data_search';
|
||||
|
||||
export const useLogEntry = ({
|
||||
sourceId,
|
||||
logViewReference,
|
||||
logEntryId,
|
||||
}: {
|
||||
sourceId: string | null | undefined;
|
||||
logViewReference: LogViewReference | null | undefined;
|
||||
logEntryId: string | null | undefined;
|
||||
}) => {
|
||||
const { search: fetchLogEntry, requests$: logEntrySearchRequests$ } = useDataSearch({
|
||||
getRequest: useCallback(() => {
|
||||
return !!logEntryId && !!sourceId
|
||||
return !!logEntryId && !!logViewReference
|
||||
? {
|
||||
request: {
|
||||
params: logEntrySearchRequestParamsRT.encode({
|
||||
logView: { type: 'log-view-reference', logViewId: sourceId },
|
||||
logView: logViewReference,
|
||||
logEntryId,
|
||||
}),
|
||||
},
|
||||
options: { strategy: LOG_ENTRY_SEARCH_STRATEGY },
|
||||
}
|
||||
: null;
|
||||
}, [sourceId, logEntryId]),
|
||||
}, [logViewReference, logEntryId]),
|
||||
parseResponses: parseLogEntrySearchResponses,
|
||||
});
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { LogViewReference } from '../../../../common/log_views';
|
||||
import { LogEntriesHighlightsResponse } from '../../../../common/http_api';
|
||||
import { LogEntry } from '../../../../common/log_entry';
|
||||
import { TimeKey } from '../../../../common/time';
|
||||
|
@ -14,7 +15,7 @@ import { useTrackedPromise } from '../../../utils/use_tracked_promise';
|
|||
import { fetchLogEntriesHighlights } from './api/fetch_log_entries_highlights';
|
||||
|
||||
export const useLogEntryHighlights = (
|
||||
sourceId: string,
|
||||
logViewReference: LogViewReference,
|
||||
sourceVersion: string | undefined,
|
||||
startTimestamp: number | null,
|
||||
endTimestamp: number | null,
|
||||
|
@ -37,7 +38,7 @@ export const useLogEntryHighlights = (
|
|||
|
||||
return await fetchLogEntriesHighlights(
|
||||
{
|
||||
logView: { type: 'log-view-reference', logViewId: sourceId },
|
||||
logView: logViewReference,
|
||||
startTimestamp,
|
||||
endTimestamp,
|
||||
center: centerPoint,
|
||||
|
@ -52,7 +53,7 @@ export const useLogEntryHighlights = (
|
|||
setLogEntryHighlights(response.data);
|
||||
},
|
||||
},
|
||||
[sourceId, startTimestamp, endTimestamp, centerPoint, size, filterQuery, highlightTerms]
|
||||
[logViewReference, startTimestamp, endTimestamp, centerPoint, size, filterQuery, highlightTerms]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import createContainer from 'constate';
|
||||
import { useState } from 'react';
|
||||
import useThrottle from 'react-use/lib/useThrottle';
|
||||
import { LogViewReference } from '../../../../common/log_views';
|
||||
import { useLogEntryHighlights } from './log_entry_highlights';
|
||||
import { useLogSummaryHighlights } from './log_summary_highlights';
|
||||
import { useNextAndPrevious } from './next_and_previous';
|
||||
|
@ -17,7 +18,7 @@ import { TimeKey } from '../../../../common/time';
|
|||
const FETCH_THROTTLE_INTERVAL = 3000;
|
||||
|
||||
interface UseLogHighlightsStateProps {
|
||||
sourceId: string;
|
||||
logViewReference: LogViewReference;
|
||||
sourceVersion: string | undefined;
|
||||
centerCursor: TimeKey | null;
|
||||
size: number;
|
||||
|
@ -25,7 +26,7 @@ interface UseLogHighlightsStateProps {
|
|||
}
|
||||
|
||||
export const useLogHighlightsState = ({
|
||||
sourceId,
|
||||
logViewReference,
|
||||
sourceVersion,
|
||||
centerCursor,
|
||||
size,
|
||||
|
@ -40,7 +41,7 @@ export const useLogHighlightsState = ({
|
|||
|
||||
const { logEntryHighlights, logEntryHighlightsById, loadLogEntryHighlightsRequest } =
|
||||
useLogEntryHighlights(
|
||||
sourceId,
|
||||
logViewReference,
|
||||
sourceVersion,
|
||||
throttledStartTimestamp,
|
||||
throttledEndTimestamp,
|
||||
|
@ -51,7 +52,7 @@ export const useLogHighlightsState = ({
|
|||
);
|
||||
|
||||
const { logSummaryHighlights, loadLogSummaryHighlightsRequest } = useLogSummaryHighlights(
|
||||
sourceId,
|
||||
logViewReference,
|
||||
sourceVersion,
|
||||
throttledStartTimestamp,
|
||||
throttledEndTimestamp,
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { debounce } from 'lodash';
|
||||
|
||||
import { LogViewReference } from '../../../../common/log_views';
|
||||
import { useTrackedPromise } from '../../../utils/use_tracked_promise';
|
||||
import { fetchLogSummaryHighlights } from './api/fetch_log_summary_highlights';
|
||||
import { LogEntriesSummaryHighlightsResponse } from '../../../../common/http_api';
|
||||
|
@ -15,7 +16,7 @@ import { useBucketSize } from '../log_summary/bucket_size';
|
|||
import { useKibanaContextForPlugin } from '../../../hooks/use_kibana';
|
||||
|
||||
export const useLogSummaryHighlights = (
|
||||
sourceId: string,
|
||||
logViewReference: LogViewReference,
|
||||
sourceVersion: string | undefined,
|
||||
startTimestamp: number | null,
|
||||
endTimestamp: number | null,
|
||||
|
@ -39,7 +40,7 @@ export const useLogSummaryHighlights = (
|
|||
|
||||
return await fetchLogSummaryHighlights(
|
||||
{
|
||||
logView: { type: 'log-view-reference', logViewId: sourceId },
|
||||
logView: logViewReference,
|
||||
startTimestamp,
|
||||
endTimestamp,
|
||||
bucketSize,
|
||||
|
@ -53,7 +54,7 @@ export const useLogSummaryHighlights = (
|
|||
setLogSummaryHighlights(response.data);
|
||||
},
|
||||
},
|
||||
[sourceId, startTimestamp, endTimestamp, bucketSize, filterQuery, highlightTerms]
|
||||
[logViewReference, startTimestamp, endTimestamp, bucketSize, filterQuery, highlightTerms]
|
||||
);
|
||||
|
||||
const debouncedLoadSummaryHighlights = useMemo(
|
||||
|
|
|
@ -12,7 +12,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react';
|
|||
import usePrevious from 'react-use/lib/usePrevious';
|
||||
import useSetState from 'react-use/lib/useSetState';
|
||||
import { LogEntry, LogEntryCursor } from '../../../../common/log_entry';
|
||||
import { LogViewColumnConfiguration } from '../../../../common/log_views';
|
||||
import { LogViewColumnConfiguration, LogViewReference } from '../../../../common/log_views';
|
||||
import { useSubscription } from '../../../utils/use_observable';
|
||||
import { useFetchLogEntriesAfter } from './use_fetch_log_entries_after';
|
||||
import { useFetchLogEntriesAround } from './use_fetch_log_entries_around';
|
||||
|
@ -21,7 +21,7 @@ import { useFetchLogEntriesBefore } from './use_fetch_log_entries_before';
|
|||
export type BuiltEsQuery = ReturnType<typeof buildEsQuery>;
|
||||
|
||||
interface LogStreamProps {
|
||||
sourceId: string;
|
||||
logViewReference: LogViewReference;
|
||||
startTimestamp: number;
|
||||
endTimestamp: number;
|
||||
query?: BuiltEsQuery;
|
||||
|
@ -52,7 +52,7 @@ const INITIAL_STATE: LogStreamState = {
|
|||
const LOG_ENTRIES_CHUNK_SIZE = 200;
|
||||
|
||||
export function useLogStream({
|
||||
sourceId,
|
||||
logViewReference,
|
||||
startTimestamp,
|
||||
endTimestamp,
|
||||
query,
|
||||
|
@ -85,13 +85,13 @@ export function useLogStream({
|
|||
|
||||
const commonFetchArguments = useMemo(
|
||||
() => ({
|
||||
sourceId,
|
||||
logViewReference,
|
||||
startTimestamp,
|
||||
endTimestamp,
|
||||
query: cachedQuery,
|
||||
columnOverrides: columns,
|
||||
}),
|
||||
[columns, endTimestamp, cachedQuery, sourceId, startTimestamp]
|
||||
[columns, endTimestamp, cachedQuery, logViewReference, startTimestamp]
|
||||
);
|
||||
|
||||
const {
|
||||
|
|
|
@ -11,7 +11,7 @@ import { Observable } from 'rxjs';
|
|||
import { exhaustMap } from 'rxjs/operators';
|
||||
import { IKibanaSearchRequest } from '@kbn/data-plugin/public';
|
||||
import { LogEntryAfterCursor } from '../../../../common/log_entry';
|
||||
import { LogViewColumnConfiguration } from '../../../../common/log_views';
|
||||
import { LogViewColumnConfiguration, LogViewReference } from '../../../../common/log_views';
|
||||
import { decodeOrThrow } from '../../../../common/runtime_types';
|
||||
import {
|
||||
logEntriesSearchRequestParamsRT,
|
||||
|
@ -34,21 +34,21 @@ export const useLogEntriesAfterRequest = ({
|
|||
endTimestamp,
|
||||
highlightPhrase,
|
||||
query,
|
||||
sourceId,
|
||||
logViewReference,
|
||||
startTimestamp,
|
||||
}: {
|
||||
columnOverrides?: LogViewColumnConfiguration[];
|
||||
endTimestamp: number;
|
||||
highlightPhrase?: string;
|
||||
query?: LogEntriesSearchRequestQuery;
|
||||
sourceId: string;
|
||||
logViewReference: LogViewReference;
|
||||
startTimestamp: number;
|
||||
}) => {
|
||||
const { search: fetchLogEntriesAfter, requests$: logEntriesAfterSearchRequests$ } = useDataSearch(
|
||||
{
|
||||
getRequest: useCallback(
|
||||
(cursor: LogEntryAfterCursor['after'], params: { size: number; extendTo?: number }) => {
|
||||
return !!sourceId
|
||||
return !!logViewReference
|
||||
? {
|
||||
request: {
|
||||
params: logEntriesSearchRequestParamsRT.encode({
|
||||
|
@ -58,7 +58,7 @@ export const useLogEntriesAfterRequest = ({
|
|||
highlightPhrase,
|
||||
query: query as JsonObject,
|
||||
size: params.size,
|
||||
logView: { type: 'log-view-reference', logViewId: sourceId },
|
||||
logView: logViewReference,
|
||||
startTimestamp,
|
||||
}),
|
||||
},
|
||||
|
@ -66,7 +66,7 @@ export const useLogEntriesAfterRequest = ({
|
|||
}
|
||||
: null;
|
||||
},
|
||||
[columnOverrides, endTimestamp, highlightPhrase, query, sourceId, startTimestamp]
|
||||
[columnOverrides, endTimestamp, highlightPhrase, query, logViewReference, startTimestamp]
|
||||
),
|
||||
parseResponses: parseLogEntriesAfterSearchResponses,
|
||||
}
|
||||
|
@ -107,14 +107,14 @@ export const useFetchLogEntriesAfter = ({
|
|||
endTimestamp,
|
||||
highlightPhrase,
|
||||
query,
|
||||
sourceId,
|
||||
logViewReference,
|
||||
startTimestamp,
|
||||
}: {
|
||||
columnOverrides?: LogViewColumnConfiguration[];
|
||||
endTimestamp: number;
|
||||
highlightPhrase?: string;
|
||||
query?: LogEntriesSearchRequestQuery;
|
||||
sourceId: string;
|
||||
logViewReference: LogViewReference;
|
||||
startTimestamp: number;
|
||||
}) => {
|
||||
const { fetchLogEntriesAfter, logEntriesAfterSearchRequests$ } = useLogEntriesAfterRequest({
|
||||
|
@ -122,7 +122,7 @@ export const useFetchLogEntriesAfter = ({
|
|||
endTimestamp,
|
||||
highlightPhrase,
|
||||
query,
|
||||
sourceId,
|
||||
logViewReference,
|
||||
startTimestamp,
|
||||
});
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ import { useCallback } from 'react';
|
|||
import { combineLatest, Observable, ReplaySubject } from 'rxjs';
|
||||
import { last, map, startWith, switchMap } from 'rxjs/operators';
|
||||
import { LogEntryCursor } from '../../../../common/log_entry';
|
||||
import { LogViewColumnConfiguration } from '../../../../common/log_views';
|
||||
import { LogViewColumnConfiguration, LogViewReference } from '../../../../common/log_views';
|
||||
import { LogEntriesSearchRequestQuery } from '../../../../common/search_strategies/log_entries/log_entries';
|
||||
import { flattenDataSearchResponseDescriptor } from '../../../utils/data_search';
|
||||
import { useObservable, useObservableState } from '../../../utils/use_observable';
|
||||
|
@ -21,14 +21,14 @@ export const useFetchLogEntriesAround = ({
|
|||
endTimestamp,
|
||||
highlightPhrase,
|
||||
query,
|
||||
sourceId,
|
||||
logViewReference,
|
||||
startTimestamp,
|
||||
}: {
|
||||
columnOverrides?: LogViewColumnConfiguration[];
|
||||
endTimestamp: number;
|
||||
highlightPhrase?: string;
|
||||
query?: LogEntriesSearchRequestQuery;
|
||||
sourceId: string;
|
||||
logViewReference: LogViewReference;
|
||||
startTimestamp: number;
|
||||
}) => {
|
||||
const { fetchLogEntriesBefore } = useLogEntriesBeforeRequest({
|
||||
|
@ -36,7 +36,7 @@ export const useFetchLogEntriesAround = ({
|
|||
endTimestamp,
|
||||
highlightPhrase,
|
||||
query,
|
||||
sourceId,
|
||||
logViewReference,
|
||||
startTimestamp,
|
||||
});
|
||||
|
||||
|
@ -45,7 +45,7 @@ export const useFetchLogEntriesAround = ({
|
|||
endTimestamp,
|
||||
highlightPhrase,
|
||||
query,
|
||||
sourceId,
|
||||
logViewReference,
|
||||
startTimestamp,
|
||||
});
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ import { Observable } from 'rxjs';
|
|||
import { exhaustMap } from 'rxjs/operators';
|
||||
import { IKibanaSearchRequest } from '@kbn/data-plugin/public';
|
||||
import { LogEntryBeforeCursor } from '../../../../common/log_entry';
|
||||
import { LogViewColumnConfiguration } from '../../../../common/log_views';
|
||||
import { LogViewColumnConfiguration, LogViewReference } from '../../../../common/log_views';
|
||||
import { decodeOrThrow } from '../../../../common/runtime_types';
|
||||
import {
|
||||
logEntriesSearchRequestParamsRT,
|
||||
|
@ -34,21 +34,21 @@ export const useLogEntriesBeforeRequest = ({
|
|||
endTimestamp,
|
||||
highlightPhrase,
|
||||
query,
|
||||
sourceId,
|
||||
logViewReference,
|
||||
startTimestamp,
|
||||
}: {
|
||||
columnOverrides?: LogViewColumnConfiguration[];
|
||||
endTimestamp: number;
|
||||
highlightPhrase?: string;
|
||||
query?: LogEntriesSearchRequestQuery;
|
||||
sourceId: string;
|
||||
logViewReference: LogViewReference;
|
||||
startTimestamp: number;
|
||||
}) => {
|
||||
const { search: fetchLogEntriesBefore, requests$: logEntriesBeforeSearchRequests$ } =
|
||||
useDataSearch({
|
||||
getRequest: useCallback(
|
||||
(cursor: LogEntryBeforeCursor['before'], params: { size: number; extendTo?: number }) => {
|
||||
return !!sourceId
|
||||
return !!logViewReference
|
||||
? {
|
||||
request: {
|
||||
params: logEntriesSearchRequestParamsRT.encode({
|
||||
|
@ -58,7 +58,7 @@ export const useLogEntriesBeforeRequest = ({
|
|||
highlightPhrase,
|
||||
query: query as JsonObject,
|
||||
size: params.size,
|
||||
logView: { type: 'log-view-reference', logViewId: sourceId },
|
||||
logView: logViewReference,
|
||||
startTimestamp: params.extendTo ?? startTimestamp,
|
||||
}),
|
||||
},
|
||||
|
@ -66,7 +66,7 @@ export const useLogEntriesBeforeRequest = ({
|
|||
}
|
||||
: null;
|
||||
},
|
||||
[columnOverrides, endTimestamp, highlightPhrase, query, sourceId, startTimestamp]
|
||||
[columnOverrides, endTimestamp, highlightPhrase, query, logViewReference, startTimestamp]
|
||||
),
|
||||
parseResponses: parseLogEntriesBeforeSearchResponses,
|
||||
});
|
||||
|
@ -106,14 +106,14 @@ export const useFetchLogEntriesBefore = ({
|
|||
endTimestamp,
|
||||
highlightPhrase,
|
||||
query,
|
||||
sourceId,
|
||||
logViewReference,
|
||||
startTimestamp,
|
||||
}: {
|
||||
columnOverrides?: LogViewColumnConfiguration[];
|
||||
endTimestamp: number;
|
||||
highlightPhrase?: string;
|
||||
query?: LogEntriesSearchRequestQuery;
|
||||
sourceId: string;
|
||||
logViewReference: LogViewReference;
|
||||
startTimestamp: number;
|
||||
}) => {
|
||||
const { fetchLogEntriesBefore, logEntriesBeforeSearchRequests$ } = useLogEntriesBeforeRequest({
|
||||
|
@ -121,7 +121,7 @@ export const useFetchLogEntriesBefore = ({
|
|||
endTimestamp,
|
||||
highlightPhrase,
|
||||
query,
|
||||
sourceId,
|
||||
logViewReference,
|
||||
startTimestamp,
|
||||
});
|
||||
|
||||
|
|
|
@ -14,6 +14,12 @@ import { useLogSummary } from './log_summary';
|
|||
import { fetchLogSummary } from './api/fetch_log_summary';
|
||||
import { datemathToEpochMillis } from '../../../utils/datemath';
|
||||
|
||||
const LOG_VIEW_REFERENCE = { type: 'log-view-reference' as const, logViewId: 'LOG_VIEW_ID' };
|
||||
const CHANGED_LOG_VIEW_REFERENCE = {
|
||||
type: 'log-view-reference' as const,
|
||||
logViewId: 'CHANGED_LOG_VIEW_ID',
|
||||
};
|
||||
|
||||
// Typescript doesn't know that `fetchLogSummary` is a jest mock.
|
||||
// We use a second variable with a type cast to help the compiler further down the line.
|
||||
jest.mock('./api/fetch_log_summary', () => ({ fetchLogSummary: jest.fn() }));
|
||||
|
@ -32,7 +38,7 @@ describe('useLogSummary hook', () => {
|
|||
});
|
||||
|
||||
it('provides an empty list of buckets by default', () => {
|
||||
const { result } = renderHook(() => useLogSummary('SOURCE_ID', null, null, null));
|
||||
const { result } = renderHook(() => useLogSummary(LOG_VIEW_REFERENCE, null, null, null));
|
||||
expect(result.current.buckets).toEqual([]);
|
||||
});
|
||||
|
||||
|
@ -51,9 +57,9 @@ describe('useLogSummary hook', () => {
|
|||
.mockResolvedValueOnce(secondMockResponse);
|
||||
|
||||
const { result, waitForNextUpdate, rerender } = renderHook(
|
||||
({ sourceId }) => useLogSummary(sourceId, startTimestamp, endTimestamp, null),
|
||||
({ logViewReference }) => useLogSummary(logViewReference, startTimestamp, endTimestamp, null),
|
||||
{
|
||||
initialProps: { sourceId: 'INITIAL_SOURCE_ID' },
|
||||
initialProps: { logViewReference: LOG_VIEW_REFERENCE },
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -62,19 +68,19 @@ describe('useLogSummary hook', () => {
|
|||
expect(fetchLogSummaryMock).toHaveBeenCalledTimes(1);
|
||||
expect(fetchLogSummaryMock).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
logView: { logViewId: 'INITIAL_SOURCE_ID', type: 'log-view-reference' },
|
||||
logView: LOG_VIEW_REFERENCE,
|
||||
}),
|
||||
expect.anything()
|
||||
);
|
||||
expect(result.current.buckets).toEqual(firstMockResponse.data.buckets);
|
||||
|
||||
rerender({ sourceId: 'CHANGED_SOURCE_ID' });
|
||||
rerender({ logViewReference: CHANGED_LOG_VIEW_REFERENCE });
|
||||
await waitForNextUpdate();
|
||||
|
||||
expect(fetchLogSummaryMock).toHaveBeenCalledTimes(2);
|
||||
expect(fetchLogSummaryMock).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
logView: { logViewId: 'CHANGED_SOURCE_ID', type: 'log-view-reference' },
|
||||
logView: CHANGED_LOG_VIEW_REFERENCE,
|
||||
}),
|
||||
expect.anything()
|
||||
);
|
||||
|
@ -96,7 +102,8 @@ describe('useLogSummary hook', () => {
|
|||
.mockResolvedValueOnce(secondMockResponse);
|
||||
|
||||
const { result, waitForNextUpdate, rerender } = renderHook(
|
||||
({ filterQuery }) => useLogSummary('SOURCE_ID', startTimestamp, endTimestamp, filterQuery),
|
||||
({ filterQuery }) =>
|
||||
useLogSummary(LOG_VIEW_REFERENCE, startTimestamp, endTimestamp, filterQuery),
|
||||
{
|
||||
initialProps: { filterQuery: 'INITIAL_FILTER_QUERY' },
|
||||
}
|
||||
|
@ -134,7 +141,7 @@ describe('useLogSummary hook', () => {
|
|||
const firstRange = createMockDateRange();
|
||||
const { waitForNextUpdate, rerender } = renderHook(
|
||||
({ startTimestamp, endTimestamp }) =>
|
||||
useLogSummary('SOURCE_ID', startTimestamp, endTimestamp, null),
|
||||
useLogSummary(LOG_VIEW_REFERENCE, startTimestamp, endTimestamp, null),
|
||||
{
|
||||
initialProps: firstRange,
|
||||
}
|
||||
|
@ -171,7 +178,7 @@ describe('useLogSummary hook', () => {
|
|||
const firstRange = createMockDateRange();
|
||||
const { waitForNextUpdate, rerender } = renderHook(
|
||||
({ startTimestamp, endTimestamp }) =>
|
||||
useLogSummary('SOURCE_ID', startTimestamp, endTimestamp, null),
|
||||
useLogSummary(LOG_VIEW_REFERENCE, startTimestamp, endTimestamp, null),
|
||||
{
|
||||
initialProps: firstRange,
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import { useEffect } from 'react';
|
||||
import { exhaustMap, map, Observable } from 'rxjs';
|
||||
import { HttpHandler } from '@kbn/core-http-browser';
|
||||
import { LogViewReference } from '../../../../common/log_views';
|
||||
import { useObservableState, useReplaySubject } from '../../../utils/use_observable';
|
||||
import { fetchLogSummary } from './api/fetch_log_summary';
|
||||
import { LogEntriesSummaryRequest, LogEntriesSummaryResponse } from '../../../../common/http_api';
|
||||
|
@ -17,7 +18,7 @@ import { useKibanaContextForPlugin } from '../../../hooks/use_kibana';
|
|||
export type LogSummaryBuckets = LogEntriesSummaryResponse['data']['buckets'];
|
||||
|
||||
export const useLogSummary = (
|
||||
sourceId: string,
|
||||
logViewReference: LogViewReference,
|
||||
startTimestamp: number | null,
|
||||
endTimestamp: number | null,
|
||||
filterQuery: string | null
|
||||
|
@ -35,7 +36,7 @@ export const useLogSummary = (
|
|||
|
||||
pushLogSummaryBucketsArgs([
|
||||
{
|
||||
logView: { type: 'log-view-reference', logViewId: sourceId },
|
||||
logView: logViewReference,
|
||||
startTimestamp,
|
||||
endTimestamp,
|
||||
bucketSize,
|
||||
|
@ -49,7 +50,7 @@ export const useLogSummary = (
|
|||
filterQuery,
|
||||
pushLogSummaryBucketsArgs,
|
||||
services.http.fetch,
|
||||
sourceId,
|
||||
logViewReference,
|
||||
startTimestamp,
|
||||
]);
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ export const WithSummary = ({
|
|||
end: number | null;
|
||||
}>;
|
||||
}) => {
|
||||
const { logViewId } = useLogViewContext();
|
||||
const { logViewReference } = useLogViewContext();
|
||||
const serializedParsedQuery = useSelector(useLogStreamPageStateContext(), (logStreamPageState) =>
|
||||
logStreamPageState.matches({ hasLogViewIndices: 'initialized' })
|
||||
? stringify(logStreamPageState.context.parsedQuery)
|
||||
|
@ -38,7 +38,7 @@ export const WithSummary = ({
|
|||
const throttledEndTimestamp = useThrottle(endTimestamp, FETCH_THROTTLE_INTERVAL);
|
||||
|
||||
const { buckets, start, end } = useLogSummary(
|
||||
logViewId,
|
||||
logViewReference,
|
||||
throttledStartTimestamp,
|
||||
throttledEndTimestamp,
|
||||
serializedParsedQuery
|
||||
|
|
|
@ -7,10 +7,11 @@
|
|||
|
||||
import { useState } from 'react';
|
||||
import createContainer from 'constate';
|
||||
import { LogViewReference } from '../../../../common/log_views';
|
||||
import { LogEntry } from '../../../../common/log_entry';
|
||||
|
||||
interface ViewLogInContextProps {
|
||||
sourceId: string;
|
||||
logViewReference: LogViewReference;
|
||||
startTimestamp: number;
|
||||
endTimestamp: number;
|
||||
}
|
||||
|
@ -27,13 +28,13 @@ export const useViewLogInContext = (
|
|||
props: ViewLogInContextProps
|
||||
): [ViewLogInContextState, ViewLogInContextCallbacks] => {
|
||||
const [contextEntry, setContextEntry] = useState<LogEntry | undefined>();
|
||||
const { startTimestamp, endTimestamp, sourceId } = props;
|
||||
const { startTimestamp, endTimestamp, logViewReference } = props;
|
||||
|
||||
return [
|
||||
{
|
||||
startTimestamp,
|
||||
endTimestamp,
|
||||
sourceId,
|
||||
logViewReference,
|
||||
contextEntry,
|
||||
},
|
||||
{
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
/*
|
||||
* 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 * as runtimeTypes from 'io-ts';
|
||||
|
||||
import { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { fold } from 'fp-ts/lib/Either';
|
||||
import { constant, identity } from 'fp-ts/lib/function';
|
||||
import { useUrlState, replaceStateKeyInQueryString } from '../../utils/use_url_state';
|
||||
|
||||
const SOURCE_ID_URL_STATE_KEY = 'sourceId';
|
||||
|
||||
export const useSourceId = () => {
|
||||
return useUrlState({
|
||||
defaultState: 'default',
|
||||
decodeUrlState: decodeSourceIdUrlState,
|
||||
encodeUrlState: encodeSourceIdUrlState,
|
||||
urlStateKey: SOURCE_ID_URL_STATE_KEY,
|
||||
});
|
||||
};
|
||||
|
||||
export const replaceSourceIdInQueryString = (sourceId: string) =>
|
||||
replaceStateKeyInQueryString(SOURCE_ID_URL_STATE_KEY, sourceId);
|
||||
|
||||
const sourceIdRuntimeType = runtimeTypes.union([runtimeTypes.string, runtimeTypes.undefined]);
|
||||
const encodeSourceIdUrlState = sourceIdRuntimeType.encode;
|
||||
const decodeSourceIdUrlState = (value: unknown) =>
|
||||
pipe(sourceIdRuntimeType.decode(value), fold(constant(undefined), identity));
|
|
@ -36,12 +36,18 @@ export const createUninitializedUseLogViewMock =
|
|||
load: jest.fn(),
|
||||
retry: jest.fn(),
|
||||
logView: undefined,
|
||||
logViewId,
|
||||
logViewReference: { type: 'log-view-reference', logViewId },
|
||||
logViewStatus: undefined,
|
||||
resolvedLogView: undefined,
|
||||
update: jest.fn(),
|
||||
logViewStateService: interpret(createPureLogViewStateMachine({ logViewId })),
|
||||
changeLogViewReference: jest.fn(),
|
||||
revertToDefaultLogView: jest.fn(),
|
||||
logViewStateService: interpret(
|
||||
createPureLogViewStateMachine({ logViewReference: { type: 'log-view-reference', logViewId } })
|
||||
),
|
||||
logViewStateNotifications: createLogViewNotificationChannel(),
|
||||
isPersistedLogView: false,
|
||||
isInlineLogView: false,
|
||||
});
|
||||
|
||||
export const createLoadingUseLogViewMock =
|
||||
|
|
|
@ -7,62 +7,90 @@
|
|||
|
||||
import { useInterpret, useSelector } from '@xstate/react';
|
||||
import createContainer from 'constate';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { waitFor } from 'xstate/lib/waitFor';
|
||||
import { LogViewAttributes } from '../../common/log_views';
|
||||
import { LogViewAttributes, LogViewReference } from '../../common/log_views';
|
||||
import {
|
||||
createLogViewNotificationChannel,
|
||||
createLogViewStateMachine,
|
||||
DEFAULT_LOG_VIEW,
|
||||
} from '../observability_logs/log_view_state';
|
||||
import type { ILogViewsClient } from '../services/log_views';
|
||||
import { isDevMode } from '../utils/dev_mode';
|
||||
import { useKbnUrlStateStorageFromRouterContext } from '../utils/kbn_url_state_context';
|
||||
import { useKibanaContextForPlugin } from './use_kibana';
|
||||
|
||||
export const useLogView = ({
|
||||
logViewId,
|
||||
initialLogViewReference,
|
||||
logViews,
|
||||
useDevTools = isDevMode(),
|
||||
}: {
|
||||
logViewId: string;
|
||||
initialLogViewReference?: LogViewReference;
|
||||
logViews: ILogViewsClient;
|
||||
useDevTools?: boolean;
|
||||
}) => {
|
||||
const {
|
||||
services: {
|
||||
notifications: { toasts: toastsService },
|
||||
},
|
||||
} = useKibanaContextForPlugin();
|
||||
|
||||
const urlStateStorage = useKbnUrlStateStorageFromRouterContext();
|
||||
|
||||
const [logViewStateNotifications] = useState(() => createLogViewNotificationChannel());
|
||||
|
||||
const logViewStateService = useInterpret(
|
||||
() =>
|
||||
createLogViewStateMachine({
|
||||
initialContext: {
|
||||
logViewId,
|
||||
logViewReference: initialLogViewReference ?? DEFAULT_LOG_VIEW,
|
||||
},
|
||||
logViews,
|
||||
notificationChannel: logViewStateNotifications,
|
||||
toastsService,
|
||||
urlStateStorage,
|
||||
}),
|
||||
{
|
||||
devTools: useDevTools,
|
||||
}
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
logViewStateService.send({
|
||||
type: 'LOG_VIEW_ID_CHANGED',
|
||||
logViewId,
|
||||
});
|
||||
}, [logViewId, logViewStateService]);
|
||||
const changeLogViewReference = useCallback(
|
||||
(logViewReference: LogViewReference) => {
|
||||
logViewStateService.send({
|
||||
type: 'LOG_VIEW_REFERENCE_CHANGED',
|
||||
logViewReference,
|
||||
});
|
||||
},
|
||||
[logViewStateService]
|
||||
);
|
||||
|
||||
const logViewReference = useSelector(
|
||||
logViewStateService,
|
||||
(state) => state.context.logViewReference
|
||||
);
|
||||
|
||||
const logView = useSelector(logViewStateService, (state) =>
|
||||
state.matches('resolving') || state.matches('checkingStatus') || state.matches('resolved')
|
||||
state.matches('resolving') ||
|
||||
state.matches('checkingStatus') ||
|
||||
state.matches('resolvedPersistedLogView') ||
|
||||
state.matches('resolvedInlineLogView')
|
||||
? state.context.logView
|
||||
: undefined
|
||||
);
|
||||
|
||||
const resolvedLogView = useSelector(logViewStateService, (state) =>
|
||||
state.matches('checkingStatus') || state.matches('resolved')
|
||||
state.matches('checkingStatus') ||
|
||||
state.matches('resolvedPersistedLogView') ||
|
||||
state.matches('resolvedInlineLogView')
|
||||
? state.context.resolvedLogView
|
||||
: undefined
|
||||
);
|
||||
|
||||
const logViewStatus = useSelector(logViewStateService, (state) =>
|
||||
state.matches('resolved') ? state.context.status : undefined
|
||||
state.matches('resolvedPersistedLogView') || state.matches('resolvedInlineLogView')
|
||||
? state.context.status
|
||||
: undefined
|
||||
);
|
||||
|
||||
const isLoadingLogView = useSelector(logViewStateService, (state) => state.matches('loading'));
|
||||
|
@ -91,6 +119,13 @@ export const useLogView = ({
|
|||
state.matches('checkingStatusFailed')
|
||||
);
|
||||
|
||||
const isPersistedLogView = useSelector(
|
||||
logViewStateService,
|
||||
(state) => state.context.logViewReference.type === 'log-view-reference'
|
||||
);
|
||||
|
||||
const isInlineLogView = !isPersistedLogView;
|
||||
|
||||
const latestLoadLogViewFailures = useSelector(logViewStateService, (state) =>
|
||||
state.matches('loadingFailed') ||
|
||||
state.matches('resolutionFailed') ||
|
||||
|
@ -118,7 +153,10 @@ export const useLogView = ({
|
|||
|
||||
const doneState = await waitFor(
|
||||
logViewStateService,
|
||||
(state) => state.matches('updatingFailed') || state.matches('resolved')
|
||||
(state) =>
|
||||
state.matches('updatingFailed') ||
|
||||
state.matches('resolvedPersistedLogView') ||
|
||||
state.matches('resolvedInlineLogView')
|
||||
);
|
||||
|
||||
if (doneState.matches('updatingFailed')) {
|
||||
|
@ -128,8 +166,12 @@ export const useLogView = ({
|
|||
[logViewStateService]
|
||||
);
|
||||
|
||||
const revertToDefaultLogView = useCallback(() => {
|
||||
changeLogViewReference(DEFAULT_LOG_VIEW);
|
||||
}, [changeLogViewReference]);
|
||||
|
||||
return {
|
||||
// underlying state machine
|
||||
// Underlying state machine
|
||||
logViewStateService,
|
||||
logViewStateNotifications,
|
||||
|
||||
|
@ -147,17 +189,21 @@ export const useLogView = ({
|
|||
isLoadingLogViewStatus,
|
||||
isResolvingLogView,
|
||||
|
||||
// data
|
||||
logViewId,
|
||||
// Data
|
||||
logViewReference,
|
||||
logView,
|
||||
resolvedLogView,
|
||||
logViewStatus,
|
||||
derivedDataView: resolvedLogView?.dataViewReference,
|
||||
isInlineLogView,
|
||||
isPersistedLogView,
|
||||
|
||||
// actions
|
||||
// Actions
|
||||
load: retry,
|
||||
retry,
|
||||
update,
|
||||
changeLogViewReference,
|
||||
revertToDefaultLogView,
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -5,4 +5,8 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
export * from './source_id';
|
||||
const DEFAULT_LOG_VIEW_ID = 'default';
|
||||
export const DEFAULT_LOG_VIEW = {
|
||||
type: 'log-view-reference' as const,
|
||||
logViewId: DEFAULT_LOG_VIEW_ID,
|
||||
};
|
|
@ -8,3 +8,5 @@
|
|||
export { createLogViewNotificationChannel, type LogViewNotificationEvent } from './notifications';
|
||||
export * from './state_machine';
|
||||
export * from './types';
|
||||
export * from './defaults';
|
||||
export * from './url_state_storage_service';
|
||||
|
|
|
@ -5,14 +5,14 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { LogViewStatus, ResolvedLogView } from '../../../../common/log_views';
|
||||
import { LogViewReference, LogViewStatus, ResolvedLogView } from '../../../../common/log_views';
|
||||
import { createNotificationChannel } from '../../xstate_helpers';
|
||||
import { LogViewContext, LogViewEvent } from './types';
|
||||
|
||||
export type LogViewNotificationEvent =
|
||||
| {
|
||||
type: 'LOADING_LOG_VIEW_STARTED';
|
||||
logViewId: string;
|
||||
logViewReference: LogViewReference;
|
||||
}
|
||||
| {
|
||||
type: 'LOADING_LOG_VIEW_SUCCEEDED';
|
||||
|
@ -29,10 +29,10 @@ export const createLogViewNotificationChannel = () =>
|
|||
|
||||
export const logViewNotificationEventSelectors = {
|
||||
loadingLogViewStarted: (context: LogViewContext) =>
|
||||
'logViewId' in context
|
||||
'logViewReference' in context
|
||||
? ({
|
||||
type: 'LOADING_LOG_VIEW_STARTED',
|
||||
logViewId: context.logViewId,
|
||||
logViewReference: context.logViewReference,
|
||||
} as LogViewNotificationEvent)
|
||||
: undefined,
|
||||
loadingLogViewSucceeded: (context: LogViewContext) =>
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { IToasts } from '@kbn/core/public';
|
||||
import { IKbnUrlStateStorage } from '@kbn/kibana-utils-plugin/public';
|
||||
import { catchError, from, map, of, throwError } from 'rxjs';
|
||||
import { createMachine, actions, assign } from 'xstate';
|
||||
import { ILogViewsClient } from '../../../services/log_views';
|
||||
|
@ -13,15 +15,20 @@ import { LogViewNotificationEvent, logViewNotificationEventSelectors } from './n
|
|||
import {
|
||||
LogViewContext,
|
||||
LogViewContextWithError,
|
||||
LogViewContextWithId,
|
||||
LogViewContextWithLogView,
|
||||
LogViewContextWithReference,
|
||||
LogViewContextWithResolvedLogView,
|
||||
LogViewContextWithStatus,
|
||||
LogViewEvent,
|
||||
LogViewTypestate,
|
||||
} from './types';
|
||||
import {
|
||||
initializeFromUrl,
|
||||
updateContextInUrl,
|
||||
listenForUrlChanges,
|
||||
} from './url_state_storage_service';
|
||||
|
||||
export const createPureLogViewStateMachine = (initialContext: LogViewContextWithId) =>
|
||||
export const createPureLogViewStateMachine = (initialContext: LogViewContextWithReference) =>
|
||||
/** @xstate-layout N4IgpgJg5mDOIC5QBkD2UBqBLMB3AxMgPIDiA+hgJICiA6mZQCJkDCAEgIIByJ1jA2gAYAuolAAHVLCwAXLKgB2YkAA9EAFkEBmAHQAmAGwB2dQYAcAVkGmAjGYMAaEAE9EAWi3qjO9WcGCLIzMbLWMLAE4AX0inNEwcAgBVAAVGDgAVaiFRJBBJaTlFZTUEG20DfUEDLUtwg3CbdXUtJ1cEDz11HSMLTzMagwtjAOjY9Gw8HQBXBSxZuQBDABssAC9IfGzlfNl5JVySz3CdGwMwrRsbCK0a1sQyiqNDCMEjcK09QT8LUZA4idwOiWqAWEDmUEIRA4jEoPDIAGVEiwWNQ+HwtrkdoV9qASkZPDoLnpwmZniTgmY7ghSTorP5fFo6lorHobL9-gkgSCwQoIcRobDyAAxDiUZDokTbKS7IoHRBWMzdT4vCxDPQ3PRUvTqnThcL+OoWVmdU7qdnjTkAJzgqCWADdwfgAErUeFEZCJdKUIhcMgisUSnISaXY4qIcm0y6XJ5mdR6Iw2IxanV6g2DY3qRPm+KTa2wW0O3nO13uz3e32I5GoxiBqUFPZh0ra7z9S6CBo9G4tFyIGl06z9JlWOzZgE6ADGAAswOOANbg+EyBYyKawfDsagsADSgoR6QyiXhftF4oEksxIYbctKFhCOksxjsF3CQSTPYQ2t0qfb6ZsJqMo6clOM7zryi7Lqu65sJuO5wvC+7pIeCJIiiaJnkGeSXrKuL3K+3RnJ8eqGPGgRUm8PjEvq5jBKE6oATEfwWrmNr2hsLr8swxDkFQdAYsG9bYao9x6BYXSkjcBrqp8RiOO+Hg6NUAzhEM6gkvUNRmgxHKTMCoLgkKCxYEsbHUOkToAJp8ZhAk4kJCCMl0MlPKqoS+O2b5tJ0jynGc+KCHowR1HogHMfmSxTNiBlGSZZmWee-EyrZJT6jYCkfARVxaDJsZaqY3Q+cYWj+YFBghYCwFzguS4rrAUXGRAxaxVZWJXjhpSZt4ng2O84Qie2njdp5eUJmchXFd1BjBVpTGAlM4gQMujopGkXpwv6p7NVhSXCV43SqfU2qqYYWVUm42rHAOcb1L12gsmV0zzYtRbLRku6VqhNboXWiWNi+3j6spfSDB84TqKdZjHAY-kRE05hfMSbLTTms2PXIvJ1SZHFkFxFA0LQm02Y2xiKsyJJNPqNxQ5Scl-hYOgBEVt6fsYqn0QxCioBAcDKNpuDfaG14hIq9T2FcjSWEEgync0XQkv4k1ZQYjQRD8SNjjMcy7MsayQPzrV2XYFQi0rt6+IE9gWFSZR09qZzNN1fRDFEaucrpPJQHrgklF4ej0yJInKRDpIeYg5hKoMZhvGUbxFfRYzIzoeYFuCnvbQgcs+Gb+oib4x1UsE4e9PYZzWLe90VaBUDgTVqeNmLF1GlU9S+FDRpkcLKkhO8Vwkqr8djknrEQLX16spcCl0poRoGE0NztxP1QvmLARPM7-eu9y+mGfVI9tV4RuXOqgSJsSIlUrRJyr8fdT+FUfeMQng8RXsGPDxehPXkvlTVAmQy9Le59JqX2JPiF8gxMyR3LtOSqYFqqrlfrvA2jcfAWH6IYGePtwjnwiCcYqHxbCqk0Jpdekw5oLTRh7d+P1P5nAUp8Dq6hRJeFVKdTovtSR2CaD+TMTQzD3TIU9KACCqECzauLOmYtiZZRqAOVhxJ7ysljCEGSTQ4xs0iEAA */
|
||||
createMachine<LogViewContext, LogViewEvent, LogViewTypestate>(
|
||||
{
|
||||
|
@ -33,11 +40,22 @@ export const createPureLogViewStateMachine = (initialContext: LogViewContextWith
|
|||
states: {
|
||||
uninitialized: {
|
||||
always: {
|
||||
target: 'loading',
|
||||
target: 'initializingFromUrl',
|
||||
},
|
||||
},
|
||||
initializingFromUrl: {
|
||||
on: {
|
||||
INITIALIZED_FROM_URL: {
|
||||
target: 'loading',
|
||||
actions: ['storeLogViewReference'],
|
||||
},
|
||||
},
|
||||
invoke: {
|
||||
src: 'initializeFromUrl',
|
||||
},
|
||||
},
|
||||
loading: {
|
||||
entry: 'notifyLoadingStarted',
|
||||
entry: ['notifyLoadingStarted', 'updateContextInUrl'],
|
||||
invoke: {
|
||||
src: 'loadLogView',
|
||||
},
|
||||
|
@ -76,17 +94,68 @@ export const createPureLogViewStateMachine = (initialContext: LogViewContextWith
|
|||
target: 'checkingStatusFailed',
|
||||
actions: 'storeError',
|
||||
},
|
||||
CHECKING_STATUS_SUCCEEDED: {
|
||||
target: 'resolved',
|
||||
actions: 'storeStatus',
|
||||
CHECKING_STATUS_SUCCEEDED: [
|
||||
{
|
||||
target: 'resolvedPersistedLogView',
|
||||
actions: 'storeStatus',
|
||||
cond: 'isPersistedLogView',
|
||||
},
|
||||
{
|
||||
target: 'resolvedInlineLogView',
|
||||
actions: 'storeStatus',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
resolvedPersistedLogView: {
|
||||
invoke: {
|
||||
src: 'listenForUrlChanges',
|
||||
},
|
||||
entry: ['notifyLoadingSucceeded', 'updateContextInUrl'],
|
||||
on: {
|
||||
PERSIST_INLINE_LOG_VIEW: undefined,
|
||||
RELOAD_LOG_VIEW: {
|
||||
target: 'loading',
|
||||
},
|
||||
LOG_VIEW_URL_KEY_REMOVED: {
|
||||
actions: 'updateContextInUrl',
|
||||
},
|
||||
},
|
||||
},
|
||||
resolved: {
|
||||
entry: 'notifyLoadingSucceeded',
|
||||
resolvedInlineLogView: {
|
||||
invoke: {
|
||||
src: 'listenForUrlChanges',
|
||||
},
|
||||
entry: ['notifyLoadingSucceeded', 'updateContextInUrl'],
|
||||
on: {
|
||||
RELOAD_LOG_VIEW: {
|
||||
target: 'loading',
|
||||
PERSIST_INLINE_LOG_VIEW: {
|
||||
target: 'persistingInlineLogView',
|
||||
},
|
||||
LOG_VIEW_URL_KEY_REMOVED: {
|
||||
actions: 'updateContextInUrl',
|
||||
},
|
||||
},
|
||||
},
|
||||
persistingInlineLogView: {
|
||||
invoke: {
|
||||
src: 'persistInlineLogView',
|
||||
},
|
||||
on: {
|
||||
PERSISTING_INLINE_LOG_VIEW_FAILED: {
|
||||
target: 'persistingInlineLogViewFailed',
|
||||
actions: 'storeError',
|
||||
},
|
||||
PERSISTING_INLINE_LOG_VIEW_SUCCEEDED: {
|
||||
target: 'resolving',
|
||||
actions: ['convertInlineLogViewReferenceToPersistedLogViewReference', 'storeLogView'],
|
||||
},
|
||||
},
|
||||
},
|
||||
persistingInlineLogViewFailed: {
|
||||
entry: 'notifyPersistingInlineLogViewFailed',
|
||||
on: {
|
||||
RETRY_PERSISTING_INLINE_LOG_VIEW: {
|
||||
target: 'persistingInlineLogView',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -126,7 +195,7 @@ export const createPureLogViewStateMachine = (initialContext: LogViewContextWith
|
|||
},
|
||||
UPDATING_SUCCEEDED: {
|
||||
target: 'resolving',
|
||||
actions: 'storeLogView',
|
||||
actions: ['updateLogViewReference', 'storeLogView'],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -140,10 +209,9 @@ export const createPureLogViewStateMachine = (initialContext: LogViewContextWith
|
|||
},
|
||||
},
|
||||
on: {
|
||||
LOG_VIEW_ID_CHANGED: {
|
||||
LOG_VIEW_REFERENCE_CHANGED: {
|
||||
target: '.loading',
|
||||
actions: 'storeLogViewId',
|
||||
cond: 'isLogViewIdDifferent',
|
||||
actions: 'storeLogViewReference',
|
||||
},
|
||||
UPDATE: {
|
||||
target: '.updating',
|
||||
|
@ -155,11 +223,11 @@ export const createPureLogViewStateMachine = (initialContext: LogViewContextWith
|
|||
notifyLoadingStarted: actions.pure(() => undefined),
|
||||
notifyLoadingSucceeded: actions.pure(() => undefined),
|
||||
notifyLoadingFailed: actions.pure(() => undefined),
|
||||
storeLogViewId: assign((context, event) =>
|
||||
'logViewId' in event
|
||||
storeLogViewReference: assign((context, event) =>
|
||||
'logViewReference' in event && event.logViewReference !== null
|
||||
? ({
|
||||
logViewId: event.logViewId,
|
||||
} as LogViewContextWithId)
|
||||
logViewReference: event.logViewReference,
|
||||
} as LogViewContextWithReference)
|
||||
: {}
|
||||
),
|
||||
storeLogView: assign((context, event) =>
|
||||
|
@ -190,28 +258,55 @@ export const createPureLogViewStateMachine = (initialContext: LogViewContextWith
|
|||
} as LogViewContextWithError)
|
||||
: {}
|
||||
),
|
||||
convertInlineLogViewReferenceToPersistedLogViewReference: assign((context, event) =>
|
||||
'logView' in event && context.logViewReference.type === 'log-view-inline'
|
||||
? ({
|
||||
logViewReference: {
|
||||
type: 'log-view-reference',
|
||||
logViewId: context.logViewReference.id,
|
||||
},
|
||||
} as LogViewContextWithReference)
|
||||
: {}
|
||||
),
|
||||
updateLogViewReference: assign((context, event) =>
|
||||
'attributes' in event && context.logViewReference.type === 'log-view-inline'
|
||||
? ({
|
||||
logViewReference: {
|
||||
...context.logViewReference,
|
||||
attributes: {
|
||||
...context.logViewReference.attributes,
|
||||
...event.attributes,
|
||||
},
|
||||
},
|
||||
} as LogViewContextWithReference)
|
||||
: {}
|
||||
),
|
||||
},
|
||||
guards: {
|
||||
isLogViewIdDifferent: (context, event) =>
|
||||
'logViewId' in event ? event.logViewId !== context.logViewId : false,
|
||||
isPersistedLogView: (context, event) =>
|
||||
context.logViewReference.type === 'log-view-reference',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export interface LogViewStateMachineDependencies {
|
||||
initialContext: LogViewContextWithId;
|
||||
initialContext: LogViewContextWithReference;
|
||||
logViews: ILogViewsClient;
|
||||
notificationChannel?: NotificationChannel<LogViewContext, LogViewEvent, LogViewNotificationEvent>;
|
||||
toastsService: IToasts;
|
||||
urlStateStorage: IKbnUrlStateStorage;
|
||||
}
|
||||
|
||||
export const createLogViewStateMachine = ({
|
||||
initialContext,
|
||||
logViews,
|
||||
notificationChannel,
|
||||
toastsService,
|
||||
urlStateStorage,
|
||||
}: LogViewStateMachineDependencies) =>
|
||||
createPureLogViewStateMachine(initialContext).withConfig({
|
||||
actions:
|
||||
notificationChannel != null
|
||||
actions: {
|
||||
...(notificationChannel != null
|
||||
? {
|
||||
notifyLoadingStarted: notificationChannel.notify(
|
||||
logViewNotificationEventSelectors.loadingLogViewStarted
|
||||
|
@ -223,13 +318,17 @@ export const createLogViewStateMachine = ({
|
|||
logViewNotificationEventSelectors.loadingLogViewFailed
|
||||
),
|
||||
}
|
||||
: {},
|
||||
: {}),
|
||||
updateContextInUrl: updateContextInUrl({ toastsService, urlStateStorage }),
|
||||
},
|
||||
services: {
|
||||
initializeFromUrl: initializeFromUrl({ toastsService, urlStateStorage }),
|
||||
listenForUrlChanges: listenForUrlChanges({ urlStateStorage }),
|
||||
loadLogView: (context) =>
|
||||
from(
|
||||
'logViewId' in context
|
||||
? logViews.getLogView(context.logViewId)
|
||||
: throwError(() => new Error('Failed to load log view: No id found in context.'))
|
||||
'logViewReference' in context
|
||||
? logViews.getLogView(context.logViewReference)
|
||||
: throwError(() => new Error('Failed to load log view'))
|
||||
).pipe(
|
||||
map(
|
||||
(logView): LogViewEvent => ({
|
||||
|
@ -246,8 +345,8 @@ export const createLogViewStateMachine = ({
|
|||
),
|
||||
updateLogView: (context, event) =>
|
||||
from(
|
||||
'logViewId' in context && event.type === 'UPDATE'
|
||||
? logViews.putLogView(context.logViewId, event.attributes)
|
||||
'logViewReference' in context && event.type === 'UPDATE'
|
||||
? logViews.putLogView(context.logViewReference, event.attributes)
|
||||
: throwError(
|
||||
() =>
|
||||
new Error(
|
||||
|
@ -268,6 +367,30 @@ export const createLogViewStateMachine = ({
|
|||
})
|
||||
)
|
||||
),
|
||||
persistInlineLogView: (context, event) =>
|
||||
from(
|
||||
'logViewReference' in context &&
|
||||
event.type === 'PERSIST_INLINE_LOG_VIEW' &&
|
||||
context.logViewReference.type === 'log-view-inline'
|
||||
? logViews.putLogView(
|
||||
{ type: 'log-view-reference', logViewId: context.logViewReference.id },
|
||||
context.logViewReference.attributes
|
||||
)
|
||||
: throwError(() => new Error('Failed to persist inline Log View.'))
|
||||
).pipe(
|
||||
map(
|
||||
(logView): LogViewEvent => ({
|
||||
type: 'PERSISTING_INLINE_LOG_VIEW_SUCCEEDED',
|
||||
logView,
|
||||
})
|
||||
),
|
||||
catchError((error) =>
|
||||
of<LogViewEvent>({
|
||||
type: 'PERSISTING_INLINE_LOG_VIEW_FAILED',
|
||||
error,
|
||||
})
|
||||
)
|
||||
),
|
||||
resolveLogView: (context) =>
|
||||
from(
|
||||
'logView' in context
|
||||
|
|
|
@ -9,14 +9,15 @@ import type { ActorRef } from 'xstate';
|
|||
import type {
|
||||
LogView,
|
||||
LogViewAttributes,
|
||||
LogViewReference,
|
||||
LogViewStatus,
|
||||
ResolvedLogView,
|
||||
} from '../../../../common/log_views';
|
||||
import { type NotificationChannel } from '../../xstate_helpers';
|
||||
import { type LogViewNotificationEvent } from './notifications';
|
||||
|
||||
export interface LogViewContextWithId {
|
||||
logViewId: string;
|
||||
export interface LogViewContextWithReference {
|
||||
logViewReference: LogViewReference;
|
||||
}
|
||||
|
||||
export interface LogViewContextWithLogView {
|
||||
|
@ -38,46 +39,55 @@ export interface LogViewContextWithError {
|
|||
export type LogViewTypestate =
|
||||
| {
|
||||
value: 'uninitialized';
|
||||
context: LogViewContextWithId;
|
||||
context: LogViewContextWithReference;
|
||||
}
|
||||
| {
|
||||
value: 'loading';
|
||||
context: LogViewContextWithId;
|
||||
context: LogViewContextWithReference;
|
||||
}
|
||||
| {
|
||||
value: 'resolving';
|
||||
context: LogViewContextWithId & LogViewContextWithLogView;
|
||||
context: LogViewContextWithReference & LogViewContextWithLogView;
|
||||
}
|
||||
| {
|
||||
value: 'checkingStatus';
|
||||
context: LogViewContextWithId & LogViewContextWithLogView & LogViewContextWithResolvedLogView;
|
||||
context: LogViewContextWithReference &
|
||||
LogViewContextWithLogView &
|
||||
LogViewContextWithResolvedLogView;
|
||||
}
|
||||
| {
|
||||
value: 'resolved';
|
||||
context: LogViewContextWithId &
|
||||
value: 'resolvedPersistedLogView';
|
||||
context: LogViewContextWithReference &
|
||||
LogViewContextWithLogView &
|
||||
LogViewContextWithResolvedLogView &
|
||||
LogViewContextWithStatus;
|
||||
}
|
||||
| {
|
||||
value: 'resolvedInlineLogView';
|
||||
context: LogViewContextWithReference &
|
||||
LogViewContextWithLogView &
|
||||
LogViewContextWithResolvedLogView &
|
||||
LogViewContextWithStatus;
|
||||
}
|
||||
| {
|
||||
value: 'updating';
|
||||
context: LogViewContextWithId;
|
||||
context: LogViewContextWithReference;
|
||||
}
|
||||
| {
|
||||
value: 'loadingFailed';
|
||||
context: LogViewContextWithId & LogViewContextWithError;
|
||||
context: LogViewContextWithReference & LogViewContextWithError;
|
||||
}
|
||||
| {
|
||||
value: 'updatingFailed';
|
||||
context: LogViewContextWithId & LogViewContextWithError;
|
||||
context: LogViewContextWithReference & LogViewContextWithError;
|
||||
}
|
||||
| {
|
||||
value: 'resolutionFailed';
|
||||
context: LogViewContextWithId & LogViewContextWithLogView & LogViewContextWithError;
|
||||
context: LogViewContextWithReference & LogViewContextWithLogView & LogViewContextWithError;
|
||||
}
|
||||
| {
|
||||
value: 'checkingStatusFailed';
|
||||
context: LogViewContextWithId & LogViewContextWithLogView & LogViewContextWithError;
|
||||
context: LogViewContextWithReference & LogViewContextWithLogView & LogViewContextWithError;
|
||||
};
|
||||
|
||||
export type LogViewContext = LogViewTypestate['context'];
|
||||
|
@ -86,8 +96,12 @@ export type LogViewStateValue = LogViewTypestate['value'];
|
|||
|
||||
export type LogViewEvent =
|
||||
| {
|
||||
type: 'LOG_VIEW_ID_CHANGED';
|
||||
logViewId: string;
|
||||
type: 'LOG_VIEW_REFERENCE_CHANGED';
|
||||
logViewReference: LogViewReference;
|
||||
}
|
||||
| {
|
||||
type: 'INITIALIZED_FROM_URL';
|
||||
logViewReference: LogViewReference | null;
|
||||
}
|
||||
| {
|
||||
type: 'LOADING_SUCCEEDED';
|
||||
|
@ -130,7 +144,17 @@ export type LogViewEvent =
|
|||
}
|
||||
| {
|
||||
type: 'RELOAD_LOG_VIEW';
|
||||
};
|
||||
}
|
||||
| {
|
||||
type: 'PERSIST_INLINE_LOG_VIEW';
|
||||
}
|
||||
| { type: 'PERSISTING_INLINE_LOG_VIEW_FAILED'; error: Error }
|
||||
| { type: 'PERSISTING_INLINE_LOG_VIEW_SUCCEEDED'; logView: LogView }
|
||||
| {
|
||||
type: 'RETRY_PERSISTING_INLINE_LOG_VIEW';
|
||||
}
|
||||
| { type: 'LOG_VIEW_URL_KEY_REMOVED' }
|
||||
| { type: 'LOG_VIEW_URL_KEY_CHANGED' };
|
||||
|
||||
export type LogViewActorRef = ActorRef<LogViewEvent, LogViewContext>;
|
||||
export type LogViewNotificationChannel = NotificationChannel<
|
||||
|
|
|
@ -0,0 +1,135 @@
|
|||
/*
|
||||
* 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 * as rt from 'io-ts';
|
||||
import { IToasts } from '@kbn/core/public';
|
||||
import { IKbnUrlStateStorage, withNotifyOnErrors } from '@kbn/kibana-utils-plugin/public';
|
||||
import { InvokeCreator } from 'xstate';
|
||||
import * as Either from 'fp-ts/lib/Either';
|
||||
import { identity, pipe } from 'fp-ts/lib/function';
|
||||
import { map } from 'rxjs';
|
||||
import { createPlainError, formatErrors } from '../../../../common/runtime_types';
|
||||
import {
|
||||
LogViewReference,
|
||||
logViewReferenceRT,
|
||||
PersistedLogViewReference,
|
||||
} from '../../../../common/log_views';
|
||||
import { LogViewContext, LogViewEvent } from './types';
|
||||
import { replaceStateKeyInQueryString } from '../../../utils/url_state';
|
||||
|
||||
export const defaultLogViewKey = 'logView';
|
||||
const defaultLegacySourceIdKey = 'sourceId';
|
||||
|
||||
interface LogViewUrlStateDependencies {
|
||||
logViewKey?: string;
|
||||
sourceIdKey?: string;
|
||||
toastsService: IToasts;
|
||||
urlStateStorage: IKbnUrlStateStorage;
|
||||
}
|
||||
|
||||
export const updateContextInUrl =
|
||||
({ urlStateStorage, logViewKey = defaultLogViewKey }: LogViewUrlStateDependencies) =>
|
||||
(context: LogViewContext, _event: LogViewEvent) => {
|
||||
if (!('logViewReference' in context)) {
|
||||
throw new Error('Missing keys from context needed to sync to the URL');
|
||||
}
|
||||
|
||||
urlStateStorage.set(logViewKey, logViewStateInUrlRT.encode(context.logViewReference), {
|
||||
replace: true,
|
||||
});
|
||||
};
|
||||
|
||||
export const initializeFromUrl =
|
||||
({
|
||||
logViewKey = defaultLogViewKey,
|
||||
sourceIdKey = defaultLegacySourceIdKey,
|
||||
toastsService,
|
||||
urlStateStorage,
|
||||
}: LogViewUrlStateDependencies): InvokeCreator<LogViewContext, LogViewEvent> =>
|
||||
(_context, _event) =>
|
||||
(send) => {
|
||||
const logViewQueryValueFromUrl = urlStateStorage.get(logViewKey);
|
||||
const logViewQueryE = decodeLogViewQueryValueFromUrl(logViewQueryValueFromUrl);
|
||||
|
||||
const legacySourceIdQueryValueFromUrl = urlStateStorage.get(sourceIdKey);
|
||||
const sourceIdQueryE = decodeSourceIdQueryValueFromUrl(legacySourceIdQueryValueFromUrl);
|
||||
|
||||
if (Either.isLeft(logViewQueryE) || Either.isLeft(sourceIdQueryE)) {
|
||||
withNotifyOnErrors(toastsService).onGetError(
|
||||
createPlainError(
|
||||
formatErrors([
|
||||
...(Either.isLeft(logViewQueryE) ? logViewQueryE.left : []),
|
||||
...(Either.isLeft(sourceIdQueryE) ? sourceIdQueryE.left : []),
|
||||
])
|
||||
)
|
||||
);
|
||||
|
||||
send({
|
||||
type: 'INITIALIZED_FROM_URL',
|
||||
logViewReference: null,
|
||||
});
|
||||
} else {
|
||||
send({
|
||||
type: 'INITIALIZED_FROM_URL',
|
||||
logViewReference: pipe(
|
||||
// Via the legacy sourceId key
|
||||
pipe(
|
||||
sourceIdQueryE.right,
|
||||
Either.fromNullable(null),
|
||||
Either.map(convertSourceIdToReference)
|
||||
),
|
||||
// Via the logView key
|
||||
Either.alt(() => pipe(logViewQueryE.right, Either.fromNullable(null))),
|
||||
Either.fold(identity, identity)
|
||||
),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// NOTE: Certain navigations within the Logs solution will remove the logView URL key,
|
||||
// we want to ensure the logView key is present in the URL at all times by monitoring for it's removal.
|
||||
export const listenForUrlChanges =
|
||||
({
|
||||
urlStateStorage,
|
||||
logViewKey = defaultLogViewKey,
|
||||
}: {
|
||||
urlStateStorage: LogViewUrlStateDependencies['urlStateStorage'];
|
||||
logViewKey?: LogViewUrlStateDependencies['logViewKey'];
|
||||
}): InvokeCreator<LogViewContext, LogViewEvent> =>
|
||||
(context, event) => {
|
||||
return urlStateStorage
|
||||
.change$(logViewKey)
|
||||
.pipe(
|
||||
map((value) =>
|
||||
value === undefined || value === null
|
||||
? { type: 'LOG_VIEW_URL_KEY_REMOVED' }
|
||||
: { type: 'LOG_VIEW_URL_KEY_CHANGED' }
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const logViewStateInUrlRT = rt.union([logViewReferenceRT, rt.null]);
|
||||
const sourceIdStateInUrl = rt.union([rt.string, rt.null]);
|
||||
|
||||
const decodeLogViewQueryValueFromUrl = (queryValueFromUrl: unknown) => {
|
||||
return logViewStateInUrlRT.decode(queryValueFromUrl);
|
||||
};
|
||||
|
||||
const decodeSourceIdQueryValueFromUrl = (queryValueFromUrl: unknown) => {
|
||||
return sourceIdStateInUrl.decode(queryValueFromUrl);
|
||||
};
|
||||
|
||||
const convertSourceIdToReference = (sourceId: string): PersistedLogViewReference => {
|
||||
return {
|
||||
type: 'log-view-reference' as const,
|
||||
logViewId: sourceId,
|
||||
};
|
||||
};
|
||||
|
||||
// NOTE: Used by link-to components
|
||||
export const replaceLogViewInQueryString = (logViewReference: LogViewReference) =>
|
||||
replaceStateKeyInQueryString(defaultLogViewKey, logViewReference);
|
|
@ -9,3 +9,4 @@ export * from './invalid_state_callout';
|
|||
export * from './notification_channel';
|
||||
export * from './send_actions';
|
||||
export * from './types';
|
||||
export * from './state_machine_playground';
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* 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 } from '@elastic/eui';
|
||||
import React, { useCallback } from 'react';
|
||||
import { useLogViewContext } from '../../../hooks/use_log_view';
|
||||
|
||||
export const StateMachinePlayground = () => {
|
||||
const { changeLogViewReference, revertToDefaultLogView, update, isLoading, logViewStateService } =
|
||||
useLogViewContext();
|
||||
|
||||
const switchToInlineLogView = useCallback(() => {
|
||||
changeLogViewReference({
|
||||
type: 'log-view-inline',
|
||||
id: 'playground-log-view',
|
||||
attributes: {
|
||||
name: 'playground-log-view-name',
|
||||
description: 'from the state machine playground',
|
||||
logIndices: { type: 'index_name', indexName: 'logs-*' },
|
||||
logColumns: [
|
||||
{
|
||||
fieldColumn: {
|
||||
id: 'playground-field-column',
|
||||
field: 'event.dataset',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
}, [changeLogViewReference]);
|
||||
|
||||
const updateLogView = useCallback(() => {
|
||||
update({
|
||||
name: 'Updated playground name',
|
||||
});
|
||||
}, [update]);
|
||||
|
||||
const persistInlineLogView = useCallback(() => {
|
||||
logViewStateService.send({
|
||||
type: 'PERSIST_INLINE_LOG_VIEW',
|
||||
});
|
||||
}, [logViewStateService]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{isLoading && 'Is loading'}
|
||||
<EuiButton
|
||||
data-test-subj="infraStateMachinePlaygroundButton"
|
||||
fill
|
||||
onClick={() => switchToInlineLogView()}
|
||||
>
|
||||
{'Switch to inline Log View'}
|
||||
</EuiButton>
|
||||
<EuiButton
|
||||
data-test-subj="infraStateMachinePlaygroundButton"
|
||||
fill
|
||||
onClick={() => persistInlineLogView()}
|
||||
>
|
||||
{'Persist inline Log View'}
|
||||
</EuiButton>
|
||||
<EuiButton
|
||||
data-test-subj="infraStateMachinePlaygroundButton"
|
||||
fill
|
||||
onClick={() => revertToDefaultLogView()}
|
||||
>
|
||||
{'Revert to default (persisted) Log View'}
|
||||
</EuiButton>
|
||||
<EuiButton
|
||||
data-test-subj="infraStateMachinePlaygroundButton"
|
||||
fill
|
||||
onClick={() => updateLogView()}
|
||||
>
|
||||
{'Update log view'}
|
||||
</EuiButton>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -22,6 +22,8 @@ import { LinkToLogsPage } from './link_to_logs';
|
|||
|
||||
jest.mock('../../hooks/use_log_view');
|
||||
const useLogViewMock = useLogView as jest.MockedFunction<typeof useLogView>;
|
||||
const LOG_VIEW_REFERENCE = '(logViewId:default,type:log-view-reference)';
|
||||
const OTHER_LOG_VIEW_REFERENCE = '(logViewId:OTHER_SOURCE,type:log-view-reference)';
|
||||
|
||||
const renderRoutes = (routes: React.ReactElement) => {
|
||||
const history = createMemoryHistory();
|
||||
|
@ -71,7 +73,7 @@ describe('LinkToLogsPage component', () => {
|
|||
expect(history.location.pathname).toEqual('/stream');
|
||||
|
||||
const searchParams = new URLSearchParams(history.location.search);
|
||||
expect(searchParams.get('sourceId')).toEqual('default');
|
||||
expect(searchParams.get('logView')).toEqual(LOG_VIEW_REFERENCE);
|
||||
expect(searchParams.get('logFilter')).toMatchInlineSnapshot(
|
||||
`"(query:(language:kuery,query:'FILTER_FIELD:FILTER_VALUE'),refreshInterval:(pause:!t,value:5000),timeRange:(from:'2019-02-20T12:58:09.404Z',to:'2019-02-20T14:58:09.404Z'))"`
|
||||
);
|
||||
|
@ -92,7 +94,7 @@ describe('LinkToLogsPage component', () => {
|
|||
expect(history.location.pathname).toEqual('/stream');
|
||||
|
||||
const searchParams = new URLSearchParams(history.location.search);
|
||||
expect(searchParams.get('sourceId')).toEqual('OTHER_SOURCE');
|
||||
expect(searchParams.get('logView')).toEqual(OTHER_LOG_VIEW_REFERENCE);
|
||||
expect(searchParams.get('logFilter')).toMatchInlineSnapshot(
|
||||
`"(query:(language:kuery,query:''),refreshInterval:(pause:!t,value:5000))"`
|
||||
);
|
||||
|
@ -113,7 +115,7 @@ describe('LinkToLogsPage component', () => {
|
|||
expect(history.location.pathname).toEqual('/stream');
|
||||
|
||||
const searchParams = new URLSearchParams(history.location.search);
|
||||
expect(searchParams.get('sourceId')).toEqual('default');
|
||||
expect(searchParams.get('logView')).toEqual(LOG_VIEW_REFERENCE);
|
||||
expect(searchParams.get('logFilter')).toMatchInlineSnapshot(
|
||||
`"(query:(language:kuery,query:'FILTER_FIELD:FILTER_VALUE'),refreshInterval:(pause:!t,value:5000),timeRange:(from:'2019-02-20T12:58:09.404Z',to:'2019-02-20T14:58:09.404Z'))"`
|
||||
);
|
||||
|
@ -134,7 +136,7 @@ describe('LinkToLogsPage component', () => {
|
|||
expect(history.location.pathname).toEqual('/stream');
|
||||
|
||||
const searchParams = new URLSearchParams(history.location.search);
|
||||
expect(searchParams.get('sourceId')).toEqual('OTHER_SOURCE');
|
||||
expect(searchParams.get('logView')).toEqual(OTHER_LOG_VIEW_REFERENCE);
|
||||
expect(searchParams.get('logFilter')).toMatchInlineSnapshot(
|
||||
`"(query:(language:kuery,query:''),refreshInterval:(pause:!t,value:5000))"`
|
||||
);
|
||||
|
@ -155,7 +157,7 @@ describe('LinkToLogsPage component', () => {
|
|||
expect(history.location.pathname).toEqual('/stream');
|
||||
|
||||
const searchParams = new URLSearchParams(history.location.search);
|
||||
expect(searchParams.get('sourceId')).toEqual('default');
|
||||
expect(searchParams.get('logView')).toEqual(LOG_VIEW_REFERENCE);
|
||||
expect(searchParams.get('logFilter')).toMatchInlineSnapshot(
|
||||
`"(query:(language:kuery,query:'host.name: HOST_NAME'),refreshInterval:(pause:!t,value:5000))"`
|
||||
);
|
||||
|
@ -176,7 +178,7 @@ describe('LinkToLogsPage component', () => {
|
|||
expect(history.location.pathname).toEqual('/stream');
|
||||
|
||||
const searchParams = new URLSearchParams(history.location.search);
|
||||
expect(searchParams.get('sourceId')).toEqual('default');
|
||||
expect(searchParams.get('logView')).toEqual(LOG_VIEW_REFERENCE);
|
||||
expect(searchParams.get('logFilter')).toMatchInlineSnapshot(
|
||||
`"(query:(language:kuery,query:'(host.name: HOST_NAME) and (FILTER_FIELD:FILTER_VALUE)'),refreshInterval:(pause:!t,value:5000),timeRange:(from:'2019-02-20T12:58:09.404Z',to:'2019-02-20T14:58:09.404Z'))"`
|
||||
);
|
||||
|
@ -197,7 +199,7 @@ describe('LinkToLogsPage component', () => {
|
|||
expect(history.location.pathname).toEqual('/stream');
|
||||
|
||||
const searchParams = new URLSearchParams(history.location.search);
|
||||
expect(searchParams.get('sourceId')).toEqual('OTHER_SOURCE');
|
||||
expect(searchParams.get('logView')).toEqual(OTHER_LOG_VIEW_REFERENCE);
|
||||
expect(searchParams.get('logFilter')).toMatchInlineSnapshot(
|
||||
`"(query:(language:kuery,query:'host.name: HOST_NAME'),refreshInterval:(pause:!t,value:5000))"`
|
||||
);
|
||||
|
@ -233,7 +235,7 @@ describe('LinkToLogsPage component', () => {
|
|||
expect(history.location.pathname).toEqual('/stream');
|
||||
|
||||
const searchParams = new URLSearchParams(history.location.search);
|
||||
expect(searchParams.get('sourceId')).toEqual('default');
|
||||
expect(searchParams.get('logView')).toEqual(LOG_VIEW_REFERENCE);
|
||||
expect(searchParams.get('logFilter')).toMatchInlineSnapshot(
|
||||
`"(query:(language:kuery,query:'container.id: CONTAINER_ID'),refreshInterval:(pause:!t,value:5000))"`
|
||||
);
|
||||
|
@ -254,7 +256,7 @@ describe('LinkToLogsPage component', () => {
|
|||
expect(history.location.pathname).toEqual('/stream');
|
||||
|
||||
const searchParams = new URLSearchParams(history.location.search);
|
||||
expect(searchParams.get('sourceId')).toEqual('default');
|
||||
expect(searchParams.get('logView')).toEqual(LOG_VIEW_REFERENCE);
|
||||
expect(searchParams.get('logFilter')).toMatchInlineSnapshot(
|
||||
`"(query:(language:kuery,query:'(container.id: CONTAINER_ID) and (FILTER_FIELD:FILTER_VALUE)'),refreshInterval:(pause:!t,value:5000),timeRange:(from:'2019-02-20T12:58:09.404Z',to:'2019-02-20T14:58:09.404Z'))"`
|
||||
);
|
||||
|
@ -291,7 +293,7 @@ describe('LinkToLogsPage component', () => {
|
|||
expect(history.location.pathname).toEqual('/stream');
|
||||
|
||||
const searchParams = new URLSearchParams(history.location.search);
|
||||
expect(searchParams.get('sourceId')).toEqual('default');
|
||||
expect(searchParams.get('logView')).toEqual(LOG_VIEW_REFERENCE);
|
||||
expect(searchParams.get('logFilter')).toMatchInlineSnapshot(
|
||||
`"(query:(language:kuery,query:'kubernetes.pod.uid: POD_UID'),refreshInterval:(pause:!t,value:5000))"`
|
||||
);
|
||||
|
@ -310,7 +312,7 @@ describe('LinkToLogsPage component', () => {
|
|||
expect(history.location.pathname).toEqual('/stream');
|
||||
|
||||
const searchParams = new URLSearchParams(history.location.search);
|
||||
expect(searchParams.get('sourceId')).toEqual('default');
|
||||
expect(searchParams.get('logView')).toEqual(LOG_VIEW_REFERENCE);
|
||||
expect(searchParams.get('logFilter')).toMatchInlineSnapshot(
|
||||
`"(query:(language:kuery,query:'(kubernetes.pod.uid: POD_UID) and (FILTER_FIELD:FILTER_VALUE)'),refreshInterval:(pause:!t,value:5000),timeRange:(from:'2019-02-20T12:58:09.404Z',to:'2019-02-20T14:58:09.404Z'))"`
|
||||
);
|
||||
|
|
|
@ -26,11 +26,11 @@ export const LinkToLogsPage: React.FC<LinkToPageProps> = (props) => {
|
|||
return (
|
||||
<Switch>
|
||||
<Route
|
||||
path={`${props.match.url}/:sourceId?/:nodeType(${ITEM_TYPES})-logs/:nodeId`}
|
||||
path={`${props.match.url}/:logViewId?/:nodeType(${ITEM_TYPES})-logs/:nodeId`}
|
||||
component={RedirectToNodeLogs}
|
||||
/>
|
||||
<Route path={`${props.match.url}/:sourceId?/logs`} component={RedirectToLogs} />
|
||||
<Route path={`${props.match.url}/:sourceId?`} component={RedirectToLogs} />
|
||||
<Route path={`${props.match.url}/:logViewId?/logs`} component={RedirectToLogs} />
|
||||
<Route path={`${props.match.url}/:logViewId?`} component={RedirectToLogs} />
|
||||
<Redirect to="/" />
|
||||
</Switch>
|
||||
);
|
||||
|
|
|
@ -20,7 +20,7 @@ describe('RedirectToLogs component', () => {
|
|||
|
||||
expect(component).toMatchInlineSnapshot(`
|
||||
<Redirect
|
||||
to="/stream?sourceId=default&logPosition=(position:(tiebreaker:0,time:1550671089404))&logFilter=(query:(language:kuery,query:''),refreshInterval:(pause:!t,value:5000),timeRange:(from:'2019-02-20T12:58:09.404Z',to:'2019-02-20T14:58:09.404Z'))"
|
||||
to="/stream?logView=(logViewId:default,type:log-view-reference)&logPosition=(position:(tiebreaker:0,time:1550671089404))&logFilter=(query:(language:kuery,query:''),refreshInterval:(pause:!t,value:5000),timeRange:(from:'2019-02-20T12:58:09.404Z',to:'2019-02-20T14:58:09.404Z'))"
|
||||
/>
|
||||
`);
|
||||
});
|
||||
|
@ -34,7 +34,7 @@ describe('RedirectToLogs component', () => {
|
|||
|
||||
expect(component).toMatchInlineSnapshot(`
|
||||
<Redirect
|
||||
to="/stream?sourceId=default&logPosition=(position:(tiebreaker:0,time:1550671089404))&logFilter=(query:(language:kuery,query:'FILTER_FIELD:FILTER_VALUE'),refreshInterval:(pause:!t,value:5000),timeRange:(from:'2019-02-20T12:58:09.404Z',to:'2019-02-20T14:58:09.404Z'))"
|
||||
to="/stream?logView=(logViewId:default,type:log-view-reference)&logPosition=(position:(tiebreaker:0,time:1550671089404))&logFilter=(query:(language:kuery,query:'FILTER_FIELD:FILTER_VALUE'),refreshInterval:(pause:!t,value:5000),timeRange:(from:'2019-02-20T12:58:09.404Z',to:'2019-02-20T14:58:09.404Z'))"
|
||||
/>
|
||||
`);
|
||||
});
|
||||
|
@ -46,7 +46,7 @@ describe('RedirectToLogs component', () => {
|
|||
|
||||
expect(component).toMatchInlineSnapshot(`
|
||||
<Redirect
|
||||
to="/stream?sourceId=SOME-OTHER-SOURCE&logFilter=(query:(language:kuery,query:''),refreshInterval:(pause:!t,value:5000))"
|
||||
to="/stream?logView=(logViewId:default,type:log-view-reference)&logFilter=(query:(language:kuery,query:''),refreshInterval:(pause:!t,value:5000))"
|
||||
/>
|
||||
`);
|
||||
});
|
||||
|
|
|
@ -8,27 +8,27 @@
|
|||
import React from 'react';
|
||||
import { match as RouteMatch, Redirect, RouteComponentProps } from 'react-router-dom';
|
||||
import { flowRight } from 'lodash';
|
||||
import { replaceSourceIdInQueryString } from '../../containers/source_id';
|
||||
import { replaceLogPositionInQueryString } from '../../observability_logs/log_stream_position_state/src/url_state_storage_service';
|
||||
import { replaceLogFilterInQueryString } from '../../observability_logs/log_stream_query_state';
|
||||
import { getFilterFromLocation, getTimeFromLocation } from './query_params';
|
||||
import { replaceLogViewInQueryString } from '../../observability_logs/log_view_state';
|
||||
|
||||
type RedirectToLogsType = RouteComponentProps<{}>;
|
||||
|
||||
interface RedirectToLogsProps extends RedirectToLogsType {
|
||||
match: RouteMatch<{
|
||||
sourceId?: string;
|
||||
logViewId?: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
export const RedirectToLogs = ({ location, match }: RedirectToLogsProps) => {
|
||||
const sourceId = match.params.sourceId || 'default';
|
||||
const logViewId = match.params.logViewId || 'default';
|
||||
const filter = getFilterFromLocation(location);
|
||||
const time = getTimeFromLocation(location);
|
||||
const searchString = flowRight(
|
||||
replaceLogFilterInQueryString({ language: 'kuery', query: filter }, time),
|
||||
replaceLogPositionInQueryString(time),
|
||||
replaceSourceIdInQueryString(sourceId)
|
||||
replaceLogViewInQueryString({ type: 'log-view-reference', logViewId })
|
||||
)('');
|
||||
|
||||
return <Redirect to={`/stream?${searchString}`} />;
|
||||
|
|
|
@ -14,28 +14,28 @@ import { flowRight } from 'lodash';
|
|||
import { findInventoryFields } from '../../../common/inventory_models';
|
||||
import { InventoryItemType } from '../../../common/inventory_models/types';
|
||||
import { LoadingPage } from '../../components/loading_page';
|
||||
import { replaceSourceIdInQueryString } from '../../containers/source_id';
|
||||
import { useKibanaContextForPlugin } from '../../hooks/use_kibana';
|
||||
import { useLogView } from '../../hooks/use_log_view';
|
||||
import { replaceLogFilterInQueryString } from '../../observability_logs/log_stream_query_state';
|
||||
import { getFilterFromLocation, getTimeFromLocation } from './query_params';
|
||||
import { replaceLogPositionInQueryString } from '../../observability_logs/log_stream_position_state/src/url_state_storage_service';
|
||||
import { replaceLogViewInQueryString } from '../../observability_logs/log_view_state';
|
||||
|
||||
type RedirectToNodeLogsType = RouteComponentProps<{
|
||||
nodeId: string;
|
||||
nodeType: InventoryItemType;
|
||||
sourceId?: string;
|
||||
logViewId?: string;
|
||||
}>;
|
||||
|
||||
export const RedirectToNodeLogs = ({
|
||||
match: {
|
||||
params: { nodeId, nodeType, sourceId = 'default' },
|
||||
params: { nodeId, nodeType, logViewId = 'default' },
|
||||
},
|
||||
location,
|
||||
}: RedirectToNodeLogsType) => {
|
||||
const { services } = useKibanaContextForPlugin();
|
||||
const { isLoading, load } = useLogView({
|
||||
logViewId: sourceId,
|
||||
initialLogViewReference: { type: 'log-view-reference', logViewId },
|
||||
logViews: services.logViews.client,
|
||||
});
|
||||
|
||||
|
@ -65,7 +65,7 @@ export const RedirectToNodeLogs = ({
|
|||
const searchString = flowRight(
|
||||
replaceLogFilterInQueryString({ language: 'kuery', query: filter }, time),
|
||||
replaceLogPositionInQueryString(time),
|
||||
replaceSourceIdInQueryString(sourceId)
|
||||
replaceLogViewInQueryString({ type: 'log-view-reference', logViewId })
|
||||
)('');
|
||||
|
||||
return <Redirect to={`/stream?${searchString}`} />;
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { InlineLogViewSplashPage } from '../../../components/logging/inline_log_view_splash_page';
|
||||
import { LogAnalysisSetupFlyoutStateProvider } from '../../../components/logging/log_analysis_setup/setup_flyout';
|
||||
import { SourceLoadingPage } from '../../../components/source_loading_page';
|
||||
import { LogEntryCategoriesModuleProvider } from '../../../containers/logs/log_analysis/modules/log_entry_categories';
|
||||
|
@ -14,8 +15,15 @@ import { useLogViewContext } from '../../../hooks/use_log_view';
|
|||
import { ConnectedLogViewErrorPage } from '../shared/page_log_view_error';
|
||||
|
||||
export const LogEntryCategoriesPageProviders: React.FunctionComponent = ({ children }) => {
|
||||
const { hasFailedLoading, isLoading, isUninitialized, resolvedLogView, logViewId } =
|
||||
useLogViewContext();
|
||||
const {
|
||||
hasFailedLoading,
|
||||
isLoading,
|
||||
isUninitialized,
|
||||
resolvedLogView,
|
||||
logViewReference,
|
||||
isPersistedLogView,
|
||||
revertToDefaultLogView,
|
||||
} = useLogViewContext();
|
||||
const { space } = useActiveKibanaSpace();
|
||||
|
||||
// This is a rather crude way of guarding the dependent providers against
|
||||
|
@ -23,15 +31,20 @@ export const LogEntryCategoriesPageProviders: React.FunctionComponent = ({ child
|
|||
// React concurrent mode and Suspense in order to handle that more gracefully.
|
||||
if (space == null) {
|
||||
return null;
|
||||
} else if (!isPersistedLogView) {
|
||||
return <InlineLogViewSplashPage revertToDefaultLogView={revertToDefaultLogView} />;
|
||||
} else if (hasFailedLoading) {
|
||||
return <ConnectedLogViewErrorPage />;
|
||||
} else if (isLoading || isUninitialized) {
|
||||
return <SourceLoadingPage />;
|
||||
} else if (resolvedLogView != null) {
|
||||
if (logViewReference.type === 'log-view-inline') {
|
||||
throw new Error('Logs ML features only support persisted Log View references');
|
||||
}
|
||||
return (
|
||||
<LogEntryCategoriesModuleProvider
|
||||
indexPattern={resolvedLogView.indices}
|
||||
sourceId={logViewId}
|
||||
logViewId={logViewReference.logViewId}
|
||||
spaceId={space.id}
|
||||
timestampField={resolvedLogView.timestampField}
|
||||
runtimeMappings={resolvedLogView.runtimeMappings}
|
||||
|
|
|
@ -64,7 +64,7 @@ export const LogEntryCategoriesResultsContent: React.FunctionComponent<
|
|||
hasStoppedJobs,
|
||||
jobIds,
|
||||
categoryQualityWarnings,
|
||||
sourceConfiguration: { sourceId },
|
||||
sourceConfiguration: { sourceId: logViewId },
|
||||
} = useLogEntryCategoriesModuleContext();
|
||||
|
||||
const {
|
||||
|
@ -109,7 +109,7 @@ export const LogEntryCategoriesResultsContent: React.FunctionComponent<
|
|||
endTime: categoryQueryTimeRange.timeRange.endTime,
|
||||
filteredDatasets: categoryQueryDatasets,
|
||||
onGetTopLogEntryCategoriesError: showLoadDataErrorNotification,
|
||||
sourceId,
|
||||
logViewReference: { type: 'log-view-reference', logViewId },
|
||||
startTime: categoryQueryTimeRange.timeRange.startTime,
|
||||
});
|
||||
|
||||
|
@ -206,7 +206,7 @@ export const LogEntryCategoriesResultsContent: React.FunctionComponent<
|
|||
|
||||
return (
|
||||
<ViewLogInContextProvider
|
||||
sourceId={sourceId}
|
||||
logViewReference={{ type: 'log-view-reference', logViewId }}
|
||||
startTimestamp={categoryQueryTimeRange.timeRange.startTime}
|
||||
endTimestamp={categoryQueryTimeRange.timeRange.endTime}
|
||||
>
|
||||
|
@ -265,7 +265,7 @@ export const LogEntryCategoriesResultsContent: React.FunctionComponent<
|
|||
<TopCategoriesSection
|
||||
isLoadingTopCategories={isLoadingTopLogEntryCategories}
|
||||
jobId={jobIds['log-entry-categories-count']}
|
||||
sourceId={sourceId}
|
||||
logViewReference={{ type: 'log-view-reference', logViewId }}
|
||||
timeRange={categoryQueryTimeRange.timeRange}
|
||||
topCategories={topLogEntryCategories}
|
||||
sortOptions={sortOptions}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import React, { useEffect } from 'react';
|
||||
import { PersistedLogViewReference } from '../../../../../../common/log_views';
|
||||
import { useLogEntryCategoryExamples } from '../../use_log_entry_category_examples';
|
||||
import { LogEntryExampleMessages } from '../../../../../components/logging/log_entry_examples/log_entry_examples';
|
||||
import { TimeRange } from '../../../../../../common/time/time_range';
|
||||
|
@ -16,8 +17,8 @@ const exampleCount = 5;
|
|||
export const CategoryDetailsRow: React.FunctionComponent<{
|
||||
categoryId: number;
|
||||
timeRange: TimeRange;
|
||||
sourceId: string;
|
||||
}> = ({ categoryId, timeRange, sourceId }) => {
|
||||
logViewReference: PersistedLogViewReference;
|
||||
}> = ({ categoryId, timeRange, logViewReference }) => {
|
||||
const {
|
||||
getLogEntryCategoryExamples,
|
||||
hasFailedLoadingLogEntryCategoryExamples,
|
||||
|
@ -27,7 +28,7 @@ export const CategoryDetailsRow: React.FunctionComponent<{
|
|||
categoryId,
|
||||
endTime: timeRange.endTime,
|
||||
exampleCount,
|
||||
sourceId,
|
||||
logViewReference,
|
||||
startTime: timeRange.startTime,
|
||||
});
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ import { EuiLoadingSpinner } from '@elastic/eui';
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import React from 'react';
|
||||
|
||||
import { PersistedLogViewReference } from '../../../../../../common/log_views';
|
||||
import { LogEntryCategory } from '../../../../../../common/log_analysis';
|
||||
import { TimeRange } from '../../../../../../common/time';
|
||||
import { LoadingOverlayWrapper } from '../../../../../components/loading_overlay_wrapper';
|
||||
|
@ -18,7 +19,7 @@ import { SortOptions, ChangeSortOptions } from '../../use_log_entry_categories_r
|
|||
export const TopCategoriesSection: React.FunctionComponent<{
|
||||
isLoadingTopCategories?: boolean;
|
||||
jobId: string;
|
||||
sourceId: string;
|
||||
logViewReference: PersistedLogViewReference;
|
||||
timeRange: TimeRange;
|
||||
topCategories: LogEntryCategory[];
|
||||
sortOptions: SortOptions;
|
||||
|
@ -26,7 +27,7 @@ export const TopCategoriesSection: React.FunctionComponent<{
|
|||
}> = ({
|
||||
isLoadingTopCategories = false,
|
||||
jobId,
|
||||
sourceId,
|
||||
logViewReference,
|
||||
timeRange,
|
||||
topCategories,
|
||||
sortOptions,
|
||||
|
@ -40,7 +41,7 @@ export const TopCategoriesSection: React.FunctionComponent<{
|
|||
>
|
||||
<TopCategoriesTable
|
||||
categorizationJobId={jobId}
|
||||
sourceId={sourceId}
|
||||
logViewReference={logViewReference}
|
||||
timeRange={timeRange}
|
||||
topCategories={topCategories}
|
||||
sortOptions={sortOptions}
|
||||
|
|
|
@ -12,6 +12,7 @@ import React, { useMemo, useCallback } from 'react';
|
|||
import useSet from 'react-use/lib/useSet';
|
||||
|
||||
import { euiStyled } from '@kbn/kibana-react-plugin/common';
|
||||
import { PersistedLogViewReference } from '../../../../../../common/log_views';
|
||||
import {
|
||||
LogEntryCategory,
|
||||
LogEntryCategoryDataset,
|
||||
|
@ -31,7 +32,7 @@ export const TopCategoriesTable = euiStyled(
|
|||
({
|
||||
categorizationJobId,
|
||||
className,
|
||||
sourceId,
|
||||
logViewReference,
|
||||
timeRange,
|
||||
topCategories,
|
||||
sortOptions,
|
||||
|
@ -39,7 +40,7 @@ export const TopCategoriesTable = euiStyled(
|
|||
}: {
|
||||
categorizationJobId: string;
|
||||
className?: string;
|
||||
sourceId: string;
|
||||
logViewReference: PersistedLogViewReference;
|
||||
timeRange: TimeRange;
|
||||
topCategories: LogEntryCategory[];
|
||||
sortOptions: SortOptions;
|
||||
|
@ -80,14 +81,14 @@ export const TopCategoriesTable = euiStyled(
|
|||
[categoryId]: (
|
||||
<CategoryDetailsRow
|
||||
categoryId={categoryId}
|
||||
sourceId={sourceId}
|
||||
logViewReference={logViewReference}
|
||||
timeRange={timeRange}
|
||||
/>
|
||||
),
|
||||
}),
|
||||
{}
|
||||
),
|
||||
[expandedCategories, sourceId, timeRange]
|
||||
[expandedCategories, logViewReference, timeRange]
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import type { HttpHandler } from '@kbn/core/public';
|
||||
import { PersistedLogViewReference } from '../../../../../common/log_views';
|
||||
|
||||
import {
|
||||
getLogEntryCategoryDatasetsRequestPayloadRT,
|
||||
|
@ -15,7 +16,7 @@ import {
|
|||
import { decodeOrThrow } from '../../../../../common/runtime_types';
|
||||
|
||||
interface RequestArgs {
|
||||
sourceId: string;
|
||||
logViewReference: PersistedLogViewReference;
|
||||
startTime: number;
|
||||
endTime: number;
|
||||
}
|
||||
|
@ -24,14 +25,14 @@ export const callGetLogEntryCategoryDatasetsAPI = async (
|
|||
requestArgs: RequestArgs,
|
||||
fetch: HttpHandler
|
||||
) => {
|
||||
const { sourceId, startTime, endTime } = requestArgs;
|
||||
const { logViewReference, startTime, endTime } = requestArgs;
|
||||
|
||||
const response = await fetch(LOG_ANALYSIS_GET_LOG_ENTRY_CATEGORY_DATASETS_PATH, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(
|
||||
getLogEntryCategoryDatasetsRequestPayloadRT.encode({
|
||||
data: {
|
||||
logView: { type: 'log-view-reference', logViewId: sourceId },
|
||||
logView: logViewReference,
|
||||
timeRange: {
|
||||
startTime,
|
||||
endTime,
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import type { HttpHandler } from '@kbn/core/public';
|
||||
import { PersistedLogViewReference } from '../../../../../common/log_views';
|
||||
|
||||
import {
|
||||
getLogEntryCategoryExamplesRequestPayloadRT,
|
||||
|
@ -15,7 +16,7 @@ import {
|
|||
import { decodeOrThrow } from '../../../../../common/runtime_types';
|
||||
|
||||
interface RequestArgs {
|
||||
sourceId: string;
|
||||
logViewReference: PersistedLogViewReference;
|
||||
startTime: number;
|
||||
endTime: number;
|
||||
categoryId: number;
|
||||
|
@ -26,7 +27,7 @@ export const callGetLogEntryCategoryExamplesAPI = async (
|
|||
requestArgs: RequestArgs,
|
||||
fetch: HttpHandler
|
||||
) => {
|
||||
const { sourceId, startTime, endTime, categoryId, exampleCount } = requestArgs;
|
||||
const { logViewReference, startTime, endTime, categoryId, exampleCount } = requestArgs;
|
||||
|
||||
const response = await fetch(LOG_ANALYSIS_GET_LOG_ENTRY_CATEGORY_EXAMPLES_PATH, {
|
||||
method: 'POST',
|
||||
|
@ -35,7 +36,7 @@ export const callGetLogEntryCategoryExamplesAPI = async (
|
|||
data: {
|
||||
categoryId,
|
||||
exampleCount,
|
||||
logView: { type: 'log-view-reference', logViewId: sourceId },
|
||||
logView: logViewReference,
|
||||
timeRange: {
|
||||
startTime,
|
||||
endTime,
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import type { HttpHandler } from '@kbn/core/public';
|
||||
import { PersistedLogViewReference } from '../../../../../common/log_views';
|
||||
|
||||
import {
|
||||
getLogEntryCategoriesRequestPayloadRT,
|
||||
|
@ -16,7 +17,7 @@ import { CategoriesSort } from '../../../../../common/log_analysis';
|
|||
import { decodeOrThrow } from '../../../../../common/runtime_types';
|
||||
|
||||
interface RequestArgs {
|
||||
sourceId: string;
|
||||
logViewReference: PersistedLogViewReference;
|
||||
startTime: number;
|
||||
endTime: number;
|
||||
categoryCount: number;
|
||||
|
@ -28,7 +29,7 @@ export const callGetTopLogEntryCategoriesAPI = async (
|
|||
requestArgs: RequestArgs,
|
||||
fetch: HttpHandler
|
||||
) => {
|
||||
const { sourceId, startTime, endTime, categoryCount, datasets, sort } = requestArgs;
|
||||
const { logViewReference, startTime, endTime, categoryCount, datasets, sort } = requestArgs;
|
||||
const intervalDuration = endTime - startTime;
|
||||
|
||||
const response = await fetch(LOG_ANALYSIS_GET_LOG_ENTRY_CATEGORIES_PATH, {
|
||||
|
@ -36,7 +37,7 @@ export const callGetTopLogEntryCategoriesAPI = async (
|
|||
body: JSON.stringify(
|
||||
getLogEntryCategoriesRequestPayloadRT.encode({
|
||||
data: {
|
||||
logView: { type: 'log-view-reference', logViewId: sourceId },
|
||||
logView: logViewReference,
|
||||
timeRange: {
|
||||
startTime,
|
||||
endTime,
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import { useMemo, useState } from 'react';
|
||||
|
||||
import { PersistedLogViewReference } from '../../../../common/log_views';
|
||||
import {
|
||||
GetLogEntryCategoriesSuccessResponsePayload,
|
||||
GetLogEntryCategoryDatasetsSuccessResponsePayload,
|
||||
|
@ -30,7 +31,7 @@ export const useLogEntryCategoriesResults = ({
|
|||
endTime,
|
||||
onGetLogEntryCategoryDatasetsError,
|
||||
onGetTopLogEntryCategoriesError,
|
||||
sourceId,
|
||||
logViewReference,
|
||||
startTime,
|
||||
}: {
|
||||
categoriesCount: number;
|
||||
|
@ -38,7 +39,7 @@ export const useLogEntryCategoriesResults = ({
|
|||
endTime: number;
|
||||
onGetLogEntryCategoryDatasetsError?: (error: Error) => void;
|
||||
onGetTopLogEntryCategoriesError?: (error: Error) => void;
|
||||
sourceId: string;
|
||||
logViewReference: PersistedLogViewReference;
|
||||
startTime: number;
|
||||
}) => {
|
||||
const [sortOptions, setSortOptions] = useState<SortOptions>({
|
||||
|
@ -56,7 +57,7 @@ export const useLogEntryCategoriesResults = ({
|
|||
createPromise: async () => {
|
||||
return await callGetTopLogEntryCategoriesAPI(
|
||||
{
|
||||
sourceId,
|
||||
logViewReference,
|
||||
startTime,
|
||||
endTime,
|
||||
categoryCount: categoriesCount,
|
||||
|
@ -79,7 +80,7 @@ export const useLogEntryCategoriesResults = ({
|
|||
}
|
||||
},
|
||||
},
|
||||
[categoriesCount, endTime, filteredDatasets, sourceId, startTime, sortOptions]
|
||||
[categoriesCount, endTime, filteredDatasets, logViewReference, startTime, sortOptions]
|
||||
);
|
||||
|
||||
const [getLogEntryCategoryDatasetsRequest, getLogEntryCategoryDatasets] = useTrackedPromise(
|
||||
|
@ -87,7 +88,7 @@ export const useLogEntryCategoriesResults = ({
|
|||
cancelPreviousOn: 'creation',
|
||||
createPromise: async () => {
|
||||
return await callGetLogEntryCategoryDatasetsAPI(
|
||||
{ sourceId, startTime, endTime },
|
||||
{ logViewReference, startTime, endTime },
|
||||
services.http.fetch
|
||||
);
|
||||
},
|
||||
|
@ -104,7 +105,7 @@ export const useLogEntryCategoriesResults = ({
|
|||
}
|
||||
},
|
||||
},
|
||||
[categoriesCount, endTime, sourceId, startTime]
|
||||
[categoriesCount, endTime, logViewReference, startTime]
|
||||
);
|
||||
|
||||
const isLoadingTopLogEntryCategories = useMemo(
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import { useMemo, useState } from 'react';
|
||||
import { PersistedLogViewReference } from '../../../../common/log_views';
|
||||
|
||||
import { LogEntryCategoryExample } from '../../../../common/http_api';
|
||||
import { useKibanaContextForPlugin } from '../../../hooks/use_kibana';
|
||||
|
@ -16,13 +17,13 @@ export const useLogEntryCategoryExamples = ({
|
|||
categoryId,
|
||||
endTime,
|
||||
exampleCount,
|
||||
sourceId,
|
||||
logViewReference,
|
||||
startTime,
|
||||
}: {
|
||||
categoryId: number;
|
||||
endTime: number;
|
||||
exampleCount: number;
|
||||
sourceId: string;
|
||||
logViewReference: PersistedLogViewReference;
|
||||
startTime: number;
|
||||
}) => {
|
||||
const { services } = useKibanaContextForPlugin();
|
||||
|
@ -37,7 +38,7 @@ export const useLogEntryCategoryExamples = ({
|
|||
createPromise: async () => {
|
||||
return await callGetLogEntryCategoryExamplesAPI(
|
||||
{
|
||||
sourceId,
|
||||
logViewReference,
|
||||
startTime,
|
||||
endTime,
|
||||
categoryId,
|
||||
|
@ -50,7 +51,7 @@ export const useLogEntryCategoryExamples = ({
|
|||
setLogEntryCategoryExamples(examples);
|
||||
},
|
||||
},
|
||||
[categoryId, endTime, exampleCount, sourceId, startTime]
|
||||
[categoryId, endTime, exampleCount, logViewReference, startTime]
|
||||
);
|
||||
|
||||
const isLoadingLogEntryCategoryExamples = useMemo(
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { InlineLogViewSplashPage } from '../../../components/logging/inline_log_view_splash_page';
|
||||
import { LogAnalysisSetupFlyoutStateProvider } from '../../../components/logging/log_analysis_setup/setup_flyout';
|
||||
import { SourceLoadingPage } from '../../../components/source_loading_page';
|
||||
import { LogEntryCategoriesModuleProvider } from '../../../containers/logs/log_analysis/modules/log_entry_categories';
|
||||
|
@ -16,8 +17,16 @@ import { useLogViewContext } from '../../../hooks/use_log_view';
|
|||
import { ConnectedLogViewErrorPage } from '../shared/page_log_view_error';
|
||||
|
||||
export const LogEntryRatePageProviders: React.FunctionComponent = ({ children }) => {
|
||||
const { hasFailedLoading, isLoading, isUninitialized, logViewId, resolvedLogView } =
|
||||
useLogViewContext();
|
||||
const {
|
||||
hasFailedLoading,
|
||||
isLoading,
|
||||
isUninitialized,
|
||||
logViewReference,
|
||||
resolvedLogView,
|
||||
isPersistedLogView,
|
||||
revertToDefaultLogView,
|
||||
} = useLogViewContext();
|
||||
|
||||
const { space } = useActiveKibanaSpace();
|
||||
|
||||
// This is a rather crude way of guarding the dependent providers against
|
||||
|
@ -25,23 +34,28 @@ export const LogEntryRatePageProviders: React.FunctionComponent = ({ children })
|
|||
// React concurrent mode and Suspense in order to handle that more gracefully.
|
||||
if (space == null) {
|
||||
return null;
|
||||
} else if (!isPersistedLogView) {
|
||||
return <InlineLogViewSplashPage revertToDefaultLogView={revertToDefaultLogView} />;
|
||||
} else if (isLoading || isUninitialized) {
|
||||
return <SourceLoadingPage />;
|
||||
} else if (hasFailedLoading) {
|
||||
return <ConnectedLogViewErrorPage />;
|
||||
} else if (resolvedLogView != null) {
|
||||
if (logViewReference.type === 'log-view-inline') {
|
||||
throw new Error('Logs ML features only support persisted Log Views');
|
||||
}
|
||||
return (
|
||||
<LogEntryFlyoutProvider>
|
||||
<LogEntryRateModuleProvider
|
||||
indexPattern={resolvedLogView.indices}
|
||||
sourceId={logViewId}
|
||||
logViewId={logViewReference.logViewId}
|
||||
spaceId={space.id}
|
||||
timestampField={resolvedLogView.timestampField}
|
||||
runtimeMappings={resolvedLogView.runtimeMappings}
|
||||
>
|
||||
<LogEntryCategoriesModuleProvider
|
||||
indexPattern={resolvedLogView.indices}
|
||||
sourceId={logViewId}
|
||||
logViewId={logViewReference.logViewId}
|
||||
spaceId={space.id}
|
||||
timestampField={resolvedLogView.timestampField}
|
||||
runtimeMappings={resolvedLogView.runtimeMappings}
|
||||
|
|
|
@ -52,7 +52,11 @@ export const LogEntryRateResultsContent: React.FunctionComponent<{
|
|||
|
||||
const navigateToApp = useKibana().services.application?.navigateToApp;
|
||||
|
||||
const { logViewId, logViewStatus } = useLogViewContext();
|
||||
const { logViewReference, logViewStatus } = useLogViewContext();
|
||||
|
||||
if (logViewReference.type === 'log-view-inline') {
|
||||
throw new Error('Logs ML features only support persisted Log Views');
|
||||
}
|
||||
|
||||
const { hasLogAnalysisSetupCapabilities } = useLogAnalysisCapabilitiesContext();
|
||||
|
||||
|
@ -142,7 +146,7 @@ export const LogEntryRateResultsContent: React.FunctionComponent<{
|
|||
datasets,
|
||||
isLoadingDatasets,
|
||||
} = useLogEntryAnomaliesResults({
|
||||
sourceId: logViewId,
|
||||
logViewReference,
|
||||
startTime: timeRange.value.startTime,
|
||||
endTime: timeRange.value.endTime,
|
||||
defaultSortOptions: SORT_DEFAULTS,
|
||||
|
@ -272,7 +276,7 @@ export const LogEntryRateResultsContent: React.FunctionComponent<{
|
|||
logEntryId={flyoutLogEntryId}
|
||||
onCloseFlyout={closeLogEntryFlyout}
|
||||
onSetFieldFilter={linkToLogStream}
|
||||
sourceId={logViewId}
|
||||
logViewReference={logViewReference}
|
||||
/>
|
||||
) : null}
|
||||
</LogsPageTemplate>
|
||||
|
|
|
@ -28,7 +28,11 @@ export const AnomaliesTableExpandedRow: React.FunctionComponent<{
|
|||
anomaly: LogEntryAnomaly;
|
||||
timeRange: TimeRange;
|
||||
}> = ({ anomaly, timeRange }) => {
|
||||
const { logViewId } = useLogViewContext();
|
||||
const { logViewReference } = useLogViewContext();
|
||||
|
||||
if (logViewReference.type === 'log-view-inline') {
|
||||
throw new Error('Logs ML features only support persisted Log Views');
|
||||
}
|
||||
|
||||
const {
|
||||
getLogEntryExamples,
|
||||
|
@ -39,7 +43,7 @@ export const AnomaliesTableExpandedRow: React.FunctionComponent<{
|
|||
dataset: anomaly.dataset,
|
||||
endTime: anomaly.startTime + anomaly.duration,
|
||||
exampleCount: EXAMPLE_COUNT,
|
||||
sourceId: logViewId,
|
||||
logViewReference,
|
||||
startTime: anomaly.startTime,
|
||||
categoryId: isCategoryAnomaly(anomaly) ? anomaly.categoryId : undefined,
|
||||
});
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import type { HttpHandler } from '@kbn/core/public';
|
||||
import { PersistedLogViewReference } from '../../../../../common/log_views';
|
||||
import {
|
||||
getLogEntryAnomaliesRequestPayloadRT,
|
||||
getLogEntryAnomaliesSuccessReponsePayloadRT,
|
||||
|
@ -15,7 +16,7 @@ import { decodeOrThrow } from '../../../../../common/runtime_types';
|
|||
import { AnomaliesSort, Pagination } from '../../../../../common/log_analysis';
|
||||
|
||||
interface RequestArgs {
|
||||
sourceId: string;
|
||||
logViewReference: PersistedLogViewReference;
|
||||
startTime: number;
|
||||
endTime: number;
|
||||
sort: AnomaliesSort;
|
||||
|
@ -24,13 +25,13 @@ interface RequestArgs {
|
|||
}
|
||||
|
||||
export const callGetLogEntryAnomaliesAPI = async (requestArgs: RequestArgs, fetch: HttpHandler) => {
|
||||
const { sourceId, startTime, endTime, sort, pagination, datasets } = requestArgs;
|
||||
const { logViewReference, startTime, endTime, sort, pagination, datasets } = requestArgs;
|
||||
const response = await fetch(LOG_ANALYSIS_GET_LOG_ENTRY_ANOMALIES_PATH, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(
|
||||
getLogEntryAnomaliesRequestPayloadRT.encode({
|
||||
data: {
|
||||
logView: { type: 'log-view-reference', logViewId: sourceId },
|
||||
logView: logViewReference,
|
||||
timeRange: {
|
||||
startTime,
|
||||
endTime,
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import type { HttpHandler } from '@kbn/core/public';
|
||||
import { PersistedLogViewReference } from '../../../../../common/log_views';
|
||||
import { decodeOrThrow } from '../../../../../common/runtime_types';
|
||||
import {
|
||||
getLogEntryAnomaliesDatasetsRequestPayloadRT,
|
||||
|
@ -14,7 +15,7 @@ import {
|
|||
} from '../../../../../common/http_api/log_analysis';
|
||||
|
||||
interface RequestArgs {
|
||||
sourceId: string;
|
||||
logViewReference: PersistedLogViewReference;
|
||||
startTime: number;
|
||||
endTime: number;
|
||||
}
|
||||
|
@ -23,13 +24,13 @@ export const callGetLogEntryAnomaliesDatasetsAPI = async (
|
|||
requestArgs: RequestArgs,
|
||||
fetch: HttpHandler
|
||||
) => {
|
||||
const { sourceId, startTime, endTime } = requestArgs;
|
||||
const { logViewReference, startTime, endTime } = requestArgs;
|
||||
const response = await fetch(LOG_ANALYSIS_GET_LOG_ENTRY_ANOMALIES_DATASETS_PATH, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(
|
||||
getLogEntryAnomaliesDatasetsRequestPayloadRT.encode({
|
||||
data: {
|
||||
logView: { type: 'log-view-reference', logViewId: sourceId },
|
||||
logView: logViewReference,
|
||||
timeRange: {
|
||||
startTime,
|
||||
endTime,
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import type { HttpHandler } from '@kbn/core/public';
|
||||
import { PersistedLogViewReference } from '../../../../../common/log_views';
|
||||
|
||||
import {
|
||||
getLogEntryExamplesRequestPayloadRT,
|
||||
|
@ -15,7 +16,7 @@ import {
|
|||
import { decodeOrThrow } from '../../../../../common/runtime_types';
|
||||
|
||||
interface RequestArgs {
|
||||
sourceId: string;
|
||||
logViewReference: PersistedLogViewReference;
|
||||
startTime: number;
|
||||
endTime: number;
|
||||
dataset: string;
|
||||
|
@ -24,7 +25,7 @@ interface RequestArgs {
|
|||
}
|
||||
|
||||
export const callGetLogEntryExamplesAPI = async (requestArgs: RequestArgs, fetch: HttpHandler) => {
|
||||
const { sourceId, startTime, endTime, dataset, exampleCount, categoryId } = requestArgs;
|
||||
const { logViewReference, startTime, endTime, dataset, exampleCount, categoryId } = requestArgs;
|
||||
const response = await fetch(LOG_ANALYSIS_GET_LOG_ENTRY_RATE_EXAMPLES_PATH, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(
|
||||
|
@ -32,7 +33,7 @@ export const callGetLogEntryExamplesAPI = async (requestArgs: RequestArgs, fetch
|
|||
data: {
|
||||
dataset,
|
||||
exampleCount,
|
||||
logView: { type: 'log-view-reference', logViewId: sourceId },
|
||||
logView: logViewReference,
|
||||
timeRange: {
|
||||
startTime,
|
||||
endTime,
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import { useMemo, useState, useCallback, useEffect, useReducer } from 'react';
|
||||
import useMount from 'react-use/lib/useMount';
|
||||
import { PersistedLogViewReference } from '../../../../common/log_views';
|
||||
import { useTrackedPromise, CanceledPromiseError } from '../../../utils/use_tracked_promise';
|
||||
import { callGetLogEntryAnomaliesAPI } from './service_calls/get_log_entry_anomalies';
|
||||
import { callGetLogEntryAnomaliesDatasetsAPI } from './service_calls/get_log_entry_anomalies_datasets';
|
||||
|
@ -137,7 +138,7 @@ const STATE_DEFAULTS: ReducerStateDefaults = {
|
|||
export const useLogEntryAnomaliesResults = ({
|
||||
endTime,
|
||||
startTime,
|
||||
sourceId,
|
||||
logViewReference,
|
||||
defaultSortOptions,
|
||||
defaultPaginationOptions,
|
||||
onGetLogEntryAnomaliesDatasetsError,
|
||||
|
@ -145,7 +146,7 @@ export const useLogEntryAnomaliesResults = ({
|
|||
}: {
|
||||
endTime: number;
|
||||
startTime: number;
|
||||
sourceId: string;
|
||||
logViewReference: PersistedLogViewReference;
|
||||
defaultSortOptions: AnomaliesSort;
|
||||
defaultPaginationOptions: Pick<Pagination, 'pageSize'>;
|
||||
onGetLogEntryAnomaliesDatasetsError?: (error: Error) => void;
|
||||
|
@ -183,7 +184,7 @@ export const useLogEntryAnomaliesResults = ({
|
|||
} = reducerState;
|
||||
return await callGetLogEntryAnomaliesAPI(
|
||||
{
|
||||
sourceId,
|
||||
logViewReference,
|
||||
startTime: queryStartTime,
|
||||
endTime: queryEndTime,
|
||||
sort: sortOptions,
|
||||
|
@ -216,7 +217,7 @@ export const useLogEntryAnomaliesResults = ({
|
|||
},
|
||||
},
|
||||
[
|
||||
sourceId,
|
||||
logViewReference,
|
||||
dispatch,
|
||||
reducerState.timeRange,
|
||||
reducerState.sortOptions,
|
||||
|
@ -294,7 +295,7 @@ export const useLogEntryAnomaliesResults = ({
|
|||
cancelPreviousOn: 'creation',
|
||||
createPromise: async () => {
|
||||
return await callGetLogEntryAnomaliesDatasetsAPI(
|
||||
{ sourceId, startTime, endTime },
|
||||
{ logViewReference, startTime, endTime },
|
||||
services.http.fetch
|
||||
);
|
||||
},
|
||||
|
@ -311,7 +312,7 @@ export const useLogEntryAnomaliesResults = ({
|
|||
}
|
||||
},
|
||||
},
|
||||
[endTime, sourceId, startTime]
|
||||
[endTime, logViewReference, startTime]
|
||||
);
|
||||
|
||||
const isLoadingDatasets = useMemo(
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import { useMemo, useState } from 'react';
|
||||
import { PersistedLogViewReference } from '../../../../common/log_views';
|
||||
|
||||
import { LogEntryExample } from '../../../../common/log_analysis';
|
||||
import { useKibanaContextForPlugin } from '../../../hooks/use_kibana';
|
||||
|
@ -16,14 +17,14 @@ export const useLogEntryExamples = ({
|
|||
dataset,
|
||||
endTime,
|
||||
exampleCount,
|
||||
sourceId,
|
||||
logViewReference,
|
||||
startTime,
|
||||
categoryId,
|
||||
}: {
|
||||
dataset: string;
|
||||
endTime: number;
|
||||
exampleCount: number;
|
||||
sourceId: string;
|
||||
logViewReference: PersistedLogViewReference;
|
||||
startTime: number;
|
||||
categoryId?: string;
|
||||
}) => {
|
||||
|
@ -36,7 +37,7 @@ export const useLogEntryExamples = ({
|
|||
createPromise: async () => {
|
||||
return await callGetLogEntryExamplesAPI(
|
||||
{
|
||||
sourceId,
|
||||
logViewReference,
|
||||
startTime,
|
||||
endTime,
|
||||
dataset,
|
||||
|
@ -50,7 +51,7 @@ export const useLogEntryExamples = ({
|
|||
setLogEntryExamples(examples);
|
||||
},
|
||||
},
|
||||
[dataset, endTime, exampleCount, sourceId, startTime]
|
||||
[dataset, endTime, exampleCount, logViewReference, startTime]
|
||||
);
|
||||
|
||||
const isLoadingLogEntryExamples = useMemo(
|
||||
|
|
|
@ -12,7 +12,7 @@ import { Switch } from 'react-router-dom';
|
|||
import { Route } from '@kbn/shared-ux-router';
|
||||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import { HeaderMenuPortal, useLinkProps } from '@kbn/observability-plugin/public';
|
||||
import { AlertDropdown } from '../../alerting/log_threshold';
|
||||
import { LazyAlertDropdownWrapper } from '../../alerting/log_threshold';
|
||||
import { HelpCenterContent } from '../../components/help_center_content';
|
||||
import { useReadOnlyBadge } from '../../hooks/use_readonly_badge';
|
||||
import { HeaderActionMenuContext } from '../../utils/header_action_menu_provider';
|
||||
|
@ -21,9 +21,12 @@ import { LogEntryCategoriesPage } from './log_entry_categories';
|
|||
import { LogEntryRatePage } from './log_entry_rate';
|
||||
import { LogsSettingsPage } from './settings';
|
||||
import { StreamPage } from './stream';
|
||||
import { isDevMode } from '../../utils/dev_mode';
|
||||
import { StateMachinePlayground } from '../../observability_logs/xstate_helpers';
|
||||
import { NotFoundPage } from '../404';
|
||||
|
||||
export const LogsPageContent: React.FunctionComponent = () => {
|
||||
const enableDeveloperRoutes = isDevMode();
|
||||
const uiCapabilities = useKibana().services.application?.capabilities;
|
||||
const { setHeaderActionMenu, theme$ } = useContext(HeaderActionMenuContext);
|
||||
|
||||
|
@ -71,7 +74,7 @@ export const LogsPageContent: React.FunctionComponent = () => {
|
|||
<EuiHeaderLink color={'text'} {...settingsLinkProps}>
|
||||
{settingsTabTitle}
|
||||
</EuiHeaderLink>
|
||||
<AlertDropdown />
|
||||
<LazyAlertDropdownWrapper />
|
||||
<EuiHeaderLink
|
||||
href={kibana.services?.application?.getUrlForApp('/integrations/browse')}
|
||||
color="primary"
|
||||
|
@ -88,6 +91,9 @@ export const LogsPageContent: React.FunctionComponent = () => {
|
|||
<Route path={anomaliesTab.pathname} component={LogEntryRatePage} />
|
||||
<Route path={logCategoriesTab.pathname} component={LogEntryCategoriesPage} />
|
||||
<Route path={settingsTab.pathname} component={LogsSettingsPage} />
|
||||
{enableDeveloperRoutes && (
|
||||
<Route path={'/state-machine-playground'} component={StateMachinePlayground} />
|
||||
)}
|
||||
<RedirectWithQueryParams from={'/analysis'} to={anomaliesTab.pathname} exact />
|
||||
<RedirectWithQueryParams from={'/log-rate'} to={anomaliesTab.pathname} exact />
|
||||
<RedirectWithQueryParams from={'/'} to={streamTab.pathname} exact />
|
||||
|
|
|
@ -7,16 +7,14 @@
|
|||
|
||||
import React from 'react';
|
||||
import { LogAnalysisCapabilitiesProvider } from '../../containers/logs/log_analysis';
|
||||
import { useSourceId } from '../../containers/source_id';
|
||||
import { useKibanaContextForPlugin } from '../../hooks/use_kibana';
|
||||
import { LogViewProvider } from '../../hooks/use_log_view';
|
||||
|
||||
export const LogsPageProviders: React.FunctionComponent = ({ children }) => {
|
||||
const [sourceId] = useSourceId();
|
||||
const { services } = useKibanaContextForPlugin();
|
||||
|
||||
return (
|
||||
<LogViewProvider logViewId={sourceId} logViews={services.logViews.client}>
|
||||
<LogViewProvider logViews={services.logViews.client}>
|
||||
<LogAnalysisCapabilitiesProvider>{children}</LogAnalysisCapabilitiesProvider>
|
||||
</LogViewProvider>
|
||||
);
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* 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 } from '@elastic/eui';
|
||||
import { EuiCallOut } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
|
||||
export const InlineLogViewCallout = ({
|
||||
revertToDefaultLogView,
|
||||
}: {
|
||||
revertToDefaultLogView: () => void;
|
||||
}) => {
|
||||
return (
|
||||
<EuiCallOut
|
||||
color="warning"
|
||||
title={i18n.translate('xpack.infra.logs.settings.inlineLogViewCalloutTitle', {
|
||||
defaultMessage: 'Inline Log View in use',
|
||||
})}
|
||||
>
|
||||
<>
|
||||
<p>
|
||||
{i18n.translate('xpack.infra.logs.settings.inlineLogViewCalloutDescription', {
|
||||
defaultMessage:
|
||||
'An inline Log View is currently being used, changes will be synchronized to the URL, but they will not be persisted.',
|
||||
})}
|
||||
</p>
|
||||
<EuiButton
|
||||
data-test-subj="infraInlineLogViewCalloutRevertToDefaultPersistedLogViewButton"
|
||||
fullWidth={false}
|
||||
fill
|
||||
onClick={revertToDefaultLogView}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.logs.settings.inlineLogViewCalloutButtonText"
|
||||
defaultMessage="Revert to default (persisted) Log View"
|
||||
/>
|
||||
</EuiButton>
|
||||
</>
|
||||
</EuiCallOut>
|
||||
);
|
||||
};
|
|
@ -28,6 +28,7 @@ import { LogColumnsConfigurationPanel } from './log_columns_configuration_panel'
|
|||
import { NameConfigurationPanel } from './name_configuration_panel';
|
||||
import { LogSourceConfigurationFormErrors } from './source_configuration_form_errors';
|
||||
import { useLogSourceConfigurationFormState } from './source_configuration_form_state';
|
||||
import { InlineLogViewCallout } from './inline_log_view_callout';
|
||||
|
||||
export const LogsSettingsPage = () => {
|
||||
const uiCapabilities = useKibana().services.application?.capabilities;
|
||||
|
@ -46,8 +47,16 @@ export const LogsSettingsPage = () => {
|
|||
},
|
||||
]);
|
||||
|
||||
const { logView, hasFailedLoadingLogView, isLoading, isUninitialized, update, resolvedLogView } =
|
||||
useLogViewContext();
|
||||
const {
|
||||
logView,
|
||||
hasFailedLoadingLogView,
|
||||
isLoading,
|
||||
isUninitialized,
|
||||
update,
|
||||
resolvedLogView,
|
||||
isInlineLogView,
|
||||
revertToDefaultLogView,
|
||||
} = useLogViewContext();
|
||||
|
||||
const availableFields = useMemo(
|
||||
() => resolvedLogView?.fields.map((field) => field.name) ?? [],
|
||||
|
@ -141,6 +150,14 @@ export const LogsSettingsPage = () => {
|
|||
</EuiFlexGroup>
|
||||
) : (
|
||||
<>
|
||||
{isInlineLogView && (
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<InlineLogViewCallout revertToDefaultLogView={revertToDefaultLogView} />
|
||||
<EuiSpacer />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
)}
|
||||
<EuiFlexGroup justifyContent="flexEnd">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
|
|
|
@ -52,7 +52,7 @@ export const StreamPageLogsContent = React.memo<{
|
|||
query: { queryString },
|
||||
},
|
||||
} = useKibanaContextForPlugin().services;
|
||||
const { resolvedLogView, logView, logViewId } = useLogViewContext();
|
||||
const { resolvedLogView, logView, logViewReference } = useLogViewContext();
|
||||
const { textScale, textWrap } = useLogViewConfigurationContext();
|
||||
const {
|
||||
surroundingLogsId,
|
||||
|
@ -228,10 +228,16 @@ export const StreamPageLogsContent = React.memo<{
|
|||
logEntryId={flyoutLogEntryId}
|
||||
onCloseFlyout={closeLogEntryFlyout}
|
||||
onSetFieldFilter={setFilter}
|
||||
sourceId={logViewId}
|
||||
logViewReference={logViewReference}
|
||||
/>
|
||||
) : null}
|
||||
<PageContent key={`${logViewId}-${logView?.version}`}>
|
||||
<PageContent
|
||||
key={`${
|
||||
logViewReference.type === 'log-view-reference'
|
||||
? logViewReference.logViewId
|
||||
: logViewReference.id
|
||||
}-${logView?.version}`}
|
||||
>
|
||||
<ScrollableLogTextStreamView
|
||||
columnConfigurations={(resolvedLogView && resolvedLogView.columns) || []}
|
||||
hasMoreAfterEnd={hasMoreAfterEnd}
|
||||
|
|
|
@ -25,7 +25,7 @@ import { MatchedStateFromActor } from '../../../observability_logs/xstate_helper
|
|||
|
||||
const ViewLogInContext: React.FC = ({ children }) => {
|
||||
const { startTimestamp, endTimestamp } = useLogPositionStateContext();
|
||||
const { logViewId } = useLogViewContext();
|
||||
const { logViewReference } = useLogViewContext();
|
||||
|
||||
if (!startTimestamp || !endTimestamp) {
|
||||
return null;
|
||||
|
@ -35,7 +35,7 @@ const ViewLogInContext: React.FC = ({ children }) => {
|
|||
<ViewLogInContextProvider
|
||||
startTimestamp={startTimestamp}
|
||||
endTimestamp={endTimestamp}
|
||||
sourceId={logViewId}
|
||||
logViewReference={logViewReference}
|
||||
>
|
||||
{children}
|
||||
</ViewLogInContextProvider>
|
||||
|
@ -45,7 +45,7 @@ const ViewLogInContext: React.FC = ({ children }) => {
|
|||
const LogEntriesStateProvider: React.FC<{
|
||||
logStreamPageState: InitializedLogStreamPageState;
|
||||
}> = ({ children, logStreamPageState }) => {
|
||||
const { logViewId } = useLogViewContext();
|
||||
const { logViewReference } = useLogViewContext();
|
||||
const { startTimestamp, endTimestamp, targetPosition } = useLogPositionStateContext();
|
||||
const {
|
||||
context: { parsedQuery },
|
||||
|
@ -58,7 +58,7 @@ const LogEntriesStateProvider: React.FC<{
|
|||
|
||||
return (
|
||||
<LogStreamProvider
|
||||
sourceId={logViewId}
|
||||
logViewReference={logViewReference}
|
||||
startTimestamp={startTimestamp}
|
||||
endTimestamp={endTimestamp}
|
||||
query={parsedQuery}
|
||||
|
@ -72,7 +72,7 @@ const LogEntriesStateProvider: React.FC<{
|
|||
const LogHighlightsState: React.FC<{
|
||||
logStreamPageState: InitializedLogStreamPageState;
|
||||
}> = ({ children, logStreamPageState }) => {
|
||||
const { logViewId, logView } = useLogViewContext();
|
||||
const { logViewReference, logView } = useLogViewContext();
|
||||
const { topCursor, bottomCursor, entries } = useLogStreamContext();
|
||||
const serializedParsedQuery = useMemo(
|
||||
() => stringify(logStreamPageState.context.parsedQuery),
|
||||
|
@ -80,7 +80,7 @@ const LogHighlightsState: React.FC<{
|
|||
);
|
||||
|
||||
const highlightsProps = {
|
||||
sourceId: logViewId,
|
||||
logViewReference,
|
||||
sourceVersion: logView?.version,
|
||||
entriesStart: topCursor,
|
||||
entriesEnd: bottomCursor,
|
||||
|
|
|
@ -25,7 +25,7 @@ import { LogStream } from '../../../components/log_stream';
|
|||
const MODAL_MARGIN = 25;
|
||||
|
||||
export const PageViewLogInContext: React.FC = () => {
|
||||
const [{ contextEntry, startTimestamp, endTimestamp, sourceId }, { setContextEntry }] =
|
||||
const [{ contextEntry, startTimestamp, endTimestamp, logViewReference }, { setContextEntry }] =
|
||||
useViewLogInProviderContext();
|
||||
const closeModal = useCallback(() => setContextEntry(undefined), [setContextEntry]);
|
||||
const { width: vw, height: vh } = useViewportDimensions();
|
||||
|
@ -56,7 +56,7 @@ export const PageViewLogInContext: React.FC = () => {
|
|||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={1}>
|
||||
<LogStream
|
||||
logView={{ type: 'log-view-reference', logViewId: sourceId }}
|
||||
logView={logViewReference}
|
||||
startTimestamp={startTimestamp}
|
||||
endTimestamp={endTimestamp}
|
||||
query={contextQuery}
|
||||
|
|
|
@ -20,6 +20,8 @@ import {
|
|||
FetchLogViewStatusError,
|
||||
LogView,
|
||||
LogViewAttributes,
|
||||
logViewAttributesRT,
|
||||
LogViewReference,
|
||||
LogViewsStaticConfig,
|
||||
LogViewStatus,
|
||||
PutLogViewError,
|
||||
|
@ -37,7 +39,15 @@ export class LogViewsClient implements ILogViewsClient {
|
|||
private readonly config: LogViewsStaticConfig
|
||||
) {}
|
||||
|
||||
public async getLogView(logViewId: string): Promise<LogView> {
|
||||
public async getLogView(logViewReference: LogViewReference): Promise<LogView> {
|
||||
if (logViewReference.type === 'log-view-inline') {
|
||||
return {
|
||||
...logViewReference,
|
||||
origin: 'inline',
|
||||
};
|
||||
}
|
||||
|
||||
const { logViewId } = logViewReference;
|
||||
const response = await this.http.get(getLogViewUrl(logViewId)).catch((error) => {
|
||||
throw new FetchLogViewError(`Failed to fetch log view "${logViewId}": ${error}`);
|
||||
});
|
||||
|
@ -51,8 +61,8 @@ export class LogViewsClient implements ILogViewsClient {
|
|||
return data;
|
||||
}
|
||||
|
||||
public async getResolvedLogView(logViewId: string): Promise<ResolvedLogView> {
|
||||
const logView = await this.getLogView(logViewId);
|
||||
public async getResolvedLogView(logViewReference: LogViewReference): Promise<ResolvedLogView> {
|
||||
const logView = await this.getLogView(logViewReference);
|
||||
const resolvedLogView = await this.resolveLogView(logView.id, logView.attributes);
|
||||
return resolvedLogView;
|
||||
}
|
||||
|
@ -98,24 +108,44 @@ export class LogViewsClient implements ILogViewsClient {
|
|||
}
|
||||
|
||||
public async putLogView(
|
||||
logViewId: string,
|
||||
logViewReference: LogViewReference,
|
||||
logViewAttributes: Partial<LogViewAttributes>
|
||||
): Promise<LogView> {
|
||||
const response = await this.http
|
||||
.put(getLogViewUrl(logViewId), {
|
||||
body: JSON.stringify(putLogViewRequestPayloadRT.encode({ attributes: logViewAttributes })),
|
||||
})
|
||||
.catch((error) => {
|
||||
throw new PutLogViewError(`Failed to write log view "${logViewId}": ${error}`);
|
||||
});
|
||||
if (logViewReference.type === 'log-view-inline') {
|
||||
const { id } = logViewReference;
|
||||
const attributes = decodeOrThrow(
|
||||
rt.partial(logViewAttributesRT.type.props),
|
||||
(message: string) =>
|
||||
new PutLogViewError(`Failed to decode inline log view "${id}": ${message}"`)
|
||||
)(logViewAttributes);
|
||||
return {
|
||||
id,
|
||||
attributes: {
|
||||
...logViewReference.attributes,
|
||||
...attributes,
|
||||
},
|
||||
origin: 'inline',
|
||||
};
|
||||
} else {
|
||||
const { logViewId } = logViewReference;
|
||||
const response = await this.http
|
||||
.put(getLogViewUrl(logViewId), {
|
||||
body: JSON.stringify(
|
||||
putLogViewRequestPayloadRT.encode({ attributes: logViewAttributes })
|
||||
),
|
||||
})
|
||||
.catch((error) => {
|
||||
throw new PutLogViewError(`Failed to write log view "${logViewId}": ${error}`);
|
||||
});
|
||||
|
||||
const { data } = decodeOrThrow(
|
||||
getLogViewResponsePayloadRT,
|
||||
(message: string) =>
|
||||
new PutLogViewError(`Failed to decode written log view "${logViewId}": ${message}"`)
|
||||
)(response);
|
||||
const { data } = decodeOrThrow(
|
||||
getLogViewResponsePayloadRT,
|
||||
(message: string) =>
|
||||
new PutLogViewError(`Failed to decode written log view "${logViewId}": ${message}"`)
|
||||
)(response);
|
||||
|
||||
return data;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
public async resolveLogView(
|
||||
|
|
|
@ -11,6 +11,7 @@ import { DataViewsContract } from '@kbn/data-views-plugin/public';
|
|||
import {
|
||||
LogView,
|
||||
LogViewAttributes,
|
||||
LogViewReference,
|
||||
LogViewStatus,
|
||||
ResolvedLogView,
|
||||
} from '../../../common/log_views';
|
||||
|
@ -28,9 +29,12 @@ export interface LogViewsServiceStartDeps {
|
|||
}
|
||||
|
||||
export interface ILogViewsClient {
|
||||
getLogView(logViewId: string): Promise<LogView>;
|
||||
getLogView(logViewReference: LogViewReference): Promise<LogView>;
|
||||
getResolvedLogViewStatus(resolvedLogView: ResolvedLogView): Promise<LogViewStatus>;
|
||||
getResolvedLogView(logViewId: string): Promise<ResolvedLogView>;
|
||||
putLogView(logViewId: string, logViewAttributes: Partial<LogViewAttributes>): Promise<LogView>;
|
||||
getResolvedLogView(logViewReference: LogViewReference): Promise<ResolvedLogView>;
|
||||
putLogView(
|
||||
logViewReference: LogViewReference,
|
||||
logViewAttributes: Partial<LogViewAttributes>
|
||||
): Promise<LogView>;
|
||||
resolveLogView(logViewId: string, logViewAttributes: LogViewAttributes): Promise<ResolvedLogView>;
|
||||
}
|
||||
|
|
|
@ -12,8 +12,9 @@ import {
|
|||
FetchDataParams,
|
||||
LogsFetchDataResponse,
|
||||
} from '@kbn/observability-plugin/public';
|
||||
import { DEFAULT_SOURCE_ID, TIMESTAMP_FIELD } from '../../common/constants';
|
||||
import { TIMESTAMP_FIELD } from '../../common/constants';
|
||||
import { InfraClientStartDeps, InfraClientStartServicesAccessor } from '../types';
|
||||
import { DEFAULT_LOG_VIEW } from '../observability_logs/log_view_state/src/defaults';
|
||||
|
||||
interface StatsAggregation {
|
||||
buckets: Array<{
|
||||
|
@ -38,7 +39,7 @@ type StatsAndSeries = Pick<LogsFetchDataResponse, 'stats' | 'series'>;
|
|||
export function getLogsHasDataFetcher(getStartServices: InfraClientStartServicesAccessor) {
|
||||
return async () => {
|
||||
const [, , { logViews }] = await getStartServices();
|
||||
const resolvedLogView = await logViews.client.getResolvedLogView(DEFAULT_SOURCE_ID);
|
||||
const resolvedLogView = await logViews.client.getResolvedLogView(DEFAULT_LOG_VIEW);
|
||||
const logViewStatus = await logViews.client.getResolvedLogViewStatus(resolvedLogView);
|
||||
|
||||
const hasData = logViewStatus.index === 'available';
|
||||
|
@ -56,7 +57,7 @@ export function getLogsOverviewDataFetcher(
|
|||
): FetchData<LogsFetchDataResponse> {
|
||||
return async (params) => {
|
||||
const [, { data }, { logViews }] = await getStartServices();
|
||||
const resolvedLogView = await logViews.client.getResolvedLogView(DEFAULT_SOURCE_ID);
|
||||
const resolvedLogView = await logViews.client.getResolvedLogView(DEFAULT_LOG_VIEW);
|
||||
|
||||
const { stats, series } = await fetchLogsOverview(
|
||||
{
|
||||
|
|
|
@ -10,6 +10,7 @@ import { URL } from 'url';
|
|||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
|
||||
const ONE_HOUR = 60 * 60 * 1000;
|
||||
const LOG_VIEW_REFERENCE = '(logViewId:default,type:log-view-reference)';
|
||||
|
||||
export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
||||
const pageObjects = getPageObjects(['common']);
|
||||
|
@ -51,7 +52,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
|||
expect(parsedUrl.searchParams.get('logPosition')).to.be(
|
||||
`(position:(tiebreaker:0,time:${timestamp}))`
|
||||
);
|
||||
expect(parsedUrl.searchParams.get('sourceId')).to.be('default');
|
||||
expect(parsedUrl.searchParams.get('logView')).to.be(LOG_VIEW_REFERENCE);
|
||||
expect(documentTitle).to.contain('Stream - Logs - Observability - Elastic');
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue