From 86044c75485a734fcfbe26f7efa0ad494babe45a Mon Sep 17 00:00:00 2001 From: Stacey Gammon Date: Wed, 10 Oct 2018 14:18:26 +0100 Subject: [PATCH 1/4] Typescript state_monitor_factory --- src/ui/public/state_management/app_state.d.ts | 3 +- .../public/state_management/global_state.d.ts | 22 ++++++ src/ui/public/state_management/state.d.ts | 20 +++++ ...or_factory.js => state_monitor_factory.ts} | 74 ++++++++++++------- 4 files changed, 91 insertions(+), 28 deletions(-) create mode 100644 src/ui/public/state_management/global_state.d.ts create mode 100644 src/ui/public/state_management/state.d.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/global_state.d.ts b/src/ui/public/state_management/global_state.d.ts new file mode 100644 index 00000000000000..66a85d88956c77 --- /dev/null +++ b/src/ui/public/state_management/global_state.d.ts @@ -0,0 +1,22 @@ +/* + * 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 { State } from './state'; + +export type GlobalState = State; diff --git a/src/ui/public/state_management/state.d.ts b/src/ui/public/state_management/state.d.ts new file mode 100644 index 00000000000000..4130dbfb3eeeeb --- /dev/null +++ b/src/ui/public/state_management/state.d.ts @@ -0,0 +1,20 @@ +/* + * 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. + */ + +export type State = Record; 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); - } + }, }; } From 1068366abc4202ee8d7c4f4fe50c43158348f016 Mon Sep 17 00:00:00 2001 From: Stacey Gammon Date: Wed, 10 Oct 2018 14:52:04 +0100 Subject: [PATCH 2/4] Fix linter error with possibly undefined --- src/ui/public/visualize/loader/embedded_visualize_handler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/public/visualize/loader/embedded_visualize_handler.ts b/src/ui/public/visualize/loader/embedded_visualize_handler.ts index ea5c776ae1a42c..3c25a2b3cf2bcd 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 appState?: AppState; private uiState: PersistedState; private dataLoader: VisualizeDataLoader; From 1407dc5f47f5523edac9145eaf8a4b378d158d2d Mon Sep 17 00:00:00 2001 From: Stacey Gammon Date: Wed, 10 Oct 2018 15:19:29 +0100 Subject: [PATCH 3/4] Expand typings to include hash stuff and expand the State type definition more. --- src/ui/public/state_management/state.d.ts | 8 +++++++- ...provider.js => get_unhashable_states_provider.ts} | 8 ++++++-- ...unhash_query_string.js => unhash_query_string.ts} | 12 +++++++++++- 3 files changed, 24 insertions(+), 4 deletions(-) rename src/ui/public/state_management/state_hashing/{get_unhashable_states_provider.js => get_unhashable_states_provider.ts} (75%) rename src/ui/public/state_management/state_hashing/{unhash_query_string.js => unhash_query_string.ts} (65%) diff --git a/src/ui/public/state_management/state.d.ts b/src/ui/public/state_management/state.d.ts index 4130dbfb3eeeeb..39e4e672d02034 100644 --- a/src/ui/public/state_management/state.d.ts +++ b/src/ui/public/state_management/state.d.ts @@ -17,4 +17,10 @@ * under the License. */ -export type State = Record; +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.js b/src/ui/public/state_management/state_hashing/get_unhashable_states_provider.ts similarity index 75% rename from src/ui/public/state_management/state_hashing/get_unhashable_states_provider.js rename to src/ui/public/state_management/state_hashing/get_unhashable_states_provider.ts index 693da1b990c45d..6c43947640ed39 100644 --- a/src/ui/public/state_management/state_hashing/get_unhashable_states_provider.js +++ b/src/ui/public/state_management/state_hashing/get_unhashable_states_provider.ts @@ -17,8 +17,12 @@ * under the License. */ -export function getUnhashableStatesProvider(getAppState, globalState) { - return function getUnhashableStates() { +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.js b/src/ui/public/state_management/state_hashing/unhash_query_string.ts similarity index 65% rename from src/ui/public/state_management/state_hashing/unhash_query_string.js rename to src/ui/public/state_management/state_hashing/unhash_query_string.ts index be1f9c94a3c5ae..242b840282f39e 100644 --- a/src/ui/public/state_management/state_hashing/unhash_query_string.js +++ b/src/ui/public/state_management/state_hashing/unhash_query_string.ts @@ -18,8 +18,18 @@ */ import { mapValues } from 'lodash'; +import { ParsedUrlQuery } from 'querystring'; +import { State } from '../state'; -export function unhashQueryString(parsedQueryString, states) { +/** + * 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; From 26b797c416352e74a2586f36a2aec08a6edee060 Mon Sep 17 00:00:00 2001 From: Stacey Gammon Date: Thu, 18 Oct 2018 16:27:22 -0400 Subject: [PATCH 4/4] Mark readonly --- src/ui/public/visualize/loader/embedded_visualize_handler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/public/visualize/loader/embedded_visualize_handler.ts b/src/ui/public/visualize/loader/embedded_visualize_handler.ts index 3c25a2b3cf2bcd..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;