[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

![Screenshot 2023-03-10 at 14 55
34](https://user-images.githubusercontent.com/471693/224348959-8db70d91-dc8b-4f4e-926b-ec05e7481b78.png)

### Alert dropdown warning

![Screenshot 2023-03-10 at 14 55
43](https://user-images.githubusercontent.com/471693/224349026-cdf17767-225a-4ecd-af8a-b90e2a21816f.png)


### Settings page warning

![Screenshot 2023-03-10 at 14 56
02](https://user-images.githubusercontent.com/471693/224349066-bcb63ba8-fee8-4b7a-b41b-7d89e09f002a.png)


## 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:
Kerry Gallagher 2023-03-29 09:15:39 -04:00 committed by GitHub
parent e13f1c5fb2
commit 4c586a71d4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
81 changed files with 1098 additions and 416 deletions

View file

@ -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';

View file

@ -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({

View file

@ -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,

View file

@ -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 }}

View file

@ -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;

View file

@ -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>

View file

@ -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,

View file

@ -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;

View file

@ -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,

View file

@ -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>
);

View file

@ -6,4 +6,4 @@
*/
export * from './log_threshold_rule_type';
export { AlertDropdown } from './components/alert_dropdown';
export { LazyAlertDropdownWrapper } from './components/lazy_alert_dropdown';

View file

@ -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,

View file

@ -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}
/>
);
};

View file

@ -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">

View file

@ -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)),
})
),
});

View file

@ -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)),
})
),
});

View file

@ -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));

View file

@ -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 {

View file

@ -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>
);

View file

@ -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: (

View file

@ -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 (

View file

@ -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({

View file

@ -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 (

View file

@ -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({

View file

@ -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,
});

View file

@ -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(() => {

View file

@ -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,

View file

@ -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(

View file

@ -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 {

View file

@ -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,
});

View file

@ -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,
});

View file

@ -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,
});

View file

@ -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,
}

View file

@ -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,
]);

View file

@ -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

View file

@ -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,
},
{

View file

@ -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));

View file

@ -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 =

View file

@ -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,
};
};

View file

@ -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,
};

View file

@ -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';

View file

@ -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) =>

View file

@ -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

View file

@ -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<

View file

@ -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);

View file

@ -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';

View file

@ -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>
</>
);
};

View file

@ -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'))"`
);

View file

@ -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>
);

View file

@ -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))"
/>
`);
});

View file

@ -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}`} />;

View file

@ -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}`} />;

View file

@ -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}

View file

@ -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}

View file

@ -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,
});

View file

@ -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}

View file

@ -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 (

View file

@ -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,

View file

@ -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,

View file

@ -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,

View file

@ -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(

View file

@ -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(

View file

@ -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}

View file

@ -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>

View file

@ -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,
});

View file

@ -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,

View file

@ -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,

View file

@ -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,

View file

@ -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(

View file

@ -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(

View file

@ -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 />

View file

@ -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>
);

View file

@ -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>
);
};

View file

@ -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

View file

@ -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}

View file

@ -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,

View file

@ -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}

View file

@ -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(

View file

@ -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>;
}

View file

@ -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(
{

View file

@ -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');
});
});