"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.flushContextAfterIndex = exports.flushContext = exports.fetchRenderableWithContext = exports.fetchRenderable = exports.fetchContext = exports.fetchAllRenderables = exports.elementLayer = exports.deleteArgumentAtIndex = exports.addElement = exports.addArgumentValue = void 0; exports.getSiblingContext = getSiblingContext; exports.setMultiplePositions = exports.setFilter = exports.setExpression = exports.setAstAtIndex = exports.setArgument = exports.removeElements = exports.insertNodes = void 0; var _reduxActions = require("redux-actions"); var _objectPathImmutable = _interopRequireDefault(require("object-path-immutable")); var _lodash = require("lodash"); var _interpreter = require("@kbn/interpreter"); var _create_thunk = require("../../lib/create_thunk"); var _workpad = require("../../lib/workpad"); var _workpad2 = require("../selectors/workpad"); var _resolved_args = require("../selectors/resolved_args"); var _defaults = require("../defaults"); var _i18n = require("../../../i18n"); var _functional = require("../../lib/aeroelastic/functional"); var _services = require("../../services"); var _transient = require("./transient"); var args = _interopRequireWildcard(require("./resolved_args")); function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } /* * 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. */ const { actionsElements: strings } = _i18n.ErrorStrings; const { set, del } = _objectPathImmutable.default; function getSiblingContext(state, elementId, checkIndex, path = ['ast.chain']) { const prevContextPath = [elementId, 'expressionContext', ...path, checkIndex]; const prevContextValue = (0, _resolved_args.getValue)(state, prevContextPath); // if a value is found, return it, along with the index it was found at if (prevContextValue != null) { return { index: checkIndex, context: prevContextValue }; } // check previous index while we're still above 0 const prevContextIndex = checkIndex - 1; if (prevContextIndex < 0) { return {}; } // walk back up to find the closest cached context available return getSiblingContext(state, elementId, prevContextIndex, path); } function getBareElement(el, includeId = false) { const props = ['position', 'expression', 'filter']; if (includeId) { return (0, _lodash.pick)(el, props.concat('id')); } return (0, _lodash.cloneDeep)((0, _lodash.pick)(el, props)); } const elementLayer = (0, _reduxActions.createAction)('elementLayer'); exports.elementLayer = elementLayer; const setMultiplePositions = (0, _reduxActions.createAction)('setMultiplePosition', repositionedElements => ({ repositionedElements })); exports.setMultiplePositions = setMultiplePositions; const flushContext = (0, _reduxActions.createAction)('flushContext'); exports.flushContext = flushContext; const flushContextAfterIndex = (0, _reduxActions.createAction)('flushContextAfterIndex'); exports.flushContextAfterIndex = flushContextAfterIndex; const fetchContextFn = ({ dispatch, getState }, index, element, fullRefresh = false, path) => { const pathToTarget = [...path.split('.'), 'chain']; const chain = (0, _lodash.get)(element, pathToTarget); const invalidIndex = chain ? index >= chain.length : true; if (!element || !chain || invalidIndex) { throw new Error(strings.getInvalidArgIndexErrorMessage(index)); } // cache context as the previous index const contextIndex = index - 1; const contextPath = [element.id, 'expressionContext', path, contextIndex]; // set context state to loading dispatch(args.setLoading({ path: contextPath })); // function to walk back up to find the closest context available const getContext = () => getSiblingContext(getState(), element.id, contextIndex - 1, [path]); const { index: prevContextIndex, context: prevContextValue } = fullRefresh !== true ? getContext() : {}; // modify the ast chain passed to the interpreter const astChain = chain.filter((exp, i) => { if (prevContextValue != null) { return i > prevContextIndex && i < index; } return i < index; }); const variables = (0, _workpad2.getWorkpadVariablesAsObject)(getState()); const { expressions } = _services.pluginServices.getServices(); const elementWithNewAst = set(element, pathToTarget, astChain); // get context data from a partial AST return expressions.interpretAst(elementWithNewAst.ast, variables, prevContextValue).then(value => { dispatch(args.setValue({ path: contextPath, value })); }); }; const fetchContext = (0, _create_thunk.createThunk)('fetchContext', fetchContextFn); exports.fetchContext = fetchContext; const fetchRenderableWithContextFn = ({ dispatch, getState }, element, ast, context) => { const argumentPath = [element.id, 'expressionRenderable']; dispatch(args.setLoading({ path: argumentPath })); const getAction = renderable => args.setValue({ path: argumentPath, value: renderable }); const variables = (0, _workpad2.getWorkpadVariablesAsObject)(getState()); const { expressions, notify } = _services.pluginServices.getServices(); return expressions.runInterpreter(ast, context, variables, { castToRender: true }).then(renderable => { dispatch(getAction(renderable)); }).catch(err => { notify.error(err); dispatch(getAction(err)); }); }; const fetchRenderableWithContext = (0, _create_thunk.createThunk)('fetchRenderableWithContext', fetchRenderableWithContextFn); exports.fetchRenderableWithContext = fetchRenderableWithContext; const fetchRenderable = (0, _create_thunk.createThunk)('fetchRenderable', ({ dispatch }, element) => { const ast = element.ast || (0, _interpreter.safeElementFromExpression)(element.expression); dispatch(fetchRenderableWithContext(element, ast, null)); }); exports.fetchRenderable = fetchRenderable; const fetchAllRenderables = (0, _create_thunk.createThunk)('fetchAllRenderables', ({ dispatch, getState }, { onlyActivePage = false } = {}) => { const workpadPages = (0, _workpad2.getPages)(getState()); const currentPageIndex = (0, _workpad2.getSelectedPageIndex)(getState()); const currentPage = workpadPages[currentPageIndex]; const otherPages = (0, _lodash.without)(workpadPages, currentPage); dispatch(args.inFlightActive()); function fetchElementsOnPages(pages) { const elements = []; pages.forEach(page => { page.elements.forEach(element => { elements.push(element); }); }); const renderablePromises = elements.map(element => { const ast = element.ast || (0, _interpreter.safeElementFromExpression)(element.expression); const argumentPath = [element.id, 'expressionRenderable']; const variables = (0, _workpad2.getWorkpadVariablesAsObject)(getState()); const { expressions, notify } = _services.pluginServices.getServices(); return expressions.runInterpreter(ast, null, variables, { castToRender: true }).then(renderable => ({ path: argumentPath, value: renderable })).catch(err => { notify.error(err); return { path: argumentPath, value: err }; }); }); return Promise.all(renderablePromises).then(renderables => { dispatch(args.setValues(renderables)); }); } if (onlyActivePage) { fetchElementsOnPages([currentPage]).then(() => dispatch(args.inFlightComplete())); } else { fetchElementsOnPages([currentPage]).then(() => { return otherPages.reduce((chain, page) => { return chain.then(() => fetchElementsOnPages([page])); }, Promise.resolve()); }).then(() => dispatch(args.inFlightComplete())); } }); exports.fetchAllRenderables = fetchAllRenderables; const insertNodes = (0, _create_thunk.createThunk)('insertNodes', ({ dispatch, type }, elements, pageId) => { const _insertNodes = (0, _reduxActions.createAction)(type); const newElements = elements.map(_lodash.cloneDeep); // move the root element so users can see that it was added newElements.forEach(newElement => { newElement.position.top = newElement.position.top + 10; newElement.position.left = newElement.position.left + 10; }); dispatch(_insertNodes({ pageId, elements: newElements })); // refresh all elements just once per `insertNodes call` if there's a filter on any, otherwise just render the new element if (elements.some(element => element.filter)) { dispatch(fetchAllRenderables()); } else { newElements.forEach(newElement => dispatch(fetchRenderable(newElement))); } }); exports.insertNodes = insertNodes; const removeElements = (0, _create_thunk.createThunk)('removeElements', ({ dispatch, getState }, rootElementIds, pageId) => { const state = getState(); // todo consider doing the group membership collation in aeroelastic, or the Redux reducer, when adding templates const allElements = (0, _workpad2.getNodes)(state, pageId); const allRoots = rootElementIds.map(id => allElements.find(e => id === e.id)).filter(d => d); const elementIds = (0, _functional.subMultitree)(e => e.id, e => e.position.parent, allElements, allRoots).map(e => e.id); const shouldRefresh = elementIds.some(elementId => { const element = (0, _workpad2.getNodeById)(state, elementId, pageId); const filterIsApplied = element.filter && element.filter.length > 0; return filterIsApplied; }); const _removeElements = (0, _reduxActions.createAction)('removeElements', (elementIds, pageId) => ({ elementIds, pageId })); dispatch(_removeElements(elementIds, pageId)); if (shouldRefresh) { dispatch(fetchAllRenderables()); } }); exports.removeElements = removeElements; const setFilter = (0, _create_thunk.createThunk)('setFilter', ({ dispatch }, filter, elementId, doRender = true) => { const _setFilter = (0, _reduxActions.createAction)('setFilter'); dispatch(_setFilter({ filter, elementId })); if (doRender === true) { dispatch(fetchAllRenderables()); } }); exports.setFilter = setFilter; const setExpression = (0, _create_thunk.createThunk)('setExpression', setExpressionFn); exports.setExpression = setExpression; function setExpressionFn({ dispatch, getState }, expression, elementId, pageId, doRender = true) { // dispatch action to update the element in state const _setExpression = (0, _reduxActions.createAction)('setExpression'); dispatch(_setExpression({ expression, elementId, pageId })); // read updated element from state and fetch renderable const updatedElement = (0, _workpad2.getNodeById)(getState(), elementId, pageId); // reset element.filter if element is no longer a filter // TODO: find a way to extract a list of filter renderers from the functions registry if (updatedElement.filter && !['dropdownControl', 'timefilterControl', 'exactly'].some(filter => updatedElement.expression.includes(filter))) { const filter = ''; dispatch(setFilter(filter, elementId, pageId, doRender)); // setFilter will trigger a re-render so we can skip the fetch here } else if (doRender === true) { dispatch(fetchRenderable(updatedElement)); } } const setAst = (0, _create_thunk.createThunk)('setAst', ({ dispatch }, ast, element, pageId, doRender = true) => { try { const expression = (0, _interpreter.toExpression)(ast); dispatch(setExpression(expression, element.id, pageId, doRender)); } catch (err) { const notifyService = _services.pluginServices.getServices().notify; notifyService.error(err); // TODO: remove this, may have been added just to cause a re-render, but why? dispatch(setExpression(element.expression, element.id, pageId, doRender)); } }); // index here is the top-level argument in the expression. for example in the expression // demodata().pointseries().plot(), demodata is 0, pointseries is 1, and plot is 2 const setAstAtIndex = (0, _create_thunk.createThunk)('setAstAtIndex', ({ dispatch, getState }, index, ast, element, pageId) => { // invalidate cached context for elements after this index dispatch(flushContextAfterIndex({ elementId: element.id, index })); const newElement = set(element, `ast.chain.${index}`, ast); const newAst = (0, _lodash.get)(newElement, 'ast'); // fetch renderable using existing context, if available (value is null if not cached) const { index: contextIndex, context: contextValue } = getSiblingContext(getState(), element.id, index - 1); // if we have a cached context, update the expression, but use cache when updating the renderable if (contextValue) { // set the expression, but skip the fetchRenderable step dispatch(setAst(newAst, element, pageId, false)); // use context when updating the expression, it will be passed to the intepreter const partialAst = { ...newAst, chain: newAst.chain.filter((exp, i) => { if (contextValue) { return i > contextIndex; } return i >= index; }) }; return dispatch(fetchRenderableWithContext(newElement, partialAst, contextValue)); } // if no cached context, update the ast like normal dispatch(setAst(newAst, element, pageId)); }); /** * Updating the value of the given argument of the element's expression. * @param {string} args.path - the path to the argument at the AST. Example: "ast.chain.0.arguments.some_arg.chain.1.arguments". * @param {string} args.argName - the argument name at the AST. * @param {number} args.valueIndex - the index of the value in the array of argument's values. * @param {any} args.value - the value to be set to the AST. * @param {any} args.element - the element, which contains the expression. * @param {any} args.pageId - the workpad's page, where element is located. */ exports.setAstAtIndex = setAstAtIndex; const setArgument = (0, _create_thunk.createThunk)('setArgument', ({ dispatch, getState }, args) => { const { argName, value, valueIndex, elementId, pageId, path } = args; let selector = `${path}.${argName}`; if (valueIndex != null) { selector += '.' + valueIndex; } const element = (0, _workpad2.getElementById)(getState(), elementId); const newElement = set(element, selector, value); const pathTerms = path.split('.'); const argumentChainPath = pathTerms.slice(0, 3); const argumnentChainIndex = (0, _lodash.last)(argumentChainPath); const newAst = (0, _lodash.get)(newElement, argumentChainPath); dispatch(setAstAtIndex(argumnentChainIndex, newAst, element, pageId)); }); /** * Adding the value to the given argument of the element's expression. * @param {string} args.path - the path to the argument at the AST. Example: "ast.chain.0.arguments.some_arg.chain.1.arguments". * @param {string} args.argName - the argument name at the given path of the AST. * @param {any} args.value - the value to be added to the array of argument's values at the AST. * @param {any} args.element - the element, which contains the expression. * @param {any} args.pageId - the workpad's page, where element is located. */ exports.setArgument = setArgument; const addArgumentValue = (0, _create_thunk.createThunk)('addArgumentValue', ({ dispatch, getState }, args) => { const { argName, value, elementId, path } = args; const element = (0, _workpad2.getElementById)(getState(), elementId); const values = (0, _lodash.get)(element, [...path.split('.'), argName], []); const newValue = values.concat(value); dispatch(setArgument({ ...args, elementId, value: newValue })); }); exports.addArgumentValue = addArgumentValue; const deleteArgumentAtIndex = (0, _create_thunk.createThunk)('deleteArgumentAtIndex', ({ dispatch }, args) => { const { element, pageId, argName, argIndex, path } = args; const pathTerms = path.split('.'); const argumentChainPath = pathTerms.slice(0, 3); const argumnentChainIndex = (0, _lodash.last)(argumentChainPath); const curVal = (0, _lodash.get)(element, [...pathTerms, argName]); let newElement = argIndex != null && curVal.length > 1 ? // if more than one val, remove the specified val del(element, `${path}.${argName}.${argIndex}`) : // otherwise, remove the entire key del(element, argName ? `${path}.${argName}` : path); const parentPath = pathTerms.slice(0, pathTerms.length - 1); const updatedArgument = (0, _lodash.get)(newElement, parentPath); if (Array.isArray(updatedArgument) && !updatedArgument.length) { newElement = del(element, parentPath); } dispatch(setAstAtIndex(argumnentChainIndex, (0, _lodash.get)(newElement, argumentChainPath), element, pageId)); }); /* payload: element defaults. Eg {expression: 'foo'} */ exports.deleteArgumentAtIndex = deleteArgumentAtIndex; const addElement = (0, _create_thunk.createThunk)('addElement', ({ dispatch }, pageId, element) => { const newElement = { ...(0, _defaults.getDefaultElement)(), ...getBareElement(element, true) }; if (element.width) { newElement.position.width = element.width; } if (element.height) { newElement.position.height = element.height; } const _addElement = (0, _reduxActions.createAction)('addElement'); dispatch(_addElement({ pageId, element: newElement })); // refresh all elements if there's a filter, otherwise just render the new element if (element.filter) { dispatch(fetchAllRenderables()); // element, which represents the group, should not be rendered. Its elements are rendered separately. } else if (!(0, _workpad.isGroupId)(newElement.id)) { dispatch(fetchRenderable(newElement)); } // select the new element dispatch((0, _transient.selectToplevelNodes)([newElement.id])); }); exports.addElement = addElement;