Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/kbn-optimizer/limits.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ pageLoadAssetSize:
actions: 20000
advancedSettings: 27596
aiAssistantManagementSelection: 19146
aiops: 16670
aiops: 17122
alerting: 106936
apm: 64385
banners: 17946
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,8 @@ export interface DocumentCountChartProps {
dataTestSubj?: string;
/** Optional change point metadata */
changePoint?: DocumentCountStatsChangePoint;
/** Whether the brush should be non-interactive */
nonInteractive?: boolean;
}

const SPEC_ID = 'document_count';
Expand Down Expand Up @@ -190,6 +192,7 @@ export const DocumentCountChart: FC<DocumentCountChartProps> = (props) => {
barHighlightColorOverride,
deviationBrush = {},
baselineBrush = {},
nonInteractive,
} = props;

const { data, uiSettings, fieldFormats, charts } = dependencies;
Expand Down Expand Up @@ -470,6 +473,7 @@ export const DocumentCountChart: FC<DocumentCountChartProps> = (props) => {
marginLeft={mlBrushMarginLeft}
snapTimestamps={snapTimestamps}
width={mlBrushWidth}
nonInteractive={nonInteractive}
/>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,11 @@ interface DualBrushProps {
* Width
*/
width: number;
/**
* Whether the brush should be non-interactive. When true, the brush is still visible
* but cannot be moved or resized by the user.
*/
nonInteractive?: boolean;
}

/**
Expand All @@ -98,7 +103,16 @@ interface DualBrushProps {
* @returns The DualBrush component.
*/
export const DualBrush: FC<DualBrushProps> = (props) => {
const { windowParameters, min, max, onChange, marginLeft, snapTimestamps, width } = props;
const {
windowParameters,
min,
max,
onChange,
marginLeft,
snapTimestamps,
width,
nonInteractive,
} = props;
const d3BrushContainer = useRef(null);
const brushes = useRef<DualBrush[]>([]);

Expand Down Expand Up @@ -301,6 +315,10 @@ export const DualBrush: FC<DualBrushProps> = (props) => {
.attr('rx', BRUSH_HANDLE_ROUNDED_CORNER)
.attr('ry', BRUSH_HANDLE_ROUNDED_CORNER);

if (nonInteractive) {
mlBrushSelection.merge(mlBrushSelection).attr('pointer-events', 'none');
}

mlBrushSelection.exit().remove();
}

Expand Down Expand Up @@ -355,6 +373,7 @@ export const DualBrush: FC<DualBrushProps> = (props) => {
deviationMax,
snapTimestamps,
onChange,
nonInteractive,
]);

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ interface ProgressControlProps {
shouldRerunAnalysis: boolean;
runAnalysisDisabled?: boolean;
analysisInfo?: React.ReactNode;
resetDisabled?: boolean;
}

/**
Expand All @@ -63,6 +64,7 @@ export const ProgressControls: FC<PropsWithChildren<ProgressControlProps>> = (pr
shouldRerunAnalysis,
runAnalysisDisabled = false,
analysisInfo = null,
resetDisabled = false,
} = props;

const progressOutput = Math.round(progress * 100);
Expand Down Expand Up @@ -120,6 +122,7 @@ export const ProgressControls: FC<PropsWithChildren<ProgressControlProps>> = (pr
size="s"
onClick={onReset}
color="text"
disabled={resetDisabled}
>
<FormattedMessage id="xpack.aiops.resetLabel" defaultMessage="Reset" />
</EuiButton>
Expand Down
2 changes: 2 additions & 0 deletions x-pack/packages/ml/aiops_log_rate_analysis/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,5 @@ export const EMBEDDABLE_LOG_RATE_ANALYSIS_TYPE = 'aiopsLogRateAnalysisEmbeddable

/** */
export const LOG_RATE_ANALYSIS_DATA_VIEW_REF_NAME = 'aiopsLogRateAnalysisEmbeddableDataViewId';

export const CASES_ATTACHMENT_LOG_RATE_ANALYSIS = 'aiopsLogRateAnalysisEmbeddable';
52 changes: 52 additions & 0 deletions x-pack/plugins/aiops/public/cases/log_rate_analysis_attachment.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* 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 { memoize } from 'lodash';
import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public';
import type { PersistableStateAttachmentViewProps } from '@kbn/cases-plugin/public/client/attachment_framework/types';
import { FIELD_FORMAT_IDS } from '@kbn/field-formats-plugin/common';
import { FormattedMessage } from '@kbn/i18n-react';
import { EuiDescriptionList } from '@elastic/eui';
import type {
LogRateAnalysisEmbeddableWrapper,
LogRateAnalysisEmbeddableWrapperProps,
} from '../shared_components/log_rate_analysis_embeddable_wrapper';

export const initComponent = memoize(
(fieldFormats: FieldFormatsStart, LogRateAnalysisComponent: LogRateAnalysisEmbeddableWrapper) => {
return React.memo((props: PersistableStateAttachmentViewProps) => {
const { persistableStateAttachmentState } = props;
const dataFormatter = fieldFormats.deserialize({
id: FIELD_FORMAT_IDS.DATE,
});
const inputProps =
persistableStateAttachmentState as unknown as LogRateAnalysisEmbeddableWrapperProps;

const listItems = [
{
title: (
<FormattedMessage
id="xpack.aiops.logRateAnalysis.cases.timeRangeLabel"
defaultMessage="Time range"
/>
),
description: `${dataFormatter.convert(
inputProps.timeRange.from
)} - ${dataFormatter.convert(inputProps.timeRange.to)}`,
},
];

return (
<>
<EuiDescriptionList compressed type={'inline'} listItems={listItems} />
<LogRateAnalysisComponent {...inputProps} embeddingOrigin={'cases'} />
</>
);
});
}
);
34 changes: 34 additions & 0 deletions x-pack/plugins/aiops/public/cases/register_cases.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ import type { CasesPublicSetup } from '@kbn/cases-plugin/public';
import type { CoreStart } from '@kbn/core/public';
import { CASES_ATTACHMENT_CHANGE_POINT_CHART } from '@kbn/aiops-change-point-detection/constants';
import { CASES_ATTACHMENT_LOG_PATTERN } from '@kbn/aiops-log-pattern-analysis/constants';
import { CASES_ATTACHMENT_LOG_RATE_ANALYSIS } from '@kbn/aiops-log-rate-analysis/constants';
import {
getChangePointDetectionComponent,
getLogRateAnalysisEmbeddableWrapperComponent,
getPatternAnalysisComponent,
} from '../shared_components';
import type { AiopsPluginStartDeps } from '../types';
Expand Down Expand Up @@ -72,4 +74,36 @@ export function registerCases(
}),
}),
});

