Skip to content

Commit

Permalink
[Logs UI] Support inline Log Views in the UI (#152933)
Browse files Browse the repository at this point in the history
## 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 <[email protected]>
  • Loading branch information
Kerry350 and kibanamachine authored Mar 29, 2023
1 parent e13f1c5 commit 4c586a7
Show file tree
Hide file tree
Showing 81 changed files with 1,097 additions and 415 deletions.
1 change: 0 additions & 1 deletion x-pack/plugins/infra/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
8 changes: 4 additions & 4 deletions x-pack/plugins/infra/common/log_analysis/job_parameters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/infra/common/log_views/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -26,14 +27,30 @@ 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: {
application: { capabilities },
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);

Expand Down Expand Up @@ -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"
Expand All @@ -76,7 +105,7 @@ export const AlertDropdown = () => {
/>
</EuiContextMenuItem>,
];
}, [manageRulesLinkProps, canCreateAlerts, openFlyout]);
}, [canCreateAlerts, openFlyout, readOnly, manageRulesLinkProps]);

return (
<>
Expand Down Expand Up @@ -104,3 +133,7 @@ export const AlertDropdown = () => {
</>
);
};

// Allow for lazy loading
// eslint-disable-next-line import/no-default-export
export default AlertDropdown;
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -39,7 +42,7 @@ interface SharedProps {
defaultCriterion: PartialCriterionType;
errors: Errors['criteria'];
ruleParams: PartialRuleParams;
sourceId: string;
logViewReference: PersistedLogViewReference;
updateCriteria: (criteria: PartialCriteriaType) => void;
}

Expand All @@ -64,7 +67,7 @@ interface CriteriaWrapperProps {
addCriterion: () => void;
criteria: PartialCountCriteriaType;
errors: CriterionErrors;
sourceId: SharedProps['sourceId'];
logViewReference: SharedProps['logViewReference'];
isRatio?: boolean;
}

Expand All @@ -77,7 +80,7 @@ const CriteriaWrapper: React.FC<CriteriaWrapperProps> = (props) => {
fields,
errors,
ruleParams,
sourceId,
logViewReference,
isRatio = false,
} = props;

Expand Down Expand Up @@ -105,7 +108,7 @@ const CriteriaWrapper: React.FC<CriteriaWrapperProps> = (props) => {
<CriterionPreview
ruleParams={ruleParams}
chartCriterion={criterion}
sourceId={sourceId}
logViewReference={logViewReference}
showThreshold={!isRatio}
/>
</EuiAccordion>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -55,15 +56,15 @@ const GROUP_LIMIT = 5;
interface Props {
ruleParams: PartialRuleParams;
chartCriterion: Partial<Criterion>;
sourceId: string;
logViewReference: PersistedLogViewReference;
showThreshold: boolean;
executionTimeRange?: ExecutionTimeRange;
}

export const CriterionPreview: React.FC<Props> = ({
ruleParams,
chartCriterion,
sourceId,
logViewReference,
showThreshold,
executionTimeRange,
}) => {
Expand Down Expand Up @@ -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}
Expand All @@ -116,7 +117,7 @@ export const CriterionPreview: React.FC<Props> = ({

interface ChartProps {
buckets: number;
sourceId: string;
logViewReference: PersistedLogViewReference;
threshold?: Threshold;
chartAlertParams: GetLogAlertsChartPreviewDataAlertParamsSubset;
showThreshold: boolean;
Expand All @@ -125,7 +126,7 @@ interface ChartProps {

const CriterionPreviewChart: React.FC<ChartProps> = ({
buckets,
sourceId,
logViewReference,
threshold,
chartAlertParams,
showThreshold,
Expand All @@ -141,7 +142,7 @@ const CriterionPreviewChart: React.FC<ChartProps> = ({
hasError,
chartPreviewData: series,
} = useChartPreviewData({
sourceId,
logViewReference,
ruleParams: chartAlertParams,
buckets,
executionTimeRange,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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']
Expand Down Expand Up @@ -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
Expand All @@ -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>
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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,
Expand All @@ -43,7 +44,7 @@ export const useChartPreviewData = ({
createPromise: async () => {
setHasError(false);
return await callGetChartPreviewDataAPI(
sourceId,
logViewReference,
http!.fetch,
ruleParams,
buckets,
Expand All @@ -58,7 +59,7 @@ export const useChartPreviewData = ({
setHasError(true);
},
},
[sourceId, http, ruleParams, buckets]
[logViewReference, http, ruleParams, buckets]
);

const isLoading = useMemo(
Expand All @@ -75,7 +76,7 @@ export const useChartPreviewData = ({
};

export const callGetChartPreviewDataAPI = async (
sourceId: string,
logViewReference: PersistedLogViewReference,
fetch: HttpHandler,
alertParams: GetLogAlertsChartPreviewDataAlertParamsSubset,
buckets: number,
Expand All @@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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>
);
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@
*/

export * from './log_threshold_rule_type';
export { AlertDropdown } from './components/alert_dropdown';
export { LazyAlertDropdownWrapper } from './components/lazy_alert_dropdown';
Loading

0 comments on commit 4c586a7

Please sign in to comment.