"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getAllElements = getAllElements; exports.getAutoplay = getAutoplay; exports.getContextForIndex = getContextForIndex; exports.getElementById = getElementById; exports.getElementCounts = getElementCounts; exports.getElementStats = getElementStats; exports.getElements = getElements; exports.getFullWorkpadPersisted = getFullWorkpadPersisted; exports.getGlobalFilterGroups = getGlobalFilterGroups; exports.getGlobalFilters = getGlobalFilters; exports.getNodeById = getNodeById; exports.getNodes = getNodes; exports.getNodesForPage = getNodesForPage; exports.getPageById = getPageById; exports.getPageIndexById = getPageIndexById; exports.getPages = getPages; exports.getRefreshInterval = getRefreshInterval; exports.getRenderedWorkpad = getRenderedWorkpad; exports.getRenderedWorkpadExpressions = getRenderedWorkpadExpressions; exports.getResolvedArgs = getResolvedArgs; exports.getSelectedElement = getSelectedElement; exports.getSelectedElementId = getSelectedElementId; exports.getSelectedPage = getSelectedPage; exports.getSelectedPageIndex = getSelectedPageIndex; exports.getSelectedResolvedArgs = getSelectedResolvedArgs; exports.getSelectedToplevelNodes = getSelectedToplevelNodes; exports.getWorkpad = getWorkpad; exports.getWorkpadBoundingBox = getWorkpadBoundingBox; exports.getWorkpadColors = getWorkpadColors; exports.getWorkpadHeight = getWorkpadHeight; exports.getWorkpadInfo = getWorkpadInfo; exports.getWorkpadName = getWorkpadName; exports.getWorkpadPersisted = getWorkpadPersisted; exports.getWorkpadVariables = getWorkpadVariables; exports.getWorkpadVariablesAsObject = getWorkpadVariablesAsObject; exports.getWorkpadWidth = getWorkpadWidth; exports.isWriteable = isWriteable; var _lodash = require("lodash"); var _interpreter = require("@kbn/interpreter"); var _modify_path = require("../../lib/modify_path"); var _assets = require("./assets"); var _filter = require("../../lib/filter"); /* * 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 workpadRoot = 'persistent.workpad'; const appendAst = element => ({ ...element, ast: (0, _interpreter.safeElementFromExpression)(element.expression) }); // workpad getters function getWorkpad(state) { return (0, _lodash.get)(state, workpadRoot); } // should we split `workpad.js` to eg. `workpad.js` (full) and `persistentWorkpadStructure.js` (persistent.workpad)? // how can we better disambiguate the two? now both the entire state and `persistent.workpad` are informally called workpad function getFullWorkpadPersisted(state) { return { ...getWorkpad(state), assets: (0, _assets.getAssets)(state) }; } function getWorkpadPersisted(state) { return getWorkpad(state); } function getWorkpadVariables(state) { const workpad = getWorkpad(state); return (0, _lodash.get)(workpad, 'variables', []); } function getWorkpadVariablesAsObject(state) { const variables = getWorkpadVariables(state); if (variables.length === 0) { return {}; } return variables.reduce((vars, v) => { vars[v.name] = v.value; return vars; }, {}); } function getWorkpadInfo(state) { return { ...getWorkpad(state), pages: undefined }; } function isWriteable(state) { return (0, _lodash.get)(state, (0, _modify_path.append)(workpadRoot, 'isWriteable'), true); } // page getters function getSelectedPageIndex(state) { return (0, _lodash.get)(state, (0, _modify_path.append)(workpadRoot, 'page')); } function getSelectedPage(state) { const pageIndex = getSelectedPageIndex(state); const pages = getPages(state); return (0, _lodash.get)(pages, `[${pageIndex}].id`); } function getPages(state) { return (0, _lodash.get)(state, (0, _modify_path.append)(workpadRoot, 'pages'), []); } function getPageById(state, id) { const pages = getPages(state); return pages.find(page => page.id === id); } function getPageIndexById(state, id) { const pages = getPages(state); return pages.findIndex(page => page.id === id); } function getWorkpadName(state) { return (0, _lodash.get)(state, (0, _modify_path.append)(workpadRoot, 'name')); } function getWorkpadHeight(state) { return (0, _lodash.get)(state, (0, _modify_path.append)(workpadRoot, 'height')); } function getWorkpadWidth(state) { return (0, _lodash.get)(state, (0, _modify_path.append)(workpadRoot, 'width')); } function getWorkpadBoundingBox(state) { return getPages(state).reduce((boundingBox, page) => { page.elements.forEach(({ position }) => { const { left, top, width, height } = position; const right = left + width; const bottom = top + height; if (left < boundingBox.left) { boundingBox.left = left; } if (top < boundingBox.top) { boundingBox.top = top; } if (right > boundingBox.right) { boundingBox.right = right; } if (bottom > boundingBox.bottom) { boundingBox.bottom = bottom; } }); return boundingBox; }, { left: 0, right: getWorkpadWidth(state), top: 0, bottom: getWorkpadHeight(state) }); } function getWorkpadColors(state) { return (0, _lodash.get)(state, (0, _modify_path.append)(workpadRoot, 'colors')); } function getAllElements(state) { return getPages(state).reduce((elements, page) => elements.concat(page.elements), []); } function getElementCounts(state) { const resolvedArgs = state.transient.resolvedArgs; const results = { ready: 0, pending: 0, error: 0 }; Object.values(resolvedArgs).filter(maybeResolvedArg => maybeResolvedArg !== undefined).forEach(resolvedArg => { const { expressionRenderable } = resolvedArg; if (!expressionRenderable) { results.pending++; return; } const { value, state: readyState } = expressionRenderable; if (value && value.as === 'error') { results.error++; } else if (readyState === 'ready') { results.ready++; } else { results.pending++; } }); return results; } function getElementStats(state) { return (0, _lodash.get)(state, 'transient.elementStats'); } function getGlobalFilters(state) { return getAllElements(state).reduce((acc, el) => { // check that a filter is defined if (el.filter != null && el.filter.length) { return acc.concat(el.filter); } return acc; }, []); } function buildGroupValues(args, onValue) { const argNames = Object.keys(args); return argNames.reduce((values, argName) => { // we only care about group values if (argName !== '_' && argName !== 'group') { return values; } return args[argName].reduce((acc, argValue) => { // delegate to passed function to buyld list return acc.concat(onValue(argValue, argName, args) || []); }, values); }, []); } function extractFilterGroups(ast) { if (ast.type !== 'expression') { throw new Error('AST must be an expression'); } return ast.chain.reduce((groups, item) => { // TODO: we always get a function here, right? const { function: fn, arguments: args } = item; if ((0, _filter.isExpressionWithFilters)(fn)) { // we have a filter function, extract groups from args return groups.concat(buildGroupValues(args, argValue => { // this only handles simple values if (argValue !== null && typeof argValue !== 'object') { return argValue; } })); } else { // dig into other functions, looking for filters function return groups.concat(buildGroupValues(args, argValue => { // recursively collect filter groups if (argValue !== null && typeof argValue === 'object' && argValue.type === 'expression') { return extractFilterGroups(argValue); } })); } }, []); } function getGlobalFilterGroups(state) { const filterGroups = getAllElements(state).reduce((acc, el) => { // check that a filter is defined if (el.filter != null && el.filter.length) { // extract the filter group const filterAst = (0, _interpreter.fromExpression)(el.filter); const filterGroup = (0, _lodash.get)(filterAst, `chain[0].arguments.filterGroup[0]`); // add any new group to the array if (filterGroup && filterGroup !== '' && !acc.includes(String(filterGroup))) { acc.push(String(filterGroup)); } } // extract groups from all expressions that use filters function if (el.expression != null && el.expression.length) { const expressionAst = (0, _interpreter.fromExpression)(el.expression); const groups = extractFilterGroups(expressionAst); groups.forEach(group => { if (!acc.includes(String(group))) { acc.push(String(group)); } }); } return acc; }, []); return filterGroups.sort(); } // element getters function getSelectedToplevelNodes(state) { return (0, _lodash.get)(state, 'transient.selectedToplevelNodes', []); } function getSelectedElementId(state) { const toplevelNodes = getSelectedToplevelNodes(state); return toplevelNodes.length === 1 ? toplevelNodes[0] : null; } function getSelectedElement(state) { return getElementById(state, getSelectedElementId(state)); } function getElements(state, pageId = undefined, withAst = true) { const id = pageId || getSelectedPage(state); if (!id) { return []; } const page = getPageById(state, id); const elements = (0, _lodash.get)(page, 'elements'); if (!elements) { return []; } // explicitly strip the ast, basically a fix for corrupted workpads // due to https://github.com/elastic/kibana-canvas/issues/260 // TODO: remove this once it's been in the wild a bit if (!withAst) { // @ts-expect-error 'ast' is no longer on the CanvasElement type, but since we // have JS calling into this, we can't be certain this call isn't necessary. return elements.map(el => (0, _lodash.omit)(el, ['ast'])); } const elementAppendAst = elem => appendAst(elem); return elements.map(elementAppendAst); } const augment = type => n => ({ ...n, position: { ...n.position, type }, ...(type === 'group' && { expression: 'shape fill="rgba(255,255,255,0)" | render' }) // fixme unify with mw/aeroelastic }); const getNodesOfPage = page => { const elements = (0, _lodash.get)(page, 'elements').map(augment('element')); const groups = (0, _lodash.get)(page, 'groups', []).map(augment('group')); return elements.concat(groups); }; function getNodesForPage(page, withAst) { const elements = getNodesOfPage(page); if (!elements) { return []; } // explicitly strip the ast, basically a fix for corrupted workpads // due to https://github.com/elastic/kibana-canvas/issues/260 // TODO: remove this once it's been in the wild a bit if (!withAst) { // @ts-expect-error 'ast' is no longer on the CanvasElement type, but since we // have JS calling into this, we can't be certain this call isn't necessary. return elements.map(el => (0, _lodash.omit)(el, ['ast'])); } // @ts-expect-error All of this AST business needs to be cleaned up. return elements.map(appendAst); } // todo unify or DRY up with `getElements` function getNodes(state, pageId, withAst = true) { const id = pageId || getSelectedPage(state); if (!id) { return []; } const page = getPageById(state, id); if (!page) { return []; } return getNodesForPage(page, withAst); } function getElementById(state, id, pageId) { const element = getElements(state, pageId, true).find(el => el.id === id); if (element) { return appendAst(element); } } function getNodeById(state, id, pageId) { // do we need to pass a truthy empty array instead of `true`? const group = getNodes(state, pageId, true).find(el => el.id === id); if (group) { return appendAst(group); } } // FIX: Fix the "any" typings below. Need to figure out how to properly type any "resolvedArg" function getResolvedArgs(state, elementId, path) { if (!elementId) { return; } const args = (0, _lodash.get)(state, ['transient', 'resolvedArgs', elementId]); if (path) { return (0, _lodash.get)(args, path); } return args; } function getSelectedResolvedArgs(state, path) { const elementId = getSelectedElementId(state); if (elementId) { return getResolvedArgs(state, elementId, path); } } function getContextForIndex(state, parentPath, index) { return getSelectedResolvedArgs(state, ['expressionContext', parentPath, index - 1]); } function getRefreshInterval(state) { return (0, _lodash.get)(state, 'transient.refresh.interval', 0); } function getAutoplay(state) { return (0, _lodash.get)(state, 'transient.autoplay'); } function getRenderedWorkpad(state) { const currentPages = getPages(state); const args = state.transient.resolvedArgs; const renderedPages = currentPages.map(page => { const { elements, ...rest } = page; return { ...rest, elements: elements.map(element => { const { id, position } = element; const arg = args[id]; if (!arg) { return null; } const { expressionRenderable } = arg; return { id, position, expressionRenderable }; }) }; }); const workpad = getWorkpad(state); const { pages, variables, ...rest } = workpad; return { pages: renderedPages, ...rest }; } function getRenderedWorkpadExpressions(state) { const workpad = getRenderedWorkpad(state); const { pages } = workpad; const expressions = []; pages.forEach(page => page.elements.forEach(element => { if (element && element.expressionRenderable) { const { value } = element.expressionRenderable; if (value) { const { as } = value; if (!expressions.includes(as)) { expressions.push(as); } } } })); return expressions; }