From c5e61c35922b6592dda69d4324d0a371a97dce71 Mon Sep 17 00:00:00 2001 From: Stacey Gammon Date: Fri, 19 Oct 2018 13:21:12 -0400 Subject: [PATCH] EAH - Typescript state_monitor_factory (#23945) * Typescript state_monitor_factory * Fix linter error with possibly undefined * Expand typings to include hash stuff and expand the State type definition more. * Mark readonly --- src/ui/public/state_management/app_state.d.ts | 3 +- ...e_states_provider.js => global_state.d.ts} | 8 +- .../unhash_query_string.js => state.d.ts} | 13 ++-- .../get_unhashable_states_provider.ts | 28 +++++++ .../state_hashing/unhash_query_string.ts | 37 ++++++++++ ...or_factory.js => state_monitor_factory.ts} | 74 ++++++++++++------- .../loader/embedded_visualize_handler.ts | 2 +- 7 files changed, 124 insertions(+), 41 deletions(-) rename src/ui/public/state_management/{state_hashing/get_unhashable_states_provider.js => global_state.d.ts} (81%) rename src/ui/public/state_management/{state_hashing/unhash_query_string.js => state.d.ts} (73%) create mode 100644 src/ui/public/state_management/state_hashing/get_unhashable_states_provider.ts create mode 100644 src/ui/public/state_management/state_hashing/unhash_query_string.ts rename src/ui/public/state_management/{state_monitor_factory.js => state_monitor_factory.ts} (58%) diff --git a/src/ui/public/state_management/app_state.d.ts b/src/ui/public/state_management/app_state.d.ts index 314583746fca14..3a1dbc8d9d70e4 100644 --- a/src/ui/public/state_management/app_state.d.ts +++ b/src/ui/public/state_management/app_state.d.ts @@ -16,5 +16,6 @@ * specific language governing permissions and limitations * under the License. */ +import { State } from './state'; -export type AppState = any; +export type AppState = State; diff --git a/src/ui/public/state_management/state_hashing/get_unhashable_states_provider.js b/src/ui/public/state_management/global_state.d.ts similarity index 81% rename from src/ui/public/state_management/state_hashing/get_unhashable_states_provider.js rename to src/ui/public/state_management/global_state.d.ts index 693da1b990c45d..66a85d88956c77 100644 --- a/src/ui/public/state_management/state_hashing/get_unhashable_states_provider.js +++ b/src/ui/public/state_management/global_state.d.ts @@ -17,8 +17,6 @@ * under the License. */ -export function getUnhashableStatesProvider(getAppState, globalState) { - return function getUnhashableStates() { - return [getAppState(), globalState].filter(Boolean); - }; -} +import { State } from './state'; + +export type GlobalState = State; diff --git a/src/ui/public/state_management/state_hashing/unhash_query_string.js b/src/ui/public/state_management/state.d.ts similarity index 73% rename from src/ui/public/state_management/state_hashing/unhash_query_string.js rename to src/ui/public/state_management/state.d.ts index be1f9c94a3c5ae..39e4e672d02034 100644 --- a/src/ui/public/state_management/state_hashing/unhash_query_string.js +++ b/src/ui/public/state_management/state.d.ts @@ -17,11 +17,10 @@ * under the License. */ -import { mapValues } from 'lodash'; - -export function unhashQueryString(parsedQueryString, states) { - return mapValues(parsedQueryString, (val, key) => { - const state = states.find(s => key === s.getQueryParamName()); - return state ? state.translateHashToRison(val) : val; - }); +export interface State { + [key: string]: any; + translateHashToRison: ( + stateHashOrRison: string | string[] | undefined + ) => string | string[] | undefined; + getQueryParamName: () => string; } diff --git a/src/ui/public/state_management/state_hashing/get_unhashable_states_provider.ts b/src/ui/public/state_management/state_hashing/get_unhashable_states_provider.ts new file mode 100644 index 00000000000000..6c43947640ed39 --- /dev/null +++ b/src/ui/public/state_management/state_hashing/get_unhashable_states_provider.ts @@ -0,0 +1,28 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { AppState } from '../app_state'; +import { GlobalState } from '../global_state'; +import { State } from '../state'; + +export function getUnhashableStatesProvider(getAppState: () => AppState, globalState: GlobalState) { + return function getUnhashableStates(): State[] { + return [getAppState(), globalState].filter(Boolean); + }; +} diff --git a/src/ui/public/state_management/state_hashing/unhash_query_string.ts b/src/ui/public/state_management/state_hashing/unhash_query_string.ts new file mode 100644 index 00000000000000..242b840282f39e --- /dev/null +++ b/src/ui/public/state_management/state_hashing/unhash_query_string.ts @@ -0,0 +1,37 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { mapValues } from 'lodash'; +import { ParsedUrlQuery } from 'querystring'; +import { State } from '../state'; + +/** + * Takes in a parsed url query and state objects, finding the state objects that match the query parameters and expanding + * the hashed state. For example, a url query string like '?_a=@12353&_g=@19028df' will become + * '?_a=[expanded app state here]&_g=[expanded global state here]. This is used when storeStateInSessionStorage is turned on. + */ +export function unhashQueryString( + parsedQueryString: ParsedUrlQuery, + states: State[] +): ParsedUrlQuery { + return mapValues(parsedQueryString, (val, key) => { + const state = states.find(s => key === s.getQueryParamName()); + return state ? state.translateHashToRison(val) : val; + }); +} diff --git a/src/ui/public/state_management/state_monitor_factory.js b/src/ui/public/state_management/state_monitor_factory.ts similarity index 58% rename from src/ui/public/state_management/state_monitor_factory.js rename to src/ui/public/state_management/state_monitor_factory.ts index b09195a1dd3b7f..2ed138d24bf142 100644 --- a/src/ui/public/state_management/state_monitor_factory.js +++ b/src/ui/public/state_management/state_monitor_factory.ts @@ -16,34 +16,41 @@ * specific language governing permissions and limitations * under the License. */ - -import { cloneDeep, isEqual, set, isPlainObject } from 'lodash'; +import { cloneDeep, isEqual, isPlainObject, set } from 'lodash'; +import { State } from './state'; export const stateMonitorFactory = { - create: (state, customInitialState) => stateMonitor(state, customInitialState) + create: (state: State, customInitialState: State) => stateMonitor(state, customInitialState), }; -function stateMonitor(state, customInitialState) { +interface StateStatus { + clean: boolean; + dirty: boolean; +} + +type ChangeHandlerFn = (status: StateStatus, type: string | null, keys: string[]) => void; + +function stateMonitor(state: State, customInitialState: State) { let destroyed = false; - let ignoredProps = []; - let changeHandlers = []; - let initialState; + let ignoredProps: string[] = []; + let changeHandlers: ChangeHandlerFn[] | undefined = []; + let initialState: State; setInitialState(customInitialState); - function setInitialState(customInitialState) { + function setInitialState(innerCustomInitialState: State) { // state.toJSON returns a reference, clone so we can mutate it safely - initialState = cloneDeep(customInitialState) || cloneDeep(state.toJSON()); + initialState = cloneDeep(innerCustomInitialState) || cloneDeep(state.toJSON()); } - function removeIgnoredProps(state) { + function removeIgnoredProps(innerState: State) { ignoredProps.forEach(path => { - set(state, path, true); + set(innerState, path, true); }); - return state; + return innerState; } - function getStatus() { + function getStatus(): StateStatus { // state.toJSON returns a reference, clone so we can mutate it safely const currentState = removeIgnoredProps(cloneDeep(state.toJSON())); const isClean = isEqual(currentState, initialState); @@ -54,49 +61,60 @@ function stateMonitor(state, customInitialState) { }; } - function dispatchChange(type = null, keys = []) { + function dispatchChange(type: string | null = null, keys: string[] = []) { const status = getStatus(); + if (!changeHandlers) { + throw new Error('Change handlers is undefined, this object has been destroyed'); + } changeHandlers.forEach(changeHandler => { changeHandler(status, type, keys); }); } - function dispatchFetch(keys) { + function dispatchFetch(keys: string[]) { dispatchChange('fetch_with_changes', keys); } - function dispatchSave(keys) { + function dispatchSave(keys: string[]) { dispatchChange('save_with_changes', keys); } - function dispatchReset(keys) { + function dispatchReset(keys: string[]) { dispatchChange('reset_with_changes', keys); } return { - setInitialState(customInitialState) { - if (!isPlainObject(customInitialState)) throw new TypeError('The default state must be an object'); + setInitialState(innerCustomInitialState: State) { + if (!isPlainObject(innerCustomInitialState)) { + throw new TypeError('The default state must be an object'); + } // check the current status const previousStatus = getStatus(); // update the initialState and apply ignoredProps - setInitialState(customInitialState); + setInitialState(innerCustomInitialState); removeIgnoredProps(initialState); // fire the change handler if the status has changed - if (!isEqual(previousStatus, getStatus())) dispatchChange(); + if (!isEqual(previousStatus, getStatus())) { + dispatchChange(); + } }, - ignoreProps(props) { + ignoreProps(props: string[]) { ignoredProps = ignoredProps.concat(props); removeIgnoredProps(initialState); return this; }, - onChange(callback) { - if (destroyed) throw new Error('Monitor has been destroyed'); - if (typeof callback !== 'function') throw new Error('onChange handler must be a function'); + onChange(callback: ChangeHandlerFn) { + if (destroyed || !changeHandlers) { + throw new Error('Monitor has been destroyed'); + } + if (typeof callback !== 'function') { + throw new Error('onChange handler must be a function'); + } changeHandlers.push(callback); @@ -107,7 +125,9 @@ function stateMonitor(state, customInitialState) { // if the state is already dirty, fire the change handler immediately const status = getStatus(); - if (status.dirty) dispatchChange(); + if (status.dirty) { + dispatchChange(); + } return this; }, @@ -118,6 +138,6 @@ function stateMonitor(state, customInitialState) { state.off('fetch_with_changes', dispatchFetch); state.off('save_with_changes', dispatchSave); state.off('reset_with_changes', dispatchReset); - } + }, }; } diff --git a/src/ui/public/visualize/loader/embedded_visualize_handler.ts b/src/ui/public/visualize/loader/embedded_visualize_handler.ts index ea5c776ae1a42c..c11df35afae306 100644 --- a/src/ui/public/visualize/loader/embedded_visualize_handler.ts +++ b/src/ui/public/visualize/loader/embedded_visualize_handler.ts @@ -64,7 +64,7 @@ export class EmbeddedVisualizeHandler { }, 100); private dataLoaderParams: RequestHandlerParams; - private appState: AppState; + private readonly appState?: AppState; private uiState: PersistedState; private dataLoader: VisualizeDataLoader;