mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
* [RAC] Store Alerts View table state in localStorage * Use Redux store subscriber instead of callback * Fix typecheck * Fix bad merge * Add tests * Remove persisting selected rows * Fix bad merge * onTGridStateChange => onStateChange * Remove non-null assertion * Put non-null assertion back because typescript hates me, personally * Fix checks Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
42decf18dc
commit
6c987e5d8b
7 changed files with 133 additions and 7 deletions
|
@ -34,12 +34,18 @@ import {
|
|||
import styled from 'styled-components';
|
||||
import React, { Suspense, useMemo, useState, useCallback, useEffect } from 'react';
|
||||
import usePrevious from 'react-use/lib/usePrevious';
|
||||
import { get } from 'lodash';
|
||||
import { get, pick } from 'lodash';
|
||||
import {
|
||||
getAlertsPermissions,
|
||||
useGetUserAlertsPermissions,
|
||||
} from '../../hooks/use_alert_permission';
|
||||
import type { TimelinesUIStart, TGridType, SortDirection } from '../../../../timelines/public';
|
||||
import type {
|
||||
TimelinesUIStart,
|
||||
TGridType,
|
||||
TGridState,
|
||||
TGridModel,
|
||||
SortDirection,
|
||||
} from '../../../../timelines/public';
|
||||
import { useStatusBulkActionItems } from '../../../../timelines/public';
|
||||
import type { TopAlert } from './';
|
||||
import { useKibana } from '../../../../../../src/plugins/kibana_react/public';
|
||||
|
@ -60,6 +66,8 @@ import { parseAlert } from './parse_alert';
|
|||
import { CoreStart } from '../../../../../../src/core/public';
|
||||
import { translations, paths } from '../../config';
|
||||
|
||||
const ALERT_TABLE_STATE_STORAGE_KEY = 'xpack.observability.alert.tableState';
|
||||
|
||||
interface AlertsTableTGridProps {
|
||||
indexNames: string[];
|
||||
rangeFrom: string;
|
||||
|
@ -330,6 +338,9 @@ export function AlertsTableTGrid(props: AlertsTableTGridProps) {
|
|||
} = useKibana<CoreStart & { timelines: TimelinesUIStart }>().services;
|
||||
|
||||
const [flyoutAlert, setFlyoutAlert] = useState<TopAlert | undefined>(undefined);
|
||||
const [tGridState, setTGridState] = useState<Partial<TGridModel> | null>(
|
||||
JSON.parse(localStorage.getItem(ALERT_TABLE_STATE_STORAGE_KEY) ?? 'null')
|
||||
);
|
||||
|
||||
const casePermissions = useGetUserCasesPermissions();
|
||||
|
||||
|
@ -351,6 +362,20 @@ export function AlertsTableTGrid(props: AlertsTableTGridProps) {
|
|||
}
|
||||
}, [workflowStatus, prevWorkflowStatus]);
|
||||
|
||||
useEffect(() => {
|
||||
if (tGridState) {
|
||||
const newState = JSON.stringify({
|
||||
...tGridState,
|
||||
columns: tGridState.columns?.map((c) =>
|
||||
pick(c, ['columnHeaderType', 'displayAsText', 'id', 'initialWidth', 'linkField'])
|
||||
),
|
||||
});
|
||||
if (newState !== localStorage.getItem(ALERT_TABLE_STATE_STORAGE_KEY)) {
|
||||
localStorage.setItem(ALERT_TABLE_STATE_STORAGE_KEY, newState);
|
||||
}
|
||||
}
|
||||
}, [tGridState]);
|
||||
|
||||
const setEventsDeleted = useCallback<ObservabilityActionsProps['setEventsDeleted']>((action) => {
|
||||
if (action.isDeleted) {
|
||||
setDeletedEventIds((ids) => [...ids, ...action.eventIds]);
|
||||
|
@ -379,6 +404,20 @@ export function AlertsTableTGrid(props: AlertsTableTGridProps) {
|
|||
];
|
||||
}, [workflowStatus, setEventsDeleted]);
|
||||
|
||||
const onStateChange = useCallback(
|
||||
(state: TGridState) => {
|
||||
const pickedState = pick(state.timelineById['standalone-t-grid'], [
|
||||
'columns',
|
||||
'sort',
|
||||
'selectedEventIds',
|
||||
]);
|
||||
if (JSON.stringify(pickedState) !== JSON.stringify(tGridState)) {
|
||||
setTGridState(pickedState);
|
||||
}
|
||||
},
|
||||
[tGridState]
|
||||
);
|
||||
|
||||
const tGridProps = useMemo(() => {
|
||||
const type: TGridType = 'standalone';
|
||||
const sortDirection: SortDirection = 'desc';
|
||||
|
@ -387,7 +426,7 @@ export function AlertsTableTGrid(props: AlertsTableTGridProps) {
|
|||
casesOwner: observabilityFeatureId,
|
||||
casePermissions,
|
||||
type,
|
||||
columns,
|
||||
columns: tGridState?.columns ?? columns,
|
||||
deletedEventIds,
|
||||
defaultCellActions: getDefaultCellActions({ addToQuery }),
|
||||
disabledCellActions: FIELDS_WITHOUT_CELL_ACTIONS,
|
||||
|
@ -398,6 +437,7 @@ export function AlertsTableTGrid(props: AlertsTableTGridProps) {
|
|||
itemsPerPageOptions: [10, 25, 50],
|
||||
loadingText: translations.alertsTable.loadingTextLabel,
|
||||
footerText: translations.alertsTable.footerTextLabel,
|
||||
onStateChange,
|
||||
query: {
|
||||
query: `${ALERT_WORKFLOW_STATUS}: ${workflowStatus}${kuery !== '' ? ` and ${kuery}` : ''}`,
|
||||
language: 'kuery',
|
||||
|
@ -408,7 +448,7 @@ export function AlertsTableTGrid(props: AlertsTableTGridProps) {
|
|||
runtimeMappings: {},
|
||||
start: rangeFrom,
|
||||
setRefetch,
|
||||
sort: [
|
||||
sort: tGridState?.sort ?? [
|
||||
{
|
||||
columnId: '@timestamp',
|
||||
columnType: 'date',
|
||||
|
@ -432,6 +472,8 @@ export function AlertsTableTGrid(props: AlertsTableTGridProps) {
|
|||
setRefetch,
|
||||
leadingControlColumns,
|
||||
deletedEventIds,
|
||||
onStateChange,
|
||||
tGridState,
|
||||
]);
|
||||
|
||||
const handleFlyoutClose = () => setFlyoutAlert(undefined);
|
||||
|
|
|
@ -105,6 +105,7 @@ export interface TGridStandaloneProps {
|
|||
itemsPerPageOptions: number[];
|
||||
query: Query;
|
||||
onRuleChange?: () => void;
|
||||
onStateChange?: (state: State) => void;
|
||||
renderCellValue: (props: CellValueElementProps) => React.ReactNode;
|
||||
rowRenderers: RowRenderer[];
|
||||
runtimeMappings: MappingRuntimeFields;
|
||||
|
|
|
@ -26,7 +26,7 @@ export type {
|
|||
export { Direction } from '../common/search_strategy/common';
|
||||
export { tGridReducer } from './store/t_grid/reducer';
|
||||
export type { TGridModelForTimeline, TimelineState, TimelinesUIStart } from './types';
|
||||
export type { TGridType, SortDirection } from './types';
|
||||
export type { TGridType, SortDirection, State as TGridState, TGridModel } from './types';
|
||||
export type { OnColumnFocused } from '../common/utils/accessibility';
|
||||
export {
|
||||
ARIA_COLINDEX_ATTRIBUTE,
|
||||
|
|
|
@ -5,7 +5,8 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { Store } from 'redux';
|
||||
import { Store, Unsubscribe } from 'redux';
|
||||
import { throttle } from 'lodash';
|
||||
|
||||
import { Storage } from '../../../../src/plugins/kibana_utils/public';
|
||||
import type { CoreSetup, Plugin, CoreStart } from '../../../../src/core/public';
|
||||
|
@ -29,6 +30,7 @@ import { getHoverActions } from './components/hover_actions';
|
|||
export class TimelinesPlugin implements Plugin<void, TimelinesUIStart> {
|
||||
private _store: Store | undefined;
|
||||
private _storage = new Storage(localStorage);
|
||||
private _storeUnsubscribe: Unsubscribe | undefined;
|
||||
|
||||
public setup(core: CoreSetup) {}
|
||||
|
||||
|
@ -43,6 +45,13 @@ export class TimelinesPlugin implements Plugin<void, TimelinesUIStart> {
|
|||
const state = getState();
|
||||
if (state && state.app) {
|
||||
this._store = undefined;
|
||||
} else {
|
||||
if (props.onStateChange) {
|
||||
this._storeUnsubscribe = this._store.subscribe(
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
throttle(() => props.onStateChange!(getState()), 500)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
return getTGridLazy(props, {
|
||||
|
@ -118,5 +127,9 @@ export class TimelinesPlugin implements Plugin<void, TimelinesUIStart> {
|
|||
this._store = store;
|
||||
}
|
||||
|
||||
public stop() {}
|
||||
public stop() {
|
||||
if (this._storeUnsubscribe) {
|
||||
this._storeUnsubscribe();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,8 @@ import { Storage } from '../../../../../../src/plugins/kibana_utils/public';
|
|||
import type { ColumnHeaderOptions } from '../../../common';
|
||||
import type { TGridModel, TGridModelSettings } from './model';
|
||||
|
||||
export type { TGridModel };
|
||||
|
||||
export interface AutoSavedWarningMsg {
|
||||
timelineId: string | null;
|
||||
newTimelineModel: TGridModel | null;
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* 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 expect from '@kbn/expect';
|
||||
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
|
||||
export default ({ getService, getPageObject }: FtrProviderContext) => {
|
||||
describe('Observability alert table state storage', function () {
|
||||
this.tags('includeFirefox');
|
||||
|
||||
const observability = getService('observability');
|
||||
const esArchiver = getService('esArchiver');
|
||||
const testSubjects = getService('testSubjects');
|
||||
|
||||
before(async () => {
|
||||
await esArchiver.load('x-pack/test/functional/es_archives/observability/alerts');
|
||||
await esArchiver.load('x-pack/test/functional/es_archives/infra/metrics_and_logs');
|
||||
|
||||
await observability.alerts.common.navigateToTimeWithData();
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await esArchiver.unload('x-pack/test/functional/es_archives/infra/metrics_and_logs');
|
||||
await esArchiver.unload('x-pack/test/functional/es_archives/observability/alerts');
|
||||
});
|
||||
|
||||
it('remembers column changes', async () => {
|
||||
const durationColumnButton = await testSubjects.find(
|
||||
'dataGridHeaderCellActionButton-kibana.alert.duration.us'
|
||||
);
|
||||
await durationColumnButton.click();
|
||||
const columnMenu = await testSubjects.find(
|
||||
'dataGridHeaderCellActionGroup-kibana.alert.duration.us'
|
||||
);
|
||||
const removeButton = await columnMenu.findByCssSelector('[title="Remove column"]');
|
||||
await removeButton.click();
|
||||
|
||||
await observability.alerts.common.navigateToTimeWithData();
|
||||
|
||||
const durationColumnExists = await testSubjects.exists(
|
||||
'dataGridHeaderCellActionButton-kibana.alert.duration.us'
|
||||
);
|
||||
|
||||
expect(durationColumnExists).to.be(false);
|
||||
});
|
||||
|
||||
it('remembers sorting changes', async () => {
|
||||
const timestampColumnButton = await testSubjects.find(
|
||||
'dataGridHeaderCellActionButton-@timestamp'
|
||||
);
|
||||
await timestampColumnButton.click();
|
||||
const columnMenu = await testSubjects.find('dataGridHeaderCellActionGroup-@timestamp');
|
||||
const sortButton = await columnMenu.findByCssSelector('[title="Sort Old-New"]');
|
||||
await sortButton.click();
|
||||
|
||||
await observability.alerts.common.navigateToTimeWithData();
|
||||
|
||||
const timestampColumnHeading = await testSubjects.find('dataGridHeaderCell-@timestamp');
|
||||
expect(await timestampColumnHeading.getAttribute('aria-sort')).to.be('ascending');
|
||||
});
|
||||
});
|
||||
};
|
|
@ -19,5 +19,6 @@ export default function ({ loadTestFile }: FtrProviderContext) {
|
|||
loadTestFile(require.resolve('./alerts/add_to_case'));
|
||||
loadTestFile(require.resolve('./alerts/state_synchronization'));
|
||||
loadTestFile(require.resolve('./alerts/bulk_actions'));
|
||||
loadTestFile(require.resolve('./alerts/table_storage'));
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue