mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
* [ML] Persisted URL state for Anomalies table * [ML] adjust cell selection according to the time range
This commit is contained in:
parent
3fb9da3554
commit
b02858b20d
5 changed files with 111 additions and 20 deletions
|
@ -25,8 +25,9 @@ import { mlTableService } from '../../services/table_service';
|
|||
import { RuleEditorFlyout } from '../rule_editor';
|
||||
import { ml } from '../../services/ml_api_service';
|
||||
import { INFLUENCERS_LIMIT, ANOMALIES_TABLE_TABS, MAX_CHARS } from './anomalies_table_constants';
|
||||
import { usePageUrlState } from '../../util/url_state';
|
||||
|
||||
class AnomaliesTable extends Component {
|
||||
export class AnomaliesTableInternal extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
|
@ -145,8 +146,20 @@ class AnomaliesTable extends Component {
|
|||
});
|
||||
};
|
||||
|
||||
onTableChange = ({ page, sort }) => {
|
||||
const { tableState, updateTableState } = this.props;
|
||||
const result = {
|
||||
pageIndex: page && page.index !== undefined ? page.index : tableState.pageIndex,
|
||||
pageSize: page && page.size !== undefined ? page.size : tableState.pageSize,
|
||||
sortField: sort && sort.field !== undefined ? sort.field : tableState.sortField,
|
||||
sortDirection:
|
||||
sort && sort.direction !== undefined ? sort.direction : tableState.sortDirection,
|
||||
};
|
||||
updateTableState(result);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { bounds, tableData, filter, influencerFilter } = this.props;
|
||||
const { bounds, tableData, filter, influencerFilter, tableState } = this.props;
|
||||
|
||||
if (
|
||||
tableData === undefined ||
|
||||
|
@ -186,8 +199,8 @@ class AnomaliesTable extends Component {
|
|||
|
||||
const sorting = {
|
||||
sort: {
|
||||
field: 'severity',
|
||||
direction: 'desc',
|
||||
field: tableState.sortField,
|
||||
direction: tableState.sortDirection,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -199,8 +212,15 @@ class AnomaliesTable extends Component {
|
|||
};
|
||||
};
|
||||
|
||||
const pagination = {
|
||||
pageIndex: tableState.pageIndex,
|
||||
pageSize: tableState.pageSize,
|
||||
totalItemCount: tableData.anomalies.length,
|
||||
pageSizeOptions: [10, 25, 100],
|
||||
};
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<>
|
||||
<RuleEditorFlyout
|
||||
setShowFunction={this.setShowRuleEditorFlyoutFunction}
|
||||
unsetShowFunction={this.unsetShowRuleEditorFlyoutFunction}
|
||||
|
@ -209,26 +229,46 @@ class AnomaliesTable extends Component {
|
|||
className="ml-anomalies-table eui-textOverflowWrap"
|
||||
items={tableData.anomalies}
|
||||
columns={columns}
|
||||
pagination={{
|
||||
pageSizeOptions: [10, 25, 100],
|
||||
initialPageSize: 25,
|
||||
}}
|
||||
pagination={pagination}
|
||||
sorting={sorting}
|
||||
itemId="rowId"
|
||||
itemIdToExpandedRowMap={this.state.itemIdToExpandedRowMap}
|
||||
compressed={true}
|
||||
rowProps={getRowProps}
|
||||
data-test-subj="mlAnomaliesTable"
|
||||
onTableChange={this.onTableChange}
|
||||
/>
|
||||
</React.Fragment>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
AnomaliesTable.propTypes = {
|
||||
|
||||
export const getDefaultAnomaliesTableState = () => ({
|
||||
pageIndex: 0,
|
||||
pageSize: 25,
|
||||
sortField: 'severity',
|
||||
sortDirection: 'desc',
|
||||
});
|
||||
|
||||
export const AnomaliesTable = (props) => {
|
||||
const [tableState, updateTableState] = usePageUrlState(
|
||||
'mlAnomaliesTable',
|
||||
getDefaultAnomaliesTableState()
|
||||
);
|
||||
return (
|
||||
<AnomaliesTableInternal
|
||||
{...props}
|
||||
tableState={tableState}
|
||||
updateTableState={updateTableState}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
AnomaliesTableInternal.propTypes = {
|
||||
bounds: PropTypes.object.isRequired,
|
||||
tableData: PropTypes.object,
|
||||
filter: PropTypes.func,
|
||||
influencerFilter: PropTypes.func,
|
||||
tableState: PropTypes.object.isRequired,
|
||||
updateTableState: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export { AnomaliesTable };
|
||||
|
|
|
@ -4,14 +4,17 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
import { Duration } from 'moment';
|
||||
import { SWIMLANE_TYPE } from '../explorer_constants';
|
||||
import { AppStateSelectedCells } from '../explorer_utils';
|
||||
import { AppStateSelectedCells, TimeRangeBounds } from '../explorer_utils';
|
||||
import { ExplorerAppState } from '../../../../common/types/ml_url_generator';
|
||||
|
||||
export const useSelectedCells = (
|
||||
appState: ExplorerAppState,
|
||||
setAppState: (update: Partial<ExplorerAppState>) => void
|
||||
setAppState: (update: Partial<ExplorerAppState>) => void,
|
||||
timeBounds: TimeRangeBounds | undefined,
|
||||
bucketInterval: Duration | undefined
|
||||
): [AppStateSelectedCells | undefined, (swimlaneSelectedCells: AppStateSelectedCells) => void] => {
|
||||
// keep swimlane selection, restore selectedCells from AppState
|
||||
const selectedCells = useMemo(() => {
|
||||
|
@ -28,7 +31,7 @@ export const useSelectedCells = (
|
|||
}, [JSON.stringify(appState?.mlExplorerSwimlane)]);
|
||||
|
||||
const setSelectedCells = useCallback(
|
||||
(swimlaneSelectedCells: AppStateSelectedCells) => {
|
||||
(swimlaneSelectedCells?: AppStateSelectedCells) => {
|
||||
const mlExplorerSwimlane = {
|
||||
...appState.mlExplorerSwimlane,
|
||||
} as ExplorerAppState['mlExplorerSwimlane'];
|
||||
|
@ -65,5 +68,47 @@ export const useSelectedCells = (
|
|||
[appState?.mlExplorerSwimlane, selectedCells, setAppState]
|
||||
);
|
||||
|
||||
/**
|
||||
* Adjust cell selection with respect to the time boundaries.
|
||||
* Reset it entirely when it out of range.
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (
|
||||
timeBounds === undefined ||
|
||||
selectedCells?.times === undefined ||
|
||||
bucketInterval === undefined
|
||||
)
|
||||
return;
|
||||
|
||||
let [selectedFrom, selectedTo] = selectedCells.times;
|
||||
|
||||
const rangeFrom = timeBounds.min!.unix();
|
||||
/**
|
||||
* Because each cell on the swim lane represent the fixed bucket interval,
|
||||
* the selection range could be outside of the time boundaries with
|
||||
* correction within the bucket interval.
|
||||
*/
|
||||
const rangeTo = timeBounds.max!.unix() + bucketInterval.asSeconds();
|
||||
|
||||
selectedFrom = Math.max(selectedFrom, rangeFrom);
|
||||
|
||||
selectedTo = Math.min(selectedTo, rangeTo);
|
||||
|
||||
const isSelectionOutOfRange = rangeFrom > selectedTo || rangeTo < selectedFrom;
|
||||
|
||||
if (isSelectionOutOfRange) {
|
||||
// reset selection
|
||||
setSelectedCells();
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedFrom !== rangeFrom || selectedTo !== rangeTo) {
|
||||
setSelectedCells({
|
||||
...selectedCells,
|
||||
times: [selectedFrom, selectedTo],
|
||||
});
|
||||
}
|
||||
}, [timeBounds, selectedCells, bucketInterval]);
|
||||
|
||||
return [selectedCells, setSelectedCells];
|
||||
};
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { Duration } from 'moment';
|
||||
import { ML_RESULTS_INDEX_PATTERN } from '../../../../../common/constants/index_patterns';
|
||||
import { Dictionary } from '../../../../../common/types/common';
|
||||
|
||||
|
@ -43,7 +44,7 @@ export interface ExplorerState {
|
|||
queryString: string;
|
||||
selectedCells: AppStateSelectedCells | undefined;
|
||||
selectedJobs: ExplorerJob[] | null;
|
||||
swimlaneBucketInterval: any;
|
||||
swimlaneBucketInterval: Duration | undefined;
|
||||
swimlaneContainerWidth: number;
|
||||
tableData: AnomaliesTableData;
|
||||
tableQueryString: string;
|
||||
|
|
|
@ -205,7 +205,12 @@ const ExplorerUrlStateManager: FC<ExplorerUrlStateManagerProps> = ({ jobsWithTim
|
|||
const [tableInterval] = useTableInterval();
|
||||
const [tableSeverity] = useTableSeverity();
|
||||
|
||||
const [selectedCells, setSelectedCells] = useSelectedCells(explorerUrlState, setExplorerUrlState);
|
||||
const [selectedCells, setSelectedCells] = useSelectedCells(
|
||||
explorerUrlState,
|
||||
setExplorerUrlState,
|
||||
explorerState?.bounds,
|
||||
explorerState?.swimlaneBucketInterval
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
explorerService.setSelectedCells(selectedCells);
|
||||
|
|
|
@ -162,7 +162,7 @@ export const useUrlState = (accessor: Accessor) => {
|
|||
return [urlState, setUrlState];
|
||||
};
|
||||
|
||||
type AppStateKey = 'mlSelectSeverity' | 'mlSelectInterval' | MlPages;
|
||||
type AppStateKey = 'mlSelectSeverity' | 'mlSelectInterval' | 'mlAnomaliesTable' | MlPages;
|
||||
|
||||
/**
|
||||
* Hook for managing the URL state of the page.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue