kibana/x-pack/plugins/monitoring/public/url_state.ts
Chris Roberson 06b1820df7
[Monitoring] Out of the box alerting (#68805)
* First draft, not quite working but a good start

* More working

* Support configuring throttle

* Get the other alerts working too

* More

* Separate into individual files

* Menu support as well as better integration in existing UIs

* Red borders!

* New overview style, and renamed alert

* more visual updates

* Update cpu usage and improve settings configuration in UI

* Convert cluster health and license expiration alert to use legacy data model

* Remove most of the custom UI and use the flyout

* Add the actual alerts

* Remove more code

* Fix formatting

* Fix up some errors

* Remove unnecessary code

* Updates

* add more links here

* Fix up linkage

* Added nodes changed alert

* Most of the version mismatch working

* Add kibana mismatch

* UI tweaks

* Add timestamp

* Support actions in the enable api

* Move this around

* Better support for changing legacy alerts

* Add missing files

* Update alerts

* Enable alerts whenever any page is visited in SM

* Tweaks

* Use more practical default

* Remove the buggy renderer and ensure setup mode can show all alerts

* Updates

* Remove unnecessary code

* Remove some dead code

* Cleanup

* Fix snapshot

* Fixes

* Fixes

* Fix test

* Add alerts to kibana and logstash listing pages

* Fix test

* Add disable/mute options

* Tweaks

* Fix linting

* Fix i18n

* Adding a couple tests

* Fix localization

* Use http

* Ensure we properly handle when an alert is resolved

* Fix tests

* Hide legacy alerts if not the right license

* Design tweaks

* Fix tests

* PR feedback

* Moar tests

* Fix i18n

* Ensure we have a control over the messaging

* Fix translations

* Tweaks

* More localization

* Copy changes

* Type
2020-07-14 17:50:22 -04:00

170 lines
5.4 KiB
TypeScript

/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { Subscription } from 'rxjs';
import { History, createHashHistory } from 'history';
import { MonitoringStartPluginDependencies } from './types';
import { Legacy } from './legacy_shims';
import {
RefreshInterval,
TimeRange,
syncQueryStateWithUrl,
} from '../../../../src/plugins/data/public';
import {
createStateContainer,
createKbnUrlStateStorage,
StateContainer,
INullableBaseStateContainer,
IKbnUrlStateStorage,
ISyncStateRef,
syncState,
} from '../../../../src/plugins/kibana_utils/public';
interface Route {
params: { _g: unknown };
}
interface RawObject {
[key: string]: unknown;
}
export interface MonitoringAppState {
[key: string]: unknown;
cluster_uuid?: string;
ccs?: boolean;
inSetupMode?: boolean;
refreshInterval?: RefreshInterval;
time?: TimeRange;
filters?: any[];
}
export interface MonitoringAppStateTransitions {
set: (
state: MonitoringAppState
) => <T extends keyof MonitoringAppState>(
prop: T,
value: MonitoringAppState[T]
) => MonitoringAppState;
}
const GLOBAL_STATE_KEY = '_g';
const objectEquals = (objA: any, objB: any) => JSON.stringify(objA) === JSON.stringify(objB);
export class GlobalState {
private readonly stateSyncRef: ISyncStateRef;
private readonly stateContainer: StateContainer<
MonitoringAppState,
MonitoringAppStateTransitions
>;
private readonly stateStorage: IKbnUrlStateStorage;
private readonly stateContainerChangeSub: Subscription;
private readonly syncQueryStateWithUrlManager: { stop: () => void };
private readonly timefilterRef: MonitoringStartPluginDependencies['data']['query']['timefilter']['timefilter'];
private lastAssignedState: MonitoringAppState = {};
private lastKnownGlobalState?: string;
constructor(
queryService: MonitoringStartPluginDependencies['data']['query'],
rootScope: ng.IRootScopeService,
ngLocation: ng.ILocationService,
externalState: RawObject
) {
this.timefilterRef = queryService.timefilter.timefilter;
const history: History = createHashHistory();
this.stateStorage = createKbnUrlStateStorage({ useHash: false, history });
const initialStateFromUrl = this.stateStorage.get(GLOBAL_STATE_KEY) as MonitoringAppState;
this.stateContainer = createStateContainer(initialStateFromUrl, {
set: (state) => (prop, value) => ({ ...state, [prop]: value }),
});
this.stateSyncRef = syncState({
storageKey: GLOBAL_STATE_KEY,
stateContainer: this.stateContainer as INullableBaseStateContainer<MonitoringAppState>,
stateStorage: this.stateStorage,
});
this.stateContainerChangeSub = this.stateContainer.state$.subscribe(() => {
this.lastAssignedState = this.getState();
if (!this.stateContainer.get() && this.lastKnownGlobalState) {
ngLocation.search(`${GLOBAL_STATE_KEY}=${this.lastKnownGlobalState}`).replace();
}
Legacy.shims.breadcrumbs.update();
this.syncExternalState(externalState);
rootScope.$applyAsync();
});
this.syncQueryStateWithUrlManager = syncQueryStateWithUrl(queryService, this.stateStorage);
this.stateSyncRef.start();
this.startHashSync(rootScope, ngLocation);
this.lastAssignedState = this.getState();
rootScope.$on('$destroy', () => this.destroy());
}
private syncExternalState(externalState: { [key: string]: unknown }) {
const currentState = this.stateContainer.get();
for (const key in currentState) {
if (
({ save: 1, time: 1, refreshInterval: 1, filters: 1 } as { [key: string]: number })[key]
) {
continue;
}
if (currentState[key] !== externalState[key]) {
externalState[key] = currentState[key];
}
}
}
private startHashSync(rootScope: ng.IRootScopeService, ngLocation: ng.ILocationService) {
rootScope.$on(
'$routeChangeStart',
(_: { preventDefault: () => void }, newState: Route, oldState: Route) => {
const currentGlobalState = oldState?.params?._g;
const nextGlobalState = newState?.params?._g;
if (!nextGlobalState && currentGlobalState && typeof currentGlobalState === 'string') {
newState.params._g = currentGlobalState;
ngLocation.search(`${GLOBAL_STATE_KEY}=${currentGlobalState}`).replace();
}
this.lastKnownGlobalState = (nextGlobalState || currentGlobalState) as string;
}
);
}
public setState(state?: { [key: string]: unknown }) {
const currentAppState = this.getState();
const newAppState = { ...currentAppState, ...state };
if (state && objectEquals(newAppState, currentAppState)) {
return;
}
const newState = {
...newAppState,
refreshInterval: this.timefilterRef.getRefreshInterval(),
time: this.timefilterRef.getTime(),
};
this.lastAssignedState = newState;
this.stateContainer.set(newState);
}
public getState(): MonitoringAppState {
const currentState = { ...this.lastAssignedState, ...this.stateContainer.get() };
delete currentState.filters;
const { refreshInterval: _nullA, time: _nullB, ...currentAppState } = currentState;
return currentAppState || {};
}
public destroy() {
this.syncQueryStateWithUrlManager.stop();
this.stateContainerChangeSub.unsubscribe();
this.stateSyncRef.stop();
}
}