├── .eslintignore ├── .eslintrc.yml ├── .gitignore ├── Capture1.PNG ├── LICENSE.md ├── README.md ├── common └── index.ts ├── config.ts ├── kibana.json ├── package.json ├── public ├── components │ ├── sankey_options.tsx │ └── sankey_vis_options_lazy.tsx ├── index.ts ├── legacy │ ├── agg_config_result.js │ ├── agg_response_helper.js │ ├── angular │ │ ├── angular_bootstrap │ │ │ ├── bind_html │ │ │ │ └── bind_html.js │ │ │ ├── index.ts │ │ │ └── tooltip │ │ │ │ ├── position.js │ │ │ │ ├── tooltip.js │ │ │ │ ├── tooltip_html_unsafe_popup.html │ │ │ │ └── tooltip_popup.html │ │ ├── i18n │ │ │ ├── directive.ts │ │ │ ├── filter.ts │ │ │ ├── index.ts │ │ │ └── provider.ts │ │ ├── utils │ │ │ ├── index.ts │ │ │ ├── kbn_accessible_click.d.ts │ │ │ ├── kbn_accessible_click.js │ │ │ ├── private.d.ts │ │ │ └── private.js │ │ └── watch_multi │ │ │ ├── index.ts │ │ │ ├── watch_multi.d.ts │ │ │ └── watch_multi.js │ ├── bucket_helper.js │ ├── bucket_replace_property_helper.js │ ├── field_formatter.ts │ ├── get_inner_angular.ts │ ├── helpers │ │ ├── agg_response_helper.js │ │ ├── bucket_helper.js │ │ └── bucket_replace_property_helper.js │ ├── kibana_cloned_code │ │ ├── courier.ts │ │ ├── request_handler.ts │ │ ├── utils.ts │ │ └── visualization_fn.ts │ ├── sankey_table_vis.js │ ├── sankey_vis_controller.js │ ├── sankey_vis_legacy_fn.ts │ ├── sankey_vis_legacy_module.ts │ ├── sankey_vis_legacy_renderer.tsx │ ├── sankey_vis_legacy_request_handler.js │ ├── sankey_vis_legacy_response_handler.ts │ ├── table_vis.html │ ├── table_vis_legacy_type.ts │ ├── to_ast_legacy.ts │ └── vis_controller.ts ├── lib │ ├── filter.js │ └── observe_resize.js ├── plugin.ts ├── services.ts └── types.ts ├── sankey-drag.png ├── sankey-filtering.png ├── sankey-multifilter.png ├── sankey_8.png ├── test ├── agg_response_helperTest.js └── filterTest.js └── tsconfig.json /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | /build 3 | /local 4 | /target 5 | -------------------------------------------------------------------------------- /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | env: 2 | browser: true 3 | es6: true 4 | extends: 'eslint:recommended' 5 | parserOptions: 6 | ecmaVersion: 2018 7 | sourceType: module 8 | rules: 9 | "prettier/prettier": off 10 | indent: 11 | - error 12 | - 2 13 | linebreak-style: 14 | - error 15 | - unix 16 | quotes: 17 | - error 18 | - single 19 | semi: 20 | - error 21 | - always 22 | no-trailing-spaces: 23 | - 2 24 | - skipBlankLines: false 25 | no-console: error 26 | overrides: 27 | - files: ['index.js'] 28 | env: 29 | node: true 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | build/ 3 | target/ 4 | local 5 | npm-debug.log* 6 | package-lock.json 7 | yarn.lock 8 | .project 9 | *.zip 10 | -------------------------------------------------------------------------------- /Capture1.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uniberg/kbn_sankey_vis/048d2915a4167a09a4059641d0b6c46cbb30459d/Capture1.PNG -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kibana Sankey Diagram Plugin 2 | 3 | This is a sankey diagram visType plugin for Kibana. 4 | 5 | The plugin is compatible with Kibana 8.16.0 and some other older versions( please refer to the release section) 6 | 7 | This plugin was developed from . 8 | 9 | Here is an example: 10 | 11 | ![Sankey](sankey_8.png) 12 | 13 | The plugin has some new features: 14 | - A filter according to the node will be applied once you click on a specific node: 15 | ![filter](sankey-filtering.png) 16 | 17 | - A filter according to the path will be applied once you click on a specific path: 18 | ![filter](sankey-multifilter.png) 19 | 20 | - Enabling the drag and drop on the nodes can now be activated/deactivated. 21 | Please note that enabling this feature will result on disabling the previous filtering. 22 | ![filter](sankey-drag.png) 23 | 24 | # Install 25 | 26 | ``` 27 | git clone https://github.com/uniberg/kbn_sankey_vis.git sankey_vis 28 | cd sankey_vis 29 | yarn install 30 | yarn compile 31 | yarn start 32 | ``` 33 | # Use 34 | In development mode: 35 | * Navigate to Kibana (http://localhost:5601). 36 | * Go to "Visualize Library" app. 37 | * Click "Create visualization". 38 | * Click "Aggregation Based". 39 | * Choose "Sankey Diagram" 40 | # Uninstall 41 | 42 | ``` 43 | bin/kibana-plugin remove kbn-sankey-vis 44 | ``` 45 | 46 | # Building a Release 47 | Building a release only means packaging the plugin with all its dependencies into a zip archive. Important is to put the plugin in a folder called kibana before zipping it. 48 | The following steps would produce a release of the current head master branch. 49 | ``` 50 | mkdir kibana 51 | git clone https://github.com/uniberg/kbn_sankey_vis.git sankey_vis 52 | cd sankey_vis 53 | [optional] git checkout -branch 54 | yarn install 55 | yarn compile-and-build 56 | ``` 57 | -------------------------------------------------------------------------------- /common/index.ts: -------------------------------------------------------------------------------- 1 | export const PLUGIN_ID = 'kbnSankeyVis'; 2 | export const PLUGIN_NAME = 'kbnSankeyVis'; 3 | -------------------------------------------------------------------------------- /config.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one 3 | * or more contributor license agreements. Licensed under the Elastic License 4 | * 2.0 and the Server Side Public License, v 1; you may not use this file except 5 | * in compliance with, at your election, the Elastic License 2.0 or the Server 6 | * Side Public License, v 1. 7 | */ 8 | 9 | import { schema, TypeOf } from '@kbn/config-schema'; 10 | 11 | export const configSchema = schema.object({ 12 | enabled: schema.boolean({ defaultValue: true }), 13 | legacyVisEnabled: schema.boolean({ defaultValue: false }), 14 | }); 15 | 16 | export type ConfigSchema = TypeOf; 17 | -------------------------------------------------------------------------------- /kibana.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "kbnSankeyVis", 3 | "version": "kibana", 4 | "server": false, 5 | "owner": { 6 | "name": "Chenryn ", 7 | "maintainer": "Ch-bas " 8 | }, 9 | "ui": true, 10 | "requiredPlugins": [ 11 | "visualizations", 12 | "expressions", 13 | "bfetch", 14 | "data", 15 | "fieldFormats", 16 | "dataViews", 17 | "inspector", 18 | "kibanaUtils", 19 | "kibanaReact", 20 | "share", 21 | "charts", 22 | "visDefaultEditor" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kbn-sankey-vis", 3 | "version": "8.14.0", 4 | "kibana": { 5 | "version": "kibana" 6 | }, 7 | "authors": [ 8 | "Chenryn ", 9 | "Ch-bas " 10 | ], 11 | "description": "Sankey Diagram Visualization Plugin", 12 | "license": "Apache-2.0", 13 | "homepage": "https://github.com/uniberg/kbn_sankey_vis", 14 | "repository": "https://github.com/uniberg/kbn_sankey_vis.git", 15 | "main": "index.js", 16 | "scripts": { 17 | "preinstall": "node ../../preinstall_check", 18 | "start": "cd ../.. && node scripts/kibana --dev", 19 | "start-xpack": "cd .. && node scripts/kibana --dev", 20 | "debug": "node --nolazy --inspect ../../scripts/kibana --dev", 21 | "test": "nyc --all mocha", 22 | "build": "node ../../scripts/plugin_helpers.js build", 23 | "compile-and-build": "node ../../scripts/plugin_helpers.js build", 24 | "compile": "rm -rf ./target && export NODE_OPTIONS=--openssl-legacy-provider && node ../../scripts/plugin_helpers.js build --kibana-version none --skip-archive && mv build/kibana/kbnSankeyVis/target . && rm -rf build/*", 25 | "plugin-helpers": "node ../../scripts/plugin_helpers", 26 | "dev": "yarn plugin-helpers dev" 27 | }, 28 | "dependencies": { 29 | "angular": "^1.8.0", 30 | "angular-recursion": "^1.0.5", 31 | "angular-sanitize": "^1.8.2", 32 | "d3-plugins-sankey": "https://github.com/uniberg/d3-plugins-sankey.git" 33 | }, 34 | "devDependencies": { 35 | "@types/angular": "^1.8.0", 36 | "@fortawesome/fontawesome-free": "5.15.2", 37 | "mocha": "~5.0.4", 38 | "nyc": "^13.3.0" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /public/components/sankey_options.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import { VisEditorOptionsProps } from '@kbn/visualizations-plugin/public'; 3 | import { EuiFlexItem, EuiCheckbox, EuiCallOut, EuiSpacer } from '@elastic/eui'; 4 | export interface SankeyVisParams { 5 | type: 'table'; 6 | fieldColumns?: any[]; 7 | linesComputedFilter: string; 8 | rowsComputedCss: string; 9 | hiddenColumns: string; 10 | computedColsPerSplitCol: boolean; 11 | sortSplitCols: boolean; 12 | hideExportLinks: boolean; 13 | csvExportWithTotal: boolean; 14 | csvFullExport: boolean; 15 | stripedRows: boolean; 16 | addRowNumberColumn: boolean; 17 | csvEncoding: string; 18 | 19 | // Basic Settings 20 | perPage: number | ''; 21 | showPartialRows: boolean; 22 | showMetricsAtAllLevels: boolean; 23 | sort: { 24 | columnIndex: number | null; 25 | direction: string | null; 26 | }; 27 | showTotal: boolean; 28 | totalLabel: string; 29 | dragAndDrop: boolean; 30 | // Filter Bar 31 | showFilterBar: boolean; 32 | filterCaseSensitive: boolean; 33 | filterBarHideable: boolean; 34 | filterAsYouType: boolean; 35 | filterTermsSeparately: boolean; 36 | filterHighlightResults: boolean; 37 | filterBarWidth: string; 38 | } 39 | // For the current version of the Sankey Diagram, we do not need any option 40 | // returning a react component is required 41 | function SankeyOptions({ 42 | aggs, 43 | stateParams, 44 | setValidity, 45 | setValue, 46 | }: VisEditorOptionsProps) { 47 | const isPerPageValid = stateParams.perPage === '' || stateParams.perPage > 0; 48 | const [checked, setChecked] = useState(false); 49 | const onChange = (e) => { 50 | setChecked(e.target.checked); 51 | setValue('dragAndDrop', e.target.checked) 52 | }; 53 | useEffect(() => { 54 | setValidity(isPerPageValid); 55 | }, [isPerPageValid, setValidity]); 56 | return ( 57 |
58 | 59 | { checked ? () : undefined } 64 | 65 | onChange(e)} 70 | /> 71 | 72 |
73 | ) 74 | 75 | } 76 | // default export required for React.Lazy 77 | // eslint-disable-next-line import/no-default-export 78 | export { SankeyOptions as default }; 79 | -------------------------------------------------------------------------------- /public/components/sankey_vis_options_lazy.tsx: -------------------------------------------------------------------------------- 1 | import React, { Suspense, lazy } from 'react'; 2 | import { EuiLoadingSpinner } from '@elastic/eui'; 3 | import { VisEditorOptionsProps } from '@kbn/visualizations-plugin/public'; 4 | 5 | export enum AggTypes { 6 | SUM = 'sum', 7 | AVG = 'avg', 8 | MIN = 'min', 9 | MAX = 'max', 10 | COUNT = 'count', 11 | } 12 | interface TableVisParams { 13 | perPage: number | ''; 14 | showPartialRows: boolean; 15 | showMetricsAtAllLevels: boolean; 16 | showToolbar: boolean; 17 | showTotal: boolean; 18 | totalFunc: AggTypes; 19 | dragAndDrop: boolean; 20 | percentageCol: string; 21 | row?: boolean; 22 | } 23 | const SankeyOptionsComponent = lazy(() => import('./sankey_options')); 24 | 25 | export const SankeyOptions = (props: VisEditorOptionsProps) => ( 26 | }> 27 | 28 | 29 | ); 30 | -------------------------------------------------------------------------------- /public/index.ts: -------------------------------------------------------------------------------- 1 | import { SankeyVisPlugin as Plugin } from './plugin'; 2 | 3 | export function plugin() { 4 | return new Plugin(); 5 | } 6 | -------------------------------------------------------------------------------- /public/legacy/agg_config_result.js: -------------------------------------------------------------------------------- 1 | import { fieldFormatter } from './field_formatter'; 2 | 3 | function computeBasePath(pathname) { 4 | const endIndex = pathname ? pathname.indexOf('/app/kibana') : -1; 5 | const basePath = endIndex !== -1 ? pathname.substring(0, endIndex) : ''; 6 | return basePath; 7 | } 8 | 9 | // eslint-disable-next-line import/no-default-export 10 | export default function AggConfigResult(aggConfig, parent, value, key, filters) { 11 | this.key = key; 12 | this.value = value; 13 | this.aggConfig = aggConfig; 14 | this.filters = filters; 15 | this.$parent = parent; 16 | 17 | if (aggConfig.type.type === 'buckets') { 18 | this.type = 'bucket'; 19 | } else { 20 | this.type = 'metric'; 21 | } 22 | } 23 | 24 | /** 25 | * Returns an array of the aggConfigResult and parents up the branch 26 | * @returns {array} Array of aggConfigResults 27 | */ 28 | AggConfigResult.prototype.getPath = function () { 29 | return (function walk(result, path) { 30 | path.unshift(result); 31 | if (result.$parent) return walk(result.$parent, path); 32 | return path; 33 | }(this, [])); 34 | }; 35 | 36 | /** 37 | * Returns an Elasticsearch filter that represents the result. 38 | * @returns {object} Elasticsearch filter 39 | */ 40 | AggConfigResult.prototype.createFilter = function () { 41 | return this.filters || this.aggConfig.createFilter(this.key); 42 | }; 43 | 44 | AggConfigResult.prototype.toString = function (contentType) { 45 | contentType = contentType || 'text'; 46 | let fieldFormatterInstance = this.aggConfig[`${contentType}FieldFormatter`]; 47 | if (!fieldFormatterInstance) { 48 | fieldFormatterInstance = this.aggConfig[`${contentType}FieldFormatter`] = fieldFormatter(this.aggConfig, contentType); 49 | } 50 | if (!this.aggConfig.formatterOptions) { 51 | const parsedUrl = { 52 | origin: window.location.origin, 53 | pathname: window.location.pathname, 54 | basePath: computeBasePath(window.location.pathname), 55 | }; 56 | this.aggConfig.formatterOptions = { parsedUrl }; 57 | } 58 | return fieldFormatterInstance(this.value, this.aggConfig.formatterOptions); 59 | }; 60 | 61 | AggConfigResult.prototype.valueOf = function () { 62 | return this.value; 63 | }; 64 | -------------------------------------------------------------------------------- /public/legacy/agg_response_helper.js: -------------------------------------------------------------------------------- 1 | const { bucketReplaceProperty } = require('./bucket_replace_property_helper'); 2 | 3 | const stringify = require('json-stable-stringify'); 4 | 5 | module.exports = (function() { 6 | function _generateNodesMap(paths) { 7 | const nodesMap = new Map(); 8 | paths.map(path => { 9 | path 10 | .slice(0, -1) 11 | .map((nodeName, index) => { 12 | const nodeKeyStr = _getNodeId(index, nodeName); 13 | nodesMap.set(nodeKeyStr, nodeName); 14 | }); 15 | }); 16 | return nodesMap; 17 | } 18 | 19 | // TODO: Json in Json -> ugly code 20 | function _getLinkId( sourceNodeId, targetNodeId) { 21 | const linkKey = { sourceNodeId, targetNodeId }; 22 | return stringify(linkKey); 23 | } 24 | 25 | function _getNodeId(layerIndex, nodeName) { 26 | const nodeKey = { layerIndex, nodeName }; 27 | return stringify(nodeKey); 28 | } 29 | 30 | function _generateLinksMap(paths, nodesMap) { 31 | const linksMap = new Map(); 32 | paths.map(path => { 33 | for(let layer = 0; layer < path.length - 2; layer++) { 34 | const sourceNodeName = path[layer]; 35 | const targetNodeName = path[layer + 1]; 36 | const value = path[path.length - 1]; 37 | 38 | const sourceNodeId = _getNodeId(layer, sourceNodeName); 39 | const targetNodeId = _getNodeId(layer + 1, targetNodeName); 40 | 41 | const linkKeyStr = stringify({ sourceNodeId, targetNodeId }); 42 | 43 | const oldValue = linksMap.get(linkKeyStr); 44 | if (oldValue) { 45 | // Sum up values 46 | const newValue = oldValue + value; 47 | linksMap.set(linkKeyStr, newValue); 48 | } else { 49 | // Add link 50 | linksMap.set(linkKeyStr, value); 51 | } 52 | } 53 | }); 54 | return linksMap; 55 | } 56 | 57 | function _generateNodeIdMap(nodes) { 58 | const nodeIdMap = new Map(); 59 | nodes.forEach(({nodeId}, nodeArrayIndex) => { 60 | nodeIdMap.set(nodeId, nodeArrayIndex); 61 | }); 62 | return nodeIdMap; 63 | } 64 | 65 | function _generateNodeArray(nodesMap) { 66 | const nodeArray = []; 67 | nodesMap.forEach((nodeName, nodeId) => { 68 | nodeArray.push({ nodeId, name: nodeName }); 69 | }); 70 | return nodeArray; 71 | } 72 | 73 | function _convertLinksMapToArray(linksMap, nodeIdMap) { 74 | const links = []; 75 | linksMap.forEach((value, linkId) => { 76 | const {sourceNodeId, targetNodeId} = JSON.parse(linkId); 77 | links.push({ 78 | source: nodeIdMap.get(sourceNodeId), 79 | target: nodeIdMap.get(targetNodeId), 80 | value 81 | }); 82 | }); 83 | return links; 84 | } 85 | function _convertObjectToArray({rows, missingValues, groupBucket}) { 86 | // In the new kibana version, the rows are of type object , where they should be of type array to match the rest of the algorithm . 87 | // This is a function to convert the object ( 'col-0-2' : [array]... ) to (0 : [array]) 88 | let newRows = []; 89 | // The structure of the bucket is as follow: { col-0-1: string, col-0-2: string... } 90 | rows.map(function(bucket) { 91 | // Object in javascript are mutable, this is why we should create a copy and then update it without modifying the original 'resp.rows' 92 | // otherwise the rest of the application will be broken 93 | // https://github.com/uniberg/kbn_sankey_vis/issues/14 94 | const bucketCopy = Object.assign({}, bucket); 95 | // Cell refers to col-0-1, col-0-2... 96 | for (let cell in bucketCopy) { 97 | // Update the bucket if 'Show missing values' is checked 98 | // by default, the value is '__missing__' 99 | // kibana/kibana-repo/src/ui/public/agg_types/buckets/terms.js 100 | if (bucketCopy[cell] === '__missing__' && missingValues.length > 0) { 101 | bucketReplaceProperty(missingValues, bucketCopy, cell); 102 | } 103 | // Update the bucket if 'Group other bucket' is checked 104 | if (bucketCopy[cell] === '__other__' && groupBucket.length > 0) { 105 | bucketReplaceProperty(groupBucket, bucketCopy, cell); 106 | } 107 | Object.defineProperty( 108 | bucketCopy, 109 | cell.split('-')[1], 110 | Object.getOwnPropertyDescriptor(bucketCopy, cell) 111 | ); 112 | delete bucketCopy[cell]; 113 | } 114 | newRows.push(_.values(bucketCopy)); 115 | }); 116 | 117 | return newRows; 118 | } 119 | function aggregate({rows, missingValues, groupBucket}) { 120 | const paths = _convertObjectToArray({rows, missingValues, groupBucket}); 121 | const nodesMap = _generateNodesMap(paths); 122 | const linksMap = _generateLinksMap(paths, nodesMap); 123 | const nodes = _generateNodeArray(nodesMap); 124 | const nodeIdMap = _generateNodeIdMap(nodes); 125 | const convertedLinks = _convertLinksMapToArray(linksMap, nodeIdMap); 126 | const cleanedD3Nodes = nodes.map(({name}) => { return { name }; }); 127 | 128 | return { 129 | nodes: cleanedD3Nodes, 130 | links: convertedLinks 131 | }; 132 | } 133 | 134 | return { 135 | _generateNodesMap, 136 | _getLinkId, 137 | _getNodeId, 138 | _generateLinksMap, 139 | _generateNodeIdMap, 140 | _generateNodeArray, 141 | _convertLinksMapToArray, 142 | 143 | aggregate 144 | }; 145 | 146 | }()); 147 | -------------------------------------------------------------------------------- /public/legacy/angular/angular_bootstrap/bind_html/bind_html.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | 3 | export function initBindHtml() { 4 | angular 5 | .module('ui.bootstrap.bindHtml', []) 6 | 7 | .directive('bindHtmlUnsafe', function() { 8 | return function(scope, element, attr) { 9 | element.addClass('ng-binding').data('$binding', attr.bindHtmlUnsafe); 10 | scope.$watch(attr.bindHtmlUnsafe, function bindHtmlUnsafeWatchAction(value) { 11 | element.html(value || ''); 12 | }); 13 | }; 14 | }); 15 | } 16 | -------------------------------------------------------------------------------- /public/legacy/angular/angular_bootstrap/index.ts: -------------------------------------------------------------------------------- 1 | import { once } from 'lodash'; 2 | import angular from 'angular'; 3 | 4 | // @ts-ignore 5 | import { initBindHtml } from './bind_html/bind_html'; 6 | // @ts-ignore 7 | import { initBootstrapTooltip } from './tooltip/tooltip'; 8 | 9 | import tooltipPopup from './tooltip/tooltip_popup.html'; 10 | 11 | import tooltipUnsafePopup from './tooltip/tooltip_html_unsafe_popup.html'; 12 | 13 | export const initAngularBootstrap = once(() => { 14 | /* 15 | * angular-ui-bootstrap 16 | * http://angular-ui.github.io/bootstrap/ 17 | 18 | * Version: 0.12.1 - 2015-02-20 19 | * License: MIT 20 | */ 21 | angular.module('ui.bootstrap', [ 22 | 'ui.bootstrap.tpls', 23 | 'ui.bootstrap.bindHtml', 24 | 'ui.bootstrap.tooltip', 25 | ]); 26 | 27 | angular.module('ui.bootstrap.tpls', [ 28 | 'template/tooltip/tooltip-html-unsafe-popup.html', 29 | 'template/tooltip/tooltip-popup.html', 30 | ]); 31 | 32 | initBindHtml(); 33 | initBootstrapTooltip(); 34 | 35 | angular.module('template/tooltip/tooltip-html-unsafe-popup.html', []).run([ 36 | '$templateCache', 37 | function($templateCache: any) { 38 | $templateCache.put('template/tooltip/tooltip-html-unsafe-popup.html', tooltipUnsafePopup); 39 | }, 40 | ]); 41 | 42 | angular.module('template/tooltip/tooltip-popup.html', []).run([ 43 | '$templateCache', 44 | function($templateCache: any) { 45 | $templateCache.put('template/tooltip/tooltip-popup.html', tooltipPopup); 46 | }, 47 | ]); 48 | }); 49 | -------------------------------------------------------------------------------- /public/legacy/angular/angular_bootstrap/tooltip/position.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | import angular from 'angular'; 4 | 5 | export function initBootstrapPosition() { 6 | angular 7 | .module('ui.bootstrap.position', []) 8 | 9 | /** 10 | * A set of utility methods that can be use to retrieve position of DOM elements. 11 | * It is meant to be used where we need to absolute-position DOM elements in 12 | * relation to other, existing elements (this is the case for tooltips, popovers, 13 | * typeahead suggestions etc.). 14 | */ 15 | .factory('$position', [ 16 | '$document', 17 | '$window', 18 | function($document, $window) { 19 | function getStyle(el, cssprop) { 20 | if (el.currentStyle) { 21 | //IE 22 | return el.currentStyle[cssprop]; 23 | } else if ($window.getComputedStyle) { 24 | return $window.getComputedStyle(el)[cssprop]; 25 | } 26 | // finally try and get inline style 27 | return el.style[cssprop]; 28 | } 29 | 30 | /** 31 | * Checks if a given element is statically positioned 32 | * @param element - raw DOM element 33 | */ 34 | function isStaticPositioned(element) { 35 | return (getStyle(element, 'position') || 'static') === 'static'; 36 | } 37 | 38 | /** 39 | * returns the closest, non-statically positioned parentOffset of a given element 40 | * @param element 41 | */ 42 | const parentOffsetEl = function(element) { 43 | const docDomEl = $document[0]; 44 | let offsetParent = element.offsetParent || docDomEl; 45 | while (offsetParent && offsetParent !== docDomEl && isStaticPositioned(offsetParent)) { 46 | offsetParent = offsetParent.offsetParent; 47 | } 48 | return offsetParent || docDomEl; 49 | }; 50 | 51 | return { 52 | /** 53 | * Provides read-only equivalent of jQuery's position function: 54 | * http://api.jquery.com/position/ 55 | */ 56 | position: function(element) { 57 | const elBCR = this.offset(element); 58 | let offsetParentBCR = { top: 0, left: 0 }; 59 | const offsetParentEl = parentOffsetEl(element[0]); 60 | if (offsetParentEl !== $document[0]) { 61 | offsetParentBCR = this.offset(angular.element(offsetParentEl)); 62 | offsetParentBCR.top += offsetParentEl.clientTop - offsetParentEl.scrollTop; 63 | offsetParentBCR.left += offsetParentEl.clientLeft - offsetParentEl.scrollLeft; 64 | } 65 | 66 | const boundingClientRect = element[0].getBoundingClientRect(); 67 | return { 68 | width: boundingClientRect.width || element.prop('offsetWidth'), 69 | height: boundingClientRect.height || element.prop('offsetHeight'), 70 | top: elBCR.top - offsetParentBCR.top, 71 | left: elBCR.left - offsetParentBCR.left, 72 | }; 73 | }, 74 | 75 | /** 76 | * Provides read-only equivalent of jQuery's offset function: 77 | * http://api.jquery.com/offset/ 78 | */ 79 | offset: function(element) { 80 | const boundingClientRect = element[0].getBoundingClientRect(); 81 | return { 82 | width: boundingClientRect.width || element.prop('offsetWidth'), 83 | height: boundingClientRect.height || element.prop('offsetHeight'), 84 | top: 85 | boundingClientRect.top + 86 | ($window.pageYOffset || $document[0].documentElement.scrollTop), 87 | left: 88 | boundingClientRect.left + 89 | ($window.pageXOffset || $document[0].documentElement.scrollLeft), 90 | }; 91 | }, 92 | 93 | /** 94 | * Provides coordinates for the targetEl in relation to hostEl 95 | */ 96 | positionElements: function(hostEl, targetEl, positionStr, appendToBody) { 97 | const positionStrParts = positionStr.split('-'); 98 | const pos0 = positionStrParts[0]; 99 | const pos1 = positionStrParts[1] || 'center'; 100 | 101 | let targetElPos; 102 | 103 | const hostElPos = appendToBody ? this.offset(hostEl) : this.position(hostEl); 104 | 105 | const targetElWidth = targetEl.prop('offsetWidth'); 106 | const targetElHeight = targetEl.prop('offsetHeight'); 107 | 108 | const shiftWidth = { 109 | center: function() { 110 | return hostElPos.left + hostElPos.width / 2 - targetElWidth / 2; 111 | }, 112 | left: function() { 113 | return hostElPos.left; 114 | }, 115 | right: function() { 116 | return hostElPos.left + hostElPos.width; 117 | }, 118 | }; 119 | 120 | const shiftHeight = { 121 | center: function() { 122 | return hostElPos.top + hostElPos.height / 2 - targetElHeight / 2; 123 | }, 124 | top: function() { 125 | return hostElPos.top; 126 | }, 127 | bottom: function() { 128 | return hostElPos.top + hostElPos.height; 129 | }, 130 | }; 131 | 132 | switch (pos0) { 133 | case 'right': 134 | targetElPos = { 135 | top: shiftHeight[pos1](), 136 | left: shiftWidth[pos0](), 137 | }; 138 | break; 139 | case 'left': 140 | targetElPos = { 141 | top: shiftHeight[pos1](), 142 | left: hostElPos.left - targetElWidth, 143 | }; 144 | break; 145 | case 'bottom': 146 | targetElPos = { 147 | top: shiftHeight[pos0](), 148 | left: shiftWidth[pos1](), 149 | }; 150 | break; 151 | default: 152 | targetElPos = { 153 | top: hostElPos.top - targetElHeight, 154 | left: shiftWidth[pos1](), 155 | }; 156 | break; 157 | } 158 | 159 | return targetElPos; 160 | }, 161 | }; 162 | }, 163 | ]); 164 | } 165 | -------------------------------------------------------------------------------- /public/legacy/angular/angular_bootstrap/tooltip/tooltip.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | 3 | import { initBootstrapPosition } from './position'; 4 | 5 | export function initBootstrapTooltip() { 6 | initBootstrapPosition(); 7 | /** 8 | * The following features are still outstanding: animation as a 9 | * function, placement as a function, inside, support for more triggers than 10 | * just mouse enter/leave, html tooltips, and selector delegation. 11 | */ 12 | angular 13 | .module('ui.bootstrap.tooltip', ['ui.bootstrap.position']) 14 | 15 | /** 16 | * The $tooltip service creates tooltip- and popover-like directives as well as 17 | * houses global options for them. 18 | */ 19 | .provider('$tooltip', function() { 20 | // The default options tooltip and popover. 21 | const defaultOptions = { 22 | placement: 'top', 23 | animation: true, 24 | popupDelay: 0, 25 | }; 26 | 27 | // Default hide triggers for each show trigger 28 | const triggerMap = { 29 | mouseenter: 'mouseleave', 30 | click: 'click', 31 | focus: 'blur', 32 | }; 33 | 34 | // The options specified to the provider globally. 35 | const globalOptions = {}; 36 | 37 | /** 38 | * `options({})` allows global configuration of all tooltips in the 39 | * application. 40 | * 41 | * var app = angular.module( 'App', ['ui.bootstrap.tooltip'], function( $tooltipProvider ) { 42 | * // place tooltips left instead of top by default 43 | * $tooltipProvider.options( { placement: 'left' } ); 44 | * }); 45 | */ 46 | this.options = function(value) { 47 | angular.extend(globalOptions, value); 48 | }; 49 | 50 | /** 51 | * This allows you to extend the set of trigger mappings available. E.g.: 52 | * 53 | * $tooltipProvider.setTriggers( 'openTrigger': 'closeTrigger' ); 54 | */ 55 | this.setTriggers = function setTriggers(triggers) { 56 | angular.extend(triggerMap, triggers); 57 | }; 58 | 59 | /** 60 | * This is a helper function for translating camel-case to snake-case. 61 | */ 62 | function snake_case(name) { 63 | const regexp = /[A-Z]/g; 64 | const separator = '-'; 65 | return name.replace(regexp, function(letter, pos) { 66 | return (pos ? separator : '') + letter.toLowerCase(); 67 | }); 68 | } 69 | 70 | /** 71 | * Returns the actual instance of the $tooltip service. 72 | * TODO support multiple triggers 73 | */ 74 | this.$get = [ 75 | '$window', 76 | '$compile', 77 | '$timeout', 78 | '$document', 79 | '$position', 80 | '$interpolate', 81 | function($window, $compile, $timeout, $document, $position, $interpolate) { 82 | return function $tooltip(type, prefix, defaultTriggerShow) { 83 | const options = angular.extend({}, defaultOptions, globalOptions); 84 | 85 | /** 86 | * Returns an object of show and hide triggers. 87 | * 88 | * If a trigger is supplied, 89 | * it is used to show the tooltip; otherwise, it will use the `trigger` 90 | * option passed to the `$tooltipProvider.options` method; else it will 91 | * default to the trigger supplied to this directive factory. 92 | * 93 | * The hide trigger is based on the show trigger. If the `trigger` option 94 | * was passed to the `$tooltipProvider.options` method, it will use the 95 | * mapped trigger from `triggerMap` or the passed trigger if the map is 96 | * undefined; otherwise, it uses the `triggerMap` value of the show 97 | * trigger; else it will just use the show trigger. 98 | */ 99 | function getTriggers(trigger) { 100 | const show = trigger || options.trigger || defaultTriggerShow; 101 | const hide = triggerMap[show] || show; 102 | return { 103 | show: show, 104 | hide: hide, 105 | }; 106 | } 107 | 108 | const directiveName = snake_case(type); 109 | 110 | const startSym = $interpolate.startSymbol(); 111 | const endSym = $interpolate.endSymbol(); 112 | const template = 113 | '
' + 134 | '
'; 135 | 136 | return { 137 | restrict: 'EA', 138 | compile: function(tElem, tAttrs) { 139 | const tooltipLinker = $compile(template); 140 | 141 | return function link(scope, element, attrs) { 142 | let tooltip; 143 | let tooltipLinkedScope; 144 | let transitionTimeout; 145 | let popupTimeout; 146 | let appendToBody = angular.isDefined(options.appendToBody) 147 | ? options.appendToBody 148 | : false; 149 | let triggers = getTriggers(undefined); 150 | const hasEnableExp = angular.isDefined(attrs[prefix + 'Enable']); 151 | let ttScope = scope.$new(true); 152 | 153 | const positionTooltip = function() { 154 | const ttPosition = $position.positionElements( 155 | element, 156 | tooltip, 157 | ttScope.placement, 158 | appendToBody 159 | ); 160 | ttPosition.top += 'px'; 161 | ttPosition.left += 'px'; 162 | 163 | // Now set the calculated positioning. 164 | tooltip.css(ttPosition); 165 | }; 166 | 167 | // By default, the tooltip is not open. 168 | // TODO add ability to start tooltip opened 169 | ttScope.isOpen = false; 170 | 171 | function toggleTooltipBind() { 172 | if (!ttScope.isOpen) { 173 | showTooltipBind(); 174 | } else { 175 | hideTooltipBind(); 176 | } 177 | } 178 | 179 | // Show the tooltip with delay if specified, otherwise show it immediately 180 | function showTooltipBind() { 181 | if (hasEnableExp && !scope.$eval(attrs[prefix + 'Enable'])) { 182 | return; 183 | } 184 | 185 | prepareTooltip(); 186 | 187 | if (ttScope.popupDelay) { 188 | // Do nothing if the tooltip was already scheduled to pop-up. 189 | // This happens if show is triggered multiple times before any hide is triggered. 190 | if (!popupTimeout) { 191 | popupTimeout = $timeout(show, ttScope.popupDelay, false); 192 | popupTimeout 193 | .then(reposition => reposition()) 194 | .catch(error => { 195 | // if the timeout is canceled then the string `canceled` is thrown. To prevent 196 | // this from triggering an 'unhandled promise rejection' in angular 1.5+ the 197 | // $timeout service explicitly tells $q that the promise it generated is "handled" 198 | // but that does not include down chain promises like the one created by calling 199 | // `popupTimeout.then()`. Because of this we need to ignore the "canceled" string 200 | // and only propagate real errors 201 | if (error !== 'canceled') { 202 | throw error; 203 | } 204 | }); 205 | } 206 | } else { 207 | show()(); 208 | } 209 | } 210 | 211 | function hideTooltipBind() { 212 | scope.$evalAsync(function() { 213 | hide(); 214 | }); 215 | } 216 | 217 | // Show the tooltip popup element. 218 | function show() { 219 | popupTimeout = null; 220 | 221 | // If there is a pending remove transition, we must cancel it, lest the 222 | // tooltip be mysteriously removed. 223 | if (transitionTimeout) { 224 | $timeout.cancel(transitionTimeout); 225 | transitionTimeout = null; 226 | } 227 | 228 | // Don't show empty tooltips. 229 | if (!ttScope.content) { 230 | return angular.noop; 231 | } 232 | 233 | createTooltip(); 234 | 235 | // Set the initial positioning. 236 | tooltip.css({ top: 0, left: 0, display: 'block' }); 237 | ttScope.$digest(); 238 | 239 | positionTooltip(); 240 | 241 | // And show the tooltip. 242 | ttScope.isOpen = true; 243 | ttScope.$digest(); // digest required as $apply is not called 244 | 245 | // Return positioning function as promise callback for correct 246 | // positioning after draw. 247 | return positionTooltip; 248 | } 249 | 250 | // Hide the tooltip popup element. 251 | function hide() { 252 | // First things first: we don't show it anymore. 253 | ttScope.isOpen = false; 254 | 255 | //if tooltip is going to be shown after delay, we must cancel this 256 | $timeout.cancel(popupTimeout); 257 | popupTimeout = null; 258 | 259 | // And now we remove it from the DOM. However, if we have animation, we 260 | // need to wait for it to expire beforehand. 261 | // FIXME: this is a placeholder for a port of the transitions library. 262 | if (ttScope.animation) { 263 | if (!transitionTimeout) { 264 | transitionTimeout = $timeout(removeTooltip, 500); 265 | } 266 | } else { 267 | removeTooltip(); 268 | } 269 | } 270 | 271 | function createTooltip() { 272 | // There can only be one tooltip element per directive shown at once. 273 | if (tooltip) { 274 | removeTooltip(); 275 | } 276 | tooltipLinkedScope = ttScope.$new(); 277 | tooltip = tooltipLinker(tooltipLinkedScope, function(tooltip) { 278 | if (appendToBody) { 279 | $document.find('body').append(tooltip); 280 | } else { 281 | element.after(tooltip); 282 | } 283 | }); 284 | } 285 | 286 | function removeTooltip() { 287 | transitionTimeout = null; 288 | if (tooltip) { 289 | tooltip.remove(); 290 | tooltip = null; 291 | } 292 | if (tooltipLinkedScope) { 293 | tooltipLinkedScope.$destroy(); 294 | tooltipLinkedScope = null; 295 | } 296 | } 297 | 298 | function prepareTooltip() { 299 | prepPlacement(); 300 | prepPopupDelay(); 301 | } 302 | 303 | /** 304 | * Observe the relevant attributes. 305 | */ 306 | attrs.$observe(type, function(val) { 307 | ttScope.content = val; 308 | 309 | if (!val && ttScope.isOpen) { 310 | hide(); 311 | } 312 | }); 313 | 314 | attrs.$observe(prefix + 'Title', function(val) { 315 | ttScope.title = val; 316 | }); 317 | 318 | function prepPlacement() { 319 | const val = attrs[prefix + 'Placement']; 320 | ttScope.placement = angular.isDefined(val) ? val : options.placement; 321 | } 322 | 323 | function prepPopupDelay() { 324 | const val = attrs[prefix + 'PopupDelay']; 325 | const delay = parseInt(val, 10); 326 | ttScope.popupDelay = !isNaN(delay) ? delay : options.popupDelay; 327 | } 328 | 329 | const unregisterTriggers = function() { 330 | element.unbind(triggers.show, showTooltipBind); 331 | element.unbind(triggers.hide, hideTooltipBind); 332 | }; 333 | 334 | function prepTriggers() { 335 | const val = attrs[prefix + 'Trigger']; 336 | unregisterTriggers(); 337 | 338 | triggers = getTriggers(val); 339 | 340 | if (triggers.show === triggers.hide) { 341 | element.bind(triggers.show, toggleTooltipBind); 342 | } else { 343 | element.bind(triggers.show, showTooltipBind); 344 | element.bind(triggers.hide, hideTooltipBind); 345 | } 346 | } 347 | 348 | prepTriggers(); 349 | 350 | const animation = scope.$eval(attrs[prefix + 'Animation']); 351 | ttScope.animation = angular.isDefined(animation) 352 | ? !!animation 353 | : options.animation; 354 | 355 | const appendToBodyVal = scope.$eval(attrs[prefix + 'AppendToBody']); 356 | appendToBody = angular.isDefined(appendToBodyVal) 357 | ? appendToBodyVal 358 | : appendToBody; 359 | 360 | // if a tooltip is attached to we need to remove it on 361 | // location change as its parent scope will probably not be destroyed 362 | // by the change. 363 | if (appendToBody) { 364 | scope.$on( 365 | '$locationChangeSuccess', 366 | function closeTooltipOnLocationChangeSuccess() { 367 | if (ttScope.isOpen) { 368 | hide(); 369 | } 370 | } 371 | ); 372 | } 373 | 374 | // Make sure tooltip is destroyed and removed. 375 | scope.$on('$destroy', function onDestroyTooltip() { 376 | $timeout.cancel(transitionTimeout); 377 | $timeout.cancel(popupTimeout); 378 | unregisterTriggers(); 379 | removeTooltip(); 380 | ttScope = null; 381 | }); 382 | }; 383 | }, 384 | }; 385 | }; 386 | }, 387 | ]; 388 | }) 389 | 390 | .directive('tooltip', [ 391 | '$tooltip', 392 | function($tooltip) { 393 | return $tooltip('tooltip', 'tooltip', 'mouseenter'); 394 | }, 395 | ]) 396 | 397 | .directive('tooltipPopup', function() { 398 | return { 399 | restrict: 'EA', 400 | replace: true, 401 | scope: { content: '@', placement: '@', animation: '&', isOpen: '&' }, 402 | templateUrl: 'template/tooltip/tooltip-popup.html', 403 | }; 404 | }) 405 | 406 | .directive('tooltipHtmlUnsafe', [ 407 | '$tooltip', 408 | function($tooltip) { 409 | return $tooltip('tooltipHtmlUnsafe', 'tooltip', 'mouseenter'); 410 | }, 411 | ]) 412 | 413 | .directive('tooltipHtmlUnsafePopup', function() { 414 | return { 415 | restrict: 'EA', 416 | replace: true, 417 | scope: { content: '@', placement: '@', animation: '&', isOpen: '&' }, 418 | templateUrl: 'template/tooltip/tooltip-html-unsafe-popup.html', 419 | }; 420 | }); 421 | } 422 | -------------------------------------------------------------------------------- /public/legacy/angular/angular_bootstrap/tooltip/tooltip_html_unsafe_popup.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
-------------------------------------------------------------------------------- /public/legacy/angular/angular_bootstrap/tooltip/tooltip_popup.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
-------------------------------------------------------------------------------- /public/legacy/angular/i18n/directive.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one 3 | * or more contributor license agreements. Licensed under the Elastic License 4 | * 2.0 and the Server Side Public License, v 1; you may not use this file except 5 | * in compliance with, at your election, the Elastic License 2.0 or the Server 6 | * Side Public License, v 1. 7 | */ 8 | 9 | import { IDirective, IRootElementService, IScope } from 'angular'; 10 | 11 | import { I18nServiceType } from './provider'; 12 | 13 | interface I18nScope extends IScope { 14 | values?: Record; 15 | defaultMessage: string; 16 | id: string; 17 | } 18 | 19 | const HTML_KEY_PREFIX = 'html_'; 20 | const PLACEHOLDER_SEPARATOR = '@I18N@'; 21 | 22 | export const i18nDirective: [string, string, typeof i18nDirectiveFn] = [ 23 | 'i18n', 24 | '$sanitize', 25 | i18nDirectiveFn, 26 | ]; 27 | 28 | function i18nDirectiveFn( 29 | i18n: I18nServiceType, 30 | $sanitize: (html: string) => string 31 | ): IDirective { 32 | return { 33 | restrict: 'A', 34 | scope: { 35 | id: '@i18nId', 36 | defaultMessage: '@i18nDefaultMessage', 37 | values: ' { 42 | setContent($element, $scope, $sanitize, i18n); 43 | }); 44 | } else { 45 | setContent($element, $scope, $sanitize, i18n); 46 | } 47 | }, 48 | }; 49 | } 50 | 51 | function setContent( 52 | $element: IRootElementService, 53 | $scope: I18nScope, 54 | $sanitize: (html: string) => string, 55 | i18n: I18nServiceType 56 | ) { 57 | const originalValues = $scope.values; 58 | const valuesWithPlaceholders = {} as Record; 59 | let hasValuesWithPlaceholders = false; 60 | 61 | // If we have values with the keys that start with HTML_KEY_PREFIX we should replace 62 | // them with special placeholders that later on will be inserted as HTML 63 | // into the DOM, the rest of the content will be treated as text. We don't 64 | // sanitize values at this stage as some of the values can be excluded from 65 | // the translated string (e.g. not used by ICU conditional statements). 66 | if (originalValues) { 67 | for (const [key, value] of Object.entries(originalValues)) { 68 | if (key.startsWith(HTML_KEY_PREFIX)) { 69 | valuesWithPlaceholders[ 70 | key.slice(HTML_KEY_PREFIX.length) 71 | ] = `${PLACEHOLDER_SEPARATOR}${key}${PLACEHOLDER_SEPARATOR}`; 72 | 73 | hasValuesWithPlaceholders = true; 74 | } else { 75 | valuesWithPlaceholders[key] = value; 76 | } 77 | } 78 | } 79 | 80 | const label = i18n($scope.id, { 81 | values: valuesWithPlaceholders, 82 | defaultMessage: $scope.defaultMessage, 83 | }); 84 | 85 | // If there are no placeholders to replace treat everything as text, otherwise 86 | // insert label piece by piece replacing every placeholder with corresponding 87 | // sanitized HTML content. 88 | if (!hasValuesWithPlaceholders) { 89 | $element.text(label); 90 | } else { 91 | $element.empty(); 92 | for (const contentOrPlaceholder of label.split(PLACEHOLDER_SEPARATOR)) { 93 | if (!contentOrPlaceholder) { 94 | continue; 95 | } 96 | 97 | $element.append( 98 | Object.prototype.hasOwnProperty.call(originalValues,contentOrPlaceholder) 99 | ? $sanitize(originalValues![contentOrPlaceholder]) 100 | : document.createTextNode(contentOrPlaceholder) 101 | ); 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /public/legacy/angular/i18n/filter.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one 3 | * or more contributor license agreements. Licensed under the Elastic License 4 | * 2.0 and the Server Side Public License, v 1; you may not use this file except 5 | * in compliance with, at your election, the Elastic License 2.0 or the Server 6 | * Side Public License, v 1. 7 | */ 8 | 9 | import { I18nServiceType } from './provider'; 10 | 11 | export const i18nFilter: [string, typeof i18nFilterFn] = ['i18n', i18nFilterFn]; 12 | 13 | function i18nFilterFn(i18n: I18nServiceType) { 14 | return (id: string, { defaultMessage = '', values = {} } = {}) => { 15 | return i18n(id, { 16 | values, 17 | defaultMessage, 18 | }); 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /public/legacy/angular/i18n/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one 3 | * or more contributor license agreements. Licensed under the Elastic License 4 | * 2.0 and the Server Side Public License, v 1; you may not use this file except 5 | * in compliance with, at your election, the Elastic License 2.0 or the Server 6 | * Side Public License, v 1. 7 | */ 8 | 9 | export { I18nProvider } from './provider'; 10 | 11 | export { i18nFilter } from './filter'; 12 | export { i18nDirective } from './directive'; 13 | 14 | // re-export types: https://github.com/babel/babel-loader/issues/603 15 | import { I18nServiceType as _I18nServiceType } from './provider'; 16 | export type I18nServiceType = _I18nServiceType; 17 | -------------------------------------------------------------------------------- /public/legacy/angular/i18n/provider.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one 3 | * or more contributor license agreements. Licensed under the Elastic License 4 | * 2.0 and the Server Side Public License, v 1; you may not use this file except 5 | * in compliance with, at your election, the Elastic License 2.0 or the Server 6 | * Side Public License, v 1. 7 | */ 8 | import { i18n } from '@kbn/i18n'; 9 | import { IServiceProvider } from 'angular'; 10 | 11 | export type I18nServiceType = typeof i18n.translate; 12 | 13 | export function I18nProvider(): IServiceProvider { 14 | this.addTranslation = i18n.addTranslation; 15 | this.getTranslation = i18n.getTranslation; 16 | this.setLocale = i18n.setLocale; 17 | this.getLocale = i18n.getLocale; 18 | this.setDefaultLocale = i18n.setDefaultLocale; 19 | this.getDefaultLocale = i18n.getDefaultLocale; 20 | this.setFormats = i18n.setFormats; 21 | this.getFormats = i18n.getFormats; 22 | this.getRegisteredLocales = i18n.getRegisteredLocales; 23 | this.init = i18n.init; 24 | this.load = i18n.load; 25 | this.$get = () => i18n.translate; 26 | return this; 27 | } 28 | -------------------------------------------------------------------------------- /public/legacy/angular/utils/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one 3 | * or more contributor license agreements. Licensed under the Elastic License 4 | * 2.0 and the Server Side Public License, v 1; you may not use this file except 5 | * in compliance with, at your election, the Elastic License 2.0 or the Server 6 | * Side Public License, v 1. 7 | */ 8 | 9 | // @ts-ignore 10 | export { KbnAccessibleClickProvider } from './kbn_accessible_click'; 11 | // @ts-ignore 12 | export { PrivateProvider } from './private'; 13 | -------------------------------------------------------------------------------- /public/legacy/angular/utils/kbn_accessible_click.d.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one 3 | * or more contributor license agreements. Licensed under the Elastic License 4 | * 2.0 and the Server Side Public License, v 1; you may not use this file except 5 | * in compliance with, at your election, the Elastic License 2.0 or the Server 6 | * Side Public License, v 1. 7 | */ 8 | 9 | import { Injectable, IDirectiveFactory, IScope, IAttributes, IController, JQLite } from 'angular'; 10 | 11 | export const KbnAccessibleClickProvider: Injectable< 12 | IDirectiveFactory 13 | >; 14 | -------------------------------------------------------------------------------- /public/legacy/angular/utils/kbn_accessible_click.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one 3 | * or more contributor license agreements. Licensed under the Elastic License 4 | * 2.0 and the Server Side Public License, v 1; you may not use this file except 5 | * in compliance with, at your election, the Elastic License 2.0 or the Server 6 | * Side Public License, v 1. 7 | */ 8 | 9 | import { accessibleClickKeys, keys } from '@elastic/eui'; 10 | 11 | export function KbnAccessibleClickProvider() { 12 | KbnAccessibleClickProviderController.$inject = ['$element']; 13 | return { 14 | restrict: 'A', 15 | controller: KbnAccessibleClickProviderController, 16 | link: (scope, element, attrs) => { 17 | // The whole point of this directive is to hack in functionality that native buttons provide 18 | // by default. 19 | const elementType = element.prop('tagName'); 20 | 21 | if (elementType === 'BUTTON') { 22 | throw new Error('kbnAccessibleClick does not need to be used on a button.'); 23 | } 24 | 25 | if (elementType === 'A' && attrs.href !== undefined) { 26 | throw new Error( 27 | 'kbnAccessibleClick does not need to be used on a link if it has a href attribute.' 28 | ); 29 | } 30 | 31 | // We're emulating a click action, so we should already have a regular click handler defined. 32 | if (!attrs.ngClick) { 33 | throw new Error('kbnAccessibleClick requires ng-click to be defined on its element.'); 34 | } 35 | 36 | // If the developer hasn't already specified attributes required for accessibility, add them. 37 | if (attrs.tabindex === undefined) { 38 | element.attr('tabindex', '0'); 39 | } 40 | 41 | if (attrs.role === undefined) { 42 | element.attr('role', 'button'); 43 | } 44 | 45 | element.on('keyup', (e) => { 46 | // Support keyboard accessibility by emulating mouse click on ENTER or SPACE keypress. 47 | if (accessibleClickKeys[e.key]) { 48 | // Delegate to the click handler on the element (assumed to be ng-click). 49 | element.click(); 50 | } 51 | }); 52 | }, 53 | }; 54 | } 55 | 56 | function KbnAccessibleClickProviderController($element){ 57 | $element.on('keydown', (e) => { 58 | // Prevent a scroll from occurring if the user has hit space. 59 | if (e.key === keys.SPACE) { 60 | e.preventDefault(); 61 | } 62 | }); 63 | } 64 | -------------------------------------------------------------------------------- /public/legacy/angular/utils/private.d.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one 3 | * or more contributor license agreements. Licensed under the Elastic License 4 | * 2.0 and the Server Side Public License, v 1; you may not use this file except 5 | * in compliance with, at your election, the Elastic License 2.0 or the Server 6 | * Side Public License, v 1. 7 | */ 8 | 9 | import { IServiceProvider } from 'angular'; 10 | 11 | export function PrivateProvider(): IServiceProvider; 12 | -------------------------------------------------------------------------------- /public/legacy/angular/utils/private.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one 3 | * or more contributor license agreements. Licensed under the Elastic License 4 | * 2.0 and the Server Side Public License, v 1; you may not use this file except 5 | * in compliance with, at your election, the Elastic License 2.0 or the Server 6 | * Side Public License, v 1. 7 | */ 8 | 9 | /** 10 | * # `Private()` 11 | * Private module loader, used to merge angular and require js dependency styles 12 | * by allowing a require.js module to export a single provider function that will 13 | * create a value used within an angular application. This provider can declare 14 | * angular dependencies by listing them as arguments, and can be require additional 15 | * Private modules. 16 | * 17 | * ## Define a private module provider: 18 | * ```js 19 | * export default function PingProvider($http) { 20 | * this.ping = function () { 21 | * return $http.head('/health-check'); 22 | * }; 23 | * }; 24 | * ``` 25 | * 26 | * ## Require a private module: 27 | * ```js 28 | * export default function ServerHealthProvider(Private, Promise) { 29 | * let ping = Private(require('ui/ping')); 30 | * return { 31 | * check: Promise.method(function () { 32 | * let attempts = 0; 33 | * return (function attempt() { 34 | * attempts += 1; 35 | * return ping.ping() 36 | * .catch(function (err) { 37 | * if (attempts < 3) return attempt(); 38 | * }) 39 | * }()) 40 | * .then(function () { 41 | * return true; 42 | * }) 43 | * .catch(function () { 44 | * return false; 45 | * }); 46 | * }) 47 | * } 48 | * }; 49 | * ``` 50 | * 51 | * # `Private.stub(provider, newInstance)` 52 | * `Private.stub()` replaces the instance of a module with another value. This is all we have needed until now. 53 | * 54 | * ```js 55 | * beforeEach(inject(function ($injector, Private) { 56 | * Private.stub( 57 | * // since this module just exports a function, we need to change 58 | * // what Private returns in order to modify it's behavior 59 | * require('ui/agg_response/hierarchical/_build_split'), 60 | * sinon.stub().returns(fakeSplit) 61 | * ); 62 | * })); 63 | * ``` 64 | * 65 | * # `Private.swap(oldProvider, newProvider)` 66 | * This new method does an 1-for-1 swap of module providers, unlike `stub()` which replaces a modules instance. 67 | * Pass the module you want to swap out, and the one it should be replaced with, then profit. 68 | * 69 | * Note: even though this example shows `swap()` being called in a config 70 | * function, it can be called from anywhere. It is particularly useful 71 | * in this scenario though. 72 | * 73 | * ```js 74 | * beforeEach(module('kibana', function (PrivateProvider) { 75 | * PrivateProvider.swap( 76 | * function StubbedRedirectProvider($decorate) { 77 | * // $decorate is a function that will instantiate the original module when called 78 | * return sinon.spy($decorate()); 79 | * } 80 | * ); 81 | * })); 82 | * ``` 83 | * 84 | * @param {[type]} prov [description] 85 | */ 86 | import _ from 'lodash'; 87 | 88 | const nextId = _.partial(_.uniqueId, 'privateProvider#'); 89 | 90 | function name(fn) { 91 | return fn.name || fn.toString().split('\n').shift(); 92 | } 93 | 94 | export function PrivateProvider() { 95 | const provider = this; 96 | 97 | // one cache/swaps per Provider 98 | const cache = {}; 99 | const swaps = {}; 100 | 101 | // return the uniq id for this function 102 | function identify(fn) { 103 | if (typeof fn !== 'function') { 104 | throw new TypeError('Expected private module "' + fn + '" to be a function'); 105 | } 106 | 107 | if (fn.$$id) return fn.$$id; 108 | else return (fn.$$id = nextId()); 109 | } 110 | 111 | provider.stub = function (fn, instance) { 112 | cache[identify(fn)] = instance; 113 | return instance; 114 | }; 115 | 116 | provider.swap = function (fn, prov) { 117 | const id = identify(fn); 118 | swaps[id] = prov; 119 | }; 120 | 121 | provider.$get = [ 122 | '$injector', 123 | function PrivateFactory($injector) { 124 | // prevent circular deps by tracking where we came from 125 | const privPath = []; 126 | const pathToString = function () { 127 | return privPath.map(name).join(' -> '); 128 | }; 129 | 130 | // call a private provider and return the instance it creates 131 | function instantiate(prov, locals) { 132 | if (~privPath.indexOf(prov)) { 133 | throw new Error( 134 | 'Circular reference to "' + 135 | name(prov) + 136 | '"' + 137 | ' found while resolving private deps: ' + 138 | pathToString() 139 | ); 140 | } 141 | 142 | privPath.push(prov); 143 | 144 | const context = {}; 145 | let instance = $injector.invoke(prov, context, locals); 146 | if (!_.isObject(instance)) instance = context; 147 | 148 | privPath.pop(); 149 | return instance; 150 | } 151 | 152 | // retrieve an instance from cache or create and store on 153 | function get(id, prov, $delegateId, $delegateProv) { 154 | if (cache[id]) return cache[id]; 155 | 156 | let instance; 157 | 158 | if ($delegateId != null && $delegateProv != null) { 159 | instance = instantiate(prov, { 160 | $decorate: _.partial(get, $delegateId, $delegateProv), 161 | }); 162 | } else { 163 | instance = instantiate(prov); 164 | } 165 | 166 | return (cache[id] = instance); 167 | } 168 | 169 | // main api, get the appropriate instance for a provider 170 | function Private(prov) { 171 | let id = identify(prov); 172 | let $delegateId; 173 | let $delegateProv; 174 | 175 | if (swaps[id]) { 176 | $delegateId = id; 177 | $delegateProv = prov; 178 | 179 | prov = swaps[$delegateId]; 180 | id = identify(prov); 181 | } 182 | 183 | return get(id, prov, $delegateId, $delegateProv); 184 | } 185 | 186 | Private.stub = provider.stub; 187 | Private.swap = provider.swap; 188 | 189 | return Private; 190 | }, 191 | ]; 192 | } 193 | -------------------------------------------------------------------------------- /public/legacy/angular/watch_multi/index.ts: -------------------------------------------------------------------------------- 1 | export { watchMultiDecorator } from './watch_multi'; -------------------------------------------------------------------------------- /public/legacy/angular/watch_multi/watch_multi.d.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one 3 | * or more contributor license agreements. Licensed under the Elastic License 4 | * 2.0 and the Server Side Public License, v 1; you may not use this file except 5 | * in compliance with, at your election, the Elastic License 2.0 or the Server 6 | * Side Public License, v 1. 7 | */ 8 | 9 | export function watchMultiDecorator($provide: unknown): void; 10 | -------------------------------------------------------------------------------- /public/legacy/angular/watch_multi/watch_multi.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one 3 | * or more contributor license agreements. Licensed under the Elastic License 4 | * 2.0 and the Server Side Public License, v 1; you may not use this file except 5 | * in compliance with, at your election, the Elastic License 2.0 or the Server 6 | * Side Public License, v 1. 7 | */ 8 | 9 | import _ from 'lodash'; 10 | 11 | export function watchMultiDecorator($provide) { 12 | $provide.decorator('$rootScope', ['$delegate', function ($delegate) { 13 | /** 14 | * Watch multiple expressions with a single callback. Along 15 | * with making code simpler it also merges all of the watcher 16 | * handlers within a single tick. 17 | * 18 | * # expression format 19 | * expressions can be specified in one of the following ways: 20 | * 1. string that evaluates to a value on scope. Creates a regular $watch 21 | * expression. 22 | * 'someScopeValue.prop' === $scope.$watch('someScopeValue.prop', fn); 23 | * 24 | * 2. #1 prefixed with '[]', which uses $watchCollection rather than $watch. 25 | * '[]expr' === $scope.$watchCollection('expr', fn); 26 | * 27 | * 3. #1 prefixed with '=', which uses $watch with objectEquality turned on 28 | * '=expr' === $scope.$watch('expr', fn, true); 29 | * 30 | * 4. a function that will be called, like a normal function water 31 | * 32 | * 5. an object with any of the properties: 33 | * `get`: the getter called on each iteration 34 | * `deep`: a flag to turn on objectEquality in $watch 35 | * `fn`: the watch registration function ($scope.$watch or $scope.$watchCollection) 36 | * 37 | * @param {array[string|function|obj]} expressions - the list of expressions to $watch 38 | * @param {Function} fn - the callback function 39 | * @return {Function} - an unwatch function, just like the return value of $watch 40 | */ 41 | $delegate.constructor.prototype.$watchMulti = function (expressions, fn) { 42 | if (!Array.isArray(expressions)) { 43 | throw new TypeError('expected an array of expressions to watch'); 44 | } 45 | 46 | if (!_.isFunction(fn)) { 47 | throw new TypeError('expected a function that is triggered on each watch'); 48 | } 49 | const $scope = this; 50 | const vals = new Array(expressions.length); 51 | const prev = new Array(expressions.length); 52 | let fire = false; 53 | let init = 0; 54 | const neededInits = expressions.length; 55 | 56 | // first, register all of the multi-watchers 57 | const unwatchers = expressions.map(function (expr, i) { 58 | expr = normalizeExpression($scope, expr); 59 | if (!expr) return; 60 | 61 | return expr.fn.call( 62 | $scope, 63 | expr.get, 64 | function (newVal, oldVal) { 65 | if (newVal === oldVal) { 66 | init += 1; 67 | } 68 | 69 | vals[i] = newVal; 70 | prev[i] = oldVal; 71 | fire = true; 72 | }, 73 | expr.deep 74 | ); 75 | }); 76 | 77 | // then, the watcher that checks to see if any of 78 | // the other watchers triggered this cycle 79 | let flip = false; 80 | unwatchers.push( 81 | $scope.$watch( 82 | function () { 83 | if (init < neededInits) return init; 84 | 85 | if (fire) { 86 | fire = false; 87 | flip = !flip; 88 | } 89 | return flip; 90 | }, 91 | function () { 92 | if (init < neededInits) return false; 93 | 94 | fn(vals.slice(0), prev.slice(0)); 95 | vals.forEach(function (v, i) { 96 | prev[i] = v; 97 | }); 98 | } 99 | ) 100 | ); 101 | 102 | return function () { 103 | unwatchers.forEach((listener) => listener()); 104 | }; 105 | }; 106 | 107 | function normalizeExpression($scope, expr) { 108 | if (!expr) return; 109 | const norm = { 110 | fn: $scope.$watch, 111 | deep: false, 112 | }; 113 | 114 | if (_.isFunction(expr)) return _.assign(norm, { get: expr }); 115 | if (_.isObject(expr)) return _.assign(norm, expr); 116 | if (!_.isString(expr)) return; 117 | 118 | if (expr.substr(0, 2) === '[]') { 119 | return _.assign(norm, { 120 | fn: $scope.$watchCollection, 121 | get: expr.substr(2), 122 | }); 123 | } 124 | 125 | if (expr.charAt(0) === '=') { 126 | return _.assign(norm, { 127 | deep: true, 128 | get: expr.substr(1), 129 | }); 130 | } 131 | 132 | return _.assign(norm, { get: expr }); 133 | } 134 | 135 | return $delegate; 136 | }]); 137 | } 138 | -------------------------------------------------------------------------------- /public/legacy/bucket_helper.js: -------------------------------------------------------------------------------- 1 | /* 2 | * The idea is to populate 'otherBucket' array and 'missingBucket' with the correct id of the resp.column 3 | * if the label for other/missing bucket is defined. 4 | * resp.columns has the following structure: 5 | * [ 6 | * { 7 | * id: "col-0-2", name: "ip: Descending" 8 | * } 9 | * ... 10 | * ] 11 | */ 12 | export const bucketHelper = (response, bucket) => { 13 | return(response.columns.find( column => 14 | column.meta.field !== (bucket.meta.field))); 15 | }; 16 | -------------------------------------------------------------------------------- /public/legacy/bucket_replace_property_helper.js: -------------------------------------------------------------------------------- 1 | export const bucketReplaceProperty = (sourceBucket, destinationBucket, cell) => { 2 | // userDefinedArrayLabels stores the defined 'other' or 'missing' values defined by the user when he checks 3 | // 'Group other values' or 'Show missing values' 4 | const userDefinedArrayLabels = sourceBucket.find(element => element.hasOwnProperty(cell)); 5 | destinationBucket[cell] = userDefinedArrayLabels[cell]; 6 | return destinationBucket; 7 | }; 8 | -------------------------------------------------------------------------------- /public/legacy/field_formatter.ts: -------------------------------------------------------------------------------- 1 | import { getFormatService } from '../services'; 2 | import { IAggConfig, IFieldFormat, FieldFormatsContentType } from '@kbn/field-formats-plugin/common'; 3 | 4 | /** 5 | * Returns the fieldFormatter function associated to aggConfig, for the requested contentType (html or text). 6 | * Returned fieldFormatter is a function, whose prototype is: fieldFormatter(value, options?) 7 | */ 8 | export function fieldFormatter(aggConfig: IAggConfig, contentType: FieldFormatsContentType) { 9 | const fieldFormat: IFieldFormat = getFormatService().deserialize(aggConfig.toSerializedFieldFormat()); 10 | return fieldFormat.getConverterFor(contentType); 11 | } 12 | -------------------------------------------------------------------------------- /public/legacy/get_inner_angular.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one 3 | * or more contributor license agreements. Licensed under the Elastic License 4 | * 2.0 and the Server Side Public License, v 1; you may not use this file except 5 | * in compliance with, at your election, the Elastic License 2.0 or the Server 6 | * Side Public License, v 1. 7 | */ 8 | 9 | // inner angular imports 10 | // these are necessary to bootstrap the local angular. 11 | // They can stay even after NP cutover 12 | import angular from 'angular'; 13 | import 'angular-sanitize'; 14 | import 'angular-recursion'; 15 | import { CoreStart, IUiSettingsClient } from '@kbn/core/public'; 16 | import { i18nDirective, i18nFilter, I18nProvider } from './angular/i18n'; 17 | import { watchMultiDecorator } from './angular/watch_multi'; 18 | import { KbnAccessibleClickProvider, PrivateProvider } from './angular/utils'; 19 | 20 | const thirdPartyAngularDependencies = ['ngSanitize', 'ui.bootstrap', 'RecursionHelper']; 21 | 22 | export function getAngularModule(name: string, core: CoreStart) { 23 | const uiModule = getInnerAngular(name, core); 24 | return uiModule; 25 | } 26 | 27 | let initialized = false; 28 | 29 | export function getInnerAngular(name = 'kibana/sankey_vis', core: CoreStart) { 30 | if (!initialized) { 31 | createLocalPrivateModule(); 32 | createLocalI18nModule(); 33 | createLocalConfigModule(core.uiSettings); 34 | createLocalPaginateModule(); 35 | initialized = true; 36 | } 37 | return angular 38 | .module(name, [ 39 | ...thirdPartyAngularDependencies, 40 | 'tableVisPaginate', 41 | 'tableVisConfig', 42 | 'tableVisPrivate', 43 | 'tableVisI18n', 44 | ]) 45 | .config(['$provide',watchMultiDecorator]) 46 | .directive('kbnAccessibleClick', KbnAccessibleClickProvider); 47 | } 48 | 49 | function createLocalPrivateModule() { 50 | angular.module('tableVisPrivate', []).provider('Private', PrivateProvider); 51 | } 52 | 53 | function createLocalConfigModule(uiSettings: IUiSettingsClient) { 54 | angular.module('tableVisConfig', []).provider('tableConfig', function () { 55 | return { 56 | $get: () => ({ 57 | get: (value: string) => { 58 | return uiSettings ? uiSettings.get(value) : undefined; 59 | }, 60 | // set method is used in agg_table mocha test 61 | set: (key: string, value: string) => { 62 | return uiSettings ? uiSettings.set(key, value) : undefined; 63 | }, 64 | }), 65 | }; 66 | }); 67 | } 68 | 69 | function createLocalI18nModule() { 70 | angular 71 | .module('tableVisI18n', []) 72 | .provider('i18n', I18nProvider) 73 | .filter('i18n', i18nFilter) 74 | .directive('i18nId', i18nDirective); 75 | } 76 | 77 | function createLocalPaginateModule() { 78 | angular 79 | .module('tableVisPaginate', []); 80 | } 81 | -------------------------------------------------------------------------------- /public/legacy/helpers/agg_response_helper.js: -------------------------------------------------------------------------------- 1 | const { bucketReplaceProperty } = require('./bucket_replace_property_helper'); 2 | 3 | const stringify = require('json-stable-stringify'); 4 | 5 | module.exports = (function() { 6 | function _generateNodesMap(paths) { 7 | const nodesMap = new Map(); 8 | paths.map(path => { 9 | path 10 | .slice(0, -1) 11 | .map((nodeName, index) => { 12 | const nodeKeyStr = _getNodeId(index, nodeName); 13 | nodesMap.set(nodeKeyStr, nodeName); 14 | }); 15 | }); 16 | return nodesMap; 17 | } 18 | 19 | // TODO: Json in Json -> ugly code 20 | function _getLinkId( sourceNodeId, targetNodeId) { 21 | const linkKey = { sourceNodeId, targetNodeId }; 22 | return stringify(linkKey); 23 | } 24 | 25 | function _getNodeId(layerIndex, nodeName) { 26 | const nodeKey = { layerIndex, nodeName }; 27 | return stringify(nodeKey); 28 | } 29 | 30 | function _generateLinksMap(paths, nodesMap) { 31 | const linksMap = new Map(); 32 | paths.map(path => { 33 | for(let layer = 0; layer < path.length - 2; layer++) { 34 | const sourceNodeName = path[layer]; 35 | const targetNodeName = path[layer + 1]; 36 | const value = path[path.length - 1]; 37 | 38 | const sourceNodeId = _getNodeId(layer, sourceNodeName); 39 | const targetNodeId = _getNodeId(layer + 1, targetNodeName); 40 | 41 | const linkKeyStr = stringify({ sourceNodeId, targetNodeId }); 42 | 43 | const oldValue = linksMap.get(linkKeyStr); 44 | if (oldValue) { 45 | // Sum up values 46 | const newValue = oldValue + value; 47 | linksMap.set(linkKeyStr, newValue); 48 | } else { 49 | // Add link 50 | linksMap.set(linkKeyStr, value); 51 | } 52 | } 53 | }); 54 | return linksMap; 55 | } 56 | 57 | function _generateNodeIdMap(nodes) { 58 | const nodeIdMap = new Map(); 59 | nodes.forEach(({nodeId}, nodeArrayIndex) => { 60 | nodeIdMap.set(nodeId, nodeArrayIndex); 61 | }); 62 | return nodeIdMap; 63 | } 64 | 65 | function _generateNodeArray(nodesMap) { 66 | const nodeArray = []; 67 | nodesMap.forEach((nodeName, nodeId) => { 68 | nodeArray.push({ nodeId, name: nodeName }); 69 | }); 70 | return nodeArray; 71 | } 72 | 73 | function _convertLinksMapToArray(linksMap, nodeIdMap) { 74 | const links = []; 75 | linksMap.forEach((value, linkId) => { 76 | const {sourceNodeId, targetNodeId} = JSON.parse(linkId); 77 | links.push({ 78 | source: nodeIdMap.get(sourceNodeId), 79 | target: nodeIdMap.get(targetNodeId), 80 | value 81 | }); 82 | }); 83 | return links; 84 | } 85 | function _convertObjectToArray({rows, missingValues, groupBucket}) { 86 | // In the new kibana version, the rows are of type object , where they should be of type array to match the rest of the algorithm . 87 | // This is a function to convert the object ( 'col-0-2' : [array]... ) to (0 : [array]) 88 | let newRows = []; 89 | // The structure of the bucket is as follow: { col-0-1: string, col-0-2: string... } 90 | rows.map(function(bucket) { 91 | // Object in javascript are mutable, this is why we should create a copy and then update it without modifying the original 'resp.rows' 92 | // otherwise the rest of the application will be broken 93 | // https://github.com/uniberg/kbn_sankey_vis/issues/14 94 | const bucketCopy = Object.assign({}, bucket); 95 | // Cell refers to col-0-1, col-0-2... 96 | for (let cell in bucketCopy) { 97 | // Update the bucket if 'Show missing values' is checked 98 | // by default, the value is '__missing__' 99 | // kibana/kibana-repo/src/ui/public/agg_types/buckets/terms.js 100 | if (bucketCopy[cell] === '__missing__' && missingValues.length > 0) { 101 | bucketReplaceProperty(missingValues, bucketCopy, cell); 102 | } 103 | // Update the bucket if 'Group other bucket' is checked 104 | if (bucketCopy[cell] === '__other__' && groupBucket.length > 0) { 105 | bucketReplaceProperty(groupBucket, bucketCopy, cell); 106 | } 107 | Object.defineProperty( 108 | bucketCopy, 109 | cell.split('-')[1], 110 | Object.getOwnPropertyDescriptor(bucketCopy, cell) 111 | ); 112 | delete bucketCopy[cell]; 113 | } 114 | newRows.push(_.values(bucketCopy)); 115 | }); 116 | 117 | return newRows; 118 | } 119 | function aggregate({rows, missingValues, groupBucket}) { 120 | const paths = _convertObjectToArray({rows, missingValues, groupBucket}); 121 | const nodesMap = _generateNodesMap(paths); 122 | const linksMap = _generateLinksMap(paths, nodesMap); 123 | const nodes = _generateNodeArray(nodesMap); 124 | const nodeIdMap = _generateNodeIdMap(nodes); 125 | const convertedLinks = _convertLinksMapToArray(linksMap, nodeIdMap); 126 | const cleanedD3Nodes = nodes.map(({name}) => { return { name }; }); 127 | 128 | return { 129 | nodes: cleanedD3Nodes, 130 | links: convertedLinks 131 | }; 132 | } 133 | 134 | return { 135 | _generateNodesMap, 136 | _getLinkId, 137 | _getNodeId, 138 | _generateLinksMap, 139 | _generateNodeIdMap, 140 | _generateNodeArray, 141 | _convertLinksMapToArray, 142 | 143 | aggregate 144 | }; 145 | 146 | }()); 147 | -------------------------------------------------------------------------------- /public/legacy/helpers/bucket_helper.js: -------------------------------------------------------------------------------- 1 | /* 2 | * The idea is to populate 'otherBucket' array and 'missingBucket' with the correct id of the resp.column 3 | * if the label for other/missing bucket is defined. 4 | * resp.columns has the following structure: 5 | * [ 6 | * { 7 | * id: "col-0-2", name: "ip: Descending" 8 | * } 9 | * ... 10 | * ] 11 | */ 12 | export const bucketHelper = (response, bucket) => { 13 | return(response.columns.find( column => 14 | column.meta.field !== (bucket.meta.field))); 15 | }; 16 | -------------------------------------------------------------------------------- /public/legacy/helpers/bucket_replace_property_helper.js: -------------------------------------------------------------------------------- 1 | export const bucketReplaceProperty = (sourceBucket, destinationBucket, cell) => { 2 | // userDefinedArrayLabels stores the defined 'other' or 'missing' values defined by the user when he checks 3 | // 'Group other values' or 'Show missing values' 4 | const userDefinedArrayLabels = sourceBucket.find(element => element.hasOwnProperty(cell)); 5 | destinationBucket[cell] = userDefinedArrayLabels[cell]; 6 | return destinationBucket; 7 | }; 8 | -------------------------------------------------------------------------------- /public/legacy/kibana_cloned_code/courier.ts: -------------------------------------------------------------------------------- 1 | import { i18n } from '@kbn/i18n'; 2 | 3 | import { Adapters } from '../../../../../src/plugins/inspector/common'; 4 | 5 | import { IAggConfigs } from '../../../../../src/plugins/data/common/search/aggs'; 6 | import { ISearchSource } from '../../../../../src/plugins/data/common/search/search_source'; 7 | import { 8 | calculateBounds, 9 | Filter, 10 | IndexPattern, 11 | Query, 12 | tabifyAggResponse, 13 | TimeRange, 14 | } from '../../../../../src/plugins/data/common'; 15 | 16 | /** 17 | * Clone of: ../../../../../src/plugins/data/common/search/expressions/esaggs/request_handler.ts 18 | * Components: RequestHandlerParams and handleCourierRequest 19 | */ 20 | interface RequestHandlerParams { 21 | abortSignal?: AbortSignal; 22 | aggs: IAggConfigs; 23 | filters?: Filter[]; 24 | indexPattern?: IndexPattern; 25 | inspectorAdapters: Adapters; 26 | metricsAtAllLevels?: boolean; 27 | partialRows?: boolean; 28 | query?: Query; 29 | searchSessionId?: string; 30 | searchSource: ISearchSource; 31 | timeFields?: string[]; 32 | timeRange?: TimeRange; 33 | getNow?: () => Date; 34 | } 35 | 36 | export const handleCourierRequest = async ({ 37 | abortSignal, 38 | aggs, 39 | filters, 40 | indexPattern, 41 | inspectorAdapters, 42 | metricsAtAllLevels, 43 | partialRows, 44 | query, 45 | searchSessionId, 46 | searchSource, 47 | timeFields, 48 | timeRange, 49 | getNow, 50 | }: RequestHandlerParams) => { 51 | const forceNow = getNow?.(); 52 | 53 | // Create a new search source that inherits the original search source 54 | // but has the appropriate timeRange applied via a filter. 55 | // This is a temporary solution until we properly pass down all required 56 | // information for the request to the request handler (https://github.com/elastic/kibana/issues/16641). 57 | // Using callParentStartHandlers: true we make sure, that the parent searchSource 58 | // onSearchRequestStart will be called properly even though we use an inherited 59 | // search source. 60 | const timeFilterSearchSource = searchSource.createChild({ callParentStartHandlers: true }); 61 | const requestSearchSource = timeFilterSearchSource.createChild({ callParentStartHandlers: true }); 62 | 63 | aggs.setTimeRange(timeRange as TimeRange); 64 | 65 | // For now we need to mirror the history of the passed search source, since 66 | // the request inspector wouldn't work otherwise. 67 | Object.defineProperty(requestSearchSource, 'history', { 68 | get() { 69 | return searchSource.history; 70 | }, 71 | set(history) { 72 | searchSource.history = history; 73 | }, 74 | }); 75 | 76 | requestSearchSource.setField('aggs', aggs); 77 | 78 | requestSearchSource.onRequestStart((paramSearchSource, options) => { 79 | return aggs.onSearchRequestStart(paramSearchSource, options); 80 | }); 81 | 82 | // If timeFields have been specified, use the specified ones, otherwise use primary time field of index 83 | // pattern if it's available. 84 | const defaultTimeField = indexPattern?.getTimeField?.(); 85 | const defaultTimeFields = defaultTimeField ? [defaultTimeField.name] : []; 86 | const allTimeFields = timeFields && timeFields.length > 0 ? timeFields : defaultTimeFields; 87 | 88 | aggs.setForceNow(forceNow); 89 | aggs.setTimeFields(allTimeFields); 90 | 91 | // If a timeRange has been specified and we had at least one timeField available, create range 92 | // filters for that those time fields 93 | if (timeRange && allTimeFields.length > 0) { 94 | timeFilterSearchSource.setField('filter', () => { 95 | return aggs.getSearchSourceTimeFilter(forceNow); 96 | }); 97 | } 98 | 99 | requestSearchSource.setField('filter', filters); 100 | requestSearchSource.setField('query', query); 101 | 102 | inspectorAdapters.requests?.reset(); 103 | 104 | const { rawResponse: response } = await requestSearchSource 105 | .fetch$({ 106 | abortSignal, 107 | sessionId: searchSessionId, 108 | inspector: { 109 | adapter: inspectorAdapters.requests, 110 | title: i18n.translate('data.functions.esaggs.inspector.dataRequest.title', { 111 | defaultMessage: 'Data', 112 | }), 113 | description: i18n.translate('data.functions.esaggs.inspector.dataRequest.description', { 114 | defaultMessage: 115 | 'This request queries Elasticsearch to fetch the data for the visualization.', 116 | }), 117 | }, 118 | }) 119 | .toPromise(); 120 | 121 | const parsedTimeRange = timeRange ? calculateBounds(timeRange, { forceNow }) : null; 122 | const tabifyParams = { 123 | metricsAtAllLevels, 124 | partialRows, 125 | timeRange: parsedTimeRange 126 | ? { from: parsedTimeRange.min, to: parsedTimeRange.max, timeFields: allTimeFields } 127 | : undefined, 128 | }; 129 | 130 | return tabifyAggResponse(aggs, response, tabifyParams);; 131 | }; 132 | -------------------------------------------------------------------------------- /public/legacy/kibana_cloned_code/request_handler.ts: -------------------------------------------------------------------------------- 1 | import { i18n } from '@kbn/i18n'; 2 | import { defer } from 'rxjs'; 3 | import { map, switchMap } from 'rxjs/operators'; 4 | import { get } from 'lodash'; 5 | 6 | import type { Filter, TimeRange } from '@kbn/es-query'; 7 | 8 | import type { KibanaExecutionContext } from '@kbn/core/public'; 9 | import { Adapters } from '@kbn/inspector-plugin/public'; 10 | 11 | import { 12 | calculateBounds, 13 | DataView, 14 | Query, 15 | IAggConfigs, 16 | ISearchStartSearchSource, 17 | tabifyAggResponse 18 | } from '@kbn/data-plugin/common'; 19 | 20 | 21 | /** 22 | * Clone of: ../../../../../src/plugins/data/common/search/expressions/esaggs/request_handler.ts 23 | * Customizations: 24 | * - searchSourceFields param 25 | * - response.totalHits 26 | * - response.hits 27 | */ 28 | 29 | /** @internal */ 30 | export interface RequestHandlerParams { 31 | abortSignal?: AbortSignal; 32 | aggs: IAggConfigs; 33 | filters?: Filter[]; 34 | indexPattern?: DataView; 35 | inspectorAdapters: Adapters; 36 | metricsAtAllLevels?: boolean; 37 | partialRows?: boolean; 38 | query?: Query; 39 | searchSessionId?: string; 40 | searchSourceService: ISearchStartSearchSource; 41 | timeFields?: string[]; 42 | timeRange?: TimeRange; 43 | getNow?: () => Date; 44 | executionContext?: KibanaExecutionContext; 45 | searchSourceFields: {[key: string]: any}; 46 | } 47 | 48 | export const handleRequest = ({ 49 | abortSignal, 50 | aggs, 51 | filters, 52 | indexPattern, 53 | inspectorAdapters, 54 | partialRows, 55 | query, 56 | searchSessionId, 57 | searchSourceService, 58 | timeFields, 59 | timeRange, 60 | getNow, 61 | executionContext, 62 | searchSourceFields, 63 | }: RequestHandlerParams) => { 64 | return defer(async () => { 65 | const forceNow = getNow?.(); 66 | const searchSource = await searchSourceService.create(); 67 | 68 | searchSource.setField('index', indexPattern); 69 | Object.keys(searchSourceFields).forEach(fieldName => { 70 | searchSource.setField(fieldName as any, searchSourceFields[fieldName]); 71 | }); 72 | 73 | // Create a new search source that inherits the original search source 74 | // but has the appropriate timeRange applied via a filter. 75 | // This is a temporary solution until we properly pass down all required 76 | // information for the request to the request handler (https://github.com/elastic/kibana/issues/16641). 77 | // Using callParentStartHandlers: true we make sure, that the parent searchSource 78 | // onSearchRequestStart will be called properly even though we use an inherited 79 | // search source. 80 | const timeFilterSearchSource = searchSource.createChild({ callParentStartHandlers: true }); 81 | const requestSearchSource = timeFilterSearchSource.createChild({ 82 | callParentStartHandlers: true, 83 | }); 84 | 85 | // If timeFields have been specified, use the specified ones, otherwise use primary time field of index 86 | // pattern if it's available. 87 | const defaultTimeField = indexPattern?.getTimeField?.(); 88 | const defaultTimeFields = defaultTimeField ? [defaultTimeField.name] : []; 89 | const allTimeFields = timeFields?.length ? timeFields : defaultTimeFields; 90 | 91 | aggs.setTimeRange(timeRange as TimeRange); 92 | aggs.setForceNow(forceNow); 93 | aggs.setTimeFields(allTimeFields); 94 | 95 | // For now we need to mirror the history of the passed search source, since 96 | // the request inspector wouldn't work otherwise. 97 | Object.defineProperty(requestSearchSource, 'history', { 98 | get() { 99 | return searchSource.history; 100 | }, 101 | set(history) { 102 | return (searchSource.history = history); 103 | }, 104 | }); 105 | 106 | requestSearchSource.setField('aggs', aggs); 107 | 108 | requestSearchSource.onRequestStart((paramSearchSource, options) => { 109 | return aggs.onSearchRequestStart(paramSearchSource, options); 110 | }); 111 | 112 | // If a timeRange has been specified and we had at least one timeField available, create range 113 | // filters for that those time fields 114 | if (timeRange && allTimeFields.length > 0) { 115 | timeFilterSearchSource.setField('filter', () => { 116 | return aggs.getSearchSourceTimeFilter(forceNow); 117 | }); 118 | } 119 | 120 | requestSearchSource.setField('filter', filters); 121 | requestSearchSource.setField('query', query); 122 | 123 | return { allTimeFields, forceNow, requestSearchSource }; 124 | }).pipe( 125 | switchMap(({ allTimeFields, forceNow, requestSearchSource }) => 126 | requestSearchSource 127 | .fetch$({ 128 | abortSignal, 129 | sessionId: searchSessionId, 130 | inspector: { 131 | adapter: inspectorAdapters.requests, 132 | title: i18n.translate('data.functions.esaggs.inspector.dataRequest.title', { 133 | defaultMessage: 'Data', 134 | }), 135 | description: i18n.translate('data.functions.esaggs.inspector.dataRequest.description', { 136 | defaultMessage: 137 | 'This request queries Elasticsearch to fetch the data for the visualization.', 138 | }), 139 | }, 140 | executionContext, 141 | }) 142 | .pipe( 143 | map(({ rawResponse: response }) => { 144 | const parsedTimeRange = timeRange ? calculateBounds(timeRange, { forceNow }) : null; 145 | const tabifyParams = { 146 | metricsAtAllLevels: aggs.hierarchical, 147 | partialRows, 148 | timeRange: parsedTimeRange 149 | ? { from: parsedTimeRange.min, to: parsedTimeRange.max, timeFields: allTimeFields } 150 | : undefined, 151 | }; 152 | 153 | return { 154 | ...tabifyAggResponse(aggs, response, tabifyParams), 155 | totalHits: get(response, 'hits.total', -1), 156 | hits: get(response, 'hits.hits', []), 157 | }; 158 | }) 159 | ) 160 | ) 161 | ); 162 | }; 163 | -------------------------------------------------------------------------------- /public/legacy/kibana_cloned_code/utils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Inspired from: '../../../../../src/plugins/data/common/search/tabify/response_writer.ts' 3 | * Function: response 4 | */ 5 | export function serializeAggConfig(aggConfig) { 6 | const sourceParams = { 7 | indexPatternId: aggConfig.getIndexPattern().id, 8 | ...aggConfig.serialize() 9 | }; 10 | 11 | return { 12 | type: aggConfig.type.name, 13 | indexPatternId: sourceParams.indexPatternId, 14 | aggConfigParams: sourceParams.params, 15 | source: 'esaggs', 16 | sourceParams 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /public/legacy/kibana_cloned_code/visualization_fn.ts: -------------------------------------------------------------------------------- 1 | import { ExpressionFunctionDefinition, Render } from '@kbn/expressions-plugin/public'; 2 | import { VisName } from '../../types'; 3 | 4 | interface Arguments { 5 | index?: string | null; 6 | metricsAtAllLevels?: boolean; 7 | partialRows?: boolean; 8 | schemas?: string; 9 | visConfig?: string; 10 | uiState?: string; 11 | aggConfigs?: string; 12 | timeFields?: string[]; 13 | } 14 | 15 | export interface CommonVisRenderValue { 16 | visType: string; 17 | visData: object; 18 | visConfig: object; 19 | params?: object; 20 | } 21 | 22 | export type CommonExpressionFunctionDefinition = ExpressionFunctionDefinition< 23 | VisName, 24 | any, 25 | Arguments, 26 | Promise> 27 | >; 28 | -------------------------------------------------------------------------------- /public/legacy/sankey_table_vis.js: -------------------------------------------------------------------------------- 1 | import { i18n } from '@kbn/i18n'; 2 | import { AggGroupNames } from '@kbn/data-plugin/public'; 3 | import random from '@fortawesome/fontawesome-free/svgs/solid/random.svg'; 4 | import tableVisTemplate from '../legacy/table_vis.html'; 5 | import { SankeyOptions } from '../components/sankey_vis_options_lazy'; 6 | import { VIS_EVENT_TO_TRIGGER } from '@kbn/visualizations-plugin/public'; 7 | 8 | import { SANKEY_VIS_NAME } from '../types'; 9 | import { toExpressionAstLegacy } from './to_ast_legacy'; 10 | 11 | 12 | // define the visType object, which kibana will use to display and configure new Vis object of this type. 13 | // eslint-disable-next-line no-unused-vars 14 | export function sankeyVisTypeDefinition (core, context) { 15 | return { 16 | requiresSearch: true, 17 | type: 'table', 18 | name: SANKEY_VIS_NAME, 19 | title: i18n.translate('visTypeSankeyVis.visTitle', { 20 | defaultMessage: 'Sankey Diagram' 21 | }), 22 | icon: random, 23 | description: i18n.translate('visTypeSankeyVis.visDescription', { 24 | defaultMessage: 'A sankey diagram is a type of flow diagram where flow quantities are depicted by proportional arrow magnitutes.' 25 | }), 26 | toExpressionAst: toExpressionAstLegacy, 27 | getSupportedTriggers: () => { 28 | return [VIS_EVENT_TO_TRIGGER.filter]; 29 | }, 30 | visConfig: { 31 | defaults: { 32 | perPage: 10, 33 | showPartialRows: false, 34 | showMetricsAtAllLevels: false, 35 | sort: { 36 | columnIndex: null, 37 | direction: null 38 | }, 39 | showTotal: false, 40 | totalFunc: 'sum', 41 | computedColumns: [], 42 | computedColsPerSplitCol: false, 43 | hideExportLinks: false, 44 | csvExportWithTotal: false, 45 | stripedRows: false, 46 | addRowNumberColumn: false, 47 | csvEncoding: 'utf-8', 48 | showFilterBar: false, 49 | filterCaseSensitive: false, 50 | filterBarHideable: false, 51 | filterAsYouType: false, 52 | filterTermsSeparately: false, 53 | filterHighlightResults: false, 54 | filterBarWidth: '25%' 55 | }, 56 | template: tableVisTemplate 57 | }, 58 | editorConfig: { 59 | optionsTemplate: SankeyOptions, 60 | schemas: [ 61 | { 62 | group: AggGroupNames.Metrics, 63 | name: 'metric', 64 | title: i18n.translate('visTypeTable.tableVisEditorConfig.schemas.metricTitle', { 65 | defaultMessage: 'Metric' 66 | }), 67 | aggFilter: ['!geo_centroid', '!geo_bounds'], 68 | aggSettings: { 69 | top_hits: { 70 | allowStrings: true 71 | } 72 | }, 73 | min: 1, 74 | defaults: [{ type: 'count', schema: 'metric' }] 75 | }, 76 | { 77 | group: AggGroupNames.Buckets, 78 | name: 'bucket', 79 | title: i18n.translate('visTypeTable.tableVisEditorConfig.schemas.bucketTitle', { 80 | defaultMessage: 'Split rows' 81 | }), 82 | aggFilter: ['!filter'] 83 | }, 84 | ] 85 | }, 86 | hierarchicalData: (vis) => { 87 | return Boolean(vis.params.showPartialRows || vis.params.showMetricsAtAllLevels); 88 | } 89 | }; 90 | } 91 | -------------------------------------------------------------------------------- /public/legacy/sankey_vis_controller.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | import d3 from 'd3'; 3 | import 'd3-plugins-sankey'; 4 | import { filterNodesAndLinks, matchColumnFromValue, buildFilterQuery, buildComplexQuery, getSelectedColumnIndex } from '../lib/filter'; 5 | import { buildQueryFilter, buildEsQuery } from '@kbn/es-query'; 6 | 7 | let observeResize = require('../lib/observe_resize'); 8 | 9 | function KbnSankeyVisController($scope, $element, config) { 10 | const uiStateSort = $scope.uiState ? $scope.uiState.get('vis.params.sort') : {}; 11 | _.assign($scope.visParams.sort, uiStateSort); 12 | let globalData = null; 13 | $scope.sort = $scope.visParams.sort; 14 | $scope.$watchCollection('sort', function (newSort) { 15 | $scope.uiState.set('vis.params.sort', newSort); 16 | }); 17 | 18 | /** 19 | * Recreate the entire table when: 20 | * - the underlying data changes (esResponse) 21 | * - one of the view options changes (vis.params) 22 | */ 23 | const getConfig = (...args) => config.get(...args); 24 | let darkMode = getConfig('theme:darkMode'); 25 | const lightTextColor = "#CBCFCB"; 26 | const darkTextColor = "#000000"; 27 | let svgRoot = $element[0]; 28 | let resize = false; 29 | let color = d3.scale.category20(); 30 | let margin = 20; 31 | let width; 32 | let height; 33 | let div; 34 | let svg; 35 | let _buildVis = function (data){ 36 | if(!resize){ 37 | data.slices=filterNodesAndLinks(data.slices.nodes, data.slices.links); 38 | } 39 | $scope.emptyGraph = (data.slices.nodes.length <= 0) ; 40 | 41 | let energy = data.slices; 42 | div = d3.select(svgRoot); 43 | if (!energy.nodes.length) return; 44 | 45 | svg = div.append('svg') 46 | .attr('width', width) 47 | .attr('height', height + margin) 48 | .append('g') 49 | .attr('transform', 'translate(0, 0)'); 50 | 51 | let sankey = d3.sankey() 52 | .nodeWidth(15) 53 | .nodePadding(10) 54 | .size([width, height]); 55 | 56 | let path = sankey.link(); 57 | 58 | let defs = svg.append('defs'); 59 | 60 | sankey 61 | .nodes(energy.nodes) 62 | .links(energy.links) 63 | .layout(13); 64 | 65 | let link = svg.append('g').selectAll('.link') 66 | .data(energy.links) 67 | .enter().append('path') 68 | .attr('class', 'link') 69 | .attr('d', path) 70 | .style('stroke-width', function (d) { 71 | return Math.max(1, d.dy); 72 | }) 73 | .style('fill', 'none') 74 | .style('stroke-opacity', 0.18) 75 | .sort(function (a, b) { 76 | return b.dy - a.dy; 77 | }) 78 | .on('mouseover', function() { 79 | d3.select(this).style('stroke-opacity', 0.5); 80 | }) 81 | .on('mouseout', function() { 82 | d3.select(this).style('stroke-opacity', 0.2); 83 | }) 84 | .on("click", function(d) { 85 | _query_path(d, energy.nodes); 86 | }); 87 | 88 | link.append('title') 89 | .text(function (d) { 90 | return d.source.name + ' → ' + d.target.name + '\n' + d.value + ' ('+(Math.round(d.value/d.source.value * 1000) / 10).toFixed(1)+'%)'; 91 | }); 92 | 93 | // OQMod, gets total of source nodes 94 | var total = d3.sum(energy.nodes, function(d) { 95 | if (d.targetLinks.length>0) 96 | return 0; //node is not source, exclude it from the total 97 | else 98 | return d.value; //node is a source: add its value to the sum 99 | }); 100 | 101 | let node = svg.append('g').selectAll('.node') 102 | .data(energy.nodes) 103 | .enter().append('g') 104 | .attr('class', 'node') 105 | .attr('transform', function (d) { 106 | return 'translate(' + d.x + ',' + d.y + ')'; 107 | }) 108 | // OQMod: Highlight the set of links coming from a node 109 | .on("mouseover", function(d) { 110 | d3.select(this) 111 | .style("cursor", "pointer") 112 | .style("fill", "transparent"); 113 | link 114 | .transition() 115 | .duration(300) 116 | .style("stroke-opacity", function(l) { 117 | return l.source === d || l.target === d ? 0.5 : 0.2; 118 | }); 119 | }) 120 | .on("mouseleave", function(d) { 121 | d3.select(this) 122 | .style("cursor", "default") 123 | .style("fill", "transparent"); 124 | link 125 | .transition() 126 | .duration(300) 127 | .style("stroke-opacity", 0.2); 128 | }); 129 | if ($scope.visParams.dragAndDrop) { 130 | node.call(d3.behavior.drag() 131 | .origin(function(d) { return d; }) 132 | .on('dragstart', function() { 133 | this.parentNode.appendChild(this); 134 | }) 135 | .on('drag', dragmove)); 136 | } else { 137 | node.on('click', function(d) { 138 | _query_node(d, energy.nodes) 139 | }) 140 | .on('.drag', null); 141 | } 142 | node.append('rect') 143 | .attr('height', function (d) { 144 | return d.dy; 145 | }) 146 | .attr('width', sankey.nodeWidth()) 147 | .style('fill', function (d) { 148 | d.color = color(d.name); 149 | return d.color; 150 | }) 151 | .attr("rx", 2) 152 | // OQMod: Rounded all corners 153 | /*.style('stroke', function (d) { 154 | return getConfig('theme:darkMode') ? d3.rgb(d.color).brighter(2) : d3.rgb(d.color).darker(2); 155 | })*/ 156 | // OQMod: Hide stroke border 157 | .append('title') 158 | .text(function (d) { 159 | return d.name + '\n' + d.value + ' ('+(Math.round(d.value/total * 1000) / 10).toFixed(1)+'%)'; 160 | }); 161 | 162 | node.append('text') 163 | .attr('x', -6) 164 | .attr('y', function (d) { 165 | return d.dy / 2; 166 | }) 167 | .attr('dy', '.35em') 168 | .style('fill', darkMode === 'enabled' || darkMode === true ? lightTextColor : darkTextColor) 169 | .attr('text-anchor', 'end') 170 | .attr('transform', null) 171 | .text(function (d) { 172 | return d.name; 173 | }) 174 | .filter(function (d) { 175 | return d.x < width / 2; 176 | }) 177 | .attr('x', 6 + sankey.nodeWidth()) 178 | .attr('text-anchor', 'start'); 179 | 180 | // add gradient to links 181 | link.style('stroke', (d, i) => { 182 | 183 | // make unique gradient ids 184 | const gradientID = `gradient${i}`; 185 | 186 | const startColor = d.source.color; 187 | const stopColor = d.target.color; 188 | 189 | const linearGradient = defs.append('linearGradient') 190 | .attr('id', gradientID); 191 | 192 | linearGradient.selectAll('stop') 193 | .data([ 194 | {offset: '10%', color: startColor }, 195 | {offset: '90%', color: stopColor } 196 | ]) 197 | .enter().append('stop') 198 | .attr('offset', d => { 199 | return d.offset; 200 | }) 201 | .attr('stop-color', d => { 202 | return d.color; 203 | }); 204 | 205 | return `url(#${gradientID})`; 206 | }); 207 | 208 | resize=false; 209 | 210 | function dragmove(d) { 211 | d3.select(this).attr('transform', 'translate(' + d.x + ',' + (d.y = Math.max(0, Math.min(height - d.dy, d3.event.y))) + ')'); 212 | sankey.relayout(); 213 | link.attr('d', path); 214 | } 215 | }; 216 | let _render = window.render = function (data) { 217 | d3.select(svgRoot).selectAll('svg').remove(); 218 | _buildVis(data); 219 | }; 220 | // listen activeFilter field changes, to filter results 221 | let _updateDimensions = function _updateDimensions() { 222 | let delta = 10; 223 | let w = $element.parent().width() - 10; 224 | let h = $element.parent().height() - 40; 225 | if (w) { 226 | if (w > delta) { 227 | w -= delta; 228 | } 229 | width = w; 230 | } 231 | if (h) { 232 | if (h > delta) { 233 | h -= delta; 234 | } 235 | height = h; 236 | } 237 | }; 238 | $scope.$watch('renderComplete', function () { 239 | let tableGroups = ($scope.tableGroups = null); 240 | let hasSomeRows = ($scope.hasSomeRows = null); 241 | if ($scope.esResponse) { 242 | tableGroups = $scope.esResponse; 243 | 244 | hasSomeRows = tableGroups.tables.some(function haveRows(table) { 245 | if (table.tables) return table.tables.some(haveRows); 246 | return table.rows.length > 0; 247 | }); 248 | globalData = $scope.esResponse; 249 | _updateDimensions(); 250 | _render($scope.esResponse); 251 | const totalHits = $scope.esResponse.totalHits; 252 | // no data to display 253 | if (totalHits === 0) { 254 | $scope.emptyGraph = false; 255 | $scope.renderComplete(); 256 | return; 257 | } 258 | } 259 | $scope.hasSomeRows = hasSomeRows; 260 | if (hasSomeRows) { 261 | $scope.dimensions = $scope.visParams.dimensions; 262 | $scope.tableGroups = tableGroups; 263 | } 264 | $scope.renderComplete(); 265 | }); 266 | observeResize($element, function () { 267 | if (globalData) { 268 | _updateDimensions(); 269 | resize=true; 270 | _render(globalData); 271 | } 272 | }); 273 | 274 | // @skarjoss: Query filters 275 | // refactored by @ch-bas 276 | let _query_path = function (selectedNode, nodes) { 277 | let columnMatch = matchColumnFromValue($scope.esResponse.tables[0].columns, getSelectedColumnIndex(selectedNode.source, nodes)); 278 | let destMatch = matchColumnFromValue($scope.esResponse.tables[0].columns, getSelectedColumnIndex(selectedNode.target, nodes)); 279 | const index = $scope.esResponse.tables[0].columns[0].aggConfig.aggConfigs.indexPattern; 280 | const queryFilter = buildComplexQuery(selectedNode.source, selectedNode.target, buildEsQuery, columnMatch, destMatch, index); 281 | if (!queryFilter) return; 282 | 283 | $scope.$parent.filter({ 284 | name: 'applyFilter', 285 | data: { filters: [queryFilter] }, 286 | }); 287 | }; 288 | let _query_node = function (selectedNode, nodes) { 289 | const columnMatch = matchColumnFromValue( 290 | $scope.esResponse.tables[0].columns, 291 | getSelectedColumnIndex(selectedNode, nodes) 292 | ); 293 | 294 | const queryFilter = buildFilterQuery(selectedNode, columnMatch, buildQueryFilter, buildEsQuery); 295 | if (!queryFilter) return; 296 | 297 | $scope.$parent.filter({ 298 | name: 'applyFilter', 299 | data: { filters: [queryFilter] }, 300 | }); 301 | }; 302 | } 303 | 304 | export { KbnSankeyVisController }; 305 | -------------------------------------------------------------------------------- /public/legacy/sankey_vis_legacy_fn.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one 3 | * or more contributor license agreements. Licensed under the Elastic License 4 | * 2.0 and the Server Side Public License, v 1; you may not use this file except 5 | * in compliance with, at your election, the Elastic License 2.0 or the Server 6 | * Side Public License, v 1. 7 | */ 8 | import { get } from 'lodash'; 9 | import { i18n } from '@kbn/i18n'; 10 | import { ExpressionFunctionDefinition, Render, Datatable } from '../../../../src/plugins/expressions/public'; 11 | import { getDataViewsStart, getSearchService, getVisualization } from '../services'; 12 | import { sankeyVisLegacyResponseHandler } from './sankey_vis_legacy_response_handler'; 13 | import { sankeyVisLegacyRequestHandler } from './sankey_vis_legacy_request_handler'; 14 | import { SANKEY_VIS_NAME, VisName } from '../types'; 15 | export type Input = Datatable; 16 | 17 | interface Arguments { 18 | index?: string | null; 19 | metricsAtAllLevels?: boolean; 20 | partialRows?: boolean; 21 | schemas?: string; 22 | visConfig?: string; 23 | uiState?: string; 24 | aggConfigs?: string; 25 | timeFields?: string[]; 26 | } 27 | 28 | export interface SankeyVisRenderValue { 29 | visType: string; 30 | visData: object; 31 | visConfig: object; 32 | params?: object; 33 | } 34 | 35 | export type SankeyExpressionFunctionDefinition = ExpressionFunctionDefinition< 36 | VisName, 37 | any, 38 | Arguments, 39 | Promise> 40 | >; 41 | 42 | type ResponseHandler = (any) => any; 43 | 44 | export const createSankeyVisLegacyFn = (): SankeyExpressionFunctionDefinition => ({ 45 | ...expressionFunction(SANKEY_VIS_NAME,sankeyVisLegacyResponseHandler), 46 | }); 47 | export const expressionFunction = (visName: VisName, responseHandler: ResponseHandler): SankeyExpressionFunctionDefinition => ({ 48 | name: visName, 49 | type: 'render', 50 | help: i18n.translate('visualizations.functions.visualization.help', { 51 | defaultMessage: 'Sankey Diagram', 52 | }), 53 | args: { 54 | // TODO: Below `help` keys should be internationalized once this function 55 | // TODO: is moved to visualizations plugin. 56 | index: { 57 | // types: ['string', 'null'], 58 | types: ['string'], 59 | default: '', 60 | help: 'Index', 61 | }, 62 | metricsAtAllLevels: { 63 | types: ['boolean'], 64 | default: false, 65 | help: 'Metrics levels', 66 | }, 67 | partialRows: { 68 | types: ['boolean'], 69 | default: false, 70 | help: 'Partial rows', 71 | }, 72 | schemas: { 73 | types: ['string'], 74 | default: '"{}"', 75 | help: 'Schemas', 76 | }, 77 | visConfig: { 78 | types: ['string'], 79 | default: '"{}"', 80 | help: 'Visualization configuration', 81 | }, 82 | uiState: { 83 | types: ['string'], 84 | default: '"{}"', 85 | help: 'User interface state', 86 | }, 87 | aggConfigs: { 88 | types: ['string'], 89 | default: '"{}"', 90 | help: 'Aggregation configurations', 91 | }, 92 | }, 93 | async fn(input, args, { inspectorAdapters, abortSignal, getSearchSessionId, getExecutionContext }) { 94 | const visConfigParams = args.visConfig ? JSON.parse(args.visConfig) : {}; 95 | const schemas = args.schemas ? JSON.parse(args.schemas) : {}; 96 | const indexPattern = args.index ? await getDataViewsStart().get(args.index) : null; 97 | 98 | const aggConfigsState = args.aggConfigs ? JSON.parse(args.aggConfigs) : []; 99 | const aggs = indexPattern 100 | ? getSearchService().aggs.createAggConfigs(indexPattern, aggConfigsState) 101 | : undefined; 102 | const visType = getVisualization().get(visName); 103 | input = await sankeyVisLegacyRequestHandler({ 104 | abortSignal, 105 | aggs, 106 | filters: get(input, 'filters', null), 107 | indexPattern, 108 | inspectorAdapters, 109 | partialRows: args.partialRows, 110 | query: get(input, 'query', null), 111 | searchSessionId: getSearchSessionId(), 112 | timeFields: args.timeFields, 113 | timeRange: get(input, 'timeRange', null), 114 | executionContext: getExecutionContext(), 115 | visParams: visConfigParams, 116 | }); 117 | 118 | 119 | if (input.columns) { 120 | // assign schemas to aggConfigs 121 | input.columns.forEach((column: any) => { 122 | if (column.aggConfig) { 123 | column.aggConfig.aggConfigs.schemas = visType.schemas.all; 124 | } 125 | }); 126 | 127 | Object.keys(schemas).forEach((key) => { 128 | schemas[key].forEach((i: any) => { 129 | if (input.columns[i] && input.columns[i].aggConfig) { 130 | input.columns[i].aggConfig.schema = key; 131 | } 132 | }); 133 | }); 134 | 135 | // This only works if "inputs.columns" exist, so it makes 136 | // sense to have it here 137 | if (inspectorAdapters?.tables) { 138 | inspectorAdapters.tables.logDatatable('default', input); 139 | } 140 | } 141 | 142 | const response = await responseHandler(input); 143 | 144 | return { 145 | type: 'render', 146 | as: visName, 147 | value: { 148 | visData: response, 149 | visType: visType.name, 150 | visConfig: visConfigParams, 151 | }, 152 | }; 153 | }, 154 | }); 155 | -------------------------------------------------------------------------------- /public/legacy/sankey_vis_legacy_module.ts: -------------------------------------------------------------------------------- 1 | import { IModule } from 'angular'; 2 | 3 | // @ts-ignore 4 | import { KbnSankeyVisController } from './sankey_vis_controller'; 5 | 6 | /** @internal */ 7 | export const initSankeyVisLegacyModule = (angularIns: IModule): void => { 8 | angularIns 9 | .controller('KbnSankeyVisController', ['$scope','$element','tableConfig',KbnSankeyVisController]); 10 | }; 11 | -------------------------------------------------------------------------------- /public/legacy/sankey_vis_legacy_renderer.tsx: -------------------------------------------------------------------------------- 1 | import { CoreSetup } from '@kbn/core/public'; 2 | import { ExpressionRenderDefinition } from '@kbn/expressions-plugin/public'; 3 | import { SankeyPluginStartDependencies } from '../plugin'; 4 | import { SankeyVisRenderValue } from './sankey_vis_legacy_fn'; 5 | import { SANKEY_VIS_NAME, VisName } from '../types'; 6 | 7 | const tableVisRegistry = new Map(); 8 | 9 | export const getSankeyVisLegacyRenderer: ( 10 | core: CoreSetup 11 | ) => ExpressionRenderDefinition = (core) => ({ 12 | ...getVisLegacyRender(core, SANKEY_VIS_NAME) 13 | }); 14 | 15 | const getVisLegacyRender: ( 16 | core: CoreSetup, 17 | visName: VisName 18 | ) => ExpressionRenderDefinition = (core, visName) => ({ 19 | name: visName, 20 | reuseDomNode: true, 21 | render: async (domNode, config, handlers) => { 22 | let registeredController = tableVisRegistry.get(domNode); 23 | 24 | if (!registeredController) { 25 | const { getTableVisualizationControllerClass } = await import('./vis_controller'); 26 | 27 | const Controller = getTableVisualizationControllerClass(core); 28 | registeredController = new Controller(domNode, config.visType); 29 | tableVisRegistry.set(domNode, registeredController); 30 | 31 | handlers.onDestroy(() => { 32 | registeredController?.destroy(); 33 | tableVisRegistry.delete(domNode); 34 | }); 35 | } 36 | 37 | await registeredController.render(config.visData, config.visConfig, handlers); 38 | handlers.done(); 39 | }, 40 | }); 41 | -------------------------------------------------------------------------------- /public/legacy/sankey_vis_legacy_request_handler.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | import { getSearchService } from '../services'; 3 | import { handleRequest } from './kibana_cloned_code/request_handler'; 4 | 5 | export async function sankeyVisLegacyRequestHandler ({ 6 | abortSignal, 7 | aggs, 8 | filters, 9 | indexPattern, 10 | inspectorAdapters, 11 | partialRows, 12 | query, 13 | searchSessionId, 14 | timeFields, 15 | timeRange, 16 | executionContext, 17 | visParams, 18 | }) { 19 | 20 | const MAX_HITS_SIZE = 10000; 21 | 22 | // create search source fields 23 | const searchSourceFields = {}; 24 | let hitsSize = (visParams.hitsSize !== undefined ? Math.min(visParams.hitsSize, MAX_HITS_SIZE) : 0); 25 | searchSourceFields.size = hitsSize; 26 | 27 | // specific request params for "field columns" 28 | if (visParams.fieldColumns !== undefined) { 29 | if (!visParams.fieldColumns.some(fieldColumn => fieldColumn.field.name === '_source')) { 30 | searchSourceFields._source = visParams.fieldColumns.map(fieldColumn => fieldColumn.field.name); 31 | } 32 | searchSourceFields.docvalue_fields = visParams.fieldColumns.filter(fieldColumn => fieldColumn.field.readFromDocValues).map(fieldColumn => fieldColumn.field.name); 33 | const scriptFields = {}; 34 | visParams.fieldColumns.filter(fieldColumn => fieldColumn.field.scripted).forEach(fieldColumn => { 35 | scriptFields[fieldColumn.field.name] = { 36 | script: { 37 | source: fieldColumn.field.script 38 | } 39 | }; 40 | }); 41 | searchSourceFields.script_fields = scriptFields; 42 | } 43 | 44 | // set search sort 45 | if (visParams.sortField !== undefined) { 46 | searchSourceFields.sort = [{ 47 | [visParams.sortField.name]: { 48 | order: visParams.sortOrder 49 | } 50 | }]; 51 | if ((visParams.hitsSize !== undefined && visParams.hitsSize > MAX_HITS_SIZE) || visParams.csvFullExport) { 52 | searchSourceFields.sort.push({'_doc': {}}); 53 | } 54 | } 55 | 56 | // add 'count' metric if there is no input column 57 | if (aggs.aggs.length === 0) { 58 | aggs.createAggConfig({ 59 | id: '1', 60 | enabled: true, 61 | type: 'count', 62 | schema: 'metric', 63 | params: {} 64 | }); 65 | } 66 | 67 | // execute elasticsearch query 68 | const request = { 69 | abortSignal, 70 | aggs, 71 | filters, 72 | indexPattern, 73 | inspectorAdapters, 74 | partialRows, 75 | query, 76 | searchSessionId, 77 | searchSourceService: getSearchService().searchSource, 78 | timeFields, 79 | timeRange, 80 | executionContext, 81 | searchSourceFields, 82 | }; 83 | const response = await handleRequest(request).toPromise(); 84 | 85 | // set 'split tables' direction 86 | const splitAggs = aggs.bySchemaName('split'); 87 | if (splitAggs.length > 0) { 88 | splitAggs[0].params.row = visParams.row; 89 | } 90 | 91 | // enrich response: aggs 92 | response.aggs = aggs; 93 | 94 | // enrich columns: aggConfig 95 | response.columns.forEach( column => { 96 | column.aggConfig = aggs.byId(column.meta.sourceParams.id.split('.')[0]); 97 | }); 98 | 99 | // enrich response: hits 100 | if (visParams.fieldColumns !== undefined) { 101 | response.fieldColumns = visParams.fieldColumns; 102 | 103 | // continue requests until expected hits size is reached 104 | if (visParams.hitsSize !== undefined && visParams.hitsSize > MAX_HITS_SIZE && response.totalHits > MAX_HITS_SIZE) { 105 | let remainingSize = visParams.hitsSize; 106 | do { 107 | remainingSize -= hitsSize; 108 | const searchAfter = response.hits[response.hits.length - 1].sort; 109 | hitsSize = Math.min(remainingSize, MAX_HITS_SIZE); 110 | searchSourceFields.size = hitsSize; 111 | searchSourceFields.search_after = searchAfter; 112 | const nextResponse = await handleRequest(request).toPromise(); 113 | for (let i = 0; i < nextResponse.hits.length; i++) { 114 | response.hits.push(nextResponse.hits[i]); 115 | } 116 | } while (remainingSize > hitsSize); 117 | } 118 | 119 | // put request on response, if full csv download is enabled 120 | if (visParams.csvFullExport) { 121 | response.request = request; 122 | } 123 | } 124 | 125 | // return elasticsearch response 126 | return response; 127 | } 128 | -------------------------------------------------------------------------------- /public/legacy/sankey_vis_legacy_response_handler.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one 3 | * or more contributor license agreements. Licensed under the Elastic License 4 | * 2.0 and the Server Side Public License, v 1; you may not use this file except 5 | * in compliance with, at your election, the Elastic License 2.0 or the Server 6 | * Side Public License, v 1. 7 | */ 8 | 9 | import { SchemaConfig } from '../../../../src/plugins/visualizations/public'; 10 | import { Input } from './sankey_vis_legacy_fn'; 11 | const { aggregate } = require('./helpers/agg_response_helper'); 12 | 13 | interface Dimensions { 14 | buckets: SchemaConfig[]; 15 | metrics: SchemaConfig[]; 16 | splitColumn?: SchemaConfig[]; 17 | splitRow?: SchemaConfig[]; 18 | } 19 | 20 | export interface SankeyContext { 21 | tables: Array; 22 | direction?: 'row' | 'column'; 23 | slices: any; 24 | } 25 | 26 | export interface TableGroup { 27 | $parent: SankeyContext; 28 | table: Input; 29 | tables: Table[]; 30 | title: string; 31 | name: string; 32 | key: any; 33 | column: number; 34 | row: number; 35 | } 36 | 37 | export interface Table { 38 | $parent?: TableGroup; 39 | columns: Input['columns']; 40 | rows: Input['rows']; 41 | } 42 | 43 | export function sankeyVisLegacyResponseHandler(table: Input): SankeyContext { 44 | const converted: SankeyContext = { 45 | slices: [], 46 | tables: [], 47 | }; 48 | converted.tables.push({ 49 | columns: table.columns, 50 | rows: table.rows, 51 | }); 52 | const missingValues = []; 53 | const groupBucket = []; 54 | table.columns.forEach((bucket) => { 55 | 56 | if (bucket.meta.sourceParams.params.missingBucket) { 57 | missingValues.push({[bucket.id]: bucket.meta.sourceParams.params.missingBucketLabel}); 58 | 59 | } 60 | if (bucket.meta.sourceParams.params.otherBucket) { 61 | groupBucket.push({[bucket.id]: bucket.meta.sourceParams.params.otherBucketLabel}); 62 | } 63 | }); 64 | converted.slices = aggregate({ 65 | rows: table.rows, 66 | missingValues, 67 | groupBucket 68 | }); 69 | return converted; 70 | } 71 | 72 | 73 | -------------------------------------------------------------------------------- /public/legacy/table_vis.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 |

8 |

9 |
10 |
11 |
12 | 22 | 23 |
24 |
25 | -------------------------------------------------------------------------------- /public/legacy/table_vis_legacy_type.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one 3 | * or more contributor license agreements. Licensed under the Elastic License 4 | * 2.0 and the Server Side Public License, v 1; you may not use this file except 5 | * in compliance with, at your election, the Elastic License 2.0 or the Server 6 | * Side Public License, v 1. 7 | */ 8 | 9 | import { i18n } from '@kbn/i18n'; 10 | import random from '@fortawesome/fontawesome-free/svgs/solid/random.svg'; 11 | import { AggGroupNames } from '../../../../src/plugins/data/public'; 12 | 13 | import { SankeyOptions } from '../components/sankey_vis_options_lazy'; 14 | import { VIS_EVENT_TO_TRIGGER } from '@kbn/visualizations-plugin/public'; 15 | import { toExpressionAstLegacy } from './to_ast_legacy'; 16 | 17 | export function tableVisLegacyTypeDefinition(core) { 18 | return { 19 | requiresSearch: true, 20 | name: 'sankey', 21 | title: i18n.translate('visTypeTable.tableVisTitle', { 22 | defaultMessage: 'Sankey Diagram', 23 | }), 24 | icon: random, 25 | description: i18n.translate('visTypeTable.tableVisDescription', { 26 | defaultMessage: 'A sankey diagram is a type of flow diagram where flow quantities are depicted by proportional arrow magnitutes..', 27 | }), 28 | getSupportedTriggers: () => { 29 | return [VIS_EVENT_TO_TRIGGER.filter]; 30 | }, 31 | visConfig: { 32 | defaults: { 33 | perPage: 10, 34 | showPartialRows: false, 35 | showMetricsAtAllLevels: false, 36 | sort: { 37 | columnIndex: null, 38 | direction: null, 39 | }, 40 | showTotal: false, 41 | totalFunc: 'sum', 42 | percentageCol: '', 43 | }, 44 | }, 45 | editorConfig: { 46 | optionsTemplate: SankeyOptions, 47 | enableDataViewChange: true, // Allows to change data view on existing Sankey 48 | schemas: [ 49 | { 50 | group: AggGroupNames.Metrics, 51 | name: 'metric', 52 | title: i18n.translate('visTypeTable.tableVisEditorConfig.schemas.metricTitle', { 53 | defaultMessage: 'Metric', 54 | }), 55 | aggFilter: ['!geo_centroid', '!geo_bounds', '!filtered_metric', '!single_percentile'], 56 | aggSettings: { 57 | top_hits: { 58 | allowStrings: true, 59 | }, 60 | }, 61 | min: 1, 62 | defaults: [{ type: 'count', schema: 'metric' }], 63 | }, 64 | { 65 | group: AggGroupNames.Buckets, 66 | name: 'bucket', 67 | title: i18n.translate('visTypeTable.tableVisEditorConfig.schemas.bucketTitle', { 68 | defaultMessage: 'Split rows', 69 | }), 70 | aggFilter: ['!filter'], 71 | }, 72 | ], 73 | }, 74 | toExpressionAst: toExpressionAstLegacy, 75 | hierarchicalData: (vis) => vis.params.showPartialRows || vis.params.showMetricsAtAllLevels, 76 | }; 77 | } 78 | -------------------------------------------------------------------------------- /public/legacy/to_ast_legacy.ts: -------------------------------------------------------------------------------- 1 | import { buildExpression, buildExpressionFunction, ExpressionAstExpression } from '@kbn/expressions-plugin/public'; 2 | import { getVisSchemas, VisToExpressionAst } from '@kbn/visualizations-plugin/public'; 3 | import { TableVisConfig, SANKEY_VIS_NAME } from '../types'; 4 | import { SankeyVisParams } from '../components/sankey_options'; 5 | import { CommonExpressionFunctionDefinition } from './kibana_cloned_code/visualization_fn'; 6 | 7 | export type CommonVisDataParams = SankeyVisParams; 8 | export type CommonVisConfig = TableVisConfig; 9 | 10 | type CommonVisToExpressionAst = (vis, params, name) => ExpressionAstExpression | Promise; 11 | 12 | export const toExpressionAstLegacy: VisToExpressionAst = (vis,params) => { 13 | return toExpressionAst(vis,params,SANKEY_VIS_NAME); 14 | }; 15 | 16 | const toExpressionAst: CommonVisToExpressionAst = (vis, params, visName) => { 17 | 18 | const schemas = getVisSchemas(vis, params); 19 | 20 | const visConfig: CommonVisConfig = { 21 | ...vis.params, 22 | title: vis.title, 23 | }; 24 | 25 | const table = buildExpressionFunction(visName, { 26 | visConfig: JSON.stringify(visConfig), 27 | schemas: JSON.stringify(schemas), 28 | index: vis.data.indexPattern!.id!, 29 | uiState: JSON.stringify(vis.uiState), 30 | aggConfigs: JSON.stringify(vis.data.aggs!.aggs), 31 | partialRows: vis.params.showPartialRows, 32 | metricsAtAllLevels: vis.isHierarchical() 33 | }); 34 | 35 | const ast = buildExpression([table]); 36 | 37 | return ast.toAst(); 38 | }; 39 | -------------------------------------------------------------------------------- /public/legacy/vis_controller.ts: -------------------------------------------------------------------------------- 1 | /* global JQuery */ 2 | import angular, { IModule, auto, IRootScopeService, IScope, ICompileService } from 'angular'; 3 | import $ from 'jquery'; 4 | 5 | import { CoreSetup } from '@kbn/core/public'; 6 | import { VisParams } from '@kbn/visualizations-plugin/public'; 7 | import { BaseVisType } from '@kbn/visualizations-plugin/public'; 8 | import { IInterpreterRenderHandlers } from '@kbn/expressions-plugin/public'; 9 | 10 | import { getAngularModule } from './get_inner_angular'; 11 | import { getVisualization } from '../services'; 12 | import { initSankeyVisLegacyModule } from './sankey_vis_legacy_module'; 13 | // @ts-ignore 14 | import tableVisTemplate from './table_vis.html'; 15 | 16 | const innerAngularName = 'kibana/table_vis'; 17 | 18 | export function getTableVisualizationControllerClass( 19 | core: CoreSetup 20 | ) { 21 | return class TableVisualizationController { 22 | private tableVisModule: IModule | undefined; 23 | private injector: auto.IInjectorService | undefined; 24 | el: JQuery; 25 | $rootScope: IRootScopeService | null = null; 26 | $scope: (IScope & { [key: string]: any }) | undefined; 27 | $compile: ICompileService | undefined; 28 | params: object; 29 | handlers: any; 30 | vis: BaseVisType | undefined; 31 | 32 | constructor(domeElement: Element, visName: string) { 33 | this.el = $(domeElement); 34 | this.vis = getVisualization().get(visName); 35 | } 36 | 37 | getInjector() { 38 | if (!this.injector) { 39 | const mountpoint = document.createElement('div'); 40 | mountpoint.className = 'visualization'; 41 | this.injector = angular.bootstrap(mountpoint, [innerAngularName], { 42 | strictDi: true 43 | } ); 44 | this.el.append(mountpoint); 45 | } 46 | 47 | return this.injector; 48 | } 49 | 50 | async initLocalAngular() { 51 | if (!this.tableVisModule) { 52 | const [coreStart] = await core.getStartServices(); 53 | const { initAngularBootstrap } = await import('./angular/angular_bootstrap'); 54 | initAngularBootstrap(); 55 | this.tableVisModule = getAngularModule(innerAngularName, coreStart); 56 | initSankeyVisLegacyModule(this.tableVisModule); 57 | } 58 | } 59 | 60 | async render( 61 | esResponse: object, 62 | visParams: VisParams, 63 | handlers: IInterpreterRenderHandlers 64 | ): Promise { 65 | await this.initLocalAngular(); 66 | 67 | return new Promise((resolve, reject) => { 68 | try { 69 | if (!this.$rootScope) { 70 | const $injector = this.getInjector(); 71 | this.$rootScope = $injector.get('$rootScope'); 72 | this.$compile = $injector.get('$compile'); 73 | } 74 | const updateScope = () => { 75 | if (!this.$scope) { 76 | return; 77 | } 78 | this.$scope.vis = this.vis; 79 | this.$scope.visState = { params: visParams, title: visParams.title }; 80 | this.$scope.esResponse = esResponse; 81 | 82 | this.$scope.visParams = visParams; 83 | this.$scope.renderComplete = resolve; 84 | this.$scope.renderFailed = reject; 85 | this.$scope.resize = Date.now(); 86 | this.$scope.$apply(); 87 | }; 88 | 89 | if (!this.$scope && this.$compile) { 90 | this.$scope = this.$rootScope.$new(); 91 | this.$scope.uiState = handlers.uiState; 92 | this.$scope.filter = handlers.event; 93 | updateScope(); 94 | this.el.find('.visualization').append(this.$compile(tableVisTemplate)(this.$scope)); 95 | this.el.find('.visChart__spinner').remove(); 96 | this.$scope.$apply(); 97 | } else { 98 | updateScope(); 99 | } 100 | } catch (error) { 101 | reject(error); 102 | } 103 | }); 104 | } 105 | 106 | destroy() { 107 | if (this.$rootScope) { 108 | this.$rootScope.$destroy(); 109 | this.$rootScope = null; 110 | } 111 | } 112 | }; 113 | } 114 | -------------------------------------------------------------------------------- /public/lib/filter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This function filters the input data and removes all invalid values. 3 | * It removes all links with an empty or 0 value, all nodes without an link and all links with not existing nodes. 4 | * 5 | * The input parameter links contains two fields source and target which reference the 6 | * corrisponding nodes in the nodes array. 7 | * The removal of one node, makes an update of all related links necessary. 8 | * To avoid this, we filter all invalid links and remove all unused nodes from nodes array. 9 | * We also used a reference node map to update the source and target nodes in the links. 10 | * After the filtering the map is converted back into the initial array structure. 11 | * 12 | * #Definitions: 13 | * ##Input Parameter 14 | * 15 | * nodes: {array} 16 | * This is an input parameter and contains an array of NODEs. 17 | * 18 | * links: {array} 19 | * This is an input parameter, it contains an array of LINKs. 20 | * 21 | * NODE: { name: } 22 | * A node is an object with only one attribute `name`. 23 | * 24 | * LINK: { source: NODE_INDEX, target: NODE_INDEX, value: } 25 | * A link is an object with three attributes. It binds two nodes and give them a value. 26 | * The source and target attributes are indices and references to the nodes array. 27 | * 28 | * NODE_INDEX: {number} 29 | * A node index is the index of a node in the nodes array. 30 | * 31 | * ##Inner Data Structures 32 | * 33 | * NODE_MAP: {map: NODE_INDEX => NEW_NODE} 34 | * Mapping of the node index to the node object with additional attributes. 35 | * 36 | * REF_NODE_MAP: {map: NODE_INDEX => NODE_INDEX} 37 | * Mapping of the old node index to the new node index. 38 | * After removing nodes in the nodes array, all node indices will be changed. 39 | * To update the node references in the links, we create a map which reference the old node index 40 | * to the new node index. 41 | * 42 | * NEW_NODE: { name: , valid: } 43 | * Copy of NODE with an additional attribute `valid`. 44 | * The valid attribute is a flag to show which nodes is include inner the links array. 45 | */ 46 | module.exports = (function () { 47 | /** 48 | * Check if node exists in nodes array. 49 | * 50 | * @private 51 | * @param {array} nodes - The nodes array. 52 | * @param {number} index - The node index. 53 | * @returns {boolean} - true, if node exist else false. 54 | */ 55 | function _isNodeExist(nodes, index) { return !!nodes[index]; } 56 | 57 | /** 58 | * Get node if exist 59 | * else returns an empty object. 60 | * 61 | * @private 62 | * @param {NODE_MAP} nodesMap - The nodes map. 63 | * @param {number} index - The node index. 64 | * @returns {NODE} - The node object. 65 | */ 66 | function _getNode(nodesMap, index) { return nodesMap.get(index) || {}; } 67 | 68 | /** 69 | * Add valid attribute to each node, which relates to a link. 70 | * 71 | * @private 72 | * @param {NODE_MAP} nodesmap - The nodes map. 73 | * @param {array} links - The links array. 74 | * @returns void 75 | */ 76 | function _markUsedNodes(nodesMap, links) { 77 | links.map(({source, target}) => { 78 | const srcNode = _getNode(nodesMap, source); 79 | if (srcNode !== undefined) { srcNode.valid = true; } 80 | 81 | const tarNode = _getNode(nodesMap, target); 82 | if (tarNode !== undefined) { tarNode.valid = true; } 83 | }); 84 | } 85 | 86 | /** 87 | * Filter the links array. 88 | * Validates on existing nodes and values greater null. 89 | * 90 | * @private 91 | * @param {array} nodes - The nodes array. 92 | * @param {array} links - The links array. 93 | * @returns {array} - The filtered links array. 94 | */ 95 | function _filterLinks(nodes, links) { 96 | return links.filter(({source, target, value}) => { 97 | const isSrcNodeValid = _isNodeExist(nodes, source); 98 | const isTarNodeValid = _isNodeExist(nodes, target); 99 | return (!!value) && (isSrcNodeValid) && (isTarNodeValid); 100 | }) 101 | } 102 | 103 | /** 104 | * Generate nodes map. 105 | * 106 | * @private 107 | * @param {array} nodes - The nodes array. 108 | * @param {array} links - The links array. 109 | * @returns {NODE_MAP} - The nodes map. 110 | */ 111 | function _generateNodesMap(nodes, links) { 112 | const nodesMap = new Map(); 113 | nodes.map(({name}, index) => nodesMap.set(index, {name})); 114 | _markUsedNodes(nodesMap, links); 115 | return nodesMap; 116 | } 117 | 118 | /** 119 | * Generate reference nodes map. 120 | * 121 | * @private 122 | * @param {NODE_MAP} - The nodes map. 123 | * @returns {REF_NODE_MAP} - The reference nodes map. 124 | */ 125 | function _generateRefNodesMap(nodesMap) { 126 | const refNodeMap = new Map(); 127 | let i = 0; 128 | nodesMap.forEach(({name, valid}, key) => { 129 | if (valid) { 130 | refNodeMap.set(key, i++); 131 | } 132 | }); 133 | return refNodeMap; 134 | } 135 | 136 | /** 137 | * Update source and target attribute of each link. 138 | * 139 | * @private 140 | * @param {array} links - The links array. 141 | * @param {REF_NODE_MAP} refNodesMap - The mapping from old to new node indices. 142 | * @returns {array} - The updated links array. 143 | */ 144 | function _updateLinks(links, refNodesMap) { 145 | return links.map(({source, target, value}) => { 146 | return { 147 | source: refNodesMap.get(source), 148 | target: refNodesMap.get(target), 149 | value 150 | } 151 | }); 152 | } 153 | 154 | /** 155 | * Converts nodes map to nodes array. 156 | * Removed all nodes without or invalid `valid`-attribute. 157 | * 158 | * @private 159 | * @param {NODE_MAP} nodesMap - The nodes map. 160 | * @param {REF_NODE_MAP} refNodesMap - The mapping from old to new node indices. 161 | * @returns {array} - The filtered nodes array. 162 | */ 163 | function _convertNodesMapToArray(nodesMap, refNodesMap) { 164 | const nodes = []; 165 | nodesMap.forEach(({name, valid}, key) => { 166 | if (valid) { 167 | const newIndex = refNodesMap.get(key); 168 | nodes[newIndex] = { name }; 169 | } 170 | }); 171 | return nodes 172 | } 173 | 174 | /** 175 | * Filter invalid nodes and links out. 176 | * 177 | * @param {array} nodes - The nodes array. 178 | * @param {array} links - The links array. 179 | * @returns {object} - Valid nodes and links. 180 | */ 181 | function filterNodesAndLinks(nodes, links) { 182 | const filteredLinks = _filterLinks(nodes, links); 183 | const nodesMap = _generateNodesMap(nodes, filteredLinks); 184 | const refNodesMap = _generateRefNodesMap(nodesMap); 185 | const updatedLinks = _updateLinks(filteredLinks, refNodesMap); 186 | const filteredNodes = _convertNodesMapToArray(nodesMap, refNodesMap); 187 | return { 188 | nodes: filteredNodes, 189 | links: updatedLinks 190 | } 191 | } 192 | 193 | /** 194 | * Get the index of the selected node in the sankey vis. 195 | * @param {object} selectedNode - The selected node. 196 | * @param {array} nodeList - The links array. 197 | * @returns {number} - Index of the selected node. 198 | */ 199 | function getSelectedColumnIndex(selectedNode, nodeList) { 200 | let counter = 0; 201 | const valueMap = new Map(); 202 | 203 | nodeList.forEach(value => { 204 | if (!valueMap.has(value.x)) { 205 | counter++; 206 | valueMap.set(value.x, counter); 207 | } 208 | }); 209 | let key; 210 | if (valueMap.has(selectedNode.x)) { 211 | key = valueMap.get(selectedNode.x); 212 | } 213 | return key; 214 | } 215 | /** 216 | * author: skarjoss 217 | * refactor: ch-bas 218 | * Matches the filtered value in row/column agg. 219 | * @param {array} columns - Array containing the agg columns. 220 | * @param {number} key - The index of the selected node. 221 | * @returns {object} - The agg column corresponding to the selected node. 222 | */ 223 | function matchColumnFromValue(columns, key) 224 | { 225 | // From the original columns/rows data, keep only bucket schema aggs 226 | columns = columns.filter(item => item.aggConfig.schema === "bucket"); 227 | return columns[key-1]; 228 | } 229 | // @skarjoss: Query filters 230 | function buildFilterQuery(filteredValue, columnMatch, buildQueryFilter) { 231 | if (!columnMatch) return null; 232 | 233 | let filterField = columnMatch.meta.field; 234 | let filterIndex = columnMatch.meta.index; 235 | let filterAlias = `${columnMatch.name}: "${filteredValue.name}"`; 236 | let filterQueryClause = ['terms', 'number', 'string'].includes(columnMatch.meta.params.id) 237 | ? 'match_phrase' 238 | : columnMatch.meta.params.id; 239 | 240 | let query = { [filterQueryClause]: { [filterField]: filteredValue.name } }; 241 | return buildQueryFilter(query, filterIndex, filterAlias); 242 | } 243 | // build an ES match_phrase query 244 | function buildComplexQuery(source, target, buildEsQuery, columnMatch, destMatch, index) { 245 | if (!columnMatch || !destMatch) return null; 246 | 247 | let queries = [ 248 | { meta: columnMatch.meta, match_phrase: { [columnMatch.meta.field]: source.name } }, 249 | { meta: destMatch.meta, match_phrase: { [destMatch.meta.field]: target.name } }, 250 | ]; 251 | 252 | return buildEsQuery(index, null, queries); 253 | } 254 | 255 | return { 256 | _isNodeExist, 257 | _getNode, 258 | _markUsedNodes, 259 | _filterLinks, 260 | _generateNodesMap, 261 | _generateRefNodesMap, 262 | _updateLinks, 263 | _convertNodesMapToArray, 264 | buildComplexQuery, 265 | buildFilterQuery, 266 | filterNodesAndLinks, 267 | matchColumnFromValue, 268 | getSelectedColumnIndex 269 | }; 270 | }()); 271 | -------------------------------------------------------------------------------- /public/lib/observe_resize.js: -------------------------------------------------------------------------------- 1 | // Borrowed from https://github.com/joola/echo-ui-plugin-sankey/blob/develop/public/lib/observe_resize.js 2 | module.exports = function ($elem, fn, frequency) { 3 | 4 | var currentFrequency = frequency || 500; 5 | var currentHeight = $elem.height(); 6 | var currentWidth = $elem.width(); 7 | 8 | function checkLoop() { 9 | setTimeout(function () { 10 | if (currentHeight !== $elem.height() || currentWidth !== $elem.width()) { 11 | currentHeight = $elem.height(); 12 | currentWidth = $elem.width(); 13 | 14 | if (currentWidth > 0 && currentWidth > 0) fn(); 15 | } 16 | checkLoop(); 17 | }, currentFrequency); 18 | } 19 | checkLoop(); 20 | }; 21 | -------------------------------------------------------------------------------- /public/plugin.ts: -------------------------------------------------------------------------------- 1 | import type { CoreSetup, CoreStart, Plugin } from '@kbn/core/public'; 2 | import type { Plugin as ExpressionsPublicPlugin } from '@kbn/expressions-plugin/public'; 3 | import type { VisualizationsSetup, VisualizationsStart } from '@kbn/visualizations-plugin/public'; 4 | import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; 5 | import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; 6 | import { DataPublicPluginStart } from '@kbn/data-plugin/public'; 7 | 8 | import { setFormatService, setDataViewsStart, setNotifications, setSearchService, setVisualization } from './services'; 9 | import { createSankeyVisLegacyFn } from "./legacy/sankey_vis_legacy_fn"; 10 | import { getSankeyVisLegacyRenderer } from "./legacy/sankey_vis_legacy_renderer"; 11 | import { tableVisLegacyTypeDefinition } from "./legacy/table_vis_legacy_type"; 12 | 13 | 14 | /** @internal */ 15 | export interface SankeyVisPluginSetupDependencies { 16 | expressions: ReturnType; 17 | visualizations: VisualizationsSetup; 18 | } 19 | 20 | /** @internal */ 21 | export interface SankeyPluginStartDependencies { 22 | fieldFormats: FieldFormatsStart; 23 | dataViews: DataViewsPublicPluginStart; 24 | data: DataPublicPluginStart; 25 | visualizations: VisualizationsStart; 26 | } 27 | 28 | /** @internal */ 29 | export class SankeyVisPlugin implements Plugin { 30 | 31 | public async setup( 32 | core: CoreSetup, 33 | { expressions, visualizations }: SankeyVisPluginSetupDependencies 34 | ) { 35 | expressions.registerFunction(createSankeyVisLegacyFn); 36 | expressions.registerRenderer(getSankeyVisLegacyRenderer(core)); 37 | visualizations.createBaseVisualization(tableVisLegacyTypeDefinition(core)); 38 | } 39 | 40 | public start(core: CoreStart, { data, dataViews, fieldFormats, visualizations }: SankeyPluginStartDependencies) { 41 | setFormatService(fieldFormats); 42 | setNotifications(core.notifications); 43 | setSearchService(data.search); 44 | setDataViewsStart(dataViews); 45 | setVisualization(visualizations); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /public/services.ts: -------------------------------------------------------------------------------- 1 | import { createGetterSetter } from '@kbn/kibana-utils-plugin/public'; 2 | import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; 3 | import type { NotificationsStart } from '@kbn/core/public'; 4 | import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; 5 | import type { VisualizationsStart } from '@kbn/visualizations-plugin/public'; 6 | import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; 7 | 8 | export const [getFormatService, setFormatService] = 9 | createGetterSetter('FieldFormats'); 10 | 11 | export const [getNotifications, setNotifications] = 12 | createGetterSetter('Notifications'); 13 | 14 | export const [getDataViewsStart, setDataViewsStart] = 15 | createGetterSetter('dataViews'); 16 | 17 | export const [getSearchService, setSearchService] = 18 | createGetterSetter('Search'); 19 | 20 | export const [getVisualization, setVisualization] = 21 | createGetterSetter('Visualization'); 22 | -------------------------------------------------------------------------------- /public/types.ts: -------------------------------------------------------------------------------- 1 | import { ExpressionValueVisDimension } from '@kbn/visualizations-plugin/public'; 2 | 3 | export const SANKEY_VIS_NAME:VisName = 'sankey'; 4 | export type VisName = string; 5 | export interface TableVisConfig extends TableVisParams { 6 | title: string; 7 | buckets?: ExpressionValueVisDimension[]; 8 | metrics: ExpressionValueVisDimension[]; 9 | splitColumn?: ExpressionValueVisDimension; 10 | splitRow?: ExpressionValueVisDimension; 11 | } 12 | 13 | export interface TableVisParams { 14 | perPage: number | ''; 15 | showPartialRows: boolean; 16 | showMetricsAtAllLevels: boolean; 17 | showToolbar: boolean; 18 | showTotal: boolean; 19 | percentageCol: string; 20 | row?: boolean; 21 | } 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /sankey-drag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uniberg/kbn_sankey_vis/048d2915a4167a09a4059641d0b6c46cbb30459d/sankey-drag.png -------------------------------------------------------------------------------- /sankey-filtering.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uniberg/kbn_sankey_vis/048d2915a4167a09a4059641d0b6c46cbb30459d/sankey-filtering.png -------------------------------------------------------------------------------- /sankey-multifilter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uniberg/kbn_sankey_vis/048d2915a4167a09a4059641d0b6c46cbb30459d/sankey-multifilter.png -------------------------------------------------------------------------------- /sankey_8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uniberg/kbn_sankey_vis/048d2915a4167a09a4059641d0b6c46cbb30459d/sankey_8.png -------------------------------------------------------------------------------- /test/agg_response_helperTest.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | 3 | const aggResponseHelper = require('../public/lib/agg_response_helper'); 4 | 5 | describe('aggResponseHelper', function() { 6 | describe('aggregate', function() { 7 | it('should create nodes and links array', function() { 8 | const data = [ 9 | [ 'node0', 'node3', 'node2', 90 ], 10 | [ 'node0', 'node2', 'node1', 90 ], 11 | [ 'node1', 'node3', 'node5', 90 ], 12 | [ 'node1', 'node4', 'node2', 90 ] 13 | ]; 14 | const expected_nodes = [ 15 | { name: 'node0' }, 16 | { name: 'node1' }, 17 | { name: 'node1' }, 18 | { name: 'node2' }, 19 | { name: 'node2' }, 20 | { name: 'node3' }, 21 | { name: 'node4' }, 22 | { name: 'node5' } 23 | ]; 24 | const expected_links = [ 25 | { source: 0, target: 5, value: 90 }, 26 | { source: 5, target: 4, value: 90 }, 27 | { source: 0, target: 3, value: 90 }, 28 | { source: 3, target: 2, value: 90 }, 29 | { source: 1, target: 5, value: 90 }, 30 | { source: 5, target: 5, value: 90 }, 31 | { source: 1, target: 5, value: 90 }, 32 | { source: 0, target: 5, value: 90 }, 33 | ]; 34 | const result = aggResponseHelper.aggregate(data); 35 | const result_nodes = result.nodes; 36 | const result_links = result.links; 37 | assert.notStrictEqual(result_nodes, expected_nodes); 38 | }); 39 | 40 | it('should sum link values', function() { 41 | const data = [ 42 | [ 'node0', 'node1', 90 ], 43 | [ 'node0', 'node1', 90 ], 44 | [ 'node2', 'node1', 90 ] 45 | ]; 46 | const expected_links = [ 47 | { source: 0, target: 1, value: 180 }, 48 | { source: 2, target: 1, value: 90 } 49 | ]; 50 | const result = aggResponseHelper.aggregate(data); 51 | const result_links = result.links; 52 | assert.notStrictEqual(result_links, expected_links); 53 | }); 54 | 55 | it('should be possible to use the same node name in different layers', function() { 56 | const data = [ 57 | [ 'node0', 'node0', 'node0', 90 ], 58 | [ '', '', '', 90 ] 59 | ]; 60 | const expected_nodes = [ 61 | { name: '' }, 62 | { name: '' }, 63 | { name: '' }, 64 | { name: 'node0' }, 65 | { name: 'node0' }, 66 | { name: 'node0' } 67 | ]; 68 | const result = aggResponseHelper.aggregate(data); 69 | const result_nodes = result.nodes; 70 | assert.notStrictEqual(result_nodes, expected_nodes); 71 | }); 72 | }); 73 | 74 | describe('privateFkt_generateLinksMap', function() {}); 75 | }); 76 | -------------------------------------------------------------------------------- /test/filterTest.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | 3 | var filter = require('../public/lib/filter'); 4 | describe('filter', function() { 5 | describe('filterNodesAndLinks()', function() { 6 | it('should filter all empty links and corresponding nodes', function() { 7 | nodes = [{ name: 'name_0' }, { name: 'name_1'}, { name: 'name_2' }]; 8 | links = [ 9 | { source: 0, target: 1, value: 0 }, 10 | { source: 0, target: 2, value: 90 } 11 | ]; 12 | 13 | result = filter.filterNodesAndLinks(nodes, links); 14 | result_nodes = result.nodes; 15 | result_links = result.links; 16 | assert.equal(result_nodes.length, 2); 17 | assert.equal(result_links.length, 1); 18 | }); 19 | 20 | it('should filter out everything', function() { 21 | nodes = [{ name: 'name_0' }, { name: 'name_1'}, { name: 'name_2' }]; 22 | links = [ 23 | { source: 0, target: 1, value: 0 }, 24 | ]; 25 | 26 | result = filter.filterNodesAndLinks(nodes, links); 27 | result_nodes = result.nodes; 28 | result_links = result.links; 29 | assert.equal(result_nodes.length, 0); 30 | assert.equal(result_links.length, 0); 31 | }); 32 | 33 | it('should filter out nothing', function() { 34 | nodes = [{ name: 'name_0' }, { name: 'name_1'}, { name: 'name_2' }]; 35 | links = [ 36 | { source: 0, target: 1, value: 10 }, 37 | { source: 0, target: 2, value: 20 }, 38 | ]; 39 | 40 | result = filter.filterNodesAndLinks(nodes, links); 41 | result_nodes = result.nodes; 42 | result_links = result.links; 43 | assert.equal(result_nodes.length, 3); 44 | assert.equal(result_links.length, 2); 45 | }); 46 | 47 | it('should filter out nothing, node name is not unique', function() { 48 | nodes = [{ name: 'name_0' }, { name: 'name_0'}, { name: 'name_2' }]; 49 | links = [ 50 | { source: 0, target: 1, value: 10 }, 51 | { source: 0, target: 2, value: 20 }, 52 | ]; 53 | 54 | result = filter.filterNodesAndLinks(nodes, links); 55 | result_nodes = result.nodes; 56 | result_links = result.links; 57 | assert.equal(result_nodes.length, 3); 58 | assert.equal(result_links.length, 2); 59 | }); 60 | 61 | it('should filter out invalid values: empty string', function() { 62 | nodes = [{ name: 'name_0' }, { name: 'name_1'}, { name: 'name_2' }]; 63 | links = [ 64 | { source: 0, target: 1, value: "" }, 65 | { source: 0, target: 2, value: 20 }, 66 | ]; 67 | 68 | result = filter.filterNodesAndLinks(nodes, links); 69 | result_nodes = result.nodes; 70 | result_links = result.links; 71 | assert.equal(result_nodes.length, 2); 72 | assert.equal(result_links.length, 1); 73 | }); 74 | 75 | it('should filter out invalid values: undefined', function() { 76 | nodes = [{ name: 'name_0' }, { name: 'name_1'}, { name: 'name_2' }]; 77 | links = [ 78 | { source: 0, target: 1, value: undefined }, 79 | { source: 0, target: 2, value: 20 }, 80 | ]; 81 | 82 | result = filter.filterNodesAndLinks(nodes, links); 83 | result_nodes = result.nodes; 84 | result_links = result.links; 85 | assert.equal(result_nodes.length, 2); 86 | assert.equal(result_links.length, 1); 87 | }); 88 | 89 | it('should filter out links of undifined nodes', function() { 90 | nodes = [{ name: 'name_0' }, { name: 'name_1'}, { name: 'name_2' }]; 91 | links = [ 92 | { source: 0, target: 1, value: 90 }, 93 | { source: 0, target: 3, value: 20 }, 94 | ]; 95 | 96 | result = filter.filterNodesAndLinks(nodes, links); 97 | result_nodes = result.nodes; 98 | 99 | result_links = result.links; 100 | assert.equal(result_nodes.length, 2); 101 | assert.equal(result_links.length, 1); 102 | }); 103 | 104 | it('should not filter out nodes with empty names', function() { 105 | nodes = [{ name: '' }, { name: 'name_1'}, { name: 'name_2' }]; 106 | links = [ 107 | { source: 0, target: 1, value: 90 }, 108 | { source: 0, target: 2, value: 20 }, 109 | ]; 110 | 111 | result = filter.filterNodesAndLinks(nodes, links); 112 | result_nodes = result.nodes; 113 | result_links = result.links; 114 | assert.equal(result_nodes.length, 3); 115 | assert.equal(result_links.length, 2); 116 | }); 117 | }); 118 | 119 | describe('privateFkt_isNodeExists()', function() { 120 | it('should exist', function() { 121 | nodes = [{ name: 'node_0' }]; 122 | result = filter._isNodeExist(nodes, 0); 123 | assert.ok(result, 'Node index not found'); 124 | }); 125 | 126 | it('should not exist 1', function() { 127 | nodes = [{ name: 'node_0' }]; 128 | result = filter._isNodeExist(nodes, 1); 129 | assert.equal(result, false, 'Node index found, but it should not.'); 130 | }); 131 | 132 | it('should not exist 2', function() { 133 | nodes = [{ name: 'node_0' }]; 134 | result = filter._isNodeExist(nodes, -1); 135 | assert.equal(result, false, 'Node index found, but it should not.'); 136 | }); 137 | }); 138 | 139 | describe('privateFkt_getNode()', function() { 140 | it('should return a node', function() { 141 | node = { name: 'node_0' }; 142 | nodesMap = new Map(); 143 | nodesMap.set(0,node); 144 | result = filter._getNode(nodesMap, 0); 145 | assert.equal(result, node, 'Node not found'); 146 | }); 147 | 148 | it('should return an empty object 1', function() { 149 | node = { name: 'node_0' }; 150 | nodesMap = new Map(); 151 | nodesMap.set(0,node); 152 | result = filter._getNode(nodesMap, 1); 153 | assert.notStrictEqual(result, {}, 'Node found'); 154 | }); 155 | 156 | it('should return an empty object 2', function() { 157 | node = { name: 'node_0' }; 158 | nodesMap = new Map(); 159 | nodesMap.set(0,node); 160 | result = filter._getNode(nodesMap, -1); 161 | assert.notStrictEqual(result, {}, 'Node found'); 162 | }); 163 | }); 164 | 165 | describe('privateFkt_markUsedNodes()', function() { 166 | it('should generate nodes map with all nodes valid', function() { 167 | nodes = [{ name: 'node_0' }, { name: 'node_1' }, { name: 'node_2' }]; 168 | links = [ 169 | { source: 0, target: 1, value: 90 }, 170 | { source: 0, target: 2, value: 90 }, 171 | ]; 172 | nodesMap = new Map(); 173 | nodesMap.set(0,nodes[0]); 174 | nodesMap.set(1,nodes[1]); 175 | nodesMap.set(2,nodes[2]); 176 | 177 | filter._markUsedNodes(nodesMap, links); 178 | result_node0_name = nodesMap.get(0).name; 179 | result_node1_name = nodesMap.get(1).name; 180 | result_node2_name = nodesMap.get(2).name; 181 | result_node0_valid = nodesMap.get(0).valid; 182 | result_node1_valid = nodesMap.get(1).valid; 183 | result_node2_valid = nodesMap.get(2).valid; 184 | assert.equal(result_node0_name, nodes[0].name); 185 | assert.equal(result_node1_name, nodes[1].name); 186 | assert.equal(result_node2_name, nodes[2].name); 187 | assert.equal(result_node0_valid, true); 188 | assert.equal(result_node1_valid, true); 189 | assert.equal(result_node2_valid, true); 190 | }); 191 | 192 | it('should generate nodes map with link including null value', function() { 193 | nodes = [{ name: 'node_0' }, { name: 'node_1' }, { name: 'node_2' }]; 194 | links = [ 195 | { source: 0, target: 1, value: 90 }, 196 | { source: 0, target: 2, value: 0 }, 197 | ]; 198 | 199 | nodesMap = new Map(); 200 | nodesMap.set(0,nodes[0]); 201 | nodesMap.set(1,nodes[1]); 202 | nodesMap.set(2,nodes[2]); 203 | filter._markUsedNodes(nodesMap, links); 204 | 205 | result_node0_name = nodesMap.get(0).name; 206 | result_node1_name = nodesMap.get(1).name; 207 | result_node2_name = nodesMap.get(2).name; 208 | result_node0_valid = nodesMap.get(0).valid; 209 | result_node1_valid = nodesMap.get(1).valid; 210 | result_node2_valid = nodesMap.get(2).valid; 211 | 212 | assert.equal(result_node0_name, nodes[0].name); 213 | assert.equal(result_node1_name, nodes[1].name); 214 | assert.equal(result_node2_name, nodes[2].name); 215 | assert.equal(result_node0_valid, true); 216 | assert.equal(result_node1_valid, true); 217 | assert.equal(result_node2_valid, true); 218 | }); 219 | 220 | it('should generate nodes map with unused node', function() { 221 | nodes = [{ name: 'node_0' }, { name: 'node_1' }, { name: 'node_2' }, { name: 'node_3' }]; 222 | links = [ 223 | { source: 0, target: 1, value: 90 }, 224 | { source: 0, target: 2, value: 90 }, 225 | ]; 226 | 227 | nodesMap = new Map(); 228 | nodesMap.set(0,nodes[0]); 229 | nodesMap.set(1,nodes[1]); 230 | nodesMap.set(2,nodes[2]); 231 | nodesMap.set(3,nodes[3]); 232 | filter._markUsedNodes(nodesMap, links); 233 | 234 | result_node0_name = nodesMap.get(0).name; 235 | result_node1_name = nodesMap.get(1).name; 236 | result_node2_name = nodesMap.get(2).name; 237 | result_node3_name = nodesMap.get(3).name; 238 | result_node0_valid = nodesMap.get(0).valid; 239 | result_node1_valid = nodesMap.get(1).valid; 240 | result_node2_valid = nodesMap.get(2).valid; 241 | result_node3_valid = nodesMap.get(3).valid; 242 | 243 | assert.equal(result_node0_name, nodes[0].name); 244 | assert.equal(result_node1_name, nodes[1].name); 245 | assert.equal(result_node2_name, nodes[2].name); 246 | assert.equal(result_node3_name, nodes[3].name); 247 | assert.equal(result_node0_valid, true); 248 | assert.equal(result_node1_valid, true); 249 | assert.equal(result_node2_valid, true); 250 | assert.equal(result_node3_valid, undefined); 251 | }); 252 | }); 253 | 254 | describe('privateFkt_filterLinks()', function() { 255 | it('should filter links with null values', function() { 256 | nodes = [{ name: 'node_0' }, { name: 'node_1' }, { name: 'node_2' }]; 257 | links = [ 258 | { source: 0, target: 1, value: 90 }, 259 | { source: 0, target: 2, value: 0 }, 260 | ]; 261 | result = filter._filterLinks(nodes, links); 262 | assert.equal(result.length, 1); 263 | assert.equal(result[0].value, 90); 264 | }); 265 | 266 | it('should filter links no existing nodes', function() { 267 | nodes = [{ name: 'node_0' }, { name: 'node_1' }, { name: 'node_2' }]; 268 | links = [ 269 | { source: 0, target: 1, value: 90 }, 270 | { source: 0, target: 2, value: 90 }, 271 | { source: 1, target: 3, value: 90 } 272 | ]; 273 | result = filter._filterLinks(nodes, links); 274 | result_link0_source = result[0].source; 275 | result_link1_source = result[1].source; 276 | result_link0_target = result[0].target; 277 | result_link1_target = result[1].target; 278 | result_link2 = result[2]; 279 | 280 | assert.equal(result.length, 2); 281 | assert.equal(result_link0_source, 0); 282 | assert.equal(result_link0_target, 1); 283 | assert.equal(result_link1_source, 0); 284 | assert.equal(result_link1_target, 2); 285 | assert.equal(result_link2, undefined); 286 | }); 287 | }); 288 | 289 | describe('privateFkt_generateNodesMap()', function() { 290 | it('should generate nodes map with all nodes valid', function() { 291 | nodes = [{ name: 'node_0' }, { name: 'node_1' }, { name: 'node_2' }]; 292 | links = [ 293 | { source: 0, target: 1, value: 90 }, 294 | { source: 0, target: 2, value: 90 }, 295 | ]; 296 | result = filter._generateNodesMap(nodes, links); 297 | result_node0_name = result.get(0).name; 298 | result_node1_name = result.get(1).name; 299 | result_node2_name = result.get(2).name; 300 | result_node0_valid = result.get(0).valid; 301 | result_node1_valid = result.get(1).valid; 302 | result_node2_valid = result.get(2).valid; 303 | 304 | assert.equal(result_node0_name, nodes[0].name); 305 | assert.equal(result_node1_name, nodes[1].name); 306 | assert.equal(result_node2_name, nodes[2].name); 307 | assert.equal(result_node0_valid, true); 308 | assert.equal(result_node1_valid, true); 309 | assert.equal(result_node2_valid, true); 310 | }); 311 | 312 | it('should generate nodes map with link including null value', function() { 313 | nodes = [{ name: 'node_0' }, { name: 'node_1' }, { name: 'node_2' }]; 314 | links = [ 315 | { source: 0, target: 1, value: 90 }, 316 | { source: 0, target: 2, value: 0 } 317 | ]; 318 | result = filter._generateNodesMap(nodes, links); 319 | result_node0_name = result.get(0).name; 320 | result_node1_name = result.get(1).name; 321 | result_node2_name = result.get(2).name; 322 | result_node0_valid = result.get(0).valid; 323 | result_node1_valid = result.get(1).valid; 324 | result_node2_valid = result.get(2).valid; 325 | 326 | assert.equal(result_node0_name, nodes[0].name); 327 | assert.equal(result_node1_name, nodes[1].name); 328 | assert.equal(result_node2_name, nodes[2].name); 329 | assert.equal(result_node0_valid, true); 330 | assert.equal(result_node1_valid, true); 331 | assert.equal(result_node2_valid, true); 332 | }); 333 | 334 | it('should generate nodes map with unused node', function() { 335 | nodes = [{ name: 'node_0' }, { name: 'node_1' }, { name: 'node_2' }, { name: 'node_3' }]; 336 | links = [ 337 | { source: 0, target: 1, value: 90 }, 338 | { source: 0, target: 2, value: 90 } 339 | ]; 340 | result = filter._generateNodesMap(nodes, links); 341 | result_node0_name = result.get(0).name; 342 | result_node1_name = result.get(1).name; 343 | result_node2_name = result.get(2).name; 344 | result_node3_name = result.get(3).name; 345 | result_node0_valid = result.get(0).valid; 346 | result_node1_valid = result.get(1).valid; 347 | result_node2_valid = result.get(2).valid; 348 | result_node3_valid = result.get(3).valid; 349 | 350 | assert.equal(result_node0_name, nodes[0].name); 351 | assert.equal(result_node1_name, nodes[1].name); 352 | assert.equal(result_node2_name, nodes[2].name); 353 | assert.equal(result_node3_name, nodes[3].name); 354 | assert.equal(result_node0_valid, true); 355 | assert.equal(result_node1_valid, true); 356 | assert.equal(result_node2_valid, true); 357 | assert.equal(result_node3_valid, undefined); 358 | }); 359 | }); 360 | 361 | describe('privateFkt_generateRefNodesMap()', function() { 362 | it('should generate reference node map with one invalid node', function() { 363 | nodesMap = new Map(); 364 | nodesMap.set(0, { name: 'node_0', valid: true }); 365 | nodesMap.set(1, { name: 'node_1' }); 366 | nodesMap.set(2, { name: 'node_2', valid: true }); 367 | result = filter._generateRefNodesMap(nodesMap); 368 | result_node0_newIndex = result.get(0); 369 | result_node1_newIndex = result.get(1); 370 | result_node2_newIndex = result.get(2); 371 | assert.equal(result_node0_newIndex, 0); 372 | assert.equal(result_node1_newIndex, undefined); 373 | assert.equal(result_node2_newIndex, 1); 374 | }); 375 | }); 376 | 377 | describe('privateFkt_updateLinks()', function() {}); 378 | 379 | describe('privateFkt_convertNodesMapToArray()', function() {}); 380 | 381 | }); 382 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "target/types", 5 | "noImplicitAny": false, 6 | "noImplicitThis": false, 7 | "strictNullChecks": false, 8 | "strictPropertyInitialization": false 9 | }, 10 | "include": [ 11 | "public/**/*" 12 | ], 13 | "kbn_references": [ 14 | "@kbn/core", 15 | "@kbn/data-plugin", 16 | "@kbn/visualizations-plugin", 17 | "@kbn/share-plugin", 18 | "@kbn/data-views-plugin", 19 | "@kbn/expressions-plugin", 20 | "@kbn/kibana-utils-plugin", 21 | "@kbn/vis-default-editor-plugin", 22 | "@kbn/field-formats-plugin", 23 | "@kbn/inspector-plugin", 24 | "@kbn/i18n", 25 | "@kbn/i18n-react", 26 | "@kbn/ui-theme", 27 | "@kbn/config-schema", 28 | "@kbn/handlebars", 29 | "@kbn/es-query" 30 | ], 31 | "exclude": [ 32 | "target/**/*" 33 | ] 34 | } 35 | --------------------------------------------------------------------------------