mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[ML] Add table layout for Change Point Detection embeddable (#174348)
## Summary Part of #161248 - Adds new "View type" parameter to the Change Point Detection embeddable that allows choosing between charts and table layout <img width="616" alt="image" src="4a6580d5
-0d92-41c4-9b07-dc430c52a87c"> <img width="1640" alt="image" src="2b46c1f1
-ce10-455b-9d0f-289635fa6b0d"> - Set the view type parameter while attaching from the ML app <img width="1289" alt="image" src="b6a53c68
-c5e2-4b77-be00-79fbdf37d90d"> - Allows attachment of a change point table to a Case <img width="1265" alt="image" src="4dbe9738
-0bca-4bff-ba13-ed2e4be5bef5"> - Fixes reporting on the loading and completed render states ### Checklist - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [x] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [x] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) - [x] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [x] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers)
This commit is contained in:
parent
9762fa3cb6
commit
13981d2211
20 changed files with 469 additions and 171 deletions
|
@ -39,10 +39,11 @@ export interface UseTableState<T extends object> {
|
|||
export function useTableState<T extends object>(
|
||||
items: T[],
|
||||
initialSortField: string,
|
||||
initialSortDirection: 'asc' | 'desc' = 'asc'
|
||||
initialSortDirection: 'asc' | 'desc' = 'asc',
|
||||
initialPagionation?: Partial<Pagination>
|
||||
) {
|
||||
const [pageIndex, setPageIndex] = useState(0);
|
||||
const [pageSize, setPageSize] = useState(10);
|
||||
const [pageIndex, setPageIndex] = useState(initialPagionation?.pageIndex ?? 0);
|
||||
const [pageSize, setPageSize] = useState(initialPagionation?.pageSize ?? 10);
|
||||
const [sortField, setSortField] = useState<string>(initialSortField);
|
||||
const [sortDirection, setSortDirection] = useState<Direction>(initialSortDirection);
|
||||
|
||||
|
@ -63,7 +64,7 @@ export function useTableState<T extends object>(
|
|||
pageIndex,
|
||||
pageSize,
|
||||
totalItemCount: (items ?? []).length,
|
||||
pageSizeOptions: [10, 20, 50],
|
||||
pageSizeOptions: initialPagionation?.pageSizeOptions ?? [10, 20, 50],
|
||||
showPerPageOptions: true,
|
||||
};
|
||||
|
||||
|
|
|
@ -26,9 +26,19 @@ export const CASES_ATTACHMENT_CHANGE_POINT_CHART = 'aiopsChangePointChart';
|
|||
|
||||
export const EMBEDDABLE_CHANGE_POINT_CHART_TYPE = 'aiopsChangePointChart' as const;
|
||||
|
||||
export type EmbeddableChangePointType = typeof EMBEDDABLE_CHANGE_POINT_CHART_TYPE;
|
||||
|
||||
export const AIOPS_TELEMETRY_ID = {
|
||||
AIOPS_DEFAULT_SOURCE: 'ml_aiops_labs',
|
||||
AIOPS_ANALYSIS_RUN_ORIGIN: 'aiops-analysis-run-origin',
|
||||
} as const;
|
||||
|
||||
export const EMBEDDABLE_ORIGIN = 'embeddable';
|
||||
|
||||
export const CHANGE_POINT_DETECTION_VIEW_TYPE = {
|
||||
CHARTS: 'charts',
|
||||
TABLE: 'table',
|
||||
} as const;
|
||||
|
||||
export type ChangePointDetectionViewType =
|
||||
typeof CHANGE_POINT_DETECTION_VIEW_TYPE[keyof typeof CHANGE_POINT_DETECTION_VIEW_TYPE];
|
||||
|
|
|
@ -45,7 +45,7 @@ export const initComponent = memoize(
|
|||
return (
|
||||
<>
|
||||
<EuiDescriptionList compressed type={'inline'} listItems={listItems} />
|
||||
<EmbeddableComponent {...inputProps} />
|
||||
<EmbeddableComponent {...inputProps} embeddingOrigin={'cases'} />
|
||||
</>
|
||||
);
|
||||
},
|
||||
|
|
|
@ -10,7 +10,10 @@ import { i18n } from '@kbn/i18n';
|
|||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import type { CasesUiSetup } from '@kbn/cases-plugin/public';
|
||||
import type { CoreStart } from '@kbn/core/public';
|
||||
import { CASES_ATTACHMENT_CHANGE_POINT_CHART } from '../../common/constants';
|
||||
import {
|
||||
CASES_ATTACHMENT_CHANGE_POINT_CHART,
|
||||
EMBEDDABLE_CHANGE_POINT_CHART_TYPE,
|
||||
} from '../../common/constants';
|
||||
import { getEmbeddableChangePointChart } from '../embeddable/embeddable_change_point_chart_component';
|
||||
import { AiopsPluginStartDeps } from '../types';
|
||||
|
||||
|
@ -19,7 +22,11 @@ export function registerChangePointChartsAttachment(
|
|||
coreStart: CoreStart,
|
||||
pluginStart: AiopsPluginStartDeps
|
||||
) {
|
||||
const EmbeddableComponent = getEmbeddableChangePointChart(coreStart, pluginStart);
|
||||
const EmbeddableComponent = getEmbeddableChangePointChart(
|
||||
EMBEDDABLE_CHANGE_POINT_CHART_TYPE,
|
||||
coreStart,
|
||||
pluginStart
|
||||
);
|
||||
|
||||
cases.attachmentFramework.registerPersistableState({
|
||||
id: CASES_ATTACHMENT_CHANGE_POINT_CHART,
|
||||
|
|
|
@ -7,35 +7,37 @@
|
|||
|
||||
import {
|
||||
EuiBadge,
|
||||
type EuiBasicTableColumn,
|
||||
EuiEmptyPrompt,
|
||||
EuiIcon,
|
||||
EuiInMemoryTable,
|
||||
EuiToolTip,
|
||||
type DefaultItemAction,
|
||||
type EuiBasicTableColumn,
|
||||
} from '@elastic/eui';
|
||||
import React, { type FC, useMemo } from 'react';
|
||||
import { EuiTableSelectionType } from '@elastic/eui/src/components/basic_table/table_types';
|
||||
import { FilterStateStore, type Filter } from '@kbn/es-query';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { EuiTableSelectionType } from '@elastic/eui/src/components/basic_table/table_types';
|
||||
import { type Filter, FilterStateStore } from '@kbn/es-query';
|
||||
import { NoChangePointsWarning } from './no_change_points_warning';
|
||||
import { useTableState } from '@kbn/ml-in-memory-table';
|
||||
import React, { useCallback, useEffect, useMemo, useRef, type FC } from 'react';
|
||||
import { useAiopsAppContext } from '../../hooks/use_aiops_app_context';
|
||||
import { useDataSource } from '../../hooks/use_data_source';
|
||||
import { useCommonChartProps } from './use_common_chart_props';
|
||||
import {
|
||||
type ChangePointAnnotation,
|
||||
FieldConfig,
|
||||
SelectedChangePoint,
|
||||
useChangePointDetectionContext,
|
||||
type ChangePointAnnotation,
|
||||
} from './change_point_detection_context';
|
||||
import { type ChartComponentProps } from './chart_component';
|
||||
import { useAiopsAppContext } from '../../hooks/use_aiops_app_context';
|
||||
import { NoChangePointsWarning } from './no_change_points_warning';
|
||||
import { useCommonChartProps } from './use_common_chart_props';
|
||||
|
||||
export interface ChangePointsTableProps {
|
||||
annotations: ChangePointAnnotation[];
|
||||
fieldConfig: FieldConfig;
|
||||
isLoading: boolean;
|
||||
onSelectionChange: (update: SelectedChangePoint[]) => void;
|
||||
onSelectionChange?: (update: SelectedChangePoint[]) => void;
|
||||
onRenderComplete?: () => void;
|
||||
}
|
||||
|
||||
function getFilterConfig(
|
||||
|
@ -68,31 +70,62 @@ function getFilterConfig(
|
|||
};
|
||||
}
|
||||
|
||||
const pageSizeOptions = [5, 10, 15];
|
||||
|
||||
export const ChangePointsTable: FC<ChangePointsTableProps> = ({
|
||||
isLoading,
|
||||
annotations,
|
||||
fieldConfig,
|
||||
onSelectionChange,
|
||||
onRenderComplete,
|
||||
}) => {
|
||||
const {
|
||||
fieldFormats,
|
||||
data: {
|
||||
query: { filterManager },
|
||||
},
|
||||
embeddingOrigin,
|
||||
} = useAiopsAppContext();
|
||||
const { dataView } = useDataSource();
|
||||
|
||||
const chartLoadingCount = useRef<number>(0);
|
||||
|
||||
const { onTableChange, pagination, sorting } = useTableState<ChangePointAnnotation>(
|
||||
annotations ?? [],
|
||||
'p_value',
|
||||
'asc',
|
||||
{
|
||||
pageIndex: 0,
|
||||
pageSize: 10,
|
||||
pageSizeOptions,
|
||||
}
|
||||
);
|
||||
|
||||
const dateFormatter = useMemo(() => fieldFormats.deserialize({ id: 'date' }), [fieldFormats]);
|
||||
|
||||
const defaultSorting = {
|
||||
sort: {
|
||||
field: 'p_value',
|
||||
// Lower p_value indicates a bigger change point, hence the asc sorting
|
||||
direction: 'asc' as const,
|
||||
},
|
||||
};
|
||||
useEffect(() => {
|
||||
// Reset loading counter on pagination or sort change
|
||||
chartLoadingCount.current = 0;
|
||||
}, [pagination.pageIndex, pagination.pageSize, sorting.sort]);
|
||||
|
||||
const hasActions = fieldConfig.splitField !== undefined;
|
||||
/**
|
||||
* Callback to track render of each chart component
|
||||
* to report when all charts on the current page are ready.
|
||||
*/
|
||||
const onChartRenderCompleteCallback = useCallback(
|
||||
(isLoadingChart: boolean) => {
|
||||
if (!onRenderComplete) return;
|
||||
if (!isLoadingChart) {
|
||||
chartLoadingCount.current++;
|
||||
}
|
||||
if (chartLoadingCount.current === pagination.pageSize) {
|
||||
onRenderComplete();
|
||||
}
|
||||
},
|
||||
[onRenderComplete, pagination.pageSize]
|
||||
);
|
||||
|
||||
const hasActions = fieldConfig.splitField !== undefined && embeddingOrigin !== 'cases';
|
||||
|
||||
const { bucketInterval } = useChangePointDetectionContext();
|
||||
|
||||
|
@ -131,6 +164,7 @@ export const ChangePointsTable: FC<ChangePointsTableProps> = ({
|
|||
annotation={annotation}
|
||||
fieldConfig={fieldConfig}
|
||||
interval={bucketInterval.expression}
|
||||
onRenderComplete={onChartRenderCompleteCallback.bind(null, false)}
|
||||
/>
|
||||
);
|
||||
},
|
||||
|
@ -190,70 +224,83 @@ export const ChangePointsTable: FC<ChangePointsTableProps> = ({
|
|||
truncateText: false,
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
name: i18n.translate('xpack.aiops.changePointDetection.actionsColumn', {
|
||||
defaultMessage: 'Actions',
|
||||
}),
|
||||
actions: [
|
||||
{
|
||||
name: i18n.translate(
|
||||
'xpack.aiops.changePointDetection.actions.filterForValueAction',
|
||||
{
|
||||
defaultMessage: 'Filter for value',
|
||||
}
|
||||
),
|
||||
description: i18n.translate(
|
||||
'xpack.aiops.changePointDetection.actions.filterForValueAction',
|
||||
{
|
||||
defaultMessage: 'Filter for value',
|
||||
}
|
||||
),
|
||||
icon: 'plusInCircle',
|
||||
color: 'primary',
|
||||
type: 'icon',
|
||||
onClick: (item) => {
|
||||
filterManager.addFilters(
|
||||
getFilterConfig(dataView.id!, item as Required<ChangePointAnnotation>, false)!
|
||||
);
|
||||
...(hasActions
|
||||
? [
|
||||
{
|
||||
name: i18n.translate('xpack.aiops.changePointDetection.actionsColumn', {
|
||||
defaultMessage: 'Actions',
|
||||
}),
|
||||
actions: [
|
||||
{
|
||||
name: i18n.translate(
|
||||
'xpack.aiops.changePointDetection.actions.filterForValueAction',
|
||||
{
|
||||
defaultMessage: 'Filter for value',
|
||||
}
|
||||
),
|
||||
description: i18n.translate(
|
||||
'xpack.aiops.changePointDetection.actions.filterForValueAction',
|
||||
{
|
||||
defaultMessage: 'Filter for value',
|
||||
}
|
||||
),
|
||||
icon: 'plusInCircle',
|
||||
color: 'primary',
|
||||
type: 'icon',
|
||||
onClick: (item) => {
|
||||
filterManager.addFilters(
|
||||
getFilterConfig(
|
||||
dataView.id!,
|
||||
item as Required<ChangePointAnnotation>,
|
||||
false
|
||||
)!
|
||||
);
|
||||
},
|
||||
isPrimary: true,
|
||||
'data-test-subj': 'aiopsChangePointFilterForValue',
|
||||
},
|
||||
{
|
||||
name: i18n.translate(
|
||||
'xpack.aiops.changePointDetection.actions.filterOutValueAction',
|
||||
{
|
||||
defaultMessage: 'Filter out value',
|
||||
}
|
||||
),
|
||||
description: i18n.translate(
|
||||
'xpack.aiops.changePointDetection.actions.filterOutValueAction',
|
||||
{
|
||||
defaultMessage: 'Filter out value',
|
||||
}
|
||||
),
|
||||
icon: 'minusInCircle',
|
||||
color: 'primary',
|
||||
type: 'icon',
|
||||
onClick: (item) => {
|
||||
filterManager.addFilters(
|
||||
getFilterConfig(
|
||||
dataView.id!,
|
||||
item as Required<ChangePointAnnotation>,
|
||||
true
|
||||
)!
|
||||
);
|
||||
},
|
||||
isPrimary: true,
|
||||
'data-test-subj': 'aiopsChangePointFilterOutValue',
|
||||
},
|
||||
] as Array<DefaultItemAction<ChangePointAnnotation>>,
|
||||
},
|
||||
isPrimary: true,
|
||||
'data-test-subj': 'aiopsChangePointFilterForValue',
|
||||
},
|
||||
{
|
||||
name: i18n.translate(
|
||||
'xpack.aiops.changePointDetection.actions.filterOutValueAction',
|
||||
{
|
||||
defaultMessage: 'Filter out value',
|
||||
}
|
||||
),
|
||||
description: i18n.translate(
|
||||
'xpack.aiops.changePointDetection.actions.filterOutValueAction',
|
||||
{
|
||||
defaultMessage: 'Filter out value',
|
||||
}
|
||||
),
|
||||
icon: 'minusInCircle',
|
||||
color: 'primary',
|
||||
type: 'icon',
|
||||
onClick: (item) => {
|
||||
filterManager.addFilters(
|
||||
getFilterConfig(dataView.id!, item as Required<ChangePointAnnotation>, true)!
|
||||
);
|
||||
},
|
||||
isPrimary: true,
|
||||
'data-test-subj': 'aiopsChangePointFilterOutValue',
|
||||
},
|
||||
] as Array<DefaultItemAction<ChangePointAnnotation>>,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
]
|
||||
: []),
|
||||
];
|
||||
|
||||
const selectionValue = useMemo<EuiTableSelectionType<ChangePointAnnotation>>(() => {
|
||||
const selectionValue = useMemo<EuiTableSelectionType<ChangePointAnnotation> | undefined>(() => {
|
||||
if (!onSelectionChange) return;
|
||||
return {
|
||||
selectable: (item) => true,
|
||||
onSelectionChange: (selection) => {
|
||||
onSelectionChange(
|
||||
onSelectionChange!(
|
||||
selection.map((s) => {
|
||||
return {
|
||||
...s,
|
||||
|
@ -273,8 +320,11 @@ export const ChangePointsTable: FC<ChangePointsTableProps> = ({
|
|||
data-test-subj={`aiopsChangePointResultsTable ${isLoading ? 'loading' : 'loaded'}`}
|
||||
items={annotations}
|
||||
columns={columns}
|
||||
pagination={{ pageSizeOptions: [5, 10, 15] }}
|
||||
sorting={defaultSorting}
|
||||
pagination={
|
||||
pagination.pageSizeOptions![0] > pagination!.totalItemCount ? undefined : pagination
|
||||
}
|
||||
sorting={sorting}
|
||||
onTableChange={onTableChange}
|
||||
hasActions={hasActions}
|
||||
rowProps={(item) => ({
|
||||
'data-test-subj': `aiopsChangePointResultsTableRow row-${item.id}`,
|
||||
|
@ -300,7 +350,12 @@ export const ChangePointsTable: FC<ChangePointsTableProps> = ({
|
|||
);
|
||||
};
|
||||
|
||||
export const MiniChartPreview: FC<ChartComponentProps> = ({ fieldConfig, annotation }) => {
|
||||
export const MiniChartPreview: FC<ChartComponentProps> = ({
|
||||
fieldConfig,
|
||||
annotation,
|
||||
onRenderComplete,
|
||||
onLoading,
|
||||
}) => {
|
||||
const {
|
||||
lens: { EmbeddableComponent },
|
||||
} = useAiopsAppContext();
|
||||
|
@ -314,8 +369,31 @@ export const MiniChartPreview: FC<ChartComponentProps> = ({ fieldConfig, annotat
|
|||
bucketInterval: bucketInterval.expression,
|
||||
});
|
||||
|
||||
const chartWrapperRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const renderCompleteListener = useCallback(
|
||||
(event: Event) => {
|
||||
if (event.target === chartWrapperRef.current) return;
|
||||
if (onRenderComplete) {
|
||||
onRenderComplete();
|
||||
}
|
||||
},
|
||||
[onRenderComplete]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!chartWrapperRef.current) {
|
||||
throw new Error('Reference to the chart wrapper is not set');
|
||||
}
|
||||
const chartWrapper = chartWrapperRef.current;
|
||||
chartWrapper.addEventListener('renderComplete', renderCompleteListener);
|
||||
return () => {
|
||||
chartWrapper.removeEventListener('renderComplete', renderCompleteListener);
|
||||
};
|
||||
}, [renderCompleteListener]);
|
||||
|
||||
return (
|
||||
<div data-test-subj={'aiopChangePointPreviewChart'}>
|
||||
<div data-test-subj={'aiopChangePointPreviewChart'} ref={chartWrapperRef}>
|
||||
<EmbeddableComponent
|
||||
id={`mini_changePointChart_${annotation.group ? annotation.group.value : annotation.label}`}
|
||||
style={{ height: 80 }}
|
||||
|
@ -329,6 +407,7 @@ export const MiniChartPreview: FC<ChartComponentProps> = ({ fieldConfig, annotat
|
|||
type: 'aiops_change_point_detection_chart',
|
||||
name: 'Change point detection',
|
||||
}}
|
||||
onLoad={onLoading}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -33,7 +33,11 @@ import {
|
|||
import { EuiContextMenuProps } from '@elastic/eui/src/components/context_menu/context_menu';
|
||||
import { isDefined } from '@kbn/ml-is-defined';
|
||||
import { MaxSeriesControl } from './max_series_control';
|
||||
import { EMBEDDABLE_CHANGE_POINT_CHART_TYPE } from '../../../common/constants';
|
||||
import {
|
||||
ChangePointDetectionViewType,
|
||||
CHANGE_POINT_DETECTION_VIEW_TYPE,
|
||||
EMBEDDABLE_CHANGE_POINT_CHART_TYPE,
|
||||
} from '../../../common/constants';
|
||||
import { useCasesModal } from '../../hooks/use_cases_modal';
|
||||
import { type EmbeddableChangePointChartInput } from '../../embeddable/embeddable_change_point_chart';
|
||||
import { useDataSource } from '../../hooks/use_data_source';
|
||||
|
@ -51,6 +55,7 @@ import {
|
|||
} from './change_point_detection_context';
|
||||
import { useChangePointResults } from './use_change_point_agg_request';
|
||||
import { useSplitFieldCardinality } from './use_split_field_cardinality';
|
||||
import { ViewTypeSelector } from './view_type_selector';
|
||||
|
||||
const selectControlCss = { width: '350px' };
|
||||
|
||||
|
@ -191,10 +196,17 @@ const FieldPanel: FC<FieldPanelProps> = ({
|
|||
const [dashboardAttachment, setDashboardAttachment] = useState<{
|
||||
applyTimeRange: boolean;
|
||||
maxSeriesToPlot: number;
|
||||
viewType: ChangePointDetectionViewType;
|
||||
}>({
|
||||
applyTimeRange: false,
|
||||
maxSeriesToPlot: 6,
|
||||
viewType: CHANGE_POINT_DETECTION_VIEW_TYPE.CHARTS,
|
||||
});
|
||||
|
||||
const [caseAttachment, setCaseAttachment] = useState<{
|
||||
viewType: ChangePointDetectionViewType;
|
||||
}>({ viewType: CHANGE_POINT_DETECTION_VIEW_TYPE.CHARTS });
|
||||
|
||||
const [dashboardAttachmentReady, setDashboardAttachmentReady] = useState<boolean>(false);
|
||||
|
||||
const {
|
||||
|
@ -294,20 +306,7 @@ const FieldPanel: FC<FieldPanelProps> = ({
|
|||
}
|
||||
: {}),
|
||||
'data-test-subj': 'aiopsChangePointDetectionAttachToCaseButton',
|
||||
onClick: () => {
|
||||
openCasesModalCallback({
|
||||
timeRange,
|
||||
fn: fieldConfig.fn,
|
||||
metricField: fieldConfig.metricField,
|
||||
dataViewId: dataView.id,
|
||||
...(fieldConfig.splitField
|
||||
? {
|
||||
splitField: fieldConfig.splitField,
|
||||
partitions: selectedPartitions,
|
||||
}
|
||||
: {}),
|
||||
});
|
||||
},
|
||||
panel: 'attachToCasePanel',
|
||||
},
|
||||
]
|
||||
: []),
|
||||
|
@ -324,6 +323,17 @@ const FieldPanel: FC<FieldPanelProps> = ({
|
|||
<EuiPanel paddingSize={'s'}>
|
||||
<EuiSpacer size={'s'} />
|
||||
<EuiForm data-test-subj="aiopsChangePointDetectionDashboardAttachmentForm">
|
||||
<ViewTypeSelector
|
||||
value={dashboardAttachment.viewType}
|
||||
onChange={(v) => {
|
||||
setDashboardAttachment((prevState) => {
|
||||
return {
|
||||
...prevState,
|
||||
viewType: v,
|
||||
};
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<EuiFormRow fullWidth>
|
||||
<EuiSwitch
|
||||
label={i18n.translate('xpack.aiops.changePointDetection.applyTimeRangeLabel', {
|
||||
|
@ -366,7 +376,63 @@ const FieldPanel: FC<FieldPanelProps> = ({
|
|||
fill
|
||||
type={'submit'}
|
||||
fullWidth
|
||||
onClick={setDashboardAttachmentReady.bind(null, true)}
|
||||
onClick={() => {
|
||||
setIsActionMenuOpen(false);
|
||||
setDashboardAttachmentReady(true);
|
||||
}}
|
||||
disabled={!isDashboardFormValid}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.aiops.changePointDetection.submitDashboardAttachButtonLabel"
|
||||
defaultMessage="Attach"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiForm>
|
||||
</EuiPanel>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'attachToCasePanel',
|
||||
title: i18n.translate('xpack.aiops.changePointDetection.attachToCaseTitle', {
|
||||
defaultMessage: 'Attach to case',
|
||||
}),
|
||||
size: 's',
|
||||
content: (
|
||||
<EuiPanel paddingSize={'s'}>
|
||||
<EuiSpacer size={'s'} />
|
||||
<EuiForm data-test-subj="aiopsChangePointDetectionCasedAttachmentForm">
|
||||
<ViewTypeSelector
|
||||
value={caseAttachment.viewType}
|
||||
onChange={(v) => {
|
||||
setCaseAttachment((prevState) => {
|
||||
return {
|
||||
...prevState,
|
||||
viewType: v,
|
||||
};
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<EuiButton
|
||||
data-test-subj="aiopsChangePointDetectionSubmitCaseAttachButton"
|
||||
fill
|
||||
type={'submit'}
|
||||
fullWidth
|
||||
onClick={() => {
|
||||
setIsActionMenuOpen(false);
|
||||
openCasesModalCallback({
|
||||
timeRange,
|
||||
viewType: caseAttachment.viewType,
|
||||
fn: fieldConfig.fn,
|
||||
metricField: fieldConfig.metricField,
|
||||
dataViewId: dataView.id,
|
||||
...(fieldConfig.splitField
|
||||
? {
|
||||
splitField: fieldConfig.splitField,
|
||||
partitions: selectedPartitions,
|
||||
}
|
||||
: {}),
|
||||
});
|
||||
}}
|
||||
disabled={!isDashboardFormValid}
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -383,9 +449,11 @@ const FieldPanel: FC<FieldPanelProps> = ({
|
|||
canCreateCase,
|
||||
canEditDashboards,
|
||||
canUpdateCase,
|
||||
caseAttachment.viewType,
|
||||
caseAttachmentButtonDisabled,
|
||||
dashboardAttachment.applyTimeRange,
|
||||
dashboardAttachment.maxSeriesToPlot,
|
||||
dashboardAttachment.viewType,
|
||||
dataView.id,
|
||||
fieldConfig.fn,
|
||||
fieldConfig.metricField,
|
||||
|
@ -405,6 +473,7 @@ const FieldPanel: FC<FieldPanelProps> = ({
|
|||
const embeddableInput: Partial<EmbeddableChangePointChartInput> = {
|
||||
title: newTitle,
|
||||
description: newDescription,
|
||||
viewType: dashboardAttachment.viewType,
|
||||
dataViewId: dataView.id,
|
||||
metricField: fieldConfig.metricField,
|
||||
splitField: fieldConfig.splitField,
|
||||
|
@ -428,12 +497,13 @@ const FieldPanel: FC<FieldPanelProps> = ({
|
|||
},
|
||||
[
|
||||
embeddable,
|
||||
dashboardAttachment.viewType,
|
||||
dashboardAttachment.applyTimeRange,
|
||||
dashboardAttachment.maxSeriesToPlot,
|
||||
dataView.id,
|
||||
fieldConfig.metricField,
|
||||
fieldConfig.splitField,
|
||||
fieldConfig.fn,
|
||||
dashboardAttachment.applyTimeRange,
|
||||
dashboardAttachment.maxSeriesToPlot,
|
||||
timeRange,
|
||||
selectedChangePoints,
|
||||
panelIndex,
|
||||
|
|
|
@ -6,7 +6,14 @@
|
|||
*/
|
||||
|
||||
import React, { type FC, useState, useCallback, useMemo, useEffect } from 'react';
|
||||
import { EuiComboBox, EuiFormRow } from '@elastic/eui';
|
||||
import {
|
||||
EuiComboBox,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiFormRow,
|
||||
EuiIcon,
|
||||
EuiToolTip,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { type SearchRequest } from '@elastic/elasticsearch/lib/api/types';
|
||||
import { EuiComboBoxOptionOption } from '@elastic/eui/src/components/combo_box/types';
|
||||
|
@ -171,9 +178,26 @@ export const PartitionsSelector: FC<PartitionsSelectorProps> = ({
|
|||
return (
|
||||
<EuiFormRow
|
||||
fullWidth
|
||||
label={i18n.translate('xpack.aiops.changePointDetection.partitionsLabel', {
|
||||
defaultMessage: 'Partitions',
|
||||
})}
|
||||
label={
|
||||
<EuiFlexGroup gutterSize="xs" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
{i18n.translate('xpack.aiops.changePointDetection.partitionsLabel', {
|
||||
defaultMessage: 'Partitions',
|
||||
})}
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiToolTip
|
||||
content={i18n.translate('xpack.aiops.changePointDetection.partitionsDescription', {
|
||||
defaultMessage:
|
||||
'If not supplied, the largest change points across all split field values will be displayed.',
|
||||
})}
|
||||
position="right"
|
||||
>
|
||||
<EuiIcon size="s" type="questionInCircle" />
|
||||
</EuiToolTip>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
}
|
||||
>
|
||||
<EuiComboBox<string>
|
||||
isLoading={isLoading}
|
||||
|
|
|
@ -136,7 +136,7 @@ export function useChangePointResults(
|
|||
/**
|
||||
* null also means the fetching has been complete
|
||||
*/
|
||||
const [progress, setProgress] = useState<number | null>(null);
|
||||
const [progress, setProgress] = useState<number | null>(0);
|
||||
|
||||
const isSingleMetric = !isDefined(fieldConfig.splitField);
|
||||
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* 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, { FC } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { EuiButtonGroup, EuiFormRow, type EuiButtonGroupOptionProps } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { ChangePointDetectionViewType } from '../../../common/constants';
|
||||
|
||||
const viewTypeOptions: EuiButtonGroupOptionProps[] = [
|
||||
{
|
||||
id: `charts`,
|
||||
label: (
|
||||
<FormattedMessage
|
||||
id="xpack.aiops.embeddableChangePointChart.viewTypeSelector.chartsLabel"
|
||||
defaultMessage="Charts"
|
||||
/>
|
||||
),
|
||||
iconType: 'visLine',
|
||||
},
|
||||
{
|
||||
id: `table`,
|
||||
label: (
|
||||
<FormattedMessage
|
||||
id="xpack.aiops.embeddableChangePointChart.viewTypeSelector.tableLabel"
|
||||
defaultMessage="Table"
|
||||
/>
|
||||
),
|
||||
iconType: 'visTable',
|
||||
},
|
||||
];
|
||||
|
||||
export interface ViewTypeSelectorProps {
|
||||
value: ChangePointDetectionViewType;
|
||||
onChange: (update: ChangePointDetectionViewType) => void;
|
||||
}
|
||||
|
||||
export const ViewTypeSelector: FC<ViewTypeSelectorProps> = ({ value, onChange }) => {
|
||||
return (
|
||||
<EuiFormRow
|
||||
fullWidth
|
||||
label={i18n.translate('xpack.aiops.embeddableChangePointChart.viewTypeLabel', {
|
||||
defaultMessage: 'View type',
|
||||
})}
|
||||
>
|
||||
<EuiButtonGroup
|
||||
isFullWidth
|
||||
legend="This is a basic group"
|
||||
options={viewTypeOptions}
|
||||
idSelected={value}
|
||||
onChange={onChange as (id: string) => void}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
);
|
||||
};
|
|
@ -5,7 +5,6 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { FC, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import {
|
||||
EuiButton,
|
||||
EuiButtonEmpty,
|
||||
|
@ -18,28 +17,30 @@ import {
|
|||
EuiModalHeader,
|
||||
EuiModalHeaderTitle,
|
||||
} from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import usePrevious from 'react-use/lib/usePrevious';
|
||||
import { pick } from 'lodash';
|
||||
import { isPopulatedObject } from '@kbn/ml-is-populated-object';
|
||||
import { ES_FIELD_TYPES } from '@kbn/field-types';
|
||||
import { PartitionsSelector } from '../components/change_point_detection/partitions_selector';
|
||||
import { DEFAULT_SERIES } from './const';
|
||||
import { EmbeddableChangePointChartProps } from './embeddable_change_point_chart_component';
|
||||
import { type EmbeddableChangePointChartExplicitInput } from './types';
|
||||
import { MaxSeriesControl } from '../components/change_point_detection/max_series_control';
|
||||
import { SplitFieldSelector } from '../components/change_point_detection/split_field_selector';
|
||||
import { MetricFieldSelector } from '../components/change_point_detection/metric_field_selector';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { isPopulatedObject } from '@kbn/ml-is-populated-object';
|
||||
import { pick } from 'lodash';
|
||||
import React, { FC, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import usePrevious from 'react-use/lib/usePrevious';
|
||||
import {
|
||||
ChangePointDetectionControlsContextProvider,
|
||||
useChangePointDetectionControlsContext,
|
||||
} from '../components/change_point_detection/change_point_detection_context';
|
||||
import { useAiopsAppContext } from '../hooks/use_aiops_app_context';
|
||||
import { EmbeddableChangePointChartInput } from './embeddable_change_point_chart';
|
||||
import { FunctionPicker } from '../components/change_point_detection/function_picker';
|
||||
import { DataSourceContextProvider } from '../hooks/use_data_source';
|
||||
import { DEFAULT_AGG_FUNCTION } from '../components/change_point_detection/constants';
|
||||
import { FunctionPicker } from '../components/change_point_detection/function_picker';
|
||||
import { MaxSeriesControl } from '../components/change_point_detection/max_series_control';
|
||||
import { MetricFieldSelector } from '../components/change_point_detection/metric_field_selector';
|
||||
import { PartitionsSelector } from '../components/change_point_detection/partitions_selector';
|
||||
import { SplitFieldSelector } from '../components/change_point_detection/split_field_selector';
|
||||
import { ViewTypeSelector } from '../components/change_point_detection/view_type_selector';
|
||||
import { useAiopsAppContext } from '../hooks/use_aiops_app_context';
|
||||
import { DataSourceContextProvider } from '../hooks/use_data_source';
|
||||
import { DEFAULT_SERIES } from './const';
|
||||
import { EmbeddableChangePointChartInput } from './embeddable_change_point_chart';
|
||||
import { EmbeddableChangePointChartProps } from './embeddable_change_point_chart_component';
|
||||
import { type EmbeddableChangePointChartExplicitInput } from './types';
|
||||
|
||||
export interface AnomalyChartsInitializerProps {
|
||||
initialInput?: Partial<EmbeddableChangePointChartInput>;
|
||||
|
@ -59,6 +60,7 @@ export const ChangePointChartInitializer: FC<AnomalyChartsInitializerProps> = ({
|
|||
} = useAiopsAppContext();
|
||||
|
||||
const [dataViewId, setDataViewId] = useState(initialInput?.dataViewId ?? '');
|
||||
const [viewType, setViewType] = useState(initialInput?.viewType ?? 'charts');
|
||||
|
||||
const [formInput, setFormInput] = useState<FormControlsProps>(
|
||||
pick(initialInput ?? {}, [
|
||||
|
@ -75,6 +77,7 @@ export const ChangePointChartInitializer: FC<AnomalyChartsInitializerProps> = ({
|
|||
const updatedProps = useMemo(() => {
|
||||
return {
|
||||
...formInput,
|
||||
viewType,
|
||||
title: isPopulatedObject(formInput)
|
||||
? i18n.translate('xpack.aiops.changePointDetection.attachmentTitle', {
|
||||
defaultMessage: 'Change point: {function}({metric}){splitBy}',
|
||||
|
@ -92,7 +95,7 @@ export const ChangePointChartInitializer: FC<AnomalyChartsInitializerProps> = ({
|
|||
: '',
|
||||
dataViewId,
|
||||
};
|
||||
}, [formInput, dataViewId]);
|
||||
}, [formInput, dataViewId, viewType]);
|
||||
|
||||
return (
|
||||
<EuiModal onClose={onCancel} data-test-subj={'aiopsChangePointChartEmbeddableInitializer'}>
|
||||
|
@ -100,13 +103,14 @@ export const ChangePointChartInitializer: FC<AnomalyChartsInitializerProps> = ({
|
|||
<EuiModalHeaderTitle>
|
||||
<FormattedMessage
|
||||
id="xpack.aiops.embeddableChangePointChart.modalTitle"
|
||||
defaultMessage="Change point charts configuration"
|
||||
defaultMessage="Change point detection configuration"
|
||||
/>
|
||||
</EuiModalHeaderTitle>
|
||||
</EuiModalHeader>
|
||||
|
||||
<EuiModalBody>
|
||||
<EuiForm>
|
||||
<ViewTypeSelector value={viewType} onChange={setViewType} />
|
||||
<EuiFormRow
|
||||
fullWidth
|
||||
label={i18n.translate('xpack.aiops.embeddableChangePointChart.dataViewLabel', {
|
||||
|
@ -129,7 +133,6 @@ export const ChangePointChartInitializer: FC<AnomalyChartsInitializerProps> = ({
|
|||
}}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
|
||||
<DataSourceContextProvider dataViewId={dataViewId}>
|
||||
<EuiHorizontalRule margin={'s'} />
|
||||
<ChangePointDetectionControlsContextProvider>
|
||||
|
|
|
@ -23,8 +23,9 @@ import { LensPublicStart } from '@kbn/lens-plugin/public';
|
|||
import { Subject } from 'rxjs';
|
||||
import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public';
|
||||
import type { DataView } from '@kbn/data-views-plugin/common';
|
||||
import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public';
|
||||
import { EmbeddableInputTracker } from './embeddable_chart_component_wrapper';
|
||||
import { EMBEDDABLE_CHANGE_POINT_CHART_TYPE, EMBEDDABLE_ORIGIN } from '../../common/constants';
|
||||
import { EMBEDDABLE_ORIGIN, EmbeddableChangePointType } from '../../common/constants';
|
||||
import { AiopsAppContext, type AiopsAppDependencies } from '../hooks/use_aiops_app_context';
|
||||
|
||||
import { EmbeddableChangePointChartProps } from './embeddable_change_point_chart_component';
|
||||
|
@ -42,6 +43,7 @@ export interface EmbeddableChangePointChartDeps {
|
|||
i18n: CoreStart['i18n'];
|
||||
lens: LensPublicStart;
|
||||
usageCollection: UsageCollectionSetup;
|
||||
fieldFormats: FieldFormatsStart;
|
||||
}
|
||||
|
||||
export type IEmbeddableChangePointChart = typeof EmbeddableChangePointChart;
|
||||
|
@ -50,8 +52,6 @@ export class EmbeddableChangePointChart extends AbstractEmbeddable<
|
|||
EmbeddableChangePointChartInput,
|
||||
EmbeddableChangePointChartOutput
|
||||
> {
|
||||
public readonly type = EMBEDDABLE_CHANGE_POINT_CHART_TYPE;
|
||||
|
||||
private reload$ = new Subject<number>();
|
||||
|
||||
public reload(): void {
|
||||
|
@ -64,6 +64,7 @@ export class EmbeddableChangePointChart extends AbstractEmbeddable<
|
|||
deferEmbeddableLoad = true;
|
||||
|
||||
constructor(
|
||||
public readonly type: EmbeddableChangePointType,
|
||||
private readonly deps: EmbeddableChangePointChartDeps,
|
||||
initialInput: EmbeddableChangePointChartInput,
|
||||
parent?: IContainer
|
||||
|
@ -91,9 +92,9 @@ export class EmbeddableChangePointChart extends AbstractEmbeddable<
|
|||
return true;
|
||||
}
|
||||
|
||||
public onLoading() {
|
||||
public onLoading(isLoading: boolean) {
|
||||
this.renderComplete.dispatchInProgress();
|
||||
this.updateOutput({ loading: true, error: undefined });
|
||||
this.updateOutput({ loading: isLoading, error: undefined });
|
||||
}
|
||||
|
||||
public onError(error: Error) {
|
||||
|
@ -103,7 +104,7 @@ export class EmbeddableChangePointChart extends AbstractEmbeddable<
|
|||
|
||||
public onRenderComplete() {
|
||||
this.renderComplete.dispatchComplete();
|
||||
this.updateOutput({ loading: false, error: undefined });
|
||||
this.updateOutput({ loading: false, rendered: true, error: undefined });
|
||||
}
|
||||
|
||||
render(el: HTMLElement): void {
|
||||
|
@ -127,8 +128,8 @@ export class EmbeddableChangePointChart extends AbstractEmbeddable<
|
|||
const input$ = this.getInput$();
|
||||
|
||||
const aiopsAppContextValue = {
|
||||
embeddingOrigin: this.parent?.type ?? input.embeddingOrigin ?? EMBEDDABLE_ORIGIN,
|
||||
...this.deps,
|
||||
embeddingOrigin: this.parent?.type ?? EMBEDDABLE_ORIGIN,
|
||||
} as unknown as AiopsAppDependencies;
|
||||
|
||||
ReactDOM.render(
|
||||
|
|
|
@ -15,12 +15,16 @@ import {
|
|||
useEmbeddableFactory,
|
||||
} from '@kbn/embeddable-plugin/public';
|
||||
import { EuiLoadingChart } from '@elastic/eui';
|
||||
import { EMBEDDABLE_CHANGE_POINT_CHART_TYPE } from '../../common/constants';
|
||||
import {
|
||||
type ChangePointDetectionViewType,
|
||||
type EmbeddableChangePointType,
|
||||
} from '../../common/constants';
|
||||
import type { AiopsPluginStartDeps } from '../types';
|
||||
import type { EmbeddableChangePointChartInput } from './embeddable_change_point_chart';
|
||||
import type { ChangePointAnnotation } from '../components/change_point_detection/change_point_detection_context';
|
||||
|
||||
export interface EmbeddableChangePointChartProps {
|
||||
viewType?: ChangePointDetectionViewType;
|
||||
dataViewId: string;
|
||||
timeRange: TimeRange;
|
||||
fn: 'avg' | 'sum' | 'min' | 'max' | string;
|
||||
|
@ -40,12 +44,16 @@ export interface EmbeddableChangePointChartProps {
|
|||
* Last reload request time, can be used for manual reload
|
||||
*/
|
||||
lastReloadRequestTime?: number;
|
||||
/** Origin of the embeddable instance */
|
||||
embeddingOrigin?: string;
|
||||
}
|
||||
export function getEmbeddableChangePointChart(core: CoreStart, plugins: AiopsPluginStartDeps) {
|
||||
export function getEmbeddableChangePointChart(
|
||||
visType: EmbeddableChangePointType,
|
||||
core: CoreStart,
|
||||
plugins: AiopsPluginStartDeps
|
||||
) {
|
||||
const { embeddable: embeddableStart } = plugins;
|
||||
const factory = embeddableStart.getEmbeddableFactory<EmbeddableChangePointChartInput>(
|
||||
EMBEDDABLE_CHANGE_POINT_CHART_TYPE
|
||||
)!;
|
||||
const factory = embeddableStart.getEmbeddableFactory<EmbeddableChangePointChartInput>(visType)!;
|
||||
|
||||
return (props: EmbeddableChangePointChartProps) => {
|
||||
const input = { ...props };
|
||||
|
|
|
@ -13,7 +13,10 @@ import {
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { type DataPublicPluginStart } from '@kbn/data-plugin/public';
|
||||
import { StartServicesAccessor } from '@kbn/core-lifecycle-browser';
|
||||
import { EMBEDDABLE_CHANGE_POINT_CHART_TYPE } from '../../common/constants';
|
||||
import {
|
||||
EMBEDDABLE_CHANGE_POINT_CHART_TYPE,
|
||||
EmbeddableChangePointType,
|
||||
} from '../../common/constants';
|
||||
import type { AiopsPluginStart, AiopsPluginStartDeps } from '../types';
|
||||
import {
|
||||
EmbeddableChangePointChart,
|
||||
|
@ -27,8 +30,6 @@ export interface EmbeddableChangePointChartStartServices {
|
|||
export type EmbeddableChangePointChartType = typeof EMBEDDABLE_CHANGE_POINT_CHART_TYPE;
|
||||
|
||||
export class EmbeddableChangePointChartFactory implements EmbeddableFactoryDefinition {
|
||||
public readonly type = EMBEDDABLE_CHANGE_POINT_CHART_TYPE;
|
||||
|
||||
public readonly grouping = [
|
||||
{
|
||||
id: 'ml',
|
||||
|
@ -41,6 +42,8 @@ export class EmbeddableChangePointChartFactory implements EmbeddableFactoryDefin
|
|||
];
|
||||
|
||||
constructor(
|
||||
public readonly type: EmbeddableChangePointType,
|
||||
private readonly name: string,
|
||||
private readonly getStartServices: StartServicesAccessor<AiopsPluginStartDeps, AiopsPluginStart>
|
||||
) {}
|
||||
|
||||
|
@ -49,9 +52,7 @@ export class EmbeddableChangePointChartFactory implements EmbeddableFactoryDefin
|
|||
};
|
||||
|
||||
getDisplayName() {
|
||||
return i18n.translate('xpack.aiops.embeddableChangePointChartDisplayName', {
|
||||
defaultMessage: 'Change point detection',
|
||||
});
|
||||
return this.name;
|
||||
}
|
||||
|
||||
canCreateNew() {
|
||||
|
@ -73,10 +74,11 @@ export class EmbeddableChangePointChartFactory implements EmbeddableFactoryDefin
|
|||
try {
|
||||
const [
|
||||
{ i18n: i18nService, theme, http, uiSettings, notifications },
|
||||
{ lens, data, usageCollection },
|
||||
{ lens, data, usageCollection, fieldFormats },
|
||||
] = await this.getStartServices();
|
||||
|
||||
return new EmbeddableChangePointChart(
|
||||
this.type,
|
||||
{
|
||||
theme,
|
||||
http,
|
||||
|
@ -86,6 +88,7 @@ export class EmbeddableChangePointChartFactory implements EmbeddableFactoryDefin
|
|||
notifications,
|
||||
lens,
|
||||
usageCollection,
|
||||
fieldFormats,
|
||||
},
|
||||
input,
|
||||
parent
|
||||
|
|
|
@ -5,12 +5,14 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { BehaviorSubject, type Observable, combineLatest } from 'rxjs';
|
||||
import { map, distinctUntilChanged } from 'rxjs/operators';
|
||||
import { BehaviorSubject, combineLatest, type Observable } from 'rxjs';
|
||||
import { distinctUntilChanged, map } from 'rxjs/operators';
|
||||
import React, { FC, useEffect, useMemo, useState } from 'react';
|
||||
import { useTimefilter } from '@kbn/ml-date-picker';
|
||||
import { css } from '@emotion/react';
|
||||
import useObservable from 'react-use/lib/useObservable';
|
||||
import { ChangePointsTable } from '../components/change_point_detection/change_points_table';
|
||||
import { CHANGE_POINT_DETECTION_VIEW_TYPE } from '../../common/constants';
|
||||
import { ReloadContextProvider } from '../hooks/use_reload';
|
||||
import {
|
||||
type ChangePointAnnotation,
|
||||
|
@ -42,7 +44,7 @@ export interface EmbeddableInputTrackerProps {
|
|||
reload$: Observable<number>;
|
||||
onOutputChange: (output: Partial<EmbeddableChangePointChartOutput>) => void;
|
||||
onRenderComplete: () => void;
|
||||
onLoading: () => void;
|
||||
onLoading: (isLoading: boolean) => void;
|
||||
onError: (error: Error) => void;
|
||||
}
|
||||
|
||||
|
@ -86,6 +88,7 @@ export const EmbeddableInputTracker: FC<EmbeddableInputTrackerProps> = ({
|
|||
<ChangePointDetectionControlsContextProvider>
|
||||
<FilterQueryContextProvider timeRange={input.timeRange}>
|
||||
<ChartGridEmbeddableWrapper
|
||||
viewType={input.viewType}
|
||||
timeRange={input.timeRange}
|
||||
fn={input.fn}
|
||||
metricField={input.metricField}
|
||||
|
@ -120,10 +123,11 @@ export const EmbeddableInputTracker: FC<EmbeddableInputTrackerProps> = ({
|
|||
export const ChartGridEmbeddableWrapper: FC<
|
||||
EmbeddableChangePointChartProps & {
|
||||
onRenderComplete: () => void;
|
||||
onLoading: () => void;
|
||||
onLoading: (isLoading: boolean) => void;
|
||||
onError: (error: Error) => void;
|
||||
}
|
||||
> = ({
|
||||
viewType = CHANGE_POINT_DETECTION_VIEW_TYPE.CHARTS,
|
||||
fn,
|
||||
metricField,
|
||||
maxSeriesToPlot,
|
||||
|
@ -202,9 +206,7 @@ export const ChartGridEmbeddableWrapper: FC<
|
|||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (isLoading) {
|
||||
onLoading();
|
||||
}
|
||||
onLoading(isLoading);
|
||||
}, [onLoading, isLoading]);
|
||||
|
||||
const changePoints = useMemo<ChangePointAnnotation[]>(() => {
|
||||
|
@ -235,16 +237,27 @@ export const ChartGridEmbeddableWrapper: FC<
|
|||
`}
|
||||
>
|
||||
{changePoints.length > 0 ? (
|
||||
<ChartsGrid
|
||||
changePoints={changePoints.map((r) => ({ ...r, ...fieldConfig }))}
|
||||
interval={requestParams.interval}
|
||||
onRenderComplete={onRenderComplete}
|
||||
/>
|
||||
) : emptyState ? (
|
||||
emptyState
|
||||
) : (
|
||||
<NoChangePointsWarning onRenderComplete={onRenderComplete} />
|
||||
)}
|
||||
viewType === CHANGE_POINT_DETECTION_VIEW_TYPE.CHARTS ? (
|
||||
<ChartsGrid
|
||||
changePoints={changePoints.map((r) => ({ ...r, ...fieldConfig }))}
|
||||
interval={requestParams.interval}
|
||||
onRenderComplete={onRenderComplete}
|
||||
/>
|
||||
) : viewType === CHANGE_POINT_DETECTION_VIEW_TYPE.TABLE ? (
|
||||
<ChangePointsTable
|
||||
isLoading={false}
|
||||
annotations={changePoints}
|
||||
fieldConfig={fieldConfig}
|
||||
onRenderComplete={onRenderComplete}
|
||||
/>
|
||||
) : null
|
||||
) : !isLoading ? (
|
||||
emptyState ? (
|
||||
emptyState
|
||||
) : (
|
||||
<NoChangePointsWarning onRenderComplete={onRenderComplete} />
|
||||
)
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
import type { CoreSetup } from '@kbn/core-lifecycle-browser';
|
||||
import type { EmbeddableSetup } from '@kbn/embeddable-plugin/public';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EMBEDDABLE_CHANGE_POINT_CHART_TYPE } from '../../common/constants';
|
||||
import type { AiopsPluginStart, AiopsPluginStartDeps } from '../types';
|
||||
import { EmbeddableChangePointChartFactory } from './embeddable_change_point_chart_factory';
|
||||
|
||||
|
@ -14,6 +16,12 @@ export const registerEmbeddable = (
|
|||
core: CoreSetup<AiopsPluginStartDeps, AiopsPluginStart>,
|
||||
embeddable: EmbeddableSetup
|
||||
) => {
|
||||
const factory = new EmbeddableChangePointChartFactory(core.getStartServices);
|
||||
embeddable.registerEmbeddableFactory(factory.type, factory);
|
||||
const changePointChartFactory = new EmbeddableChangePointChartFactory(
|
||||
EMBEDDABLE_CHANGE_POINT_CHART_TYPE,
|
||||
i18n.translate('xpack.aiops.embeddableChangePointChartDisplayName', {
|
||||
defaultMessage: 'Change point detection',
|
||||
}),
|
||||
core.getStartServices
|
||||
);
|
||||
embeddable.registerEmbeddableFactory(changePointChartFactory.type, changePointChartFactory);
|
||||
};
|
||||
|
|
|
@ -5,7 +5,9 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { FC } from 'react';
|
||||
import { IEmbeddable } from '@kbn/embeddable-plugin/public';
|
||||
import type { SelectedChangePoint } from '../components/change_point_detection/change_point_detection_context';
|
||||
import {
|
||||
EmbeddableChangePointChartInput,
|
||||
EmbeddableChangePointChartOutput,
|
||||
|
@ -19,3 +21,9 @@ export type EmbeddableChangePointChartExplicitInput = {
|
|||
export interface EditChangePointChartsPanelContext {
|
||||
embeddable: IEmbeddable<EmbeddableChangePointChartInput, EmbeddableChangePointChartOutput>;
|
||||
}
|
||||
|
||||
export type ViewComponent = FC<{
|
||||
changePoints: SelectedChangePoint[];
|
||||
interval: string;
|
||||
onRenderComplete?: () => void;
|
||||
}>;
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import type { CoreStart, Plugin } from '@kbn/core/public';
|
||||
import { type CoreSetup } from '@kbn/core/public';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { EMBEDDABLE_CHANGE_POINT_CHART_TYPE } from '../common/constants';
|
||||
import type {
|
||||
AiopsPluginSetup,
|
||||
AiopsPluginSetupDeps,
|
||||
|
@ -58,7 +59,11 @@ export class AiopsPlugin
|
|||
|
||||
public start(core: CoreStart, plugins: AiopsPluginStartDeps): AiopsPluginStart {
|
||||
return {
|
||||
EmbeddableChangePointChart: getEmbeddableChangePointChart(core, plugins),
|
||||
EmbeddableChangePointChart: getEmbeddableChangePointChart(
|
||||
EMBEDDABLE_CHANGE_POINT_CHART_TYPE,
|
||||
core,
|
||||
plugins
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -11,13 +11,12 @@ import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
|
|||
import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public';
|
||||
import type { IStorageWrapper } from '@kbn/kibana-utils-plugin/public';
|
||||
import type { LensPublicStart } from '@kbn/lens-plugin/public';
|
||||
import type { LicensingPluginStart } from '@kbn/licensing-plugin/public';
|
||||
import type { LicensingPluginSetup, LicensingPluginStart } from '@kbn/licensing-plugin/public';
|
||||
import type { SharePluginStart } from '@kbn/share-plugin/public';
|
||||
import type { UiActionsStart, UiActionsSetup } from '@kbn/ui-actions-plugin/public';
|
||||
import type { UiActionsSetup, UiActionsStart } from '@kbn/ui-actions-plugin/public';
|
||||
import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public';
|
||||
import type { EmbeddableSetup, EmbeddableStart } from '@kbn/embeddable-plugin/public';
|
||||
import type { CasesUiSetup } from '@kbn/cases-plugin/public';
|
||||
import type { LicensingPluginSetup } from '@kbn/licensing-plugin/public';
|
||||
import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public';
|
||||
import type { EmbeddableChangePointChartInput } from './embeddable/embeddable_change_point_chart';
|
||||
|
||||
|
|
|
@ -60,7 +60,7 @@ export class AnomalyChartsEmbeddable extends AnomalyDetectionEmbeddable<
|
|||
|
||||
public onRenderComplete() {
|
||||
this.renderComplete.dispatchComplete();
|
||||
this.updateOutput({ loading: false, error: undefined });
|
||||
this.updateOutput({ loading: false, rendered: true, error: undefined });
|
||||
}
|
||||
|
||||
public render(node: HTMLElement) {
|
||||
|
|
|
@ -65,7 +65,7 @@ export class AnomalySwimlaneEmbeddable extends AnomalyDetectionEmbeddable<
|
|||
|
||||
public onRenderComplete() {
|
||||
this.renderComplete.dispatchComplete();
|
||||
this.updateOutput({ loading: false, error: undefined });
|
||||
this.updateOutput({ loading: false, rendered: true, error: undefined });
|
||||
}
|
||||
|
||||
public render(node: HTMLElement) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue