From 789b4ec08f3c70377bd44becfefedd0824ece05e Mon Sep 17 00:00:00 2001 From: Rashid Khan Date: Tue, 13 Feb 2018 09:07:20 -0700 Subject: [PATCH] Only big action (#329) * Try one big action * Set inflight * Move full workpad refresh into one big event --- public/lib/run_interpreter.js | 35 +++++++++ public/state/actions/elements.js | 102 ++++++++++++------------- public/state/actions/resolved_args.js | 1 + public/state/reducers/resolved_args.js | 8 ++ 4 files changed, 95 insertions(+), 51 deletions(-) create mode 100644 public/lib/run_interpreter.js diff --git a/public/lib/run_interpreter.js b/public/lib/run_interpreter.js new file mode 100644 index 0000000000000..8e4bb5eb6361f --- /dev/null +++ b/public/lib/run_interpreter.js @@ -0,0 +1,35 @@ +import { fromExpression } from '../../common/lib/ast'; +import { getType } from '../../common/lib/get_type'; +import { interpretAst } from './interpreter'; +import { notify } from './notify'; + +/** + * Runs interpreter, usually in the browser + * + * @param {object} ast - Executable AST + * @param {any} context - Initial context for AST execution + * @param {object} options + * @param {boolean} options.castToRender - try to cast to a type: render object? + * @param {boolean} options.retryRenderCasting - + * @returns {promise} + */ +export function runInterpreter(ast, context = null, options = {}) { + return interpretAst(ast, context) + .then(renderable => { + if (getType(renderable) === 'render') { + return renderable; + } + + if (options.castToRender) { + return runInterpreter(fromExpression('render'), renderable || context, { + castToRender: false, + }); + } + + return new Error(`Ack! I don't know how to render a '${getType(renderable)}'`); + }) + .catch(err => { + notify.error(err); + throw err; + }); +} diff --git a/public/state/actions/elements.js b/public/state/actions/elements.js index 2ded1c201a5bc..7055463460001 100644 --- a/public/state/actions/elements.js +++ b/public/state/actions/elements.js @@ -5,32 +5,13 @@ import { createThunk } from 'redux-thunks'; import { getPages, getElementById } from '../selectors/workpad'; import { getValue } from '../selectors/resolved_args'; import { getDefaultElement } from '../defaults'; -import { getType } from '../../../common/lib/get_type'; -import { fromExpression, toExpression, safeElementFromExpression } from '../../../common/lib/ast'; -import { interpretAst } from '../../lib/interpreter'; +import { toExpression, safeElementFromExpression } from '../../../common/lib/ast'; import { notify } from '../../lib/notify'; +import { runInterpreter } from '../../lib/run_interpreter'; +import { interpretAst } from '../../lib/interpreter'; import { selectElement } from './transient'; import * as args from './resolved_args'; -function runInterpreter(ast, context = null, retry = false) { - return interpretAst(ast, context) - .then(renderable => { - if (getType(renderable) === 'render') { - return renderable; - } - - if (!retry) { - return runInterpreter(fromExpression('render'), renderable || context, true); - } - - return new Error(`Ack! I don't know how to render a '${getType(renderable)}'`); - }) - .catch(err => { - notify.error(err); - throw err; - }); -} - function getSiblingContext(state, elementId, checkIndex) { const prevContextPath = [elementId, 'expressionContext', checkIndex]; const prevContextValue = getValue(state, prevContextPath); @@ -117,36 +98,34 @@ export const fetchContext = createThunk( } ); -export const fetchRenderableWithContext = createThunk( - 'fetchRenderableWithContext', - ({ dispatch }, element, ast, context) => { - const argumentPath = [element.id, 'expressionRenderable']; +const fetchRenderableWithContextFn = ({ dispatch }, element, ast, context) => { + const argumentPath = [element.id, 'expressionRenderable']; - dispatch( - args.setLoading({ - path: argumentPath, - }) - ); + dispatch( + args.setLoading({ + path: argumentPath, + }) + ); - return runInterpreter(ast, context) - .then(renderable => { - dispatch( - args.setValue({ - path: argumentPath, - value: renderable, - }) - ); - }) - .catch(err => { - notify.error(err); - dispatch( - args.setValue({ - path: argumentPath, - value: err, - }) - ); - }); - } + const getAction = renderable => + args.setValue({ + path: argumentPath, + value: renderable, + }); + + return runInterpreter(ast, context, { castToRender: true }) + .then(renderable => { + dispatch(getAction(renderable)); + }) + .catch(err => { + notify.error(err); + dispatch(getAction(err)); + }); +}; + +export const fetchRenderableWithContext = createThunk( + 'fetchRenderableWithContext', + fetchRenderableWithContextFn ); export const fetchRenderable = createThunk('fetchRenderable', ({ dispatch }, element) => { @@ -157,11 +136,32 @@ export const fetchRenderable = createThunk('fetchRenderable', ({ dispatch }, ele export const fetchAllRenderables = createThunk('fetchAllRenderables', ({ dispatch, getState }) => { const pages = getPages(getState()); + + const elements = []; pages.forEach(page => { page.elements.forEach(element => { - dispatch(fetchRenderable(element)); + elements.push(element); }); }); + + dispatch(args.inFlightActive()); + + const renderablePromises = elements.map(element => { + const ast = element.ast || safeElementFromExpression(element.expression); + const argumentPath = [element.id, 'expressionRenderable']; + + return runInterpreter(ast, null, { castToRender: true }) + .then(renderable => ({ path: argumentPath, value: renderable })) + .catch(err => { + notify.error(err); + return { path: argumentPath, value: err }; + }); + }); + + Promise.all(renderablePromises).then(renderables => { + dispatch(args.inFlightComplete()); + dispatch(args.setValues(renderables)); + }); }); export const duplicateElement = createThunk( diff --git a/public/state/actions/resolved_args.js b/public/state/actions/resolved_args.js index e83d61288b1c5..517daa6afda13 100644 --- a/public/state/actions/resolved_args.js +++ b/public/state/actions/resolved_args.js @@ -2,6 +2,7 @@ import { createAction } from 'redux-actions'; export const setLoading = createAction('setResolvedLoading'); export const setValue = createAction('setResolvedValue'); +export const setValues = createAction('setResolvedValues'); export const clear = createAction('clearResolvedValue'); export const inFlightActive = createAction('inFlightActive'); diff --git a/public/state/reducers/resolved_args.js b/public/state/reducers/resolved_args.js index edd9515ac1855..ce9041c7b9d32 100644 --- a/public/state/reducers/resolved_args.js +++ b/public/state/reducers/resolved_args.js @@ -66,6 +66,14 @@ export const resolvedArgsReducer = handleActions( return set(transientState, fullPath, getContext(value, false, oldVal)); }, + [actions.setValues]: (transientState, { payload }) => { + return payload.reduce((acc, setValueObj) => { + const fullPath = getFullPath(setValueObj.path); + const oldVal = get(acc, fullPath, null); + return set(acc, fullPath, getContext(setValueObj.value, false, oldVal)); + }, transientState); + }, + [actions.clear]: (transientState, { payload }) => { const { path } = payload; return del(transientState, getFullPath(path));