Clean angular from moved code, global state and legacy shims (#115420)

This commit is contained in:
Ester Martí Vilaseca 2021-10-18 23:25:01 +02:00 committed by GitHub
parent 85d7115d4a
commit eb5ffff7d0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 18 additions and 783 deletions

View file

@ -50,17 +50,13 @@ const initLegacyShims = () => {
ruleTypeRegistry: ruleTypeRegistryMock.create(),
};
const data = { query: { timefilter: { timefilter: {} } } } as any;
const ngInjector = {} as angular.auto.IInjectorService;
Legacy.init(
{
core: coreMock.createStart(),
data,
isCloud: false,
triggersActionsUi,
usageCollection: {},
} as any,
ngInjector
);
Legacy.init({
core: coreMock.createStart(),
data,
isCloud: false,
triggersActionsUi,
usageCollection: {},
} as any);
};
const ALERTS_FEATURE_ID = 'alerts';

View file

@ -1,103 +0,0 @@
/*
* 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 { IDirective, IRootElementService, IScope } from 'angular';
import { I18nServiceType } from './provider';
interface I18nScope extends IScope {
values?: Record<string, any>;
defaultMessage: string;
id: string;
}
const HTML_KEY_PREFIX = 'html_';
const PLACEHOLDER_SEPARATOR = '@I18N@';
export const i18nDirective: [string, string, typeof i18nDirectiveFn] = [
'i18n',
'$sanitize',
i18nDirectiveFn,
];
function i18nDirectiveFn(
i18n: I18nServiceType,
$sanitize: (html: string) => string
): IDirective<I18nScope> {
return {
restrict: 'A',
scope: {
id: '@i18nId',
defaultMessage: '@i18nDefaultMessage',
values: '<?i18nValues',
},
link($scope, $element) {
if ($scope.values) {
$scope.$watchCollection('values', () => {
setContent($element, $scope, $sanitize, i18n);
});
} else {
setContent($element, $scope, $sanitize, i18n);
}
},
};
}
function setContent(
$element: IRootElementService,
$scope: I18nScope,
$sanitize: (html: string) => string,
i18n: I18nServiceType
) {
const originalValues = $scope.values;
const valuesWithPlaceholders = {} as Record<string, any>;
let hasValuesWithPlaceholders = false;
// If we have values with the keys that start with HTML_KEY_PREFIX we should replace
// them with special placeholders that later on will be inserted as HTML
// into the DOM, the rest of the content will be treated as text. We don't
// sanitize values at this stage as some of the values can be excluded from
// the translated string (e.g. not used by ICU conditional statements).
if (originalValues) {
for (const [key, value] of Object.entries(originalValues)) {
if (key.startsWith(HTML_KEY_PREFIX)) {
valuesWithPlaceholders[
key.slice(HTML_KEY_PREFIX.length)
] = `${PLACEHOLDER_SEPARATOR}${key}${PLACEHOLDER_SEPARATOR}`;
hasValuesWithPlaceholders = true;
} else {
valuesWithPlaceholders[key] = value;
}
}
}
const label = i18n($scope.id, {
values: valuesWithPlaceholders,
defaultMessage: $scope.defaultMessage,
});
// If there are no placeholders to replace treat everything as text, otherwise
// insert label piece by piece replacing every placeholder with corresponding
// sanitized HTML content.
if (!hasValuesWithPlaceholders) {
$element.text(label);
} else {
$element.empty();
for (const contentOrPlaceholder of label.split(PLACEHOLDER_SEPARATOR)) {
if (!contentOrPlaceholder) {
continue;
}
$element.append(
originalValues!.hasOwnProperty(contentOrPlaceholder)
? $sanitize(originalValues![contentOrPlaceholder])
: document.createTextNode(contentOrPlaceholder)
);
}
}
}

View file

@ -1,19 +0,0 @@
/*
* 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 { I18nServiceType } from './provider';
export const i18nFilter: [string, typeof i18nFilterFn] = ['i18n', i18nFilterFn];
function i18nFilterFn(i18n: I18nServiceType) {
return (id: string, { defaultMessage = '', values = {} } = {}) => {
return i18n(id, {
values,
defaultMessage,
});
};
}

View file

@ -1,15 +0,0 @@
/*
* 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.
*/
export { I18nProvider } from './provider';
export { i18nFilter } from './filter';
export { i18nDirective } from './directive';
// re-export types: https://github.com/babel/babel-loader/issues/603
import { I18nServiceType as _I18nServiceType } from './provider';
export type I18nServiceType = _I18nServiceType;

View file

@ -1,25 +0,0 @@
/*
* 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 { i18n } from '@kbn/i18n';
export type I18nServiceType = ReturnType<I18nProvider['$get']>;
export class I18nProvider implements angular.IServiceProvider {
public addTranslation = i18n.addTranslation;
public getTranslation = i18n.getTranslation;
public setLocale = i18n.setLocale;
public getLocale = i18n.getLocale;
public setDefaultLocale = i18n.setDefaultLocale;
public getDefaultLocale = i18n.getDefaultLocale;
public setFormats = i18n.setFormats;
public getFormats = i18n.getFormats;
public getRegisteredLocales = i18n.getRegisteredLocales;
public init = i18n.init;
public load = i18n.load;
public $get = () => i18n.translate;
}

View file

@ -1,43 +0,0 @@
/*
* 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 { i18n } from '@kbn/i18n';
import type { IHttpResponse } from 'angular';
type AngularHttpError = IHttpResponse<{ message: string }>;
export function isAngularHttpError(error: any): error is AngularHttpError {
return (
error &&
typeof error.status === 'number' &&
typeof error.statusText === 'string' &&
error.data &&
typeof error.data.message === 'string'
);
}
export function formatAngularHttpError(error: AngularHttpError) {
// is an Angular $http "error object"
if (error.status === -1) {
// status = -1 indicates that the request was failed to reach the server
return i18n.translate('xpack.monitoring.notify.fatalError.unavailableServerErrorMessage', {
defaultMessage:
'An HTTP request has failed to connect. ' +
'Please check if the Kibana server is running and that your browser has a working connection, ' +
'or contact your system administrator.',
});
}
return i18n.translate('xpack.monitoring.notify.fatalError.errorStatusMessage', {
defaultMessage: 'Error {errStatus} {errStatusText}: {errMessage}',
values: {
errStatus: error.status,
errStatusText: error.statusText,
errMessage: error.data.message,
},
});
}

View file

@ -1,349 +0,0 @@
/*
* 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 {
ICompileProvider,
IHttpProvider,
IHttpService,
ILocationProvider,
IModule,
IRootScopeService,
IRequestConfig,
} from 'angular';
import $ from 'jquery';
import { set } from '@elastic/safer-lodash-set';
import { get } from 'lodash';
import * as Rx from 'rxjs';
import { ChromeBreadcrumb, EnvironmentMode, PackageInfo } from 'kibana/public';
import { History } from 'history';
import { CoreStart } from 'kibana/public';
import { formatAngularHttpError, isAngularHttpError } from '../helpers/format_angular_http_error';
export interface RouteConfiguration {
controller?: string | ((...args: any[]) => void);
redirectTo?: string;
resolveRedirectTo?: (...args: any[]) => void;
reloadOnSearch?: boolean;
reloadOnUrl?: boolean;
outerAngularWrapperRoute?: boolean;
resolve?: object;
template?: string;
k7Breadcrumbs?: (...args: any[]) => ChromeBreadcrumb[];
requireUICapability?: string;
}
function isSystemApiRequest(request: IRequestConfig) {
const { headers } = request;
return headers && !!headers['kbn-system-request'];
}
/**
* Detects whether a given angular route is a dummy route that doesn't
* require any action. There are two ways this can happen:
* If `outerAngularWrapperRoute` is set on the route config object,
* it means the local application service set up this route on the outer angular
* and the internal routes will handle the hooks.
*
* If angular did not detect a route and it is the local angular, we are currently
* navigating away from a URL controlled by a local angular router and the
* application will get unmounted. In this case the outer router will handle
* the hooks.
* @param $route Injected $route dependency
* @param isLocalAngular Flag whether this is the local angular router
*/
function isDummyRoute($route: any, isLocalAngular: boolean) {
return (
($route.current && $route.current.$$route && $route.current.$$route.outerAngularWrapperRoute) ||
(!$route.current && isLocalAngular)
);
}
export const configureAppAngularModule = (
angularModule: IModule,
newPlatform: {
core: CoreStart;
readonly env: {
mode: Readonly<EnvironmentMode>;
packageInfo: Readonly<PackageInfo>;
};
},
isLocalAngular: boolean,
getHistory?: () => History
) => {
const core = 'core' in newPlatform ? newPlatform.core : newPlatform;
const packageInfo = newPlatform.env.packageInfo;
angularModule
.value('kbnVersion', packageInfo.version)
.value('buildNum', packageInfo.buildNum)
.value('buildSha', packageInfo.buildSha)
.value('esUrl', getEsUrl(core))
.value('uiCapabilities', core.application.capabilities)
.config(setupCompileProvider(newPlatform.env.mode.dev))
.config(setupLocationProvider())
.config($setupXsrfRequestInterceptor(packageInfo.version))
.run(capture$httpLoadingCount(core))
.run(digestOnHashChange(getHistory))
.run($setupBreadcrumbsAutoClear(core, isLocalAngular))
.run($setupBadgeAutoClear(core, isLocalAngular))
.run($setupHelpExtensionAutoClear(core, isLocalAngular))
.run($setupUICapabilityRedirect(core));
};
const getEsUrl = (newPlatform: CoreStart) => {
const a = document.createElement('a');
a.href = newPlatform.http.basePath.prepend('/elasticsearch');
const protocolPort = /https/.test(a.protocol) ? 443 : 80;
const port = a.port || protocolPort;
return {
host: a.hostname,
port,
protocol: a.protocol,
pathname: a.pathname,
};
};
const digestOnHashChange = (getHistory?: () => History) => ($rootScope: IRootScopeService) => {
if (!getHistory) return;
const unlisten = getHistory().listen(() => {
// dispatch synthetic hash change event to update hash history objects and angular routing
// this is necessary because hash updates triggered by using popState won't trigger this event naturally.
// this has to happen in the next tick to not change the existing timing of angular digest cycles.
setTimeout(() => {
window.dispatchEvent(new HashChangeEvent('hashchange'));
}, 0);
});
$rootScope.$on('$destroy', unlisten);
};
const setupCompileProvider = (devMode: boolean) => ($compileProvider: ICompileProvider) => {
if (!devMode) {
$compileProvider.debugInfoEnabled(false);
}
};
const setupLocationProvider = () => ($locationProvider: ILocationProvider) => {
$locationProvider.html5Mode({
enabled: false,
requireBase: false,
rewriteLinks: false,
});
$locationProvider.hashPrefix('');
};
export const $setupXsrfRequestInterceptor = (version: string) => {
// Configure jQuery prefilter
$.ajaxPrefilter(({ kbnXsrfToken = true }: any, originalOptions, jqXHR) => {
if (kbnXsrfToken) {
jqXHR.setRequestHeader('kbn-version', version);
}
});
return ($httpProvider: IHttpProvider) => {
// Configure $httpProvider interceptor
$httpProvider.interceptors.push(() => {
return {
request(opts) {
const { kbnXsrfToken = true } = opts as any;
if (kbnXsrfToken) {
set(opts, ['headers', 'kbn-version'], version);
}
return opts;
},
};
});
};
};
/**
* Injected into angular module by ui/chrome angular integration
* and adds a root-level watcher that will capture the count of
* active $http requests on each digest loop and expose the count to
* the core.loadingCount api
*/
const capture$httpLoadingCount =
(newPlatform: CoreStart) => ($rootScope: IRootScopeService, $http: IHttpService) => {
newPlatform.http.addLoadingCountSource(
new Rx.Observable((observer) => {
const unwatch = $rootScope.$watch(() => {
const reqs = $http.pendingRequests || [];
observer.next(reqs.filter((req) => !isSystemApiRequest(req)).length);
});
return unwatch;
})
);
};
/**
* integrates with angular to automatically redirect to home if required
* capability is not met
*/
const $setupUICapabilityRedirect =
(newPlatform: CoreStart) => ($rootScope: IRootScopeService, $injector: any) => {
const isKibanaAppRoute = window.location.pathname.endsWith('/app/kibana');
// this feature only works within kibana app for now after everything is
// switched to the application service, this can be changed to handle all
// apps.
if (!isKibanaAppRoute) {
return;
}
$rootScope.$on(
'$routeChangeStart',
(event, { $$route: route }: { $$route?: RouteConfiguration } = {}) => {
if (!route || !route.requireUICapability) {
return;
}
if (!get(newPlatform.application.capabilities, route.requireUICapability)) {
$injector.get('$location').url('/home');
event.preventDefault();
}
}
);
};
/**
* internal angular run function that will be called when angular bootstraps and
* lets us integrate with the angular router so that we can automatically clear
* the breadcrumbs if we switch to a Kibana app that does not use breadcrumbs correctly
*/
const $setupBreadcrumbsAutoClear =
(newPlatform: CoreStart, isLocalAngular: boolean) =>
($rootScope: IRootScopeService, $injector: any) => {
// A flag used to determine if we should automatically
// clear the breadcrumbs between angular route changes.
let breadcrumbSetSinceRouteChange = false;
const $route = $injector.has('$route') ? $injector.get('$route') : {};
// reset breadcrumbSetSinceRouteChange any time the breadcrumbs change, even
// if it was done directly through the new platform
newPlatform.chrome.getBreadcrumbs$().subscribe({
next() {
breadcrumbSetSinceRouteChange = true;
},
});
$rootScope.$on('$routeChangeStart', () => {
breadcrumbSetSinceRouteChange = false;
});
$rootScope.$on('$routeChangeSuccess', () => {
if (isDummyRoute($route, isLocalAngular)) {
return;
}
const current = $route.current || {};
if (breadcrumbSetSinceRouteChange || (current.$$route && current.$$route.redirectTo)) {
return;
}
const k7BreadcrumbsProvider = current.k7Breadcrumbs;
if (!k7BreadcrumbsProvider) {
newPlatform.chrome.setBreadcrumbs([]);
return;
}
try {
newPlatform.chrome.setBreadcrumbs($injector.invoke(k7BreadcrumbsProvider));
} catch (error) {
if (isAngularHttpError(error)) {
error = formatAngularHttpError(error);
}
newPlatform.fatalErrors.add(error, 'location');
}
});
};
/**
* internal angular run function that will be called when angular bootstraps and
* lets us integrate with the angular router so that we can automatically clear
* the badge if we switch to a Kibana app that does not use the badge correctly
*/
const $setupBadgeAutoClear =
(newPlatform: CoreStart, isLocalAngular: boolean) =>
($rootScope: IRootScopeService, $injector: any) => {
// A flag used to determine if we should automatically
// clear the badge between angular route changes.
let badgeSetSinceRouteChange = false;
const $route = $injector.has('$route') ? $injector.get('$route') : {};
$rootScope.$on('$routeChangeStart', () => {
badgeSetSinceRouteChange = false;
});
$rootScope.$on('$routeChangeSuccess', () => {
if (isDummyRoute($route, isLocalAngular)) {
return;
}
const current = $route.current || {};
if (badgeSetSinceRouteChange || (current.$$route && current.$$route.redirectTo)) {
return;
}
const badgeProvider = current.badge;
if (!badgeProvider) {
newPlatform.chrome.setBadge(undefined);
return;
}
try {
newPlatform.chrome.setBadge($injector.invoke(badgeProvider));
} catch (error) {
if (isAngularHttpError(error)) {
error = formatAngularHttpError(error);
}
newPlatform.fatalErrors.add(error, 'location');
}
});
};
/**
* internal angular run function that will be called when angular bootstraps and
* lets us integrate with the angular router so that we can automatically clear
* the helpExtension if we switch to a Kibana app that does not set its own
* helpExtension
*/
const $setupHelpExtensionAutoClear =
(newPlatform: CoreStart, isLocalAngular: boolean) =>
($rootScope: IRootScopeService, $injector: any) => {
/**
* reset helpExtensionSetSinceRouteChange any time the helpExtension changes, even
* if it was done directly through the new platform
*/
let helpExtensionSetSinceRouteChange = false;
newPlatform.chrome.getHelpExtension$().subscribe({
next() {
helpExtensionSetSinceRouteChange = true;
},
});
const $route = $injector.has('$route') ? $injector.get('$route') : {};
$rootScope.$on('$routeChangeStart', () => {
if (isDummyRoute($route, isLocalAngular)) {
return;
}
helpExtensionSetSinceRouteChange = false;
});
$rootScope.$on('$routeChangeSuccess', () => {
if (isDummyRoute($route, isLocalAngular)) {
return;
}
const current = $route.current || {};
if (helpExtensionSetSinceRouteChange || (current.$$route && current.$$route.redirectTo)) {
return;
}
newPlatform.chrome.setHelpExtension(current.helpExtension);
});
};