const LogRateAnalysisEmbeddableWrapperComponent = getLogRateAnalysisEmbeddableWrapperComponent(
coreStart,
pluginStart
);

cases.attachmentFramework.registerPersistableState({
id: CASES_ATTACHMENT_LOG_RATE_ANALYSIS,
icon: 'machineLearningApp',
displayName: i18n.translate('xpack.aiops.logRateAnalysis.cases.attachmentName', {
defaultMessage: 'Log rate analysis',
}),
getAttachmentViewObject: () => ({
event: (
<FormattedMessage
id="xpack.aiops.logRateAnalysis.cases.attachmentEvent"
defaultMessage="added log rate analysis"
/>
),
timelineAvatar: 'machineLearningApp',
children: React.lazy(async () => {
const { initComponent } = await import('./log_rate_analysis_attachment');

return {
default: initComponent(
pluginStart.fieldFormats,
LogRateAnalysisEmbeddableWrapperComponent
),
};
}),
}),
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,19 +45,25 @@ export const DocumentCountContent: FC<DocumentCountContentProps> = ({
const { documentStats } = useAppSelector((s) => s.logRateAnalysis);
const { sampleProbability, totalCount, documentCountStats } = documentStats;

const isCasesEmbedding = embeddingOrigin === AIOPS_EMBEDDABLE_ORIGIN.CASES;
Comment thread
peteharverson marked this conversation as resolved.

const isEmbeddedInDashboardOrCases =
embeddingOrigin === AIOPS_EMBEDDABLE_ORIGIN.DASHBOARD || isCasesEmbedding;

if (documentCountStats === undefined) {
return totalCount !== undefined && embeddingOrigin !== AIOPS_EMBEDDABLE_ORIGIN.DASHBOARD ? (
<TotalCountHeader totalCount={totalCount} sampleProbability={sampleProbability} />
) : null;
}

if (embeddingOrigin === AIOPS_EMBEDDABLE_ORIGIN.DASHBOARD) {
if (isEmbeddedInDashboardOrCases) {
return (
<DocumentCountChartRedux
dependencies={{ data, uiSettings, fieldFormats, charts }}
barColorOverride={barColorOverride}
barHighlightColorOverride={barHighlightColorOverride}
changePoint={documentCountStats.changePoint}
nonInteractive={isCasesEmbedding}
{...docCountChartProps}
/>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { AIOPS_STORAGE_KEYS } from '../../types/storage';

import { LogRateAnalysisPage } from './log_rate_analysis_page';
import { timeSeriesDataViewWarning } from '../../application/utils/time_series_dataview_check';
import { FilterQueryContextProvider } from '../../hooks/use_filters_query';

const localStorage = new Storage(window.localStorage);

Expand Down Expand Up @@ -58,6 +59,8 @@ export const LogRateAnalysisAppState: FC<LogRateAnalysisAppStateProps> = ({
if (warning !== null) {
return <>{warning}</>;
}
const CasesContext = appContextValue.cases?.ui.getCasesContext() ?? React.Fragment;
const casesPermissions = appContextValue.cases?.helpers.canUseCases();

const datePickerDeps: DatePickerDependencies = {
...pick(appContextValue, ['data', 'http', 'notifications', 'theme', 'uiSettings', 'i18n']),
Expand All @@ -67,17 +70,21 @@ export const LogRateAnalysisAppState: FC<LogRateAnalysisAppStateProps> = ({

return (
<AiopsAppContext.Provider value={appContextValue}>
<UrlStateProvider>
<DataSourceContext.Provider value={{ dataView, savedSearch }}>
<LogRateAnalysisReduxProvider>
<StorageContextProvider storage={localStorage} storageKeys={AIOPS_STORAGE_KEYS}>
<DatePickerContextProvider {...datePickerDeps}>
<LogRateAnalysisPage showContextualInsights={showContextualInsights} />
</DatePickerContextProvider>
</StorageContextProvider>
</LogRateAnalysisReduxProvider>
</DataSourceContext.Provider>
</UrlStateProvider>
<CasesContext permissions={casesPermissions!} owner={[]}>
<UrlStateProvider>
<DataSourceContext.Provider value={{ dataView, savedSearch }}>
<LogRateAnalysisReduxProvider>
<StorageContextProvider storage={localStorage} storageKeys={AIOPS_STORAGE_KEYS}>
<DatePickerContextProvider {...datePickerDeps}>
<FilterQueryContextProvider>
<LogRateAnalysisPage showContextualInsights={showContextualInsights} />
</FilterQueryContextProvider>
</DatePickerContextProvider>
</StorageContextProvider>
</LogRateAnalysisReduxProvider>
</DataSourceContext.Provider>
</UrlStateProvider>
</CasesContext>
</AiopsAppContext.Provider>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,26 @@ import {
EuiSpacer,
EuiSwitch,
} from '@elastic/eui';
import type { WindowParameters } from '@kbn/aiops-log-rate-analysis/window_parameters';
import { useCasesModal } from '../../../hooks/use_cases_modal';
import { useDataSource } from '../../../hooks/use_data_source';
import type { LogRateAnalysisEmbeddableState } from '../../../embeddables/log_rate_analysis/types';
import { useAiopsAppContext } from '../../../hooks/use_aiops_app_context';

const SavedObjectSaveModalDashboard = withSuspense(LazySavedObjectSaveModalDashboard);

export const LogRateAnalysisAttachmentsMenu = () => {
interface LogRateAnalysisAttachmentsMenuProps {
windowParameters?: WindowParameters;
hasSignificantItemsToAttach: boolean;
}

export const LogRateAnalysisAttachmentsMenu = ({
windowParameters,
hasSignificantItemsToAttach,
}: LogRateAnalysisAttachmentsMenuProps) => {
const {
application: { capabilities },
cases,
embeddable,
} = useAiopsAppContext();
const { dataView } = useDataSource();
Expand All @@ -44,9 +55,17 @@ export const LogRateAnalysisAttachmentsMenu = () => {
const [dashboardAttachmentReady, setDashboardAttachmentReady] = useState(false);

const timeRange = useTimeRangeUpdates();
const absoluteTimeRange = useTimeRangeUpdates(true);

const openCasesModalCallback = useCasesModal(EMBEDDABLE_LOG_RATE_ANALYSIS_TYPE);

const canEditDashboards = capabilities.dashboard.createNew;

const { create: canCreateCase, update: canUpdateCase } = cases?.helpers?.canUseCases() ?? {
create: false,
update: false,
};

const onSave: SaveModalDashboardProps['onSave'] = useCallback(
({ dashboardId, newTitle, newDescription }) => {
const stateTransfer = embeddable!.getStateTransfer();
Expand Down Expand Up @@ -88,6 +107,37 @@ export const LogRateAnalysisAttachmentsMenu = () => {
},
]
: []),
...(canUpdateCase || canCreateCase
? [
{
name: i18n.translate('xpack.aiops.logRateAnalysis.attachToCaseLabel', {
defaultMessage: 'Add to case',
}),
'data-test-subj': 'aiopsLogRateAnalysisAttachToCaseButton',
disabled: !hasSignificantItemsToAttach,
...(!hasSignificantItemsToAttach
? {
toolTipProps: { position: 'left' as const },
toolTipContent: i18n.translate(
'xpack.aiops.logRateAnalysis.attachToCaseTooltipContent',
{
defaultMessage:
'Cannot add to case because the analysis did not produce any results',
Comment thread
peteharverson marked this conversation as resolved.
Outdated
}
),
}
: {}),
onClick: () => {
setIsActionMenuOpen(false);
openCasesModalCallback({
dataViewId: dataView.id,
timeRange: absoluteTimeRange,
...(windowParameters && { windowParameters }),
});
},
},
]
: []),
],
},
{
Expand Down Expand Up @@ -131,7 +181,17 @@ export const LogRateAnalysisAttachmentsMenu = () => {
),
},
];
}, [canEditDashboards, applyTimeRange]);
}, [
canEditDashboards,
canUpdateCase,
canCreateCase,
hasSignificantItemsToAttach,
applyTimeRange,
openCasesModalCallback,
dataView.id,
absoluteTimeRange,
windowParameters,
]);

return (
<>
Expand Down
Loading