View file

@ -1,10 +0,0 @@
/*
* 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.
*/
export * from './angular_config';
// @ts-ignore
export { createTopNavDirective, createTopNavHelper, loadKbnTopNavDirectives } from './kbn_top_nav';

View file

@ -1,16 +0,0 @@
/*
* 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 { Injectable, IDirectiveFactory, IScope, IAttributes, IController } from 'angular';
export const createTopNavDirective: Injectable<
IDirectiveFactory<IScope, JQLite, IAttributes, IController>
>;
export const createTopNavHelper: (
options: unknown
) => Injectable<IDirectiveFactory<IScope, JQLite, IAttributes, IController>>;
export function loadKbnTopNavDirectives(navUi: unknown): void;

View file

@ -1,119 +0,0 @@
/*
* 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 angular from 'angular';
import 'ngreact';
export function createTopNavDirective() {
return {
restrict: 'E',
template: '',
compile: (elem) => {
const child = document.createElement('kbn-top-nav-helper');
// Copy attributes to the child directive
for (const attr of elem[0].attributes) {
child.setAttribute(attr.name, attr.value);
}
// Add a special attribute that will change every time that one
// of the config array's disableButton function return value changes.
child.setAttribute('disabled-buttons', 'disabledButtons');
// Append helper directive
elem.append(child);
const linkFn = ($scope, _, $attr) => {
// Watch config changes
$scope.$watch(
() => {
const config = $scope.$eval($attr.config) || [];
return config.map((item) => {
// Copy key into id, as it's a reserved react propery.
// This is done for Angular directive backward compatibility.
// In React only id is recognized.
if (item.key && !item.id) {
item.id = item.key;
}
// Watch the disableButton functions
if (typeof item.disableButton === 'function') {
return item.disableButton();
}
return item.disableButton;
});
},
(newVal) => {
$scope.disabledButtons = newVal;
},
true
);
};
return linkFn;
},
};
}
export const createTopNavHelper =
({ TopNavMenu }) =>
(reactDirective) => {
return reactDirective(TopNavMenu, [
['config', { watchDepth: 'value' }],
['setMenuMountPoint', { watchDepth: 'reference' }],
['disabledButtons', { watchDepth: 'reference' }],
['query', { watchDepth: 'reference' }],
['savedQuery', { watchDepth: 'reference' }],
['intl', { watchDepth: 'reference' }],
['onQuerySubmit', { watchDepth: 'reference' }],
['onFiltersUpdated', { watchDepth: 'reference' }],
['onRefreshChange', { watchDepth: 'reference' }],
['onClearSavedQuery', { watchDepth: 'reference' }],
['onSaved', { watchDepth: 'reference' }],
['onSavedQueryUpdated', { watchDepth: 'reference' }],
['onSavedQueryIdChange', { watchDepth: 'reference' }],
['indexPatterns', { watchDepth: 'collection' }],
['filters', { watchDepth: 'collection' }],
// All modifiers default to true.
// Set to false to hide subcomponents.
'showSearchBar',
'showQueryBar',
'showQueryInput',
'showSaveQuery',
'showDatePicker',
'showFilterBar',
'appName',
'screenTitle',
'dateRangeFrom',
'dateRangeTo',
'savedQueryId',
'isRefreshPaused',
'refreshInterval',
'disableAutoFocus',
'showAutoRefreshOnly',
// temporary flag to use the stateful components
'useDefaultBehaviors',
]);
};
let isLoaded = false;
export function loadKbnTopNavDirectives(navUi) {
if (!isLoaded) {
isLoaded = true;
angular
.module('kibana')
.directive('kbnTopNav', createTopNavDirective)
.directive('kbnTopNavHelper', createTopNavHelper(navUi));
}
}

View file

@ -32,31 +32,8 @@ export const GlobalStateProvider: React.FC<GlobalStateProviderProps> = ({
toasts,
children,
}) => {
// TODO: remove fakeAngularRootScope and fakeAngularLocation when angular is removed
const fakeAngularRootScope: Partial<ng.IRootScopeService> = {
$on:
(name: string, listener: (event: ng.IAngularEvent, ...args: any[]) => any): (() => void) =>
() => {},
$applyAsync: () => {},
};
const fakeAngularLocation: Partial<ng.ILocationService> = {
search: () => {
return {} as any;
},
replace: () => {
return {} as any;
},
};
const localState: State = {};
const state = new GlobalState(
query,
toasts,
fakeAngularRootScope,
fakeAngularLocation,
localState as { [key: string]: unknown }
);
const state = new GlobalState(query, toasts, localState as { [key: string]: unknown });
const initialState: any = state.getState();
for (const key in initialState) {

View file

@ -44,7 +44,7 @@ const angularNoop = () => {
export interface IShims {
toastNotifications: CoreStart['notifications']['toasts'];
capabilities: CoreStart['application']['capabilities'];
getAngularInjector: typeof angularNoop | (() => angular.auto.IInjectorService);
getAngularInjector: typeof angularNoop;
getBasePath: () => string;
getInjected: (name: string, defaultValue?: unknown) => unknown;
breadcrumbs: {
@ -73,23 +73,18 @@ export interface IShims {
export class Legacy {
private static _shims: IShims;
public static init(
{
core,
data,
isCloud,
triggersActionsUi,
usageCollection,
appMountParameters,
}: MonitoringStartPluginDependencies,
ngInjector?: angular.auto.IInjectorService
) {
public static init({
core,
data,
isCloud,
triggersActionsUi,
usageCollection,
appMountParameters,
}: MonitoringStartPluginDependencies) {
this._shims = {
toastNotifications: core.notifications.toasts,
capabilities: core.application.capabilities,
getAngularInjector: ngInjector
? (): angular.auto.IInjectorService => ngInjector
: angularNoop,
getAngularInjector: angularNoop,
getBasePath: (): string => core.http.basePath.get(),
getInjected: (name: string, defaultValue?: unknown): string | unknown =>
core.injectedMetadata.getInjectedVar(name, defaultValue),

View file

@ -4,7 +4,6 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { Subscription } from 'rxjs';
import { History, createHashHistory } from 'history';
import { MonitoringStartPluginDependencies } from './types';
@ -27,10 +26,6 @@ import {
withNotifyOnErrors,
} from '../../../../src/plugins/kibana_utils/public';
interface Route {
params: { _g: unknown };
}
interface RawObject {
[key: string]: unknown;
}
@ -57,7 +52,6 @@ export interface MonitoringAppStateTransitions {
const GLOBAL_STATE_KEY = '_g';
const objectEquals = (objA: any, objB: any) => JSON.stringify(objA) === JSON.stringify(objB);
// TODO: clean all angular references after angular is removed
export class GlobalState {
private readonly stateSyncRef: ISyncStateRef;
private readonly stateContainer: StateContainer<
@ -70,13 +64,10 @@ export class GlobalState {
private readonly timefilterRef: MonitoringStartPluginDependencies['data']['query']['timefilter']['timefilter'];
private lastAssignedState: MonitoringAppState = {};
private lastKnownGlobalState?: string;
constructor(
queryService: MonitoringStartPluginDependencies['data']['query'],
toasts: MonitoringStartPluginDependencies['core']['notifications']['toasts'],
rootScope: Partial<ng.IRootScopeService>,
ngLocation: Partial<ng.ILocationService>,
externalState: RawObject
) {
this.timefilterRef = queryService.timefilter.timefilter;
@ -102,9 +93,6 @@ export class GlobalState {
this.stateContainerChangeSub = this.stateContainer.state$.subscribe(() => {
this.lastAssignedState = this.getState();
if (!this.stateContainer.get() && this.lastKnownGlobalState) {
ngLocation.search?.(`${GLOBAL_STATE_KEY}=${this.lastKnownGlobalState}`).replace();
}
// TODO: check if this is not needed after https://github.com/elastic/kibana/pull/109132 is merged
if (Legacy.isInitializated()) {
@ -112,15 +100,11 @@ export class GlobalState {
}
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 }) {
@ -137,24 +121,6 @@ export class GlobalState {
}
}
private startHashSync(
rootScope: Partial<ng.IRootScopeService>,
ngLocation: Partial<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 };