From 9045d09d90caac1707f867bf448f958babbbe359 Mon Sep 17 00:00:00 2001 From: Gnanesh Date: Wed, 28 Dec 2022 17:36:55 +0530 Subject: [PATCH 01/29] Add draft implementation of FT.EXPLAIN/FT.EXPLAINCLI vizualization. --- .../ui/src/packages/ri-explain/README.md | 1 + .../ui/src/packages/ri-explain/package.json | 66 + .../ui/src/packages/ri-explain/src/App.tsx | 27 + .../src/packages/ri-explain/src/Explain.tsx | 176 + .../ui/src/packages/ri-explain/src/Node.tsx | 31 + .../ri-explain/src/icons/arrow_down.js | 28 + .../ri-explain/src/icons/arrow_left.js | 28 + .../ri-explain/src/icons/arrow_right.js | 33 + .../packages/ri-explain/src/icons/check.js | 28 + .../src/packages/ri-explain/src/icons/copy.js | 29 + .../packages/ri-explain/src/icons/cross.js | 27 + .../packages/ri-explain/src/icons/empty.js | 23 + .../ui/src/packages/ri-explain/src/index.html | 18 + .../ui/src/packages/ri-explain/src/main.tsx | 46 + .../ui/src/packages/ri-explain/src/parser.ts | 548 +++ .../ri-explain/src/styles/_dark_theme.less | 3 + .../ri-explain/src/styles/_light_theme.less | 3 + .../ri-explain/src/styles/styles.less | 214 ++ .../ui/src/packages/ri-explain/src/utils.ts | 56 + .../ri-explain/types/@elastic/index.d.ts | 60 + .../ui/src/packages/ri-explain/yarn.lock | 3112 +++++++++++++++++ 21 files changed, 4557 insertions(+) create mode 100644 redisinsight/ui/src/packages/ri-explain/README.md create mode 100644 redisinsight/ui/src/packages/ri-explain/package.json create mode 100644 redisinsight/ui/src/packages/ri-explain/src/App.tsx create mode 100644 redisinsight/ui/src/packages/ri-explain/src/Explain.tsx create mode 100644 redisinsight/ui/src/packages/ri-explain/src/Node.tsx create mode 100644 redisinsight/ui/src/packages/ri-explain/src/icons/arrow_down.js create mode 100644 redisinsight/ui/src/packages/ri-explain/src/icons/arrow_left.js create mode 100644 redisinsight/ui/src/packages/ri-explain/src/icons/arrow_right.js create mode 100644 redisinsight/ui/src/packages/ri-explain/src/icons/check.js create mode 100644 redisinsight/ui/src/packages/ri-explain/src/icons/copy.js create mode 100644 redisinsight/ui/src/packages/ri-explain/src/icons/cross.js create mode 100644 redisinsight/ui/src/packages/ri-explain/src/icons/empty.js create mode 100644 redisinsight/ui/src/packages/ri-explain/src/index.html create mode 100644 redisinsight/ui/src/packages/ri-explain/src/main.tsx create mode 100644 redisinsight/ui/src/packages/ri-explain/src/parser.ts create mode 100644 redisinsight/ui/src/packages/ri-explain/src/styles/_dark_theme.less create mode 100644 redisinsight/ui/src/packages/ri-explain/src/styles/_light_theme.less create mode 100644 redisinsight/ui/src/packages/ri-explain/src/styles/styles.less create mode 100644 redisinsight/ui/src/packages/ri-explain/src/utils.ts create mode 100644 redisinsight/ui/src/packages/ri-explain/types/@elastic/index.d.ts create mode 100644 redisinsight/ui/src/packages/ri-explain/yarn.lock diff --git a/redisinsight/ui/src/packages/ri-explain/README.md b/redisinsight/ui/src/packages/ri-explain/README.md new file mode 100644 index 0000000000..d5b2221dd1 --- /dev/null +++ b/redisinsight/ui/src/packages/ri-explain/README.md @@ -0,0 +1 @@ +# RI-Explain plugin \ No newline at end of file diff --git a/redisinsight/ui/src/packages/ri-explain/package.json b/redisinsight/ui/src/packages/ri-explain/package.json new file mode 100644 index 0000000000..34f9ed9c59 --- /dev/null +++ b/redisinsight/ui/src/packages/ri-explain/package.json @@ -0,0 +1,66 @@ +{ + "author": { + "name": "Redis Ltd.", + "email": "support@redis.com", + "url": "https://redis.com/redis-enterprise/redis-insight" + }, + "bugs": { + "url": "https://github.com/" + }, + "description": "Show Profile/Explain Visualization", + "source": "./src/main.tsx", + "styles": "./dist/styles.css", + "main": "./dist/index.js", + "name": "explain-plugin", + "version": "0.0.1", + "scripts": { + "start": "cross-env NODE_ENV=development parcel serve src/index.html", + "build": "rimraf dist && cross-env NODE_ENV=production concurrently \"yarn build:js && yarn minify:js\" \"yarn build:css\"", + "build-lite": "rm dist/*.js && cross-env NODE_ENV=production concurrently \"yarn build:js && yarn minify:js\"", + "build:js": "parcel build src/main.tsx --dist-dir dist", + "build:css": "parcel build src/styles/styles.less --dist-dir dist", + "minify:js": "terser -- dist/main.js > dist/index.js && rimraf dist/main.js" + }, + "targets": { + "main": false, + "module": { + "includeNodeModules": true + } + }, + "visualizations": [ + { + "id": "explain-viz", + "name": "Explain", + "activationMethod": "renderSearchExplain", + "matchCommands": [ + "FT.EXPLAIN", + "FT.EXPLAINCLI" + ], + "description": "Example of FT.EXPLAIN plugin", + "default": true + } + ], + "devDependencies": { + "@parcel/compressor-brotli": "^2.0.0", + "@parcel/compressor-gzip": "^2.0.0", + "@parcel/transformer-less": "^2.3.2", + "concurrently": "^6.3.0", + "cross-env": "^7.0.3", + "parcel": "^2.0.0", + "rimraf": "^3.0.2", + "terser": "^5.9.0" + }, + "dependencies": { + "@antv/hierarchy": "^0.6.8", + "@antv/x6": "^2.1.3", + "@antv/x6-react-shape": "^2.1.0", + "@elastic/eui": "34.6.0", + "@emotion/react": "^11.7.1", + "classnames": "^2.3.1", + "prop-types": "^15.8.1", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "redisinsight-plugin-sdk": "^1.0.0", + "uuid": "^9.0.0" + } +} diff --git a/redisinsight/ui/src/packages/ri-explain/src/App.tsx b/redisinsight/ui/src/packages/ri-explain/src/App.tsx new file mode 100644 index 0000000000..aaa38ea353 --- /dev/null +++ b/redisinsight/ui/src/packages/ri-explain/src/App.tsx @@ -0,0 +1,27 @@ +import React from 'react' +import Explain from './Explain' + +const isDarkTheme = document.body.classList.contains('theme_DARK') + +export function ExplainApp(props: { command?: string, data: any }) { + + const ErrorResponse = HandleError(props) + + if (ErrorResponse !== null) return ErrorResponse + + return ( +
+ +
+ ) +} + +function HandleError(props: { command?: string, data: any }): JSX.Element | null { + const { data: [{ response = '', status = '' } = {}] = [] } = props + + if (status === 'fail') { + return
{JSON.stringify(response)}
+ } + + return null +} diff --git a/redisinsight/ui/src/packages/ri-explain/src/Explain.tsx b/redisinsight/ui/src/packages/ri-explain/src/Explain.tsx new file mode 100644 index 0000000000..30c0be2c40 --- /dev/null +++ b/redisinsight/ui/src/packages/ri-explain/src/Explain.tsx @@ -0,0 +1,176 @@ +import React, { useEffect, useState, useRef } from 'react' +import { Model, Graph } from '@antv/x6' +import { register} from '@antv/x6-react-shape'; +import Hierarchy from '@antv/hierarchy'; + +import { AntHierarchyInput, ASTToJson } from './parser' +import { ExplainNode } from './Node'; + +interface IExplain { + data: [{response: string[] | string}] +} + +export default function Explain(props: IExplain): JSX.Element { + + const resp = props.data[0].response + + return ( + + ) +} + +register({ + shape: 'react-node', + width: 100, + height: 100, + component: ExplainNode as any +}) + +const isDarkTheme = document.body.classList.contains('theme_DARK') + +function ExplainDraw(props: {data: any}): JSX.Element { + const [done, setDone] = useState(false) + const container = useRef(null) + + useEffect(() => { + + if (done) return + setDone(true) + + const graph = new Graph({ + container: container?.current as HTMLElement, + autoResize: false, + interacting: false, + background: { + color: isDarkTheme ? 'black' : 'white', + }, + panning: { + enabled: true, + modifiers: ['ctrl'], + }, + translating: { + restrict: true, + }, + async: true, + virtual: true, + }) + + graph.on("resize", () => { + graph.centerContent() + }) + + function resize() { + const isFullScreen = parent.document.body.getElementsByClassName('fullscreen').length > 0 + if (isFullScreen) { + graph.resize(document.body.offsetWidth, parent.document.body.offsetHeight) + } else { + graph.resize(document.body.offsetWidth, 585) + } + } + + window.addEventListener('resize', resize); + + let data = ASTToJson(props.data); + + const result = Hierarchy.dendrogram(data, { + direction: 'BT', + getHeight() { + return 200 + }, + getWidth() { + return 250 + }, + getHGap() { + return 0 + }, + getVGap() { + return 0 + }, + nodeSep: 250, + rankSep: 150, + subTreeSep: 0, + }) + + const model: Model.FromJSONData = { nodes: [], edges: [] } + const traverse = (data: any) => { + if (data) { + const myData = data.data + model.nodes?.push({ + id: data.id, + x: (data.x || 0) + document.body.clientWidth / 2, + y: (data.y || 0) + document.body.clientHeight, + shape: 'react-node', + width: 240, + height: (myData.snippet ? 64 : 42), + label: data.id.toString(), + data: {...myData, label: myData.data.data, type: myData.data.type}, + attrs: { + body: { + fill: isDarkTheme ? '#5F95FF' : '#8992B3', + stroke: 'transparent', + }, + }, + }) + } + if (data.children) { + data.children.forEach((item: any) => { + model.edges?.push({ + source: data.id, + target: item.id, + router: { + name: 'manhattan', + args: { + startDirections: ['top'], + endDirections: ['bottom'], + // cost: 33, + // step: 10, + padding: { + top: 15, + bottom: 10, + right: 20, + left: 10, + } + }, + }, + attrs: { + line: { + stroke: '#6B6B6B', + strokeWidth: 1, + targetMarker: null, + }, + }, + }) + traverse(item) + }) + } + } + traverse(result) + + graph.fromJSON(model) + // graph.centerContent() + + graph.centerContent() + // scroller.enableAutoResize() + // scroller.center() + // scroller.scrollToContent() + + // scroller.enableAutoResize() + // scroller.center() + // scroller.resize(1636) + // scroller.centerContent() + // graph.center() + // graph.centerContent() + + + + }, [done]) + + return ( +
+ ) + +} diff --git a/redisinsight/ui/src/packages/ri-explain/src/Node.tsx b/redisinsight/ui/src/packages/ri-explain/src/Node.tsx new file mode 100644 index 0000000000..d439cdde22 --- /dev/null +++ b/redisinsight/ui/src/packages/ri-explain/src/Node.tsx @@ -0,0 +1,31 @@ +import React from 'react' +import { AntHierarchyInput } from './parser'; + +interface INodeProps { + label: string + numRecords?: string + executionTime?: string + snippet?: string +} + + +export function ExplainNode(props: INodeProps) { + const data = (props as any).node.getData(); + const label = data ? data.label : ''; + const snippet = data ? data.snippet : ''; + return ( +
+
+
+
{label}
+ {data.type === 'Expr' &&
text
} +
+
+ { snippet && +
+ {snippet} +
+ } +
+ ) +} diff --git a/redisinsight/ui/src/packages/ri-explain/src/icons/arrow_down.js b/redisinsight/ui/src/packages/ri-explain/src/icons/arrow_down.js new file mode 100644 index 0000000000..9fc44fc582 --- /dev/null +++ b/redisinsight/ui/src/packages/ri-explain/src/icons/arrow_down.js @@ -0,0 +1,28 @@ +function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } + +function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; } + +function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; } + +import * as React from 'react'; + +var EuiIconArrowDown = function EuiIconArrowDown(_ref) { + var title = _ref.title, + titleId = _ref.titleId, + props = _objectWithoutProperties(_ref, ["title", "titleId"]); + + return /*#__PURE__*/React.createElement("svg", _extends({ + width: 16, + height: 16, + viewBox: "0 0 16 16", + xmlns: "http://www.w3.org/2000/svg", + "aria-labelledby": titleId + }, props), title ? /*#__PURE__*/React.createElement("title", { + id: titleId + }, title) : null, /*#__PURE__*/React.createElement("path", { + fillRule: "non-zero", + d: "M13.069 5.157L8.384 9.768a.546.546 0 01-.768 0L2.93 5.158a.552.552 0 00-.771 0 .53.53 0 000 .759l4.684 4.61c.641.631 1.672.63 2.312 0l4.684-4.61a.53.53 0 000-.76.552.552 0 00-.771 0z" + })); +}; + +export var icon = EuiIconArrowDown; diff --git a/redisinsight/ui/src/packages/ri-explain/src/icons/arrow_left.js b/redisinsight/ui/src/packages/ri-explain/src/icons/arrow_left.js new file mode 100644 index 0000000000..9acedc3e12 --- /dev/null +++ b/redisinsight/ui/src/packages/ri-explain/src/icons/arrow_left.js @@ -0,0 +1,28 @@ +function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } + +function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; } + +function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; } + +import * as React from 'react'; + +var EuiIconArrowLeft = function EuiIconArrowLeft(_ref) { + var title = _ref.title, + titleId = _ref.titleId, + props = _objectWithoutProperties(_ref, ["title", "titleId"]); + + return /*#__PURE__*/React.createElement("svg", _extends({ + width: 16, + height: 16, + viewBox: "0 0 16 16", + xmlns: "http://www.w3.org/2000/svg", + "aria-labelledby": titleId + }, props), title ? /*#__PURE__*/React.createElement("title", { + id: titleId + }, title) : null, /*#__PURE__*/React.createElement("path", { + fillRule: "nonzero", + d: "M10.843 13.069L6.232 8.384a.546.546 0 010-.768l4.61-4.685a.552.552 0 000-.771.53.53 0 00-.759 0l-4.61 4.684a1.65 1.65 0 000 2.312l4.61 4.684a.53.53 0 00.76 0 .552.552 0 000-.771z" + })); +}; + +export var icon = EuiIconArrowLeft; diff --git a/redisinsight/ui/src/packages/ri-explain/src/icons/arrow_right.js b/redisinsight/ui/src/packages/ri-explain/src/icons/arrow_right.js new file mode 100644 index 0000000000..0de7006630 --- /dev/null +++ b/redisinsight/ui/src/packages/ri-explain/src/icons/arrow_right.js @@ -0,0 +1,33 @@ +import * as React from 'react' + +function _extends() { _extends = Object.assign || function (target) { for (let i = 1; i < arguments.length; i++) { const source = arguments[i]; for (const key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key] } } } return target }; return _extends.apply(this, arguments) } + +function _objectWithoutProperties(source, excluded) { + if (source == null) return {}; const target = _objectWithoutPropertiesLoose(source, excluded); let key; let + i; if (Object.getOwnPropertySymbols) { const sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key] } } return target +} + +function _objectWithoutPropertiesLoose(source, excluded) { + if (source == null) return {}; const target = {}; const sourceKeys = Object.keys(source); let key; let + i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key] } return target +} + +const EuiIconArrowRight = function EuiIconArrowRight(_ref) { + const { title } = _ref + const { titleId } = _ref + const props = _objectWithoutProperties(_ref, ['title', 'titleId']) + + return /* #__PURE__ */React.createElement('svg', { width: 16, + height: 16, + viewBox: '0 0 16 16', + xmlns: 'http://www.w3.org/2000/svg', + 'aria-labelledby': titleId, + ...props }, title ? /* #__PURE__ */React.createElement('title', { + id: titleId + }, title) : null, /* #__PURE__ */React.createElement('path', { + fillRule: 'nonzero', + d: 'M5.157 13.069l4.611-4.685a.546.546 0 000-.768L5.158 2.93a.552.552 0 010-.771.53.53 0 01.759 0l4.61 4.684c.631.641.63 1.672 0 2.312l-4.61 4.684a.53.53 0 01-.76 0 .552.552 0 010-.771z' + })) +} + +export var icon = EuiIconArrowRight diff --git a/redisinsight/ui/src/packages/ri-explain/src/icons/check.js b/redisinsight/ui/src/packages/ri-explain/src/icons/check.js new file mode 100644 index 0000000000..4c0144cc33 --- /dev/null +++ b/redisinsight/ui/src/packages/ri-explain/src/icons/check.js @@ -0,0 +1,28 @@ +function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } + +function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; } + +function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; } + +import * as React from 'react'; + +var EuiIconCheck = function EuiIconCheck(_ref) { + var title = _ref.title, + titleId = _ref.titleId, + props = _objectWithoutProperties(_ref, ["title", "titleId"]); + + return /*#__PURE__*/React.createElement("svg", _extends({ + width: 16, + height: 16, + viewBox: "0 0 16 16", + xmlns: "http://www.w3.org/2000/svg", + "aria-labelledby": titleId + }, props), title ? /*#__PURE__*/React.createElement("title", { + id: titleId + }, title) : null, /*#__PURE__*/React.createElement("path", { + fillRule: "evenodd", + d: "M6.5 12a.502.502 0 01-.354-.146l-4-4a.502.502 0 01.708-.708L6.5 10.793l6.646-6.647a.502.502 0 01.708.708l-7 7A.502.502 0 016.5 12" + })); +}; + +export var icon = EuiIconCheck; diff --git a/redisinsight/ui/src/packages/ri-explain/src/icons/copy.js b/redisinsight/ui/src/packages/ri-explain/src/icons/copy.js new file mode 100644 index 0000000000..73146e9dea --- /dev/null +++ b/redisinsight/ui/src/packages/ri-explain/src/icons/copy.js @@ -0,0 +1,29 @@ +function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } + +function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; } + +function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; } + +import * as React from 'react'; + +var EuiIconCopy = function EuiIconCopy(_ref) { + var title = _ref.title, + titleId = _ref.titleId, + props = _objectWithoutProperties(_ref, ["title", "titleId"]); + + return /*#__PURE__*/React.createElement("svg", _extends({ + width: 16, + height: 16, + viewBox: "0 0 16 16", + xmlns: "http://www.w3.org/2000/svg", + "aria-labelledby": titleId + }, props), title ? /*#__PURE__*/React.createElement("title", { + id: titleId + }, title) : null, /*#__PURE__*/React.createElement("path", { + d: "M11.4 0c.235 0 .46.099.622.273l2.743 3c.151.162.235.378.235.602v9.25a.867.867 0 01-.857.875H3.857A.867.867 0 013 13.125V.875C3 .392 3.384 0 3.857 0H11.4zM14 4h-2.6a.4.4 0 01-.4-.4V1H4v12h10V4z" + }), /*#__PURE__*/React.createElement("path", { + d: "M3 1H2a1 1 0 00-1 1v13a1 1 0 001 1h10a1 1 0 001-1v-1h-1v1H2V2h1V1z" + })); +}; + +export var icon = EuiIconCopy; diff --git a/redisinsight/ui/src/packages/ri-explain/src/icons/cross.js b/redisinsight/ui/src/packages/ri-explain/src/icons/cross.js new file mode 100644 index 0000000000..80f56b4b98 --- /dev/null +++ b/redisinsight/ui/src/packages/ri-explain/src/icons/cross.js @@ -0,0 +1,27 @@ +function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } + +function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; } + +function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; } + +import * as React from 'react'; + +var EuiIconCross = function EuiIconCross(_ref) { + var title = _ref.title, + titleId = _ref.titleId, + props = _objectWithoutProperties(_ref, ["title", "titleId"]); + + return /*#__PURE__*/React.createElement("svg", _extends({ + width: 16, + height: 16, + viewBox: "0 0 16 16", + xmlns: "http://www.w3.org/2000/svg", + "aria-labelledby": titleId + }, props), title ? /*#__PURE__*/React.createElement("title", { + id: titleId + }, title) : null, /*#__PURE__*/React.createElement("path", { + d: "M7.293 8L3.146 3.854a.5.5 0 11.708-.708L8 7.293l4.146-4.147a.5.5 0 01.708.708L8.707 8l4.147 4.146a.5.5 0 01-.708.708L8 8.707l-4.146 4.147a.5.5 0 01-.708-.708L7.293 8z" + })); +}; + +export var icon = EuiIconCross; diff --git a/redisinsight/ui/src/packages/ri-explain/src/icons/empty.js b/redisinsight/ui/src/packages/ri-explain/src/icons/empty.js new file mode 100644 index 0000000000..9eb9b8ba43 --- /dev/null +++ b/redisinsight/ui/src/packages/ri-explain/src/icons/empty.js @@ -0,0 +1,23 @@ +function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } + +function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; } + +function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; } + +import * as React from 'react'; + +var EuiIconEmpty = function EuiIconEmpty(_ref) { + var title = _ref.title, + titleId = _ref.titleId, + props = _objectWithoutProperties(_ref, ["title", "titleId"]); + + return /*#__PURE__*/React.createElement("svg", _extends({ + width: 16, + height: 16, + viewBox: "0 0 16 16", + xmlns: "http://www.w3.org/2000/svg", + "aria-labelledby": titleId + }, props)); +}; + +export var icon = EuiIconEmpty; diff --git a/redisinsight/ui/src/packages/ri-explain/src/index.html b/redisinsight/ui/src/packages/ri-explain/src/index.html new file mode 100644 index 0000000000..8090672fce --- /dev/null +++ b/redisinsight/ui/src/packages/ri-explain/src/index.html @@ -0,0 +1,18 @@ + + + + + + + Client list plugin + + + + + + + + +
+ + diff --git a/redisinsight/ui/src/packages/ri-explain/src/main.tsx b/redisinsight/ui/src/packages/ri-explain/src/main.tsx new file mode 100644 index 0000000000..e02488b028 --- /dev/null +++ b/redisinsight/ui/src/packages/ri-explain/src/main.tsx @@ -0,0 +1,46 @@ +/* eslint-disable react/jsx-filename-extension */ +import React from 'react' +import { render } from 'react-dom' +import { ExplainApp } from './App' + +interface Props { + command?: string + data?: { response: any, status: string }[] +} + +import { appendIconComponentCache } from '@elastic/eui/es/components/icon/icon'; +import { icon as EuiIconMagnifyWithPlus } from '@elastic/eui/es/components/icon/assets/magnifyWithPlus'; +import { icon as EuiIconMagnifyWithMinus } from '@elastic/eui/es/components/icon/assets/magnifyWithMinus'; +import { icon as EuiIconBullsEye } from '@elastic/eui/es/components/icon/assets/bullseye'; +import { icon as EuiIconEditorItemAlignLeft } from '@elastic/eui/es/components/icon/assets/editorItemAlignLeft'; +import { icon as EuiIconEditorItemAlignRight } from '@elastic/eui/es/components/icon/assets/editorItemAlignRight'; +import { icon as EuiIconEditorItemAlignCenter } from '@elastic/eui/es/components/icon/assets/editorItemAlignCenter'; +import { icon as EuiIconArrowLeft } from '@elastic/eui/es/components/icon/assets/arrow_left'; +import { icon as EuiIconArrowRight } from '@elastic/eui/es/components/icon/assets/arrow_right'; +import { icon as EuiIconArrowDown } from '@elastic/eui/es/components/icon/assets/arrow_down'; +import { icon as EuiIconCross } from '@elastic/eui/es/components/icon/assets/cross'; + +appendIconComponentCache({ + magnifyWithPlus: EuiIconMagnifyWithPlus, + magnifyWithMinus: EuiIconMagnifyWithMinus, + bullseye: EuiIconBullsEye, + editorItemAlignLeft: EuiIconEditorItemAlignLeft, + editorItemAlignRight: EuiIconEditorItemAlignRight, + editorItemAlignCenter: EuiIconEditorItemAlignCenter, + arrowLeft: EuiIconArrowLeft, + arrowRight: EuiIconArrowRight, + arrowDown: EuiIconArrowDown, + cross: EuiIconCross, +}) + +const renderApp = (element: JSX.Element) => render( + element, + document.getElementById('app') +) + +const renderSearchExplain = (props: Props) => renderApp( + +) + +// This is a required action - export the main function for execution of the visualization +export default { renderSearchExplain } diff --git a/redisinsight/ui/src/packages/ri-explain/src/parser.ts b/redisinsight/ui/src/packages/ri-explain/src/parser.ts new file mode 100644 index 0000000000..c65d60607c --- /dev/null +++ b/redisinsight/ui/src/packages/ri-explain/src/parser.ts @@ -0,0 +1,548 @@ +import { v4 as uuidv4 } from 'uuid'; + +enum TokenType { + + INIT = 'INIT', + + EOF = 'EOF', + ILLEGAL = 'ILLEGAL', + + UNION = 'UNION', + INTERSECT = 'INTERSECT', + NUMERIC = 'NUMERIC', + LBRACE = 'LBRACE', + RBRACE = 'RBRACE', + LPAREN = 'LAPAREN', + RPAREN = 'RAPAREN', + NUMBER = 'NUMBER', + NEW_LINE = 'NEW_LINE', + + PLUS = 'PLUS', + MINUS = 'MINUS', + + LESS = 'LESS', + GREATER = 'GREATER', + + EQUAL = 'EQUAL', + LESS_EQUAL = 'LESS_EQUAL', + GREATER_EQUAL = 'GREATER_EQUAL', + + IDENTIFIER = 'IDENTIFIER', +} + +class Token { + T: TokenType + Data: string + + constructor(t: TokenType, data: string) { + this.T = t; + this.Data = data; + } + +} + +const KEYWORDS = { + [TokenType.EOF.toString()]: TokenType.EOF, + [TokenType.ILLEGAL.toString()]: TokenType.ILLEGAL, + + [TokenType.UNION.toString()]: TokenType.UNION, + [TokenType.INTERSECT.toString()]: TokenType.INTERSECT, + [TokenType.NUMERIC.toString()]: TokenType.NUMERIC, + + 'inf': TokenType.NUMBER, +} + +class Lexer { + Input: string + Position: number + ReadPosition: number + + C?: string + + constructor(input: string) { + this.Input = input; + this.Position = 0; + this.ReadPosition = 0; + this.C = undefined; + + this.ReadChar(); + } + + ReadChar() { + if (this.ReadPosition >= this.Input.length) { + this.C = undefined; + } else { + this.C = this.Input[this.ReadPosition]; + } + this.Position = this.ReadPosition++; + } + + PeekChar() { + if (this.ReadPosition >= this.Input.length) { + return null; + } else { + return this.Input[this.ReadPosition] + } + } + + SkipWhitespace() { + while (this.C == ' ' || this.C == '\t' || this.C == '\r') { + this.ReadChar() + } + } + + ReadIdentifier(): string { + let str = ''; + + while (this.C !== undefined && isLetter(this.C)) { + str = str + this.C; + this.ReadChar() + } + + return str; + } + + ReadNumber(): string { + let str = ''; + while (this.C !== undefined && (isDigit(this.C) || this.C === '.') && parseFloat(str + this.C) != NaN) { + str = str + this.C; + this.ReadChar(); + } + return str; + } + + NextToken() { + let t: Token | null = null; + + this.SkipWhitespace(); + + switch (this.C) { + case '\n': + t = new Token(TokenType.NEW_LINE, this.C); + break + case '{': + t = new Token(TokenType.LBRACE, this.C); + break; + case '}': + t = new Token(TokenType.RBRACE, this.C); + break; + case '(': + t = new Token(TokenType.LPAREN, this.C) + break + case ')': + t = new Token(TokenType.RPAREN, this.C) + break + case '+':// TODO: This should be PLUS token + t = new Token(TokenType.IDENTIFIER, this.C) + break + case '-':// TODO: This should be MINUS token + t = new Token(TokenType.IDENTIFIER, this.C) + break + case '@': + t = new Token(TokenType.IDENTIFIER, this.C) + break + case '<': + let lPeekChar = this.PeekChar() + if (lPeekChar !== null && lPeekChar === '=') { + t = new Token(TokenType.LESS_EQUAL, '<=') + this.ReadChar() + } else { + t = new Token(TokenType.LESS, '<') + } + break + case '>': + let rPeekChar = this.PeekChar() + if (rPeekChar !== null && rPeekChar === '=') { + t = new Token(TokenType.GREATER_EQUAL, '>=') + this.ReadChar() + } else { + t = new Token(TokenType.GREATER, '>') + } + break + case '=': + let ePeekChar = this.PeekChar() + if (ePeekChar !== null && ePeekChar === '=') { + t = new Token(TokenType.EQUAL, '==') + this.ReadChar() + } else { + // No Assign Token + t = new Token(TokenType.ILLEGAL, this.C) + } + break + case undefined: + t = new Token(TokenType.EOF, ''); + break; + default: + if (this.C !== undefined && isLetter(this.C)) { + const literal = this.ReadIdentifier(); + let tokenType = KEYWORDS[literal] || TokenType.IDENTIFIER + t = new Token(tokenType, literal); + return t; + } else if (this.C !== undefined && isDigit(this.C)) { + const n = this.ReadNumber(); + t = new Token(TokenType.NUMBER, n); + return t; + } else { + t = new Token(TokenType.ILLEGAL, this.C); + } + } + this.ReadChar(); + return t; + } +} + +type TNode = 'Expr' | 'UNION' | 'INTERSECT' | 'NUMERIC' + + +export interface AntHierarchyInput { + id: string + x?: number + y?: number + data?: { + type: TNode, + data: string + } + snippet?: string + children: AntHierarchyInput[] +} + +class Expr { + Core: string + + constructor(expr: string) { + this.Core = expr + } + + toJSON(): AntHierarchyInput { + return { + id: uuidv4(), + // data: 'Expr', + // snippet: this.Core, + data: { + type: 'Expr', + data: this.Core + }, + children: [], + } + } +} + +class NumericExpr { + Left: number + LSign: Token + + Identifier: Token + + Right: number + RSign: Token + + + constructor(left: number, lsign: Token, identifier: Token, rsign: Token, right: number) { + this.Left = left; + this.LSign = lsign; + this.Identifier = identifier; + this.Right = right; + this.RSign = rsign; + } + + toJSON(): AntHierarchyInput { + return { + id: uuidv4(), + data: { + type: 'NUMERIC', + data: 'Numeric', + }, + snippet: `${this.Left.toString()} ${this.LSign.Data} ${this.Identifier.Data} ${this.RSign.Data} ${this.Right.toString()}`, + children: [], + } + } +} + +type SearchExpr = IntersectExpr | UnionExpr | NumericExpr | Expr + +type ExprTuple2 = SearchExpr[] + +class IntersectExpr { + Core: ExprTuple2 + + constructor(e: ExprTuple2) { + this.Core = e + } + + toJSON(): AntHierarchyInput { + return { + id: uuidv4(), + data: { + type: 'INTERSECT', + data: 'INTERSECT', + }, + children: this.Core.map(x => x.toJSON()) + } + } +} + +class UnionExpr { + Core: ExprTuple2 + + constructor(e: ExprTuple2) { + this.Core = e + } + + toJSON(): AntHierarchyInput { + return { + id: uuidv4(), + data: { + type: 'UNION', + data: 'UNION', + }, + children: this.Core.map(x => x.toJSON()) + } + } +} + +class SearchResult { + Core: IntersectExpr | UnionExpr + + constructor(e: IntersectExpr | UnionExpr) { + this.Core = e + } +} + +enum PRECEDENCE { + CALL +} + + +type PrefixFunction = (T: Token, p: PRECEDENCE) => void + +class Parser { + private L: Lexer + CurrentToken: Token + PeekToken: Token + Errors: string[] + PrefixFunctions: Map + + constructor(l: Lexer) { + this.L = l; + + this.Errors = []; + this.PrefixFunctions = new Map() + this.CurrentToken = new Token(TokenType.INIT, '') + this.PeekToken = new Token(TokenType.INIT, '') + + this.nextToken() + this.nextToken() + } + + currentTokenIs(t: TokenType) { + return this.CurrentToken?.T === t + } + + peekTokenIs(t: TokenType) { + return this.PeekToken?.T === t + } + + nextToken() { + this.CurrentToken = this.PeekToken + this.PeekToken = this.L.NextToken() + + if (this.CurrentToken.T === TokenType.EOF) { + throw new Error("Didn't expect EOF token") + } + } + + parseIntersectExpr(): IntersectExpr { + + assertToken(TokenType.INTERSECT, this.CurrentToken?.T) + + this.nextToken() + + assertToken(TokenType.LBRACE, this.CurrentToken?.T) + + let Exprs: SearchExpr[] = [] + this.nextToken() + + assertToken(TokenType.NEW_LINE, this.CurrentToken?.T) + + this.nextToken() + + while (true) { + + if (this.CurrentToken.T === TokenType.RBRACE && this.PeekToken.T === TokenType.NEW_LINE) { + this.nextToken() + break + } + + if (this.CurrentToken?.T === TokenType.NUMERIC) { + Exprs.push(this.parseNumericExpr()) + } else if (this.CurrentToken?.T === TokenType.IDENTIFIER) { + Exprs.push(this.parseExpr()) + } else if (this.CurrentToken?.T === TokenType.UNION) { + Exprs.push(this.parseUnionExpr()) + } else if (this.CurrentToken.T === TokenType.INTERSECT) { + Exprs.push(this.parseIntersectExpr()) + } + + this.nextToken() + } + + return new IntersectExpr(Exprs) + } + + + parseUnionExpr(): UnionExpr { + + assertToken(TokenType.UNION, this.CurrentToken?.T) + + this.nextToken() + + assertToken(TokenType.LBRACE, this.CurrentToken.T) + + let Exprs: SearchExpr[] = [] + this.nextToken() + + assertToken(TokenType.NEW_LINE, this.CurrentToken?.T) + + this.nextToken() + + while (true) { + + if (this.CurrentToken.T === TokenType.RBRACE && this.PeekToken.T === TokenType.NEW_LINE) { + + this.nextToken() + break + } + + if (this.CurrentToken?.T === TokenType.NUMERIC) { + Exprs.push(this.parseNumericExpr()) + } else if (this.CurrentToken?.T === TokenType.IDENTIFIER) { + Exprs.push(this.parseExpr()) + } else if (this.CurrentToken?.T === TokenType.UNION) { + Exprs.push(this.parseUnionExpr()) + } else if (this.CurrentToken.T === TokenType.INTERSECT) { + Exprs.push(this.parseIntersectExpr()) + } + + this.nextToken() + } + + return new UnionExpr(Exprs) + } + + parseExpr() { + + assertToken(TokenType.IDENTIFIER, this.CurrentToken.T) + + let str = ''; + + while (this.CurrentToken.T !== TokenType.NEW_LINE) { + str = str + this.CurrentToken.Data + this.nextToken() + } + + return new Expr(str) + } + + parseNumericExpr() { + assertToken(TokenType.NUMERIC, this.CurrentToken.T) + + this.nextToken() + + assertToken(TokenType.LBRACE, this.CurrentToken.T) + + this.nextToken() + + assertToken(TokenType.NUMBER, this.CurrentToken?.T) + + let left = this.CurrentToken?.Data; + + this.nextToken() + + let lsign = this.CurrentToken; // TODO: Check sign + + this.nextToken() + + assertToken(TokenType.IDENTIFIER, this.CurrentToken?.T) + + let identifier = this.CurrentToken; + + this.nextToken() + + while (this.CurrentToken.T === TokenType.IDENTIFIER) { + identifier.Data = identifier.Data + this.CurrentToken.Data + this.nextToken() + } + + + let rsign = this.CurrentToken; + + this.nextToken() + + + assertToken(TokenType.NUMBER, this.CurrentToken?.T) + + let right = this.CurrentToken?.Data; + + this.nextToken() + + + assertToken(TokenType.RBRACE, this.CurrentToken?.T) + + this.nextToken()// read off RBRACE + + // assertToken(TokenType.NEW_LINE, this.CurrentToken?.T) + // + // this.nextToken() // read off new line + + return new NumericExpr(left !== 'inf' ? parseFloat(left) : Infinity, lsign, identifier, rsign, right !== 'inf' ? parseFloat(right) : Infinity) + } + + +} + + +function Parse(data: string): SearchExpr { + const l = new Lexer(data); + + let p = new Parser(l) + + if (p.CurrentToken?.T === TokenType.INTERSECT) { + return p.parseIntersectExpr() + } else if (p.CurrentToken?.T === TokenType.NUMERIC) { + return p.parseNumericExpr() + } else if (p.CurrentToken.T === TokenType.UNION) { + return p.parseUnionExpr() + } else { + return p.parseExpr() + } +} + +export function ASTToJson(output: string) { + return Parse(output).toJSON() +} + + +function isLetter(str: string): boolean { + return str.length === 1 && (str.match(/[a-z]/i) !== null) +} + +function isDigit(str: string): boolean { + return str >='0' && str <= '9'; +} + + +function assert(c: boolean, errorMsg: string) { + if (!c) { + throw new Error(errorMsg) + } +} + +function assertToken(expected: TokenType, actual: TokenType | undefined) { + + + if (actual === undefined) { + throw new Error("Token is undefined") + } + + assert(expected === actual, `Expected ${expected}, Actual: ${actual}`) +} diff --git a/redisinsight/ui/src/packages/ri-explain/src/styles/_dark_theme.less b/redisinsight/ui/src/packages/ri-explain/src/styles/_dark_theme.less new file mode 100644 index 0000000000..54175c82c7 --- /dev/null +++ b/redisinsight/ui/src/packages/ri-explain/src/styles/_dark_theme.less @@ -0,0 +1,3 @@ +.theme_DARK { + @import (less)'../../node_modules/@elastic/eui/dist/eui_theme_dark.min.css'; +} diff --git a/redisinsight/ui/src/packages/ri-explain/src/styles/_light_theme.less b/redisinsight/ui/src/packages/ri-explain/src/styles/_light_theme.less new file mode 100644 index 0000000000..1177707d0f --- /dev/null +++ b/redisinsight/ui/src/packages/ri-explain/src/styles/_light_theme.less @@ -0,0 +1,3 @@ +.theme_LIGHT { + @import (less)'../../node_modules/@elastic/eui/dist/eui_theme_light.min.css'; +} diff --git a/redisinsight/ui/src/packages/ri-explain/src/styles/styles.less b/redisinsight/ui/src/packages/ri-explain/src/styles/styles.less new file mode 100644 index 0000000000..0de3aa99b7 --- /dev/null +++ b/redisinsight/ui/src/packages/ri-explain/src/styles/styles.less @@ -0,0 +1,214 @@ +@import "_dark_theme.less"; +@import "_light_theme.less"; + +* { + margin: 0px; + padding: 0px; +} + +.theme_DARK { + --info-background: #20222B; + --info-color: white; + --svg-background: #010101; + --tooltip-background: #3E4B5E; + + --node-border-color: #3D3D3D; + --node-border-shadow: #17336952; + --node-background: #212021; + --edge-background: #6B6B6B; + --text-color: #DFE5EF; +} + +.theme_LIGHT { + --info-background: white; + --info-color: black; + --svg-background: #FFFFFF; + --tooltip-background: white; + + --node-border-color: #E4EAF2; + --node-border-shadow: #17336926; + --node-background: #FFF; + --edge-background: #8992B3; + --text-color: #173369; +} + + +* div, +* span { + font-family: 'Graphik', sans-serif !important; +} + +.euiPagination__list { + list-style: none; + padding-inline-start: 0px; +} + + +.responseFail { + color: #e06c75; + padding: 12px !important; + font-family: monospace !important; +} + +.responseInfo { + color: var(--info-color); + padding: 12px !important; + font-family: monospace !important; +} + +.euiToolTip { + color: var(--info-color) !important; + background-color: var(--tooltip-background) !important; + font-size: 12px !important; +} + +.euiToolTip__arrow { + background-color: var(--tooltip-background) !important; +} + +.ExplainContainer { + width: 240px; + min-height: 42px; + + box-shadow: 0px 3px 12px var(--node-border-shadow); + border: 1px solid var(--node-border-color) !important; + border-radius: 4px; + opacity: 1; + + padding: 12px 18px 12px 18px !important; + + display: flex; + flex-direction: column; + justify-content: space-around; + + background-color: var(--node-background); + + .Main { + // height: 42px; + font-size: 13px; + color: var(--text-color); + + .Info { + display: flex; + justify-content: space-between; + .Type { + color: #CE915B + } + } + + } + + .Footer{ + height: 24px; + font-size: 12px; + padding-top: 6px; + color: #B5B6C0; + } +} + + +.NodeContainer { + width: 320px; + height: 114px; + + box-shadow: 0px 3px 12px #17336952; + border: 1px solid #3D3D3D !important; + border-radius: 4px; + opacity: 1; + + padding: 12px 18px 12px 18px !important; + + display: flex; + flex-direction: column; + justify-content: space-around; +} + +.NodeHeader { + display: flex; + flex-direction: column; + justify-content: space-between; + font-size: 13px; + + .NodeHeaderInfo { + display: flex; + justify-content: space-between; + } + + .NodeHeaderMetadata{ + display: flex; + justify-content: space-between; + + padding-top: 10px; + padding-bottom: 10px; + + .MetadataSnippet { + font-family: monospace !important; + } + + .MetadataCount { + display: flex; + } + + } + + +} + +.NodeType { + color: #CE915B; +} + +.NodeMetadata { + display: flex; + justify-content: space-between; + font-size: 12px; + color: darkgrey; +} + +.NodeIcon { + height: 13px; + width: 13px; +} + +.NodeInfoDivider { + width: 284px; + height: 29px; + border-bottom: 1px solid #3D3D3D !important; + position: absolute; + color: #B5B6C0; +} + +.NodeTime { + display: flex; + align-items: center; +} + + +.NodeContentInfo { + display: flex; + align-items: center; +} + + +.NodeToolTip { + background-color: #333D4F; + padding: 10px; +} + +.NodeTimeIcon { + padding-right: 5px !important; +} + +.NodeContentIcon { + padding-left: 5px !important; +} + +.Box { + width: 5px; + height: 9px; + background-color: #FF6280; + + align-self: center; + + margin-left: 2px !important; +} diff --git a/redisinsight/ui/src/packages/ri-explain/src/utils.ts b/redisinsight/ui/src/packages/ri-explain/src/utils.ts new file mode 100644 index 0000000000..cf48c629a4 --- /dev/null +++ b/redisinsight/ui/src/packages/ri-explain/src/utils.ts @@ -0,0 +1,56 @@ +export const truncateText = (str = '', length = 100) => { + const ending = '...' + + if (str.length > length) { + return str.substring(0, length - ending.length) + ending + } + + return str +} + +function charCodeSum(str: string | undefined) { + if (str === undefined) return 0 + let sum = 0 + for (let i = 0; i < str.length; i++) { + sum += str.charCodeAt(i) + } + return sum +} + +export function invertColor(hex: string) { + if (hex.indexOf('#') === 0) { + hex = hex.slice(1) + } + // convert 3-digit hex to 6-digits. + if (hex.length === 3) { + hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2] + } + if (hex.length !== 6) { + throw new Error('Invalid HEX color.') + } + // invert color components + var r = (255 - parseInt(hex.slice(0, 2), 16)).toString(16), + g = (255 - parseInt(hex.slice(2, 4), 16)).toString(16), + b = (255 - parseInt(hex.slice(4, 6), 16)).toString(16) + // pad each with zeros and return + return '#' + padZero(r) + padZero(g) + padZero(b) +} + +function padZero(str) { + let len = str.length || 2 + var zeros = new Array(len).join('0') + return (zeros + str).slice(-len) +} + + +export function wrapText(s: string, w: number) { + return s.replace( + new RegExp(`(?![^\\n]{1,${w}}$)([^\\n]{1,${w}})\\s`, 'g'), + '$1\n' + ) +} + + +export function commandIsSuccess(resp: [{ response: any, status: string }]) { + return Array.isArray(resp) && resp.length >= 1 || resp[0].status === 'success' +} diff --git a/redisinsight/ui/src/packages/ri-explain/types/@elastic/index.d.ts b/redisinsight/ui/src/packages/ri-explain/types/@elastic/index.d.ts new file mode 100644 index 0000000000..7b41812218 --- /dev/null +++ b/redisinsight/ui/src/packages/ri-explain/types/@elastic/index.d.ts @@ -0,0 +1,60 @@ +// yourLibrary.d.ts + +declare module '@elastic/eui/es/components/icon/icon' { + export function appendIconComponentCache(args: any): any +} + +declare module '@elastic/eui/es/components/icon/assets/magnifyWithPlus' { + export { + icon + } +} + +declare module '@elastic/eui/es/components/icon/assets/magnifyWithMinus' { + export { + icon + } +} + +declare module '@elastic/eui/es/components/icon/assets/bullseye' { + export { + icon + } +} + +declare module '@elastic/eui/es/components/icon/assets/editorItemAlignLeft' { + export { + icon + } +} + +declare module '@elastic/eui/es/components/icon/assets/editorItemAlignRight' { + export { + icon + } +} + +declare module '@elastic/eui/es/components/icon/assets/editorItemAlignCenter' { + export { + icon + } +} + +declare module '@elastic/eui/es/components/icon/assets/arrow_left' { + export { + icon + } +} + +declare module '@elastic/eui/es/components/icon/assets/arrow_right' { + export { + icon + } +} + + +declare module '@elastic/eui/es/components/icon/assets/arrow_down' { + export { + icon + } +} diff --git a/redisinsight/ui/src/packages/ri-explain/yarn.lock b/redisinsight/ui/src/packages/ri-explain/yarn.lock new file mode 100644 index 0000000000..ded873c138 --- /dev/null +++ b/redisinsight/ui/src/packages/ri-explain/yarn.lock @@ -0,0 +1,3112 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@antv/hierarchy@^0.6.8": + version "0.6.8" + resolved "https://registry.yarnpkg.com/@antv/hierarchy/-/hierarchy-0.6.8.tgz#b77c490a9c2dd6da186cbacee0e7887b8c1b3fa4" + integrity sha512-wVzUl+pxny5gyGJ2mkWx8IiEypX6bnMHgr/NILgbxY6shoy0Vf4FhZpI3CY8Ez7bQT6js8fMkB2NymPW7d7i8A== + dependencies: + "@antv/util" "^2.0.7" + +"@antv/util@^2.0.7": + version "2.0.17" + resolved "https://registry.yarnpkg.com/@antv/util/-/util-2.0.17.tgz#e8ef42aca7892815b229269f3dd10c6b3c7597a9" + integrity sha512-o6I9hi5CIUvLGDhth0RxNSFDRwXeywmt6ExR4+RmVAzIi48ps6HUy+svxOCayvrPBN37uE6TAc2KDofRo0nK9Q== + dependencies: + csstype "^3.0.8" + tslib "^2.0.3" + +"@antv/x6-common@^2.0.x": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@antv/x6-common/-/x6-common-2.0.4.tgz#14dcfa4266d1ea594b0029b80defb916dbe01a7f" + integrity sha512-OeCMJUP2/fAbGLCfUuFU/p9oSdyG/RLNpDE4xUT8pBHfhhlKfoMDz9RkYr6+ZAg8F5hgN3zBkZDuLXhO6uN8hQ== + dependencies: + lodash-es "^4.17.15" + utility-types "^3.10.0" + +"@antv/x6-geometry@^2.0.x": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@antv/x6-geometry/-/x6-geometry-2.0.4.tgz#d08213e200e16c8d065a0235189239cb367a0797" + integrity sha512-dlsLNRRTiCw8TsJ/03j3y5IbbbK4Npf6zBH6kAx8nnW+edNT/TAOChvPRzizwD+vA7wY9Rp2AiRnp/vCdIYC5w== + +"@antv/x6-react-shape@^2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@antv/x6-react-shape/-/x6-react-shape-2.1.0.tgz#b364a021d9690abb1f8f35cefd2da913dfd145d3" + integrity sha512-HY9tPbMU1Z7Tu2lwBenXJe53DzT2MFt9LXRBPyN+r5qu8kP3VMU1vzS+2+LsPFdPL3s71wolGjL1GExUoh77JQ== + +"@antv/x6@^2.1.3": + version "2.1.3" + resolved "https://registry.yarnpkg.com/@antv/x6/-/x6-2.1.3.tgz#65899f44058bb7ef6dba0986aa65fb38c1737d87" + integrity sha512-1y/BU76RmENucn5wPvSvabd+RnwBJ6y61XckBPvmoDHnvsY8xsKjedTL3Hqpvf2/5nfU21YPmvo+1gV0x4nO0w== + dependencies: + "@antv/x6-common" "^2.0.x" + "@antv/x6-geometry" "^2.0.x" + utility-types "^3.10.0" + +"@babel/code-frame@^7.0.0": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.16.7.tgz#44416b6bd7624b998f5b1af5d470856c40138789" + integrity sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg== + dependencies: + "@babel/highlight" "^7.16.7" + +"@babel/helper-validator-identifier@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz#e8c602438c4a8195751243da9031d1607d247cad" + integrity sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw== + +"@babel/highlight@^7.16.7": + version "7.16.10" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.16.10.tgz#744f2eb81579d6eea753c227b0f570ad785aba88" + integrity sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw== + dependencies: + "@babel/helper-validator-identifier" "^7.16.7" + chalk "^2.0.0" + js-tokens "^4.0.0" + +"@babel/runtime@^7.0.0", "@babel/runtime@^7.12.13", "@babel/runtime@^7.15.4", "@babel/runtime@^7.9.2": + version "7.17.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.0.tgz#b8d142fc0f7664fb3d9b5833fd40dcbab89276c0" + integrity sha512-etcO/ohMNaNA2UBdaXBBSX/3aEzFMRrVfaPv8Ptc0k+cWpWW0QFiGZ2XnVqQZI1Cf734LbPGmqBKWESfW4x/dQ== + dependencies: + regenerator-runtime "^0.13.4" + +"@babel/runtime@^7.13.10": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.16.7.tgz#03ff99f64106588c9c403c6ecb8c3bafbbdff1fa" + integrity sha512-9E9FJowqAsytyOY6LG+1KuueckRL+aQW+mKvXRXnuFGyRAyepJPmEo9vgMfXUA6O9u3IeEdv9MAkppFcaQwogQ== + dependencies: + regenerator-runtime "^0.13.4" + +"@elastic/eui@34.6.0": + version "34.6.0" + resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-34.6.0.tgz#a7188bc97d9c3120cd65e52ed423377872b604bd" + integrity sha512-uVMSX0jPJU3LLwD4TRHllyJeTr+Uihh+R5qsFSAzKrCCRZjSfKMmMHKffWhzFyYjG97npdWlMvneXG5q0yobCw== + dependencies: + "@types/chroma-js" "^2.0.0" + "@types/lodash" "^4.14.160" + "@types/numeral" "^0.0.28" + "@types/react-beautiful-dnd" "^13.0.0" + "@types/react-input-autosize" "^2.2.0" + "@types/react-virtualized-auto-sizer" "^1.0.0" + "@types/react-window" "^1.8.2" + "@types/refractor" "^3.0.0" + "@types/resize-observer-browser" "^0.1.5" + "@types/vfile-message" "^2.0.0" + chroma-js "^2.1.0" + classnames "^2.2.6" + lodash "^4.17.21" + mdast-util-to-hast "^10.0.0" + numeral "^2.0.6" + prop-types "^15.6.0" + react-ace "^7.0.5" + react-beautiful-dnd "^13.0.0" + react-dropzone "^11.2.0" + react-focus-on "^3.5.0" + react-input-autosize "^2.2.2" + react-is "~16.3.0" + react-virtualized-auto-sizer "^1.0.2" + react-window "^1.8.5" + refractor "^3.4.0" + rehype-raw "^5.0.0" + rehype-react "^6.0.0" + rehype-stringify "^8.0.0" + remark-emoji "^2.1.0" + remark-parse "^8.0.3" + remark-rehype "^8.0.0" + tabbable "^3.0.0" + text-diff "^1.0.1" + unified "^9.2.0" + unist-util-visit "^2.0.3" + url-parse "^1.5.0" + uuid "^8.3.0" + vfile "^4.2.0" + +"@emotion/cache@^11.7.1": + version "11.7.1" + resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.7.1.tgz#08d080e396a42e0037848214e8aa7bf879065539" + integrity sha512-r65Zy4Iljb8oyjtLeCuBH8Qjiy107dOYC6SJq7g7GV5UCQWMObY4SJDPGFjiiVpPrOJ2hmJOoBiYTC7hwx9E2A== + dependencies: + "@emotion/memoize" "^0.7.4" + "@emotion/sheet" "^1.1.0" + "@emotion/utils" "^1.0.0" + "@emotion/weak-memoize" "^0.2.5" + stylis "4.0.13" + +"@emotion/hash@^0.8.0": + version "0.8.0" + resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.8.0.tgz#bbbff68978fefdbe68ccb533bc8cbe1d1afb5413" + integrity sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow== + +"@emotion/memoize@^0.7.4": + version "0.7.5" + resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.5.tgz#2c40f81449a4e554e9fc6396910ed4843ec2be50" + integrity sha512-igX9a37DR2ZPGYtV6suZ6whr8pTFtyHL3K/oLUotxpSVO2ASaprmAe2Dkq7tBo7CRY7MMDrAa9nuQP9/YG8FxQ== + +"@emotion/react@^11.7.1": + version "11.7.1" + resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.7.1.tgz#3f800ce9b20317c13e77b8489ac4a0b922b2fe07" + integrity sha512-DV2Xe3yhkF1yT4uAUoJcYL1AmrnO5SVsdfvu+fBuS7IbByDeTVx9+wFmvx9Idzv7/78+9Mgx2Hcmr7Fex3tIyw== + dependencies: + "@babel/runtime" "^7.13.10" + "@emotion/cache" "^11.7.1" + "@emotion/serialize" "^1.0.2" + "@emotion/sheet" "^1.1.0" + "@emotion/utils" "^1.0.0" + "@emotion/weak-memoize" "^0.2.5" + hoist-non-react-statics "^3.3.1" + +"@emotion/serialize@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.0.2.tgz#77cb21a0571c9f68eb66087754a65fa97bfcd965" + integrity sha512-95MgNJ9+/ajxU7QIAruiOAdYNjxZX7G2mhgrtDWswA21VviYIRP1R5QilZ/bDY42xiKsaktP4egJb3QdYQZi1A== + dependencies: + "@emotion/hash" "^0.8.0" + "@emotion/memoize" "^0.7.4" + "@emotion/unitless" "^0.7.5" + "@emotion/utils" "^1.0.0" + csstype "^3.0.2" + +"@emotion/sheet@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.1.0.tgz#56d99c41f0a1cda2726a05aa6a20afd4c63e58d2" + integrity sha512-u0AX4aSo25sMAygCuQTzS+HsImZFuS8llY8O7b9MDRzbJM0kVJlAz6KNDqcG7pOuQZJmj/8X/rAW+66kMnMW+g== + +"@emotion/unitless@^0.7.5": + version "0.7.5" + resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.7.5.tgz#77211291c1900a700b8a78cfafda3160d76949ed" + integrity sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg== + +"@emotion/utils@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.0.0.tgz#abe06a83160b10570816c913990245813a2fd6af" + integrity sha512-mQC2b3XLDs6QCW+pDQDiyO/EdGZYOygE8s5N5rrzjSI4M3IejPE/JPndCBwRT9z982aqQNi6beWs1UeayrQxxA== + +"@emotion/weak-memoize@^0.2.5": + version "0.2.5" + resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz#8eed982e2ee6f7f4e44c253e12962980791efd46" + integrity sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA== + +"@mapbox/hast-util-table-cell-style@^0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@mapbox/hast-util-table-cell-style/-/hast-util-table-cell-style-0.2.0.tgz#1003f59d54fae6f638cb5646f52110fb3da95b4d" + integrity sha512-gqaTIGC8My3LVSnU38IwjHVKJC94HSonjvFHDk8/aSrApL8v4uWgm8zJkK7MJIIbHuNOr/+Mv2KkQKcxs6LEZA== + dependencies: + unist-util-visit "^1.4.1" + +"@parcel/bundler-default@2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@parcel/bundler-default/-/bundler-default-2.3.2.tgz#329f171e210dfb22beaa52ae706ccde1dae384c1" + integrity sha512-JUrto4mjSD0ic9dEqRp0loL5o3HVYHja1ZIYSq+rBl2UWRV6/9cGTb07lXOCqqm0BWE+hQ4krUxB76qWaF0Lqw== + dependencies: + "@parcel/diagnostic" "2.3.2" + "@parcel/hash" "2.3.2" + "@parcel/plugin" "2.3.2" + "@parcel/utils" "2.3.2" + nullthrows "^1.1.1" + +"@parcel/cache@2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@parcel/cache/-/cache-2.3.2.tgz#ba8c2af02fd45b90c7bc6f829bfc566d1ded0a13" + integrity sha512-Xxq+ekgcFEme6Fn1v7rEOBkyMOUOUu7eNqQw0l6HQS+INZ2Q7YzzfdW7pI8rEOAAICVg5BWKpmBQZpgJlT+HxQ== + dependencies: + "@parcel/fs" "2.3.2" + "@parcel/logger" "2.3.2" + "@parcel/utils" "2.3.2" + lmdb "^2.0.2" + +"@parcel/codeframe@2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@parcel/codeframe/-/codeframe-2.3.2.tgz#73fb5a89910b977342808ca8f6ece61fa01b7690" + integrity sha512-ireQALcxxrTdIEpzTOoMo/GpfbFm1qlyezeGl3Hce3PMvHLg3a5S6u/Vcy7SAjdld5GfhHEqVY+blME6Z4CyXQ== + dependencies: + chalk "^4.1.0" + +"@parcel/compressor-brotli@^2.0.0": + version "2.2.1" + resolved "https://registry.yarnpkg.com/@parcel/compressor-brotli/-/compressor-brotli-2.2.1.tgz#0ebcb44f656e0f2093f6fa43ef074fbc12d77a49" + integrity sha512-CXmneKgudev9VthRShKL20JTV8paYS53A2LBA5nspfgEPnbFeAzX2LV0/QqY983+f+WCEWzieySGcVRxCxK5nQ== + dependencies: + "@parcel/plugin" "^2.2.1" + +"@parcel/compressor-gzip@^2.0.0": + version "2.2.1" + resolved "https://registry.yarnpkg.com/@parcel/compressor-gzip/-/compressor-gzip-2.2.1.tgz#3c9182888ca83f86e8f1da48bbe2059e50ee7bb1" + integrity sha512-aj9TQifbxqWNQxhUUMvC/UJ6+fY+nPrqJbAJHjrJGrbYncjS0jok1Wkh40iu0qY5OAz3YGUvzadr3nFgL5UsDQ== + dependencies: + "@parcel/plugin" "^2.2.1" + +"@parcel/compressor-raw@2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@parcel/compressor-raw/-/compressor-raw-2.3.2.tgz#1a808ae9e61ed86f655935e1d2a984383b3c00a7" + integrity sha512-8dIoFwinYK6bOTpnZOAwwIv0v73y0ezsctPmfMnIqVQPn7wJwfhw/gbKVcmK5AkgQMkyid98hlLZoaZtGF1Mdg== + dependencies: + "@parcel/plugin" "2.3.2" + +"@parcel/config-default@2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@parcel/config-default/-/config-default-2.3.2.tgz#3f21a37fa07b22de9cd6b1aea19bc310a02d4abb" + integrity sha512-E7/iA7fGCYvXU3u6zF9nxjeDVsgjCN6MVvDjymjaxYMoDWTIsPV245SBEXqzgtmzbMAV+VAl4rVWLMB4pzMt9g== + dependencies: + "@parcel/bundler-default" "2.3.2" + "@parcel/compressor-raw" "2.3.2" + "@parcel/namer-default" "2.3.2" + "@parcel/optimizer-cssnano" "2.3.2" + "@parcel/optimizer-htmlnano" "2.3.2" + "@parcel/optimizer-image" "2.3.2" + "@parcel/optimizer-svgo" "2.3.2" + "@parcel/optimizer-terser" "2.3.2" + "@parcel/packager-css" "2.3.2" + "@parcel/packager-html" "2.3.2" + "@parcel/packager-js" "2.3.2" + "@parcel/packager-raw" "2.3.2" + "@parcel/packager-svg" "2.3.2" + "@parcel/reporter-dev-server" "2.3.2" + "@parcel/resolver-default" "2.3.2" + "@parcel/runtime-browser-hmr" "2.3.2" + "@parcel/runtime-js" "2.3.2" + "@parcel/runtime-react-refresh" "2.3.2" + "@parcel/runtime-service-worker" "2.3.2" + "@parcel/transformer-babel" "2.3.2" + "@parcel/transformer-css" "2.3.2" + "@parcel/transformer-html" "2.3.2" + "@parcel/transformer-image" "2.3.2" + "@parcel/transformer-js" "2.3.2" + "@parcel/transformer-json" "2.3.2" + "@parcel/transformer-postcss" "2.3.2" + "@parcel/transformer-posthtml" "2.3.2" + "@parcel/transformer-raw" "2.3.2" + "@parcel/transformer-react-refresh-wrap" "2.3.2" + "@parcel/transformer-svg" "2.3.2" + +"@parcel/core@2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@parcel/core/-/core-2.3.2.tgz#1b9a79c1ff96dba5e0f53d4277bed4e7ab4590d0" + integrity sha512-gdJzpsgeUhv9H8T0UKVmyuptiXdduEfKIUx0ci+/PGhq8cCoiFnlnuhW6H7oLr79OUc+YJStabDJuG4U2A6ysw== + dependencies: + "@parcel/cache" "2.3.2" + "@parcel/diagnostic" "2.3.2" + "@parcel/events" "2.3.2" + "@parcel/fs" "2.3.2" + "@parcel/graph" "2.3.2" + "@parcel/hash" "2.3.2" + "@parcel/logger" "2.3.2" + "@parcel/package-manager" "2.3.2" + "@parcel/plugin" "2.3.2" + "@parcel/source-map" "^2.0.0" + "@parcel/types" "2.3.2" + "@parcel/utils" "2.3.2" + "@parcel/workers" "2.3.2" + abortcontroller-polyfill "^1.1.9" + base-x "^3.0.8" + browserslist "^4.6.6" + clone "^2.1.1" + dotenv "^7.0.0" + dotenv-expand "^5.1.0" + json-source-map "^0.6.1" + json5 "^2.2.0" + msgpackr "^1.5.1" + nullthrows "^1.1.1" + semver "^5.7.1" + +"@parcel/diagnostic@2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@parcel/diagnostic/-/diagnostic-2.3.2.tgz#1d3f0b55bfd9839c6f41d704ebbc89a96cca88dc" + integrity sha512-/xW93Az4AOiifuYW/c4CDbUcu3lx5FcUDAj9AGiR9NSTsF/ROC/RqnxvQ3AGtqa14R7vido4MXEpY3JEp6FsqA== + dependencies: + json-source-map "^0.6.1" + nullthrows "^1.1.1" + +"@parcel/events@2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@parcel/events/-/events-2.3.2.tgz#b6bcfbbc96d883716ee9d0e6ab232acdee862790" + integrity sha512-WiYIwXMo4Vd+pi58vRoHkul8TPE5VEfMY+3FYwVCKPl/LYqSD+vz6wMx9uG18mEbB1d/ofefv5ZFQNtPGKO4tQ== + +"@parcel/fs-search@2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@parcel/fs-search/-/fs-search-2.3.2.tgz#18611877ac1b370932c71987c2ec0e93a4a7e53d" + integrity sha512-u3DTEFnPtKuZvEtgGzfVjQUytegSSn3POi7WfwMwPIaeDPfYcyyhfl+c96z7VL9Gk/pqQ99/cGyAwFdFsnxxXA== + dependencies: + detect-libc "^1.0.3" + +"@parcel/fs@2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@parcel/fs/-/fs-2.3.2.tgz#9628441a84c2582e1f6e69549feb0da0cc143e40" + integrity sha512-XV+OsnRpN01QKU37lBN0TFKvv7uPKfQGbqFqYOrMbXH++Ae8rBU0Ykz+Yu4tv2h7shMlde+AMKgRnRTAJZpWEQ== + dependencies: + "@parcel/fs-search" "2.3.2" + "@parcel/types" "2.3.2" + "@parcel/utils" "2.3.2" + "@parcel/watcher" "^2.0.0" + "@parcel/workers" "2.3.2" + +"@parcel/graph@2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@parcel/graph/-/graph-2.3.2.tgz#4194816952ab322ab22a17f7d9ea17befbade64d" + integrity sha512-ltTBM3IEqumgmy4ABBFETT8NtAwSsjD9mY3WCyJ5P8rUshfVCg093rvBPbpuJYMaH/TV1AHVaWfZqaZ4JQDIQQ== + dependencies: + "@parcel/utils" "2.3.2" + nullthrows "^1.1.1" + +"@parcel/hash@2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@parcel/hash/-/hash-2.3.2.tgz#33b8ff04bb44f6661bdc1054b302ef1b6bd3acb3" + integrity sha512-SMtYTsHihws/wqdVnOr0QAGyGYsW9rJSJkkoRujUxo8l2ctnBN1ztv89eOUrdtgHsmcnj/oz1yw6sN38X+BUng== + dependencies: + detect-libc "^1.0.3" + xxhash-wasm "^0.4.2" + +"@parcel/logger@2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@parcel/logger/-/logger-2.3.2.tgz#b5fc7a9c1664ee0286d0f67641c7c81c8fec1561" + integrity sha512-jIWd8TXDQf+EnNWSa7Q10lSQ6C1LSH8OZkTlaINrfVIw7s+3tVxO3I4pjp7/ARw7RX2gdNPlw6fH4Gn/HvvYbw== + dependencies: + "@parcel/diagnostic" "2.3.2" + "@parcel/events" "2.3.2" + +"@parcel/markdown-ansi@2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@parcel/markdown-ansi/-/markdown-ansi-2.3.2.tgz#2a5be7ce76a506a9d238ea2257cb28e43abe4902" + integrity sha512-l01ggmag5QScCk9mYA0xHh5TWSffR84uPFP2KvaAMQQ9NLNufcFiU0mn/Mtr3pCb5L5dSzmJ+Oo9s7P1Kh/Fmg== + dependencies: + chalk "^4.1.0" + +"@parcel/namer-default@2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@parcel/namer-default/-/namer-default-2.3.2.tgz#84e17abfc84fd293b23b3f405280ed2e279c75d8" + integrity sha512-3QUMC0+5+3KMKfoAxYAbpZtuRqTgyZKsGDWzOpuqwemqp6P8ahAvNPwSCi6QSkGcTmvtYwBu9/NHPSONxIFOfg== + dependencies: + "@parcel/diagnostic" "2.3.2" + "@parcel/plugin" "2.3.2" + nullthrows "^1.1.1" + +"@parcel/node-resolver-core@2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@parcel/node-resolver-core/-/node-resolver-core-2.3.2.tgz#dd360f405949fdcd62980cd44825052ab28f6135" + integrity sha512-wmrnMNzJN4GuHw2Ftho+BWgSWR6UCkW3XoMdphqcxpw/ieAdS2a+xYSosYkZgQZ6lGutSvLyJ1CkVvP6RLIdQQ== + dependencies: + "@parcel/diagnostic" "2.3.2" + "@parcel/utils" "2.3.2" + nullthrows "^1.1.1" + +"@parcel/optimizer-cssnano@2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@parcel/optimizer-cssnano/-/optimizer-cssnano-2.3.2.tgz#70758f6646fd4debc26a90ae7dddf398928c0ce1" + integrity sha512-wTBOxMiBI38NAB9XIlQZRCjS59+EWjWR9M04D3TWyxl+dL5gYMc1cl4GNynUnmcPdz+3s1UbOdo5/8V90wjiiw== + dependencies: + "@parcel/plugin" "2.3.2" + "@parcel/source-map" "^2.0.0" + cssnano "^5.0.15" + postcss "^8.4.5" + +"@parcel/optimizer-htmlnano@2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@parcel/optimizer-htmlnano/-/optimizer-htmlnano-2.3.2.tgz#4086736866621182f5dd1a8abe78e9f5764e1a28" + integrity sha512-U8C0TDSxsx8HmHaLW0Zc7ha1fXQynzhvBjCRMGYnOiLiw0MOfLQxzQ2WKVSeCotmdlF63ayCwxWsd6BuqStiKQ== + dependencies: + "@parcel/plugin" "2.3.2" + htmlnano "^2.0.0" + nullthrows "^1.1.1" + posthtml "^0.16.5" + svgo "^2.4.0" + +"@parcel/optimizer-image@2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@parcel/optimizer-image/-/optimizer-image-2.3.2.tgz#0549cc1abc99fdd6f46bd44ce8551eb135e44d4f" + integrity sha512-HOk3r5qdvY/PmI7Q3i2qEgFH3kP2QWG4Wq3wmC4suaF1+c2gpiQc+HKHWp4QvfbH3jhT00c5NxQyqPhbXeNI9Q== + dependencies: + "@parcel/diagnostic" "2.3.2" + "@parcel/plugin" "2.3.2" + "@parcel/utils" "2.3.2" + "@parcel/workers" "2.3.2" + detect-libc "^1.0.3" + +"@parcel/optimizer-svgo@2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@parcel/optimizer-svgo/-/optimizer-svgo-2.3.2.tgz#ebf2f48f356ad557d2bbfae361520d3d29bc1c37" + integrity sha512-l7WvZ5+e7D1mVmLUxMVaSb29cviXzuvSY2OpQs0ukdPACDqag+C65hWMzwTiOSSRGPMIu96kQKpeVru2YjibhA== + dependencies: + "@parcel/diagnostic" "2.3.2" + "@parcel/plugin" "2.3.2" + "@parcel/utils" "2.3.2" + svgo "^2.4.0" + +"@parcel/optimizer-terser@2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@parcel/optimizer-terser/-/optimizer-terser-2.3.2.tgz#790b69e6ecc6ef0d8f25b57e9a13806e1f1c2943" + integrity sha512-dOapHhfy0xiNZa2IoEyHGkhhla07xsja79NPem14e5jCqY6Oi40jKNV4ab5uu5u1elWUjJuw69tiYbkDZWbKQw== + dependencies: + "@parcel/diagnostic" "2.3.2" + "@parcel/plugin" "2.3.2" + "@parcel/source-map" "^2.0.0" + "@parcel/utils" "2.3.2" + nullthrows "^1.1.1" + terser "^5.2.0" + +"@parcel/package-manager@2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@parcel/package-manager/-/package-manager-2.3.2.tgz#380f0741c9d0c79c170c437efae02506484df315" + integrity sha512-pAQfywKVORY8Ee+NHAyKzzQrKbnz8otWRejps7urwhDaTVLfAd5C/1ZV64ATZ9ALYP9jyoQ8bTaxVd4opcSuwg== + dependencies: + "@parcel/diagnostic" "2.3.2" + "@parcel/fs" "2.3.2" + "@parcel/logger" "2.3.2" + "@parcel/types" "2.3.2" + "@parcel/utils" "2.3.2" + "@parcel/workers" "2.3.2" + semver "^5.7.1" + +"@parcel/packager-css@2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@parcel/packager-css/-/packager-css-2.3.2.tgz#4994d872449843c1c0cda524b6df3327e2f0a121" + integrity sha512-ByuF9xDnQnpVL1Hdu9aY6SpxOuZowd3TH7joh1qdRPLeMHTEvUywHBXoiAyNdrhnLGum8uPEdY8Ra5Xuo1U7kg== + dependencies: + "@parcel/plugin" "2.3.2" + "@parcel/source-map" "^2.0.0" + "@parcel/utils" "2.3.2" + nullthrows "^1.1.1" + +"@parcel/packager-html@2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@parcel/packager-html/-/packager-html-2.3.2.tgz#e54085fbaa49bed4258ffef80bc36b421895965f" + integrity sha512-YqAptdU+uqfgwSii76mRGcA/3TpuC6yHr8xG+11brqj/tEFLsurmX0naombzd7FgmrTE9w+kb0HUIMl2vRBn0A== + dependencies: + "@parcel/plugin" "2.3.2" + "@parcel/types" "2.3.2" + "@parcel/utils" "2.3.2" + nullthrows "^1.1.1" + posthtml "^0.16.5" + +"@parcel/packager-js@2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@parcel/packager-js/-/packager-js-2.3.2.tgz#2d2566bde0da921042b79aa827c71109665d795c" + integrity sha512-3OP0Ro9M1J+PIKZK4Ec2N5hjIPiqk++B2kMFeiUqvaNZjJgKrPPEICBhjS52rma4IE/NgmIMB3aI5pWqE/KwNA== + dependencies: + "@parcel/diagnostic" "2.3.2" + "@parcel/hash" "2.3.2" + "@parcel/plugin" "2.3.2" + "@parcel/source-map" "^2.0.0" + "@parcel/utils" "2.3.2" + globals "^13.2.0" + nullthrows "^1.1.1" + +"@parcel/packager-raw@2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@parcel/packager-raw/-/packager-raw-2.3.2.tgz#869cc3e7bee8ff3655891a0af400cf4e7dd4f144" + integrity sha512-RnoZ7WgNAFWkEPrEefvyDqus7xfv9XGprHyTbfLittPaVAZpl+4eAv43nXyMfzk77Cgds6KcNpkosj3acEpNIQ== + dependencies: + "@parcel/plugin" "2.3.2" + +"@parcel/packager-svg@2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@parcel/packager-svg/-/packager-svg-2.3.2.tgz#a7a02e22642ae93f42b8bfd7d122b4a159988743" + integrity sha512-iIC0VeczOXynS7M5jCi3naMBRyAznBVJ3iMg92/GaI9duxPlUMGAlHzLAKNtoXkc00HMXDH7rrmMb04VX6FYSg== + dependencies: + "@parcel/plugin" "2.3.2" + "@parcel/types" "2.3.2" + "@parcel/utils" "2.3.2" + posthtml "^0.16.4" + +"@parcel/plugin@2.3.2", "@parcel/plugin@^2.2.1": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@parcel/plugin/-/plugin-2.3.2.tgz#7701c40567d2eddd5d5b2b6298949cd03a2a22fa" + integrity sha512-SaLZAJX4KH+mrAmqmcy9KJN+V7L+6YNTlgyqYmfKlNiHu7aIjLL+3prX8QRcgGtjAYziCxvPj0cl1CCJssaiGg== + dependencies: + "@parcel/types" "2.3.2" + +"@parcel/reporter-cli@2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@parcel/reporter-cli/-/reporter-cli-2.3.2.tgz#0617e088aac5ef7fa255d088e7016bb4f9d66a53" + integrity sha512-VYetmTXqW83npsvVvqlQZTbF3yVL3k/FCCl3kSWvOr9LZA0lmyqJWPjMHq37yIIOszQN/p5guLtgCjsP0UQw1Q== + dependencies: + "@parcel/plugin" "2.3.2" + "@parcel/types" "2.3.2" + "@parcel/utils" "2.3.2" + chalk "^4.1.0" + +"@parcel/reporter-dev-server@2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@parcel/reporter-dev-server/-/reporter-dev-server-2.3.2.tgz#46ee4c53ad08c8b8afd2c79fb37381b6ba55cfb5" + integrity sha512-E7LtnjAX4iiWMw2qKUyFBi3+bDz0UGjqgHoPQylUYYLi6opXjJz/oC+cCcCy4e3RZlkrl187XonvagS59YjDxA== + dependencies: + "@parcel/plugin" "2.3.2" + "@parcel/utils" "2.3.2" + +"@parcel/resolver-default@2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@parcel/resolver-default/-/resolver-default-2.3.2.tgz#286070412ad7fe506f7c88409f39b362d2041798" + integrity sha512-y3r+xOwWsATrNGUWuZ6soA7q24f8E5tY1AZ9lHCufnkK2cdKZJ5O1cyd7ohkAiKZx2/pMd+FgmVZ/J3oxetXkA== + dependencies: + "@parcel/node-resolver-core" "2.3.2" + "@parcel/plugin" "2.3.2" + +"@parcel/runtime-browser-hmr@2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@parcel/runtime-browser-hmr/-/runtime-browser-hmr-2.3.2.tgz#cb23a850324ea792168438a9be6a345ebb66eb6d" + integrity sha512-nRD6uOyF1+HGylP9GASbYmvUDOsDaNwvaxuGTSh8+5M0mmCgib+hVBiPEKbwdmKjGbUPt9wRFPyMa/JpeQZsIQ== + dependencies: + "@parcel/plugin" "2.3.2" + "@parcel/utils" "2.3.2" + +"@parcel/runtime-js@2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@parcel/runtime-js/-/runtime-js-2.3.2.tgz#c0e14251ce43f95977577e23bb9ac5c2487f3bb1" + integrity sha512-SJepcHvYO/7CEe/Q85sngk+smcJ6TypuPh4D2R8kN+cAJPi5WvbQEe7+x5BEgbN+5Jumi/Uo3FfOOE5mYh+F6g== + dependencies: + "@parcel/plugin" "2.3.2" + "@parcel/utils" "2.3.2" + nullthrows "^1.1.1" + +"@parcel/runtime-react-refresh@2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@parcel/runtime-react-refresh/-/runtime-react-refresh-2.3.2.tgz#11961d7429ae3333b7efe14c4f57515df57eb5f2" + integrity sha512-P+GRPO2XVDSBQ4HmRSj2xfbHSQvL9+ahTE/AB74IJExLTITv5l4SHAV3VsiKohuHYUAYHW3A/Oe7tEFCAb6Cug== + dependencies: + "@parcel/plugin" "2.3.2" + "@parcel/utils" "2.3.2" + react-refresh "^0.9.0" + +"@parcel/runtime-service-worker@2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@parcel/runtime-service-worker/-/runtime-service-worker-2.3.2.tgz#aa91797e57d1bb5b2aac04ac62c5410709ae0a27" + integrity sha512-iREHj/eapphC4uS/zGUkiTJvG57q+CVbTrfE42kB8ECtf/RYNo5YC9htdvPZjRSXDPrEPc5NCoKp4X09ENNikw== + dependencies: + "@parcel/plugin" "2.3.2" + "@parcel/utils" "2.3.2" + nullthrows "^1.1.1" + +"@parcel/source-map@^2.0.0": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@parcel/source-map/-/source-map-2.0.2.tgz#9aa0b00518cee31d5634de6e9c924a5539b142c1" + integrity sha512-NnUrPYLpYB6qyx2v6bcRPn/gVigmGG6M6xL8wIg/i0dP1GLkuY1nf+Hqdf63FzPTqqT7K3k6eE5yHPQVMO5jcA== + dependencies: + detect-libc "^1.0.3" + +"@parcel/transformer-babel@2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@parcel/transformer-babel/-/transformer-babel-2.3.2.tgz#2d8c0d1f95d9747936d132dc4c34edb0b6b80d39" + integrity sha512-QpWfH2V6jJ+kcUBIMM/uBBG8dGFvNaOGS+8jD6b+eTP+1owzm83RoWgqhRV2D/hhv2qMXEQzIljoc/wg2y+X4g== + dependencies: + "@parcel/diagnostic" "2.3.2" + "@parcel/plugin" "2.3.2" + "@parcel/source-map" "^2.0.0" + "@parcel/utils" "2.3.2" + browserslist "^4.6.6" + json5 "^2.2.0" + nullthrows "^1.1.1" + semver "^5.7.0" + +"@parcel/transformer-css@2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@parcel/transformer-css/-/transformer-css-2.3.2.tgz#968826e42d7cac9963dc0a67a30d393ef996e48c" + integrity sha512-8lzvDny+78DIAqhcXam2Bf9FyaUoqzHdUQdNFn+PuXTHroG/QGPvln1kvqngJjn4/cpJS9vYmAPVXe+nai3P8g== + dependencies: + "@parcel/hash" "2.3.2" + "@parcel/plugin" "2.3.2" + "@parcel/source-map" "^2.0.0" + "@parcel/utils" "2.3.2" + nullthrows "^1.1.1" + postcss "^8.4.5" + postcss-value-parser "^4.2.0" + semver "^5.7.1" + +"@parcel/transformer-html@2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@parcel/transformer-html/-/transformer-html-2.3.2.tgz#c240f09369445d287d16beba207407c925532d90" + integrity sha512-idT1I/8WM65IFYBqzRwpwT7sf0xGur4EDQDHhuPX1w+pIVZnh0lkLMAnEqs6ar1SPRMys4chzkuDNnqh0d76hg== + dependencies: + "@parcel/diagnostic" "2.3.2" + "@parcel/hash" "2.3.2" + "@parcel/plugin" "2.3.2" + nullthrows "^1.1.1" + posthtml "^0.16.5" + posthtml-parser "^0.10.1" + posthtml-render "^3.0.0" + semver "^5.7.1" + +"@parcel/transformer-image@2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@parcel/transformer-image/-/transformer-image-2.3.2.tgz#24b6eda51a6b07c195886bbb67fb2ade14c325f3" + integrity sha512-0K7cJHXysli6hZsUz/zVGO7WCoaaIeVdzAxKpLA1Yl3LKw/ODiMyXKt08LiV/ljQ2xT5qb9EsXUWDRvcZ0b96A== + dependencies: + "@parcel/plugin" "2.3.2" + "@parcel/workers" "2.3.2" + nullthrows "^1.1.1" + +"@parcel/transformer-js@2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@parcel/transformer-js/-/transformer-js-2.3.2.tgz#24bcb488d5f82678343a5630fe4bbe822789ac33" + integrity sha512-U1fbIoAoqR5P49S+DMhH8BUd9IHRPwrTTv6ARYGsYnhuNsjTFhNYE0kkfRYboe/e0z7vEbeJICZXjnZ7eQDw5A== + dependencies: + "@parcel/diagnostic" "2.3.2" + "@parcel/plugin" "2.3.2" + "@parcel/source-map" "^2.0.0" + "@parcel/utils" "2.3.2" + "@parcel/workers" "2.3.2" + "@swc/helpers" "^0.2.11" + browserslist "^4.6.6" + detect-libc "^1.0.3" + nullthrows "^1.1.1" + regenerator-runtime "^0.13.7" + semver "^5.7.1" + +"@parcel/transformer-json@2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@parcel/transformer-json/-/transformer-json-2.3.2.tgz#4c470e86659e87ee13b1f31e75a3621d3615b6bd" + integrity sha512-Pv2iPaxKINtFwOk5fDbHjQlSm2Vza/NLimQY896FLxiXPNAJxWGvMwdutgOPEBKksxRx9LZPyIOHiRVZ0KcA3w== + dependencies: + "@parcel/plugin" "2.3.2" + json5 "^2.2.0" + +"@parcel/transformer-less@^2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@parcel/transformer-less/-/transformer-less-2.3.2.tgz#5e9fe261e6d3a196f89cfb811e60c549f1034e37" + integrity sha512-elQ00PiUkadFM2DdZrqTciobXSQjPkZA5ZSfdC/lfW7aSP28bnLI1EV3FfHZhiwJcmJ/nccmT2ZwRVwJKus7hg== + dependencies: + "@parcel/plugin" "2.3.2" + "@parcel/source-map" "^2.0.0" + less "^4.1.1" + +"@parcel/transformer-postcss@2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@parcel/transformer-postcss/-/transformer-postcss-2.3.2.tgz#a428c81569dd66758c5fab866dca69b4c6e59743" + integrity sha512-Rpdxc1rt2aJFCh/y/ccaBc9J1crDjNY5o44xYoOemBoUNDMREsmg5sR5iO81qKKO5GxfoosGb2zh59aeTmywcg== + dependencies: + "@parcel/hash" "2.3.2" + "@parcel/plugin" "2.3.2" + "@parcel/utils" "2.3.2" + clone "^2.1.1" + nullthrows "^1.1.1" + postcss-value-parser "^4.2.0" + semver "^5.7.1" + +"@parcel/transformer-posthtml@2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@parcel/transformer-posthtml/-/transformer-posthtml-2.3.2.tgz#5da3f24bf240c3c49b2fdb17dcda5988d3057a30" + integrity sha512-tMdVExfdM+1G8A9KSHDsjg+S9xEGbhH5mApF2NslPnNZ4ciLKRNuHU2sSV/v8i0a6kacKvDTrwQXYBQJGOodBw== + dependencies: + "@parcel/plugin" "2.3.2" + "@parcel/utils" "2.3.2" + nullthrows "^1.1.1" + posthtml "^0.16.5" + posthtml-parser "^0.10.1" + posthtml-render "^3.0.0" + semver "^5.7.1" + +"@parcel/transformer-raw@2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@parcel/transformer-raw/-/transformer-raw-2.3.2.tgz#40d21773e295bae3b16bfe7a89e414ccf534b9c5" + integrity sha512-lY7eOCaALZ90+GH+4PZRmAPGQRXoZ66NakSdhEtH6JSSAYOmZKDvNLGTMRo/vK1oELzWMuAHGdqvbcPDtNLLVw== + dependencies: + "@parcel/plugin" "2.3.2" + +"@parcel/transformer-react-refresh-wrap@2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@parcel/transformer-react-refresh-wrap/-/transformer-react-refresh-wrap-2.3.2.tgz#43ecfe6f4567b88abb81db9fe56b8d860d6a69f7" + integrity sha512-FZaderyCExn0SBZ6D+zHPWc8JSn9YDcbfibv0wkCl+D7sYfeWZ22i7MRp5NwCe/TZ21WuxDWySCggEp/Waz2xg== + dependencies: + "@parcel/plugin" "2.3.2" + "@parcel/utils" "2.3.2" + react-refresh "^0.9.0" + +"@parcel/transformer-svg@2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@parcel/transformer-svg/-/transformer-svg-2.3.2.tgz#9a66aef5011c7bbb1fa3ce9bb52ca56d8f0f964d" + integrity sha512-k9My6bePsaGgUh+tidDjFbbVgKPTzwCAQfoloZRMt7y396KgUbvCfqDruk04k6k+cJn7Jl1o/5lUpTEruBze7g== + dependencies: + "@parcel/diagnostic" "2.3.2" + "@parcel/hash" "2.3.2" + "@parcel/plugin" "2.3.2" + nullthrows "^1.1.1" + posthtml "^0.16.5" + posthtml-parser "^0.10.1" + posthtml-render "^3.0.0" + semver "^5.7.1" + +"@parcel/types@2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@parcel/types/-/types-2.3.2.tgz#7eb6925bc852a518dd75b742419e51292418769f" + integrity sha512-C77Ct1xNM7LWjPTfe/dQ/9rq1efdsX5VJu2o8/TVi6qoFh64Wp/c5/vCHwKInOTBZUTchVO6z4PGJNIZoUVJuA== + dependencies: + "@parcel/cache" "2.3.2" + "@parcel/diagnostic" "2.3.2" + "@parcel/fs" "2.3.2" + "@parcel/package-manager" "2.3.2" + "@parcel/source-map" "^2.0.0" + "@parcel/workers" "2.3.2" + utility-types "^3.10.0" + +"@parcel/utils@2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@parcel/utils/-/utils-2.3.2.tgz#4aab052fc9f3227811a504da7b9663ca75004f55" + integrity sha512-xzZ+0vWhrXlLzGoz7WlANaO5IPtyWGeCZruGtepUL3yheRWb1UU4zFN9xz7Z+j++Dmf1Fgkc3qdk/t4O8u9HLQ== + dependencies: + "@parcel/codeframe" "2.3.2" + "@parcel/diagnostic" "2.3.2" + "@parcel/hash" "2.3.2" + "@parcel/logger" "2.3.2" + "@parcel/markdown-ansi" "2.3.2" + "@parcel/source-map" "^2.0.0" + chalk "^4.1.0" + +"@parcel/watcher@^2.0.0": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@parcel/watcher/-/watcher-2.0.5.tgz#f913a54e1601b0aac972803829b0eece48de215b" + integrity sha512-x0hUbjv891omnkcHD7ZOhiyyUqUUR6MNjq89JhEI3BxppeKWAm6NPQsqqRrAkCJBogdT/o/My21sXtTI9rJIsw== + dependencies: + node-addon-api "^3.2.1" + node-gyp-build "^4.3.0" + +"@parcel/workers@2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@parcel/workers/-/workers-2.3.2.tgz#05ffa2da9169bfb83335892c2b8abce55686ceb1" + integrity sha512-JbOm+Ceuyymd1SuKGgodC2EXAiPuFRpaNUSJpz3NAsS3lVIt2TDAPMOWBivS7sML/KltspUfl/Q9YwO0TPUFNw== + dependencies: + "@parcel/diagnostic" "2.3.2" + "@parcel/logger" "2.3.2" + "@parcel/types" "2.3.2" + "@parcel/utils" "2.3.2" + chrome-trace-event "^1.0.2" + nullthrows "^1.1.1" + +"@swc/helpers@^0.2.11": + version "0.2.14" + resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.2.14.tgz#20288c3627442339dd3d743c944f7043ee3590f0" + integrity sha512-wpCQMhf5p5GhNg2MmGKXzUNwxe7zRiCsmqYsamez2beP7mKPCSiu+BjZcdN95yYSzO857kr0VfQewmGpS77nqA== + +"@trysound/sax@0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad" + integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA== + +"@types/chroma-js@^2.0.0": + version "2.1.3" + resolved "https://registry.yarnpkg.com/@types/chroma-js/-/chroma-js-2.1.3.tgz#0b03d737ff28fad10eb884e0c6cedd5ffdc4ba0a" + integrity sha512-1xGPhoSGY1CPmXLCBcjVZSQinFjL26vlR8ZqprsBWiFyED4JacJJ9zHhh5aaUXqbY9B37mKQ73nlydVAXmr1+g== + +"@types/hast@^2.0.0": + version "2.3.4" + resolved "https://registry.yarnpkg.com/@types/hast/-/hast-2.3.4.tgz#8aa5ef92c117d20d974a82bdfb6a648b08c0bafc" + integrity sha512-wLEm0QvaoawEDoTRwzTXp4b4jpwiJDvR5KMnFnVodm3scufTlBOWRD6N1OBf9TZMhjlNsSfcO5V+7AF4+Vy+9g== + dependencies: + "@types/unist" "*" + +"@types/hoist-non-react-statics@^3.3.0": + version "3.3.1" + resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f" + integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA== + dependencies: + "@types/react" "*" + hoist-non-react-statics "^3.3.0" + +"@types/lodash@^4.14.160": + version "4.14.178" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.178.tgz#341f6d2247db528d4a13ddbb374bcdc80406f4f8" + integrity sha512-0d5Wd09ItQWH1qFbEyQ7oTQ3GZrMfth5JkbN3EvTKLXcHLRDSXeLnlvlOn0wvxVIwK5o2M8JzP/OWz7T3NRsbw== + +"@types/mdast@^3.0.0": + version "3.0.10" + resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-3.0.10.tgz#4724244a82a4598884cbbe9bcfd73dff927ee8af" + integrity sha512-W864tg/Osz1+9f4lrGTZpCSO5/z4608eUp19tbozkq2HJK6i3z1kT0H9tlADXuYIb1YYOBByU4Jsqkk75q48qA== + dependencies: + "@types/unist" "*" + +"@types/numeral@^0.0.28": + version "0.0.28" + resolved "https://registry.yarnpkg.com/@types/numeral/-/numeral-0.0.28.tgz#e43928f0bda10b169b6f7ecf99e3ddf836b8ebe4" + integrity sha512-Sjsy10w6XFHDktJJdXzBJmoondAKW+LcGpRFH+9+zXEDj0cOH8BxJuZA9vUDSMAzU1YRJlsPKmZEEiTYDlICLw== + +"@types/parse-json@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" + integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== + +"@types/parse5@^5.0.0": + version "5.0.3" + resolved "https://registry.yarnpkg.com/@types/parse5/-/parse5-5.0.3.tgz#e7b5aebbac150f8b5fdd4a46e7f0bd8e65e19109" + integrity sha512-kUNnecmtkunAoQ3CnjmMkzNU/gtxG8guhi+Fk2U/kOpIKjIMKnXGp4IJCgQJrXSgMsWYimYG4TGjz/UzbGEBTw== + +"@types/prismjs@*": + version "1.26.0" + resolved "https://registry.yarnpkg.com/@types/prismjs/-/prismjs-1.26.0.tgz#a1c3809b0ad61c62cac6d4e0c56d610c910b7654" + integrity sha512-ZTaqn/qSqUuAq1YwvOFQfVW1AR/oQJlLSZVustdjwI+GZ8kr0MSHBj0tsXPW1EqHubx50gtBEjbPGsdZwQwCjQ== + +"@types/prop-types@*": + version "15.7.4" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.4.tgz#fcf7205c25dff795ee79af1e30da2c9790808f11" + integrity sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ== + +"@types/react-beautiful-dnd@^13.0.0": + version "13.1.2" + resolved "https://registry.yarnpkg.com/@types/react-beautiful-dnd/-/react-beautiful-dnd-13.1.2.tgz#510405abb09f493afdfd898bf83995dc6385c130" + integrity sha512-+OvPkB8CdE/bGdXKyIhc/Lm2U7UAYCCJgsqmopFmh9gbAudmslkI8eOrPDjg4JhwSE6wytz4a3/wRjKtovHVJg== + dependencies: + "@types/react" "*" + +"@types/react-input-autosize@^2.2.0": + version "2.2.1" + resolved "https://registry.yarnpkg.com/@types/react-input-autosize/-/react-input-autosize-2.2.1.tgz#6a335212e7fce1e1a4da56ae2095c8c5c35fbfe6" + integrity sha512-RxzEjd4gbLAAdLQ92Q68/AC+TfsAKTc4evsArUH1aIShIMqQMIMjsxoSnwyjtbFTO/AGIW/RQI94XSdvOxCz/w== + dependencies: + "@types/react" "*" + +"@types/react-redux@^7.1.20": + version "7.1.22" + resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.22.tgz#0eab76a37ef477cc4b53665aeaf29cb60631b72a" + integrity sha512-GxIA1kM7ClU73I6wg9IRTVwSO9GS+SAKZKe0Enj+82HMU6aoESFU2HNAdNi3+J53IaOHPiUfT3kSG4L828joDQ== + dependencies: + "@types/hoist-non-react-statics" "^3.3.0" + "@types/react" "*" + hoist-non-react-statics "^3.3.0" + redux "^4.0.0" + +"@types/react-virtualized-auto-sizer@^1.0.0": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@types/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.1.tgz#b3187dae1dfc4c15880c9cfc5b45f2719ea6ebd4" + integrity sha512-GH8sAnBEM5GV9LTeiz56r4ZhMOUSrP43tAQNSRVxNexDjcNKLCEtnxusAItg1owFUFE6k0NslV26gqVClVvong== + dependencies: + "@types/react" "*" + +"@types/react-window@^1.8.2": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@types/react-window/-/react-window-1.8.5.tgz#285fcc5cea703eef78d90f499e1457e9b5c02fc1" + integrity sha512-V9q3CvhC9Jk9bWBOysPGaWy/Z0lxYcTXLtLipkt2cnRj1JOSFNF7wqGpkScSXMgBwC+fnVRg/7shwgddBG5ICw== + dependencies: + "@types/react" "*" + +"@types/react@*": + version "17.0.38" + resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.38.tgz#f24249fefd89357d5fa71f739a686b8d7c7202bd" + integrity sha512-SI92X1IA+FMnP3qM5m4QReluXzhcmovhZnLNm3pyeQlooi02qI7sLiepEYqT678uNiyc25XfCqxREFpy3W7YhQ== + dependencies: + "@types/prop-types" "*" + "@types/scheduler" "*" + csstype "^3.0.2" + +"@types/refractor@^3.0.0": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@types/refractor/-/refractor-3.0.2.tgz#2d42128d59f78f84d2c799ffc5ab5cadbcba2d82" + integrity sha512-2HMXuwGuOqzUG+KUTm9GDJCHl0LCBKsB5cg28ujEmVi/0qgTb6jOmkVSO5K48qXksyl2Fr3C0Q2VrgD4zbwyXg== + dependencies: + "@types/prismjs" "*" + +"@types/resize-observer-browser@^0.1.5": + version "0.1.7" + resolved "https://registry.yarnpkg.com/@types/resize-observer-browser/-/resize-observer-browser-0.1.7.tgz#294aaadf24ac6580b8fbd1fe3ab7b59fe85f9ef3" + integrity sha512-G9eN0Sn0ii9PWQ3Vl72jDPgeJwRWhv2Qk/nQkJuWmRmOB4HX3/BhD5SE1dZs/hzPZL/WKnvF0RHdTSG54QJFyg== + +"@types/scheduler@*": + version "0.16.2" + resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39" + integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew== + +"@types/unist@*", "@types/unist@^2.0.0", "@types/unist@^2.0.2", "@types/unist@^2.0.3": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.6.tgz#250a7b16c3b91f672a24552ec64678eeb1d3a08d" + integrity sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ== + +"@types/vfile-message@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@types/vfile-message/-/vfile-message-2.0.0.tgz#690e46af0fdfc1f9faae00cd049cc888957927d5" + integrity sha512-GpTIuDpb9u4zIO165fUy9+fXcULdD8HFRNli04GehoMVbeNq7D6OBnqSmg3lxZnC+UvgUhEWKxdKiwYUkGltIw== + dependencies: + vfile-message "*" + +abortcontroller-polyfill@^1.1.9: + version "1.7.3" + resolved "https://registry.yarnpkg.com/abortcontroller-polyfill/-/abortcontroller-polyfill-1.7.3.tgz#1b5b487bd6436b5b764fd52a612509702c3144b5" + integrity sha512-zetDJxd89y3X99Kvo4qFx8GKlt6GsvN3UcRZHwU6iFA/0KiOmhkTVhe8oRoTBiTVPZu09x3vCra47+w8Yz1+2Q== + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +aria-hidden@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/aria-hidden/-/aria-hidden-1.1.3.tgz#bb48de18dc84787a3c6eee113709c473c64ec254" + integrity sha512-RhVWFtKH5BiGMycI72q2RAFMLQi8JP9bLuQXgR5a8Znp7P5KOIADSJeyfI8PCVxLEp067B2HbP5JIiI/PXIZeA== + dependencies: + tslib "^1.0.0" + +attr-accept@^2.2.1: + version "2.2.2" + resolved "https://registry.yarnpkg.com/attr-accept/-/attr-accept-2.2.2.tgz#646613809660110749e92f2c10833b70968d929b" + integrity sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg== + +bail@^1.0.0: + version "1.0.5" + resolved "https://registry.yarnpkg.com/bail/-/bail-1.0.5.tgz#b6fa133404a392cbc1f8c4bf63f5953351e7a776" + integrity sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ== + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +base-x@^3.0.8: + version "3.0.9" + resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.9.tgz#6349aaabb58526332de9f60995e548a53fe21320" + integrity sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ== + dependencies: + safe-buffer "^5.0.1" + +boolbase@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" + integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +brace@^0.11.1: + version "0.11.1" + resolved "https://registry.yarnpkg.com/brace/-/brace-0.11.1.tgz#4896fcc9d544eef45f4bb7660db320d3b379fe58" + integrity sha1-SJb8ydVE7vRfS7dmDbMg07N5/lg= + +browserslist@^4.0.0, browserslist@^4.16.6, browserslist@^4.6.6: + version "4.19.3" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.19.3.tgz#29b7caad327ecf2859485f696f9604214bedd383" + integrity sha512-XK3X4xtKJ+Txj8G5c30B4gsm71s69lqXlkYui4s6EkKxuv49qjYlY6oVd+IFJ73d4YymtM3+djvvt/R/iJwwDg== + dependencies: + caniuse-lite "^1.0.30001312" + electron-to-chromium "^1.4.71" + escalade "^3.1.1" + node-releases "^2.0.2" + picocolors "^1.0.0" + +buffer-from@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +caniuse-api@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0" + integrity sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw== + dependencies: + browserslist "^4.0.0" + caniuse-lite "^1.0.0" + lodash.memoize "^4.1.2" + lodash.uniq "^4.5.0" + +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001312: + version "1.0.30001312" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001312.tgz#e11eba4b87e24d22697dae05455d5aea28550d5f" + integrity sha512-Wiz1Psk2MEK0pX3rUzWaunLTZzqS2JYZFzNKqAiJGiuxIjRPLgV6+VDPOg6lQOUxmDwhTlh198JsTTi8Hzw6aQ== + +ccount@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/ccount/-/ccount-1.1.0.tgz#246687debb6014735131be8abab2d93898f8d043" + integrity sha512-vlNK021QdI7PNeiUh/lKkC/mNHHfV0m/Ad5JoI0TYtlBnJAslM/JIkm/tGC88bkLIwO6OQ5uV6ztS6kVAtCDlg== + +chalk@^2.0.0: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^4.1.0: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +character-entities-html4@^1.0.0: + version "1.1.4" + resolved "https://registry.yarnpkg.com/character-entities-html4/-/character-entities-html4-1.1.4.tgz#0e64b0a3753ddbf1fdc044c5fd01d0199a02e125" + integrity sha512-HRcDxZuZqMx3/a+qrzxdBKBPUpxWEq9xw2OPZ3a/174ihfrQKVsFhqtthBInFy1zZ9GgZyFXOatNujm8M+El3g== + +character-entities-legacy@^1.0.0: + version "1.1.4" + resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz#94bc1845dce70a5bb9d2ecc748725661293d8fc1" + integrity sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA== + +character-entities@^1.0.0: + version "1.2.4" + resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-1.2.4.tgz#e12c3939b7eaf4e5b15e7ad4c5e28e1d48c5b16b" + integrity sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw== + +character-reference-invalid@^1.0.0: + version "1.1.4" + resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz#083329cda0eae272ab3dbbf37e9a382c13af1560" + integrity sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg== + +chroma-js@^2.1.0: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chroma-js/-/chroma-js-2.4.2.tgz#dffc214ed0c11fa8eefca2c36651d8e57cbfb2b0" + integrity sha512-U9eDw6+wt7V8z5NncY2jJfZa+hUH8XEj8FQHgFJTrUFnJfXYf4Ml4adI2vXZOjqRDpFWtYVWypDfZwnJ+HIR4A== + +chrome-trace-event@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac" + integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg== + +classnames@^2.2.6, classnames@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.1.tgz#dfcfa3891e306ec1dad105d0e88f4417b8535e8e" + integrity sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA== + +cliui@^7.0.2: + version "7.0.4" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" + integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^7.0.0" + +clone@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" + integrity sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18= + +collapse-white-space@^1.0.2: + version "1.0.6" + resolved "https://registry.yarnpkg.com/collapse-white-space/-/collapse-white-space-1.0.6.tgz#e63629c0016665792060dbbeb79c42239d2c5287" + integrity sha512-jEovNnrhMuqyCcjfEJA56v0Xq8SkIoPKDyaHahwo3POf4qcSXqMYuwNcOTzp74vTsR9Tn08z4MxWqAhcekogkQ== + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +colord@^2.9.1: + version "2.9.2" + resolved "https://registry.yarnpkg.com/colord/-/colord-2.9.2.tgz#25e2bacbbaa65991422c07ea209e2089428effb1" + integrity sha512-Uqbg+J445nc1TKn4FoDPS6ZZqAvEDnwrH42yo8B40JSOgSLxMZ/gt3h4nmCtPLQeXhjJJkqBx7SCY35WnIixaQ== + +comma-separated-tokens@^1.0.0: + version "1.0.8" + resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz#632b80b6117867a158f1080ad498b2fbe7e3f5ea" + integrity sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw== + +commander@^2.20.0: + version "2.20.3" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + +commander@^7.0.0, commander@^7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" + integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +concurrently@^6.3.0: + version "6.5.1" + resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-6.5.1.tgz#4518c67f7ac680cf5c34d5adf399a2a2047edc8c" + integrity sha512-FlSwNpGjWQfRwPLXvJ/OgysbBxPkWpiVjy1042b0U7on7S7qwwMIILRj7WTN1mTgqa582bG6NFuScOoh6Zgdag== + dependencies: + chalk "^4.1.0" + date-fns "^2.16.1" + lodash "^4.17.21" + rxjs "^6.6.3" + spawn-command "^0.0.2-1" + supports-color "^8.1.0" + tree-kill "^1.2.2" + yargs "^16.2.0" + +copy-anything@^2.0.1: + version "2.0.6" + resolved "https://registry.yarnpkg.com/copy-anything/-/copy-anything-2.0.6.tgz#092454ea9584a7b7ad5573062b2a87f5900fc480" + integrity sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw== + dependencies: + is-what "^3.14.1" + +cosmiconfig@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.1.tgz#714d756522cace867867ccb4474c5d01bbae5d6d" + integrity sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ== + dependencies: + "@types/parse-json" "^4.0.0" + import-fresh "^3.2.1" + parse-json "^5.0.0" + path-type "^4.0.0" + yaml "^1.10.0" + +cross-env@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-7.0.3.tgz#865264b29677dc015ba8418918965dd232fc54cf" + integrity sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw== + dependencies: + cross-spawn "^7.0.1" + +cross-spawn@^7.0.1: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +css-box-model@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/css-box-model/-/css-box-model-1.2.1.tgz#59951d3b81fd6b2074a62d49444415b0d2b4d7c1" + integrity sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw== + dependencies: + tiny-invariant "^1.0.6" + +css-declaration-sorter@^6.0.3: + version "6.1.4" + resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-6.1.4.tgz#b9bfb4ed9a41f8dcca9bf7184d849ea94a8294b4" + integrity sha512-lpfkqS0fctcmZotJGhnxkIyJWvBXgpyi2wsFd4J8VB7wzyrT6Ch/3Q+FMNJpjK4gu1+GN5khOnpU2ZVKrLbhCw== + dependencies: + timsort "^0.3.0" + +css-select@^4.1.3: + version "4.2.1" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.2.1.tgz#9e665d6ae4c7f9d65dbe69d0316e3221fb274cdd" + integrity sha512-/aUslKhzkTNCQUB2qTX84lVmfia9NyjP3WpDGtj/WxhwBzWBYUV3DgUpurHTme8UTPcPlAD1DJ+b0nN/t50zDQ== + dependencies: + boolbase "^1.0.0" + css-what "^5.1.0" + domhandler "^4.3.0" + domutils "^2.8.0" + nth-check "^2.0.1" + +css-tree@^1.1.2, css-tree@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.1.3.tgz#eb4870fb6fd7707327ec95c2ff2ab09b5e8db91d" + integrity sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q== + dependencies: + mdn-data "2.0.14" + source-map "^0.6.1" + +css-what@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-5.1.0.tgz#3f7b707aadf633baf62c2ceb8579b545bb40f7fe" + integrity sha512-arSMRWIIFY0hV8pIxZMEfmMI47Wj3R/aWpZDDxWYCPEiOMv6tfOrnpDtgxBYPEQD4V0Y/958+1TdC3iWTFcUPw== + +cssesc@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" + integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== + +cssnano-preset-default@^5.1.12: + version "5.1.12" + resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-5.1.12.tgz#64e2ad8e27a279e1413d2d2383ef89a41c909be9" + integrity sha512-rO/JZYyjW1QNkWBxMGV28DW7d98UDLaF759frhli58QFehZ+D/LSmwQ2z/ylBAe2hUlsIWTq6NYGfQPq65EF9w== + dependencies: + css-declaration-sorter "^6.0.3" + cssnano-utils "^3.0.2" + postcss-calc "^8.2.0" + postcss-colormin "^5.2.5" + postcss-convert-values "^5.0.4" + postcss-discard-comments "^5.0.3" + postcss-discard-duplicates "^5.0.3" + postcss-discard-empty "^5.0.3" + postcss-discard-overridden "^5.0.4" + postcss-merge-longhand "^5.0.6" + postcss-merge-rules "^5.0.6" + postcss-minify-font-values "^5.0.4" + postcss-minify-gradients "^5.0.6" + postcss-minify-params "^5.0.5" + postcss-minify-selectors "^5.1.3" + postcss-normalize-charset "^5.0.3" + postcss-normalize-display-values "^5.0.3" + postcss-normalize-positions "^5.0.4" + postcss-normalize-repeat-style "^5.0.4" + postcss-normalize-string "^5.0.4" + postcss-normalize-timing-functions "^5.0.3" + postcss-normalize-unicode "^5.0.4" + postcss-normalize-url "^5.0.5" + postcss-normalize-whitespace "^5.0.4" + postcss-ordered-values "^5.0.5" + postcss-reduce-initial "^5.0.3" + postcss-reduce-transforms "^5.0.4" + postcss-svgo "^5.0.4" + postcss-unique-selectors "^5.0.4" + +cssnano-utils@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/cssnano-utils/-/cssnano-utils-3.0.2.tgz#d82b4991a27ba6fec644b39bab35fe027137f516" + integrity sha512-KhprijuQv2sP4kT92sSQwhlK3SJTbDIsxcfIEySB0O+3m9esFOai7dP9bMx5enHAh2MwarVIcnwiWoOm01RIbQ== + +cssnano@^5.0.15: + version "5.0.17" + resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-5.0.17.tgz#ff45713c05cfc780a1aeb3e663b6f224d091cabf" + integrity sha512-fmjLP7k8kL18xSspeXTzRhaFtRI7DL9b8IcXR80JgtnWBpvAzHT7sCR/6qdn0tnxIaINUN6OEQu83wF57Gs3Xw== + dependencies: + cssnano-preset-default "^5.1.12" + lilconfig "^2.0.3" + yaml "^1.10.2" + +csso@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/csso/-/csso-4.2.0.tgz#ea3a561346e8dc9f546d6febedd50187cf389529" + integrity sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA== + dependencies: + css-tree "^1.1.2" + +csstype@^3.0.2: + version "3.0.10" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.10.tgz#2ad3a7bed70f35b965707c092e5f30b327c290e5" + integrity sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA== + +csstype@^3.0.8: + version "3.1.1" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.1.tgz#841b532c45c758ee546a11d5bd7b7b473c8c30b9" + integrity sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw== + +date-fns@^2.16.1: + version "2.28.0" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.28.0.tgz#9570d656f5fc13143e50c975a3b6bbeb46cd08b2" + integrity sha512-8d35hViGYx/QH0icHYCeLmsLmMUheMmTyV9Fcm6gvNwdw31yXXH+O85sOBJ+OLnLQMKZowvpKb6FgMIQjcpvQw== + +debug@^3.2.6: + version "3.2.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" + integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== + dependencies: + ms "^2.1.1" + +detect-libc@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" + integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= + +detect-node-es@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/detect-node-es/-/detect-node-es-1.1.0.tgz#163acdf643330caa0b4cd7c21e7ee7755d6fa493" + integrity sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ== + +diff-match-patch@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/diff-match-patch/-/diff-match-patch-1.0.5.tgz#abb584d5f10cd1196dfc55aa03701592ae3f7b37" + integrity sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw== + +dom-serializer@^1.0.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.3.2.tgz#6206437d32ceefaec7161803230c7a20bc1b4d91" + integrity sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig== + dependencies: + domelementtype "^2.0.1" + domhandler "^4.2.0" + entities "^2.0.0" + +domelementtype@^2.0.1, domelementtype@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.2.0.tgz#9a0b6c2782ed6a1c7323d42267183df9bd8b1d57" + integrity sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A== + +domhandler@^4.2.0, domhandler@^4.2.2, domhandler@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.3.0.tgz#16c658c626cf966967e306f966b431f77d4a5626" + integrity sha512-fC0aXNQXqKSFTr2wDNZDhsEYjCiYsDWl3D01kwt25hm1YIPyDGHvvi3rw+PLqHAl/m71MaiF7d5zvBr0p5UB2g== + dependencies: + domelementtype "^2.2.0" + +domutils@^2.8.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135" + integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A== + dependencies: + dom-serializer "^1.0.1" + domelementtype "^2.2.0" + domhandler "^4.2.0" + +dotenv-expand@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-5.1.0.tgz#3fbaf020bfd794884072ea26b1e9791d45a629f0" + integrity sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA== + +dotenv@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-7.0.0.tgz#a2be3cd52736673206e8a85fb5210eea29628e7c" + integrity sha512-M3NhsLbV1i6HuGzBUH8vXrtxOk+tWmzWKDMbAVSUp3Zsjm7ywFeuwrUXhmhQyRK1q5B5GGy7hcXPbj3bnfZg2g== + +electron-to-chromium@^1.4.71: + version "1.4.71" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.71.tgz#17056914465da0890ce00351a3b946fd4cd51ff6" + integrity sha512-Hk61vXXKRb2cd3znPE9F+2pLWdIOmP7GjiTj45y6L3W/lO+hSnUSUhq+6lEaERWBdZOHbk2s3YV5c9xVl3boVw== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +emoticon@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/emoticon/-/emoticon-3.2.0.tgz#c008ca7d7620fac742fe1bf4af8ff8fed154ae7f" + integrity sha512-SNujglcLTTg+lDAcApPNgEdudaqQFiAbJCqzjNxJkvN9vAwCGi0uu8IUVvx+f16h+V44KCY6Y2yboroc9pilHg== + +entities@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" + integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== + +entities@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/entities/-/entities-3.0.1.tgz#2b887ca62585e96db3903482d336c1006c3001d4" + integrity sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q== + +errno@^0.1.1: + version "0.1.8" + resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.8.tgz#8bb3e9c7d463be4976ff888f76b4809ebc2e811f" + integrity sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A== + dependencies: + prr "~1.0.1" + +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + +escalade@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + +extend@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== + +file-selector@^0.2.2: + version "0.2.4" + resolved "https://registry.yarnpkg.com/file-selector/-/file-selector-0.2.4.tgz#7b98286f9dbb9925f420130ea5ed0a69238d4d80" + integrity sha512-ZDsQNbrv6qRi1YTDOEWzf5J2KjZ9KMI1Q2SGeTkCJmNNW25Jg4TW4UMcmoqcg4WrAyKRcpBXdbWRxkfrOzVRbA== + dependencies: + tslib "^2.0.3" + +focus-lock@^0.10.1: + version "0.10.1" + resolved "https://registry.yarnpkg.com/focus-lock/-/focus-lock-0.10.1.tgz#5f46fa74fefb87144479c2f8e276f0eedd8081b2" + integrity sha512-b9yUklCi4fTu2GXn7dnaVf4hiLVVBp7xTiZarAHMODV2To6Bitf6F/UI67RmKbdgJQeVwI1UO0d9HYNbXt3GkA== + dependencies: + tslib "^2.0.3" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-nonce@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/get-nonce/-/get-nonce-1.0.1.tgz#fdf3f0278073820d2ce9426c18f07481b1e0cdf3" + integrity sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q== + +get-port@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/get-port/-/get-port-4.2.0.tgz#e37368b1e863b7629c43c5a323625f95cf24b119" + integrity sha512-/b3jarXkH8KJoOMQc3uVGHASwGLPq3gSFJ7tgJm2diza+bydJPTGOibin2steecKeOylE8oY2JERlVWkAJO6yw== + +glob@^7.1.3: + version "7.2.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" + integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globals@^13.2.0: + version "13.12.1" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.12.1.tgz#ec206be932e6c77236677127577aa8e50bf1c5cb" + integrity sha512-317dFlgY2pdJZ9rspXDks7073GpDmXdfbM3vYYp0HAMKGDh1FfWPleI2ljVNLQX5M5lXcAslTcPTrOrMEFOjyw== + dependencies: + type-fest "^0.20.2" + +graceful-fs@^4.1.2: + version "4.2.9" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.9.tgz#041b05df45755e587a24942279b9d113146e1c96" + integrity sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +hast-to-hyperscript@^9.0.0: + version "9.0.1" + resolved "https://registry.yarnpkg.com/hast-to-hyperscript/-/hast-to-hyperscript-9.0.1.tgz#9b67fd188e4c81e8ad66f803855334173920218d" + integrity sha512-zQgLKqF+O2F72S1aa4y2ivxzSlko3MAvxkwG8ehGmNiqd98BIN3JM1rAJPmplEyLmGLO2QZYJtIneOSZ2YbJuA== + dependencies: + "@types/unist" "^2.0.3" + comma-separated-tokens "^1.0.0" + property-information "^5.3.0" + space-separated-tokens "^1.0.0" + style-to-object "^0.3.0" + unist-util-is "^4.0.0" + web-namespaces "^1.0.0" + +hast-util-from-parse5@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/hast-util-from-parse5/-/hast-util-from-parse5-6.0.1.tgz#554e34abdeea25ac76f5bd950a1f0180e0b3bc2a" + integrity sha512-jeJUWiN5pSxW12Rh01smtVkZgZr33wBokLzKLwinYOUfSzm1Nl/c3GUGebDyOKjdsRgMvoVbV0VpAcpjF4NrJA== + dependencies: + "@types/parse5" "^5.0.0" + hastscript "^6.0.0" + property-information "^5.0.0" + vfile "^4.0.0" + vfile-location "^3.2.0" + web-namespaces "^1.0.0" + +hast-util-is-element@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/hast-util-is-element/-/hast-util-is-element-1.1.0.tgz#3b3ed5159a2707c6137b48637fbfe068e175a425" + integrity sha512-oUmNua0bFbdrD/ELDSSEadRVtWZOf3iF6Lbv81naqsIV99RnSCieTbWuWCY8BAeEfKJTKl0gRdokv+dELutHGQ== + +hast-util-parse-selector@^2.0.0: + version "2.2.5" + resolved "https://registry.yarnpkg.com/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz#d57c23f4da16ae3c63b3b6ca4616683313499c3a" + integrity sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ== + +hast-util-raw@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/hast-util-raw/-/hast-util-raw-6.1.0.tgz#e16a3c2642f65cc7c480c165400a40d604ab75d0" + integrity sha512-5FoZLDHBpka20OlZZ4I/+RBw5piVQ8iI1doEvffQhx5CbCyTtP8UCq8Tw6NmTAMtXgsQxmhW7Ly8OdFre5/YMQ== + dependencies: + "@types/hast" "^2.0.0" + hast-util-from-parse5 "^6.0.0" + hast-util-to-parse5 "^6.0.0" + html-void-elements "^1.0.0" + parse5 "^6.0.0" + unist-util-position "^3.0.0" + unist-util-visit "^2.0.0" + vfile "^4.0.0" + web-namespaces "^1.0.0" + xtend "^4.0.0" + zwitch "^1.0.0" + +hast-util-to-html@^7.1.1: + version "7.1.3" + resolved "https://registry.yarnpkg.com/hast-util-to-html/-/hast-util-to-html-7.1.3.tgz#9f339ca9bea71246e565fc79ff7dbfe98bb50f5e" + integrity sha512-yk2+1p3EJTEE9ZEUkgHsUSVhIpCsL/bvT8E5GzmWc+N1Po5gBw+0F8bo7dpxXR0nu0bQVxVZGX2lBGF21CmeDw== + dependencies: + ccount "^1.0.0" + comma-separated-tokens "^1.0.0" + hast-util-is-element "^1.0.0" + hast-util-whitespace "^1.0.0" + html-void-elements "^1.0.0" + property-information "^5.0.0" + space-separated-tokens "^1.0.0" + stringify-entities "^3.0.1" + unist-util-is "^4.0.0" + xtend "^4.0.0" + +hast-util-to-parse5@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/hast-util-to-parse5/-/hast-util-to-parse5-6.0.0.tgz#1ec44650b631d72952066cea9b1445df699f8479" + integrity sha512-Lu5m6Lgm/fWuz8eWnrKezHtVY83JeRGaNQ2kn9aJgqaxvVkFCZQBEhgodZUDUvoodgyROHDb3r5IxAEdl6suJQ== + dependencies: + hast-to-hyperscript "^9.0.0" + property-information "^5.0.0" + web-namespaces "^1.0.0" + xtend "^4.0.0" + zwitch "^1.0.0" + +hast-util-whitespace@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/hast-util-whitespace/-/hast-util-whitespace-1.0.4.tgz#e4fe77c4a9ae1cb2e6c25e02df0043d0164f6e41" + integrity sha512-I5GTdSfhYfAPNztx2xJRQpG8cuDSNt599/7YUn7Gx/WxNMsG+a835k97TDkFgk123cwjfwINaZknkKkphx/f2A== + +hastscript@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/hastscript/-/hastscript-6.0.0.tgz#e8768d7eac56c3fdeac8a92830d58e811e5bf640" + integrity sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w== + dependencies: + "@types/hast" "^2.0.0" + comma-separated-tokens "^1.0.0" + hast-util-parse-selector "^2.0.0" + property-information "^5.0.0" + space-separated-tokens "^1.0.0" + +hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.1, hoist-non-react-statics@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" + integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== + dependencies: + react-is "^16.7.0" + +html-void-elements@^1.0.0: + version "1.0.5" + resolved "https://registry.yarnpkg.com/html-void-elements/-/html-void-elements-1.0.5.tgz#ce9159494e86d95e45795b166c2021c2cfca4483" + integrity sha512-uE/TxKuyNIcx44cIWnjr/rfIATDH7ZaOMmstu0CwhFG1Dunhlp4OC6/NMbhiwoq5BpW0ubi303qnEk/PZj614w== + +htmlnano@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/htmlnano/-/htmlnano-2.0.0.tgz#07376faa064f7e1e832dfd91e1a9f606b0bc9b78" + integrity sha512-thKQfhcp2xgtsWNE27A2bliEeqVL5xjAgGn0wajyttvFFsvFWWah1ntV9aEX61gz0T6MBQ5xK/1lXuEumhJTcg== + dependencies: + cosmiconfig "^7.0.1" + posthtml "^0.16.5" + timsort "^0.3.0" + +htmlparser2@^7.1.1: + version "7.2.0" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-7.2.0.tgz#8817cdea38bbc324392a90b1990908e81a65f5a5" + integrity sha512-H7MImA4MS6cw7nbyURtLPO1Tms7C5H602LRETv95z1MxO/7CP7rDVROehUYeYBUYEON94NXXDEPmZuq+hX4sog== + dependencies: + domelementtype "^2.0.1" + domhandler "^4.2.2" + domutils "^2.8.0" + entities "^3.0.1" + +iconv-lite@^0.4.4: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +image-size@~0.5.0: + version "0.5.5" + resolved "https://registry.yarnpkg.com/image-size/-/image-size-0.5.5.tgz#09dfd4ab9d20e29eb1c3e80b8990378df9e3cb9c" + integrity sha1-Cd/Uq50g4p6xw+gLiZA3jfnjy5w= + +import-fresh@^3.2.1: + version "3.3.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +inline-style-parser@0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/inline-style-parser/-/inline-style-parser-0.1.1.tgz#ec8a3b429274e9c0a1f1c4ffa9453a7fef72cea1" + integrity sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q== + +invariant@^2.2.4: + version "2.2.4" + resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" + integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== + dependencies: + loose-envify "^1.0.0" + +is-alphabetical@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-alphabetical/-/is-alphabetical-1.0.4.tgz#9e7d6b94916be22153745d184c298cbf986a686d" + integrity sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg== + +is-alphanumerical@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz#7eb9a2431f855f6b1ef1a78e326df515696c4dbf" + integrity sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A== + dependencies: + is-alphabetical "^1.0.0" + is-decimal "^1.0.0" + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= + +is-buffer@^2.0.0: + version "2.0.5" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.5.tgz#ebc252e400d22ff8d77fa09888821a24a658c191" + integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ== + +is-decimal@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-decimal/-/is-decimal-1.0.4.tgz#65a3a5958a1c5b63a706e1b333d7cd9f630d3fa5" + integrity sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw== + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-hexadecimal@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz#cc35c97588da4bd49a8eedd6bc4082d44dcb23a7" + integrity sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw== + +is-json@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-json/-/is-json-2.0.1.tgz#6be166d144828a131d686891b983df62c39491ff" + integrity sha1-a+Fm0USCihMdaGiRuYPfYsOUkf8= + +is-plain-obj@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" + integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== + +is-what@^3.14.1: + version "3.14.1" + resolved "https://registry.yarnpkg.com/is-what/-/is-what-3.14.1.tgz#e1222f46ddda85dead0fd1c9df131760e77755c1" + integrity sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA== + +is-whitespace-character@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-whitespace-character/-/is-whitespace-character-1.0.4.tgz#0858edd94a95594c7c9dd0b5c174ec6e45ee4aa7" + integrity sha512-SDweEzfIZM0SJV0EUga669UTKlmL0Pq8Lno0QDQsPnvECB3IM2aP0gdx5TrU0A01MAPfViaZiI2V1QMZLaKK5w== + +is-word-character@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-word-character/-/is-word-character-1.0.4.tgz#ce0e73216f98599060592f62ff31354ddbeb0230" + integrity sha512-5SMO8RVennx3nZrqtKwCGyyetPE9VDba5ugvKLaD4KopPG5kR4mQ7tNt/r7feL5yt5h3lpuBbIUmCOG2eSzXHA== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= + +"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +json-parse-even-better-errors@^2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + +json-source-map@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/json-source-map/-/json-source-map-0.6.1.tgz#e0b1f6f4ce13a9ad57e2ae165a24d06e62c79a0f" + integrity sha512-1QoztHPsMQqhDq0hlXY5ZqcEdUzxQEIxgFkKl4WUp2pgShObl+9ovi4kRh2TfvAfxAoHOJ9vIMEqk3k4iex7tg== + +json5@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3" + integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA== + dependencies: + minimist "^1.2.5" + +less@^4.1.1: + version "4.1.2" + resolved "https://registry.yarnpkg.com/less/-/less-4.1.2.tgz#6099ee584999750c2624b65f80145f8674e4b4b0" + integrity sha512-EoQp/Et7OSOVu0aJknJOtlXZsnr8XE8KwuzTHOLeVSEx8pVWUICc8Q0VYRHgzyjX78nMEyC/oztWFbgyhtNfDA== + dependencies: + copy-anything "^2.0.1" + parse-node-version "^1.0.1" + tslib "^2.3.0" + optionalDependencies: + errno "^0.1.1" + graceful-fs "^4.1.2" + image-size "~0.5.0" + make-dir "^2.1.0" + mime "^1.4.1" + needle "^2.5.2" + source-map "~0.6.0" + +lilconfig@^2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.4.tgz#f4507d043d7058b380b6a8f5cb7bcd4b34cee082" + integrity sha512-bfTIN7lEsiooCocSISTWXkiWJkRqtL9wYtYy+8EK3Y41qh3mpwPU0ycTOgjdY9ErwXCc8QyrQp82bdL0Xkm9yA== + +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + +lmdb@^2.0.2: + version "2.2.1" + resolved "https://registry.yarnpkg.com/lmdb/-/lmdb-2.2.1.tgz#b7fd22ed2268ab74aa71108b793678314a7b94bb" + integrity sha512-tUlIjyJvbd4mqdotI9Xe+3PZt/jqPx70VKFDrKMYu09MtBWOT3y2PbuTajX+bJFDjbgLkQC0cTx2n6dithp/zQ== + dependencies: + msgpackr "^1.5.4" + nan "^2.14.2" + node-gyp-build "^4.2.3" + ordered-binary "^1.2.4" + weak-lru-cache "^1.2.2" + +lodash-es@^4.17.15: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee" + integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw== + +lodash.get@^4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" + integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk= + +lodash.isequal@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" + integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA= + +lodash.memoize@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= + +lodash.uniq@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" + integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= + +lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + dependencies: + js-tokens "^3.0.0 || ^4.0.0" + +make-dir@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" + integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA== + dependencies: + pify "^4.0.1" + semver "^5.6.0" + +markdown-escapes@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/markdown-escapes/-/markdown-escapes-1.0.4.tgz#c95415ef451499d7602b91095f3c8e8975f78535" + integrity sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg== + +mdast-util-definitions@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/mdast-util-definitions/-/mdast-util-definitions-4.0.0.tgz#c5c1a84db799173b4dcf7643cda999e440c24db2" + integrity sha512-k8AJ6aNnUkB7IE+5azR9h81O5EQ/cTDXtWdMq9Kk5KcEW/8ritU5CeLg/9HhOC++nALHBlaogJ5jz0Ybk3kPMQ== + dependencies: + unist-util-visit "^2.0.0" + +mdast-util-to-hast@^10.0.0, mdast-util-to-hast@^10.2.0: + version "10.2.0" + resolved "https://registry.yarnpkg.com/mdast-util-to-hast/-/mdast-util-to-hast-10.2.0.tgz#61875526a017d8857b71abc9333942700b2d3604" + integrity sha512-JoPBfJ3gBnHZ18icCwHR50orC9kNH81tiR1gs01D8Q5YpV6adHNO9nKNuFBCJQ941/32PT1a63UF/DitmS3amQ== + dependencies: + "@types/mdast" "^3.0.0" + "@types/unist" "^2.0.0" + mdast-util-definitions "^4.0.0" + mdurl "^1.0.0" + unist-builder "^2.0.0" + unist-util-generated "^1.0.0" + unist-util-position "^3.0.0" + unist-util-visit "^2.0.0" + +mdn-data@2.0.14: + version "2.0.14" + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50" + integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow== + +mdurl@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" + integrity sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4= + +"memoize-one@>=3.1.1 <6", memoize-one@^5.1.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e" + integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q== + +mime@^1.4.1: + version "1.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== + +minimatch@^3.0.4: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimist@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" + integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== + +ms@^2.1.1: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +msgpackr-extract@^1.0.14: + version "1.0.16" + resolved "https://registry.yarnpkg.com/msgpackr-extract/-/msgpackr-extract-1.0.16.tgz#701c4f6e6f25c100ae84557092274e8fffeefe45" + integrity sha512-fxdRfQUxPrL/TizyfYfMn09dK58e+d65bRD/fcaVH4052vj30QOzzqxcQIS7B0NsqlypEQ/6Du3QmP2DhWFfCA== + dependencies: + nan "^2.14.2" + node-gyp-build "^4.2.3" + +msgpackr@^1.5.1, msgpackr@^1.5.4: + version "1.5.4" + resolved "https://registry.yarnpkg.com/msgpackr/-/msgpackr-1.5.4.tgz#2b6ea6cb7d79c0ad98fc76c68163c48eda50cf0d" + integrity sha512-Z7w5Jg+2Q9z9gJxeM68d7tSuWZZGnFIRhZnyqcZCa/1dKkhOCNvR1TUV3zzJ3+vj78vlwKRzUgVDlW4jiSOeDA== + optionalDependencies: + msgpackr-extract "^1.0.14" + +nan@^2.14.2: + version "2.15.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.15.0.tgz#3f34a473ff18e15c1b5626b62903b5ad6e665fee" + integrity sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ== + +nanoid@^3.2.0: + version "3.3.1" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.1.tgz#6347a18cac88af88f58af0b3594b723d5e99bb35" + integrity sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw== + +needle@^2.5.2: + version "2.9.1" + resolved "https://registry.yarnpkg.com/needle/-/needle-2.9.1.tgz#22d1dffbe3490c2b83e301f7709b6736cd8f2684" + integrity sha512-6R9fqJ5Zcmf+uYaFgdIHmLwNldn5HbK8L5ybn7Uz+ylX/rnOsSp1AHcvQSrCaFN+qNM1wpymHqD7mVasEOlHGQ== + dependencies: + debug "^3.2.6" + iconv-lite "^0.4.4" + sax "^1.2.4" + +node-addon-api@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.2.1.tgz#81325e0a2117789c0128dab65e7e38f07ceba161" + integrity sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A== + +node-emoji@^1.10.0: + version "1.11.0" + resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-1.11.0.tgz#69a0150e6946e2f115e9d7ea4df7971e2628301c" + integrity sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A== + dependencies: + lodash "^4.17.21" + +node-gyp-build@^4.2.3, node-gyp-build@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.3.0.tgz#9f256b03e5826150be39c764bf51e993946d71a3" + integrity sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q== + +node-releases@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.2.tgz#7139fe71e2f4f11b47d4d2986aaf8c48699e0c01" + integrity sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg== + +normalize-url@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a" + integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A== + +nth-check@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.0.1.tgz#2efe162f5c3da06a28959fbd3db75dbeea9f0fc2" + integrity sha512-it1vE95zF6dTT9lBsYbxvqh0Soy4SPowchj0UBGj/V6cTPnXXtQOPUbhZ6CmGzAD/rW22LQK6E96pcdJXk4A4w== + dependencies: + boolbase "^1.0.0" + +nullthrows@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/nullthrows/-/nullthrows-1.1.1.tgz#7818258843856ae971eae4208ad7d7eb19a431b1" + integrity sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw== + +numeral@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/numeral/-/numeral-2.0.6.tgz#4ad080936d443c2561aed9f2197efffe25f4e506" + integrity sha1-StCAk21EPCVhrtnyGX7//iX05QY= + +object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +ordered-binary@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/ordered-binary/-/ordered-binary-1.2.4.tgz#51d3a03af078a0bdba6c7bc8f4fedd1f5d45d83e" + integrity sha512-A/csN0d3n+igxBPfUrjbV5GC69LWj2pjZzAAeeHXLukQ4+fytfP4T1Lg0ju7MSPSwq7KtHkGaiwO8URZN5IpLg== + +parcel@^2.0.0: + version "2.3.2" + resolved "https://registry.yarnpkg.com/parcel/-/parcel-2.3.2.tgz#d1cb475f27edae981edea7a7104e04d3a35a87ca" + integrity sha512-4jhgoBcQaiGKmnmBvNyKyOvZrxCgzgUzdEoVup/fRCOP99hNmvYIN5IErIIJxsU9ObcG/RGCFF8wa4kVRsWfIg== + dependencies: + "@parcel/config-default" "2.3.2" + "@parcel/core" "2.3.2" + "@parcel/diagnostic" "2.3.2" + "@parcel/events" "2.3.2" + "@parcel/fs" "2.3.2" + "@parcel/logger" "2.3.2" + "@parcel/package-manager" "2.3.2" + "@parcel/reporter-cli" "2.3.2" + "@parcel/reporter-dev-server" "2.3.2" + "@parcel/utils" "2.3.2" + chalk "^4.1.0" + commander "^7.0.0" + get-port "^4.2.0" + v8-compile-cache "^2.0.0" + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +parse-entities@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-2.0.0.tgz#53c6eb5b9314a1f4ec99fa0fdf7ce01ecda0cbe8" + integrity sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ== + dependencies: + character-entities "^1.0.0" + character-entities-legacy "^1.0.0" + character-reference-invalid "^1.0.0" + is-alphanumerical "^1.0.0" + is-decimal "^1.0.0" + is-hexadecimal "^1.0.0" + +parse-json@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" + +parse-node-version@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parse-node-version/-/parse-node-version-1.0.1.tgz#e2b5dbede00e7fa9bc363607f53327e8b073189b" + integrity sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA== + +parse5@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" + integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + +picocolors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== + +pify@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" + integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== + +postcss-calc@^8.2.0: + version "8.2.4" + resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-8.2.4.tgz#77b9c29bfcbe8a07ff6693dc87050828889739a5" + integrity sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q== + dependencies: + postcss-selector-parser "^6.0.9" + postcss-value-parser "^4.2.0" + +postcss-colormin@^5.2.5: + version "5.2.5" + resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-5.2.5.tgz#d1fc269ac2ad03fe641d462b5d1dada35c69968a" + integrity sha512-+X30aDaGYq81mFqwyPpnYInsZQnNpdxMX0ajlY7AExCexEFkPVV+KrO7kXwayqEWL2xwEbNQ4nUO0ZsRWGnevg== + dependencies: + browserslist "^4.16.6" + caniuse-api "^3.0.0" + colord "^2.9.1" + postcss-value-parser "^4.2.0" + +postcss-convert-values@^5.0.4: + version "5.0.4" + resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-5.0.4.tgz#3e74dd97c581f475ae7b4500bc0a7c4fb3a6b1b6" + integrity sha512-bugzSAyjIexdObovsPZu/sBCTHccImJxLyFgeV0MmNBm/Lw5h5XnjfML6gzEmJ3A6nyfCW7hb1JXzcsA4Zfbdw== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-discard-comments@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-5.0.3.tgz#011acb63418d600fdbe18804e1bbecb543ad2f87" + integrity sha512-6W5BemziRoqIdAKT+1QjM4bNcJAQ7z7zk073730NHg4cUXh3/rQHHj7pmYxUB9aGhuRhBiUf0pXvIHkRwhQP0Q== + +postcss-discard-duplicates@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-5.0.3.tgz#10f202a4cfe9d407b73dfea7a477054d21ea0c1f" + integrity sha512-vPtm1Mf+kp7iAENTG7jI1MN1lk+fBqL5y+qxyi4v3H+lzsXEdfS3dwUZD45KVhgzDEgduur8ycB4hMegyMTeRw== + +postcss-discard-empty@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-5.0.3.tgz#ec185af4a3710b88933b0ff751aa157b6041dd6a" + integrity sha512-xGJugpaXKakwKI7sSdZjUuN4V3zSzb2Y0LOlmTajFbNinEjTfVs9PFW2lmKBaC/E64WwYppfqLD03P8l9BuueA== + +postcss-discard-overridden@^5.0.4: + version "5.0.4" + resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-5.0.4.tgz#cc999d6caf18ea16eff8b2b58f48ec3ddee35c9c" + integrity sha512-3j9QH0Qh1KkdxwiZOW82cId7zdwXVQv/gRXYDnwx5pBtR1sTkU4cXRK9lp5dSdiM0r0OICO/L8J6sV1/7m0kHg== + +postcss-merge-longhand@^5.0.6: + version "5.0.6" + resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-5.0.6.tgz#090e60d5d3b3caad899f8774f8dccb33217d2166" + integrity sha512-rkmoPwQO6ymJSmWsX6l2hHeEBQa7C4kJb9jyi5fZB1sE8nSCv7sqchoYPixRwX/yvLoZP2y6FA5kcjiByeJqDg== + dependencies: + postcss-value-parser "^4.2.0" + stylehacks "^5.0.3" + +postcss-merge-rules@^5.0.6: + version "5.0.6" + resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-5.0.6.tgz#26b37411fe1e80202fcef61cab027265b8925f2b" + integrity sha512-nzJWJ9yXWp8AOEpn/HFAW72WKVGD2bsLiAmgw4hDchSij27bt6TF+sIK0cJUBAYT3SGcjtGGsOR89bwkkMuMgQ== + dependencies: + browserslist "^4.16.6" + caniuse-api "^3.0.0" + cssnano-utils "^3.0.2" + postcss-selector-parser "^6.0.5" + +postcss-minify-font-values@^5.0.4: + version "5.0.4" + resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-5.0.4.tgz#627d824406b0712243221891f40a44fffe1467fd" + integrity sha512-RN6q3tyuEesvyCYYFCRGJ41J1XFvgV+dvYGHr0CeHv8F00yILlN8Slf4t8XW4IghlfZYCeyRrANO6HpJ948ieA== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-minify-gradients@^5.0.6: + version "5.0.6" + resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-5.0.6.tgz#b07cef51a93f075e94053fd972ff1cba2eaf6503" + integrity sha512-E/dT6oVxB9nLGUTiY/rG5dX9taugv9cbLNTFad3dKxOO+BQg25Q/xo2z2ddG+ZB1CbkZYaVwx5blY8VC7R/43A== + dependencies: + colord "^2.9.1" + cssnano-utils "^3.0.2" + postcss-value-parser "^4.2.0" + +postcss-minify-params@^5.0.5: + version "5.0.5" + resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-5.0.5.tgz#86cb624358cd45c21946f8c317893f0449396646" + integrity sha512-YBNuq3Rz5LfLFNHb9wrvm6t859b8qIqfXsWeK7wROm3jSKNpO1Y5e8cOyBv6Acji15TgSrAwb3JkVNCqNyLvBg== + dependencies: + browserslist "^4.16.6" + cssnano-utils "^3.0.2" + postcss-value-parser "^4.2.0" + +postcss-minify-selectors@^5.1.3: + version "5.1.3" + resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-5.1.3.tgz#6ac12d52aa661fd509469d87ab2cebb0a1e3a1b5" + integrity sha512-9RJfTiQEKA/kZhMaEXND893nBqmYQ8qYa/G+uPdVnXF6D/FzpfI6kwBtWEcHx5FqDbA79O9n6fQJfrIj6M8jvQ== + dependencies: + postcss-selector-parser "^6.0.5" + +postcss-normalize-charset@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-5.0.3.tgz#719fb9f9ca9835fcbd4fed8d6e0d72a79e7b5472" + integrity sha512-iKEplDBco9EfH7sx4ut7R2r/dwTnUqyfACf62Unc9UiyFuI7uUqZZtY+u+qp7g8Qszl/U28HIfcsI3pEABWFfA== + +postcss-normalize-display-values@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/postcss-normalize-display-values/-/postcss-normalize-display-values-5.0.3.tgz#94cc82e20c51cc4ffba6b36e9618adc1e50db8c1" + integrity sha512-FIV5FY/qs4Ja32jiDb5mVj5iWBlS3N8tFcw2yg98+8MkRgyhtnBgSC0lxU+16AMHbjX5fbSJgw5AXLMolonuRQ== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-normalize-positions@^5.0.4: + version "5.0.4" + resolved "https://registry.yarnpkg.com/postcss-normalize-positions/-/postcss-normalize-positions-5.0.4.tgz#4001f38c99675437b83277836fb4291887fcc6cc" + integrity sha512-qynirjBX0Lc73ROomZE3lzzmXXTu48/QiEzKgMeqh28+MfuHLsuqC9po4kj84igZqqFGovz8F8hf44hA3dPYmQ== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-normalize-repeat-style@^5.0.4: + version "5.0.4" + resolved "https://registry.yarnpkg.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.0.4.tgz#d005adf9ee45fae78b673031a376c0c871315145" + integrity sha512-Innt+wctD7YpfeDR7r5Ik6krdyppyAg2HBRpX88fo5AYzC1Ut/l3xaxACG0KsbX49cO2n5EB13clPwuYVt8cMA== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-normalize-string@^5.0.4: + version "5.0.4" + resolved "https://registry.yarnpkg.com/postcss-normalize-string/-/postcss-normalize-string-5.0.4.tgz#b5e00a07597e7aa8a871817bfeac2bfaa59c3333" + integrity sha512-Dfk42l0+A1CDnVpgE606ENvdmksttLynEqTQf5FL3XGQOyqxjbo25+pglCUvziicTxjtI2NLUR6KkxyUWEVubQ== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-normalize-timing-functions@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.0.3.tgz#47210227bfcba5e52650d7a18654337090de7072" + integrity sha512-QRfjvFh11moN4PYnJ7hia4uJXeFotyK3t2jjg8lM9mswleGsNw2Lm3I5wO+l4k1FzK96EFwEVn8X8Ojrp2gP4g== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-normalize-unicode@^5.0.4: + version "5.0.4" + resolved "https://registry.yarnpkg.com/postcss-normalize-unicode/-/postcss-normalize-unicode-5.0.4.tgz#02866096937005cdb2c17116c690f29505a1623d" + integrity sha512-W79Regn+a+eXTzB+oV/8XJ33s3pDyFTND2yDuUCo0Xa3QSy1HtNIfRVPXNubHxjhlqmMFADr3FSCHT84ITW3ig== + dependencies: + browserslist "^4.16.6" + postcss-value-parser "^4.2.0" + +postcss-normalize-url@^5.0.5: + version "5.0.5" + resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-5.0.5.tgz#c39efc12ff119f6f45f0b4f516902b12c8080e3a" + integrity sha512-Ws3tX+PcekYlXh+ycAt0wyzqGthkvVtZ9SZLutMVvHARxcpu4o7vvXcNoiNKyjKuWecnjS6HDI3fjBuDr5MQxQ== + dependencies: + normalize-url "^6.0.1" + postcss-value-parser "^4.2.0" + +postcss-normalize-whitespace@^5.0.4: + version "5.0.4" + resolved "https://registry.yarnpkg.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.0.4.tgz#1d477e7da23fecef91fc4e37d462272c7b55c5ca" + integrity sha512-wsnuHolYZjMwWZJoTC9jeI2AcjA67v4UuidDrPN9RnX8KIZfE+r2Nd6XZRwHVwUiHmRvKQtxiqo64K+h8/imaw== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-ordered-values@^5.0.5: + version "5.0.5" + resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-5.0.5.tgz#e878af822a130c3f3709737e24cb815ca7c6d040" + integrity sha512-mfY7lXpq+8bDEHfP+muqibDPhZ5eP9zgBEF9XRvoQgXcQe2Db3G1wcvjbnfjXG6wYsl+0UIjikqq4ym1V2jGMQ== + dependencies: + cssnano-utils "^3.0.2" + postcss-value-parser "^4.2.0" + +postcss-reduce-initial@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-5.0.3.tgz#68891594defd648253703bbd8f1093162f19568d" + integrity sha512-c88TkSnQ/Dnwgb4OZbKPOBbCaauwEjbECP5uAuFPOzQ+XdjNjRH7SG0dteXrpp1LlIFEKK76iUGgmw2V0xeieA== + dependencies: + browserslist "^4.16.6" + caniuse-api "^3.0.0" + +postcss-reduce-transforms@^5.0.4: + version "5.0.4" + resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-5.0.4.tgz#717e72d30befe857f7d2784dba10eb1157863712" + integrity sha512-VIJB9SFSaL8B/B7AXb7KHL6/GNNbbCHslgdzS9UDfBZYIA2nx8NLY7iD/BXFSO/1sRUILzBTfHCoW5inP37C5g== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-selector-parser@^6.0.4, postcss-selector-parser@^6.0.5, postcss-selector-parser@^6.0.9: + version "6.0.9" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.9.tgz#ee71c3b9ff63d9cd130838876c13a2ec1a992b2f" + integrity sha512-UO3SgnZOVTwu4kyLR22UQ1xZh086RyNZppb7lLAKBFK8a32ttG5i87Y/P3+2bRSjZNyJ1B7hfFNo273tKe9YxQ== + dependencies: + cssesc "^3.0.0" + util-deprecate "^1.0.2" + +postcss-svgo@^5.0.4: + version "5.0.4" + resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-5.0.4.tgz#cfa8682f47b88f7cd75108ec499e133b43102abf" + integrity sha512-yDKHvULbnZtIrRqhZoA+rxreWpee28JSRH/gy9727u0UCgtpv1M/9WEWY3xySlFa0zQJcqf6oCBJPR5NwkmYpg== + dependencies: + postcss-value-parser "^4.2.0" + svgo "^2.7.0" + +postcss-unique-selectors@^5.0.4: + version "5.0.4" + resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-5.0.4.tgz#08e188126b634ddfa615fb1d6c262bafdd64826e" + integrity sha512-5ampwoSDJCxDPoANBIlMgoBcYUHnhaiuLYJR5pj1DLnYQvMRVyFuTA5C3Bvt+aHtiqWpJkD/lXT50Vo1D0ZsAQ== + dependencies: + postcss-selector-parser "^6.0.5" + +postcss-value-parser@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" + integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== + +postcss@^8.4.5: + version "8.4.6" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.6.tgz#c5ff3c3c457a23864f32cb45ac9b741498a09ae1" + integrity sha512-OovjwIzs9Te46vlEx7+uXB0PLijpwjXGKXjVGGPIGubGpq7uh5Xgf6D6FiJ/SzJMBosHDp6a2hiXOS97iBXcaA== + dependencies: + nanoid "^3.2.0" + picocolors "^1.0.0" + source-map-js "^1.0.2" + +posthtml-parser@^0.10.0, posthtml-parser@^0.10.1: + version "0.10.2" + resolved "https://registry.yarnpkg.com/posthtml-parser/-/posthtml-parser-0.10.2.tgz#df364d7b179f2a6bf0466b56be7b98fd4e97c573" + integrity sha512-PId6zZ/2lyJi9LiKfe+i2xv57oEjJgWbsHGGANwos5AvdQp98i6AtamAl8gzSVFGfQ43Glb5D614cvZf012VKg== + dependencies: + htmlparser2 "^7.1.1" + +posthtml-render@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/posthtml-render/-/posthtml-render-3.0.0.tgz#97be44931496f495b4f07b99e903cc70ad6a3205" + integrity sha512-z+16RoxK3fUPgwaIgH9NGnK1HKY9XIDpydky5eQGgAFVXTCSezalv9U2jQuNV+Z9qV1fDWNzldcw4eK0SSbqKA== + dependencies: + is-json "^2.0.1" + +posthtml@^0.16.4, posthtml@^0.16.5: + version "0.16.5" + resolved "https://registry.yarnpkg.com/posthtml/-/posthtml-0.16.5.tgz#d32f5cf32436516d49e0884b2367d0a1424136f6" + integrity sha512-1qOuPsywVlvymhTFIBniDXwUDwvlDri5KUQuBqjmCc8Jj4b/HDSVWU//P6rTWke5rzrk+vj7mms2w8e1vD0nnw== + dependencies: + posthtml-parser "^0.10.0" + posthtml-render "^3.0.0" + +prismjs@~1.25.0: + version "1.25.0" + resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.25.0.tgz#6f822df1bdad965734b310b315a23315cf999756" + integrity sha512-WCjJHl1KEWbnkQom1+SzftbtXMKQoezOCYs5rECqMN+jP+apI7ftoflyqigqzopSO3hMhTEb0mFClA8lkolgEg== + +prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1: + version "15.8.1" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" + integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== + dependencies: + loose-envify "^1.4.0" + object-assign "^4.1.1" + react-is "^16.13.1" + +property-information@^5.0.0, property-information@^5.3.0: + version "5.6.0" + resolved "https://registry.yarnpkg.com/property-information/-/property-information-5.6.0.tgz#61675545fb23002f245c6540ec46077d4da3ed69" + integrity sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA== + dependencies: + xtend "^4.0.0" + +prr@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" + integrity sha1-0/wRS6BplaRexok/SEzrHXj19HY= + +querystringify@^2.1.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" + integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ== + +raf-schd@^4.0.2: + version "4.0.3" + resolved "https://registry.yarnpkg.com/raf-schd/-/raf-schd-4.0.3.tgz#5d6c34ef46f8b2a0e880a8fcdb743efc5bfdbc1a" + integrity sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ== + +react-ace@^7.0.5: + version "7.0.5" + resolved "https://registry.yarnpkg.com/react-ace/-/react-ace-7.0.5.tgz#798299fd52ddf3a3dcc92afc5865538463544f01" + integrity sha512-3iI+Rg2bZXCn9K984ll2OF4u9SGcJH96Q1KsUgs9v4M2WePS4YeEHfW2nrxuqJrAkE5kZbxaCE79k6kqK0YBjg== + dependencies: + brace "^0.11.1" + diff-match-patch "^1.0.4" + lodash.get "^4.4.2" + lodash.isequal "^4.5.0" + prop-types "^15.7.2" + +react-beautiful-dnd@^13.0.0: + version "13.1.0" + resolved "https://registry.yarnpkg.com/react-beautiful-dnd/-/react-beautiful-dnd-13.1.0.tgz#ec97c81093593526454b0de69852ae433783844d" + integrity sha512-aGvblPZTJowOWUNiwd6tNfEpgkX5OxmpqxHKNW/4VmvZTNTbeiq7bA3bn5T+QSF2uibXB0D1DmJsb1aC/+3cUA== + dependencies: + "@babel/runtime" "^7.9.2" + css-box-model "^1.2.0" + memoize-one "^5.1.1" + raf-schd "^4.0.2" + react-redux "^7.2.0" + redux "^4.0.4" + use-memo-one "^1.1.1" + +react-clientside-effect@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/react-clientside-effect/-/react-clientside-effect-1.2.5.tgz#e2c4dc3c9ee109f642fac4f5b6e9bf5bcd2219a3" + integrity sha512-2bL8qFW1TGBHozGGbVeyvnggRpMjibeZM2536AKNENLECutp2yfs44IL8Hmpn8qjFQ2K7A9PnYf3vc7aQq/cPA== + dependencies: + "@babel/runtime" "^7.12.13" + +react-dom@^18.2.0: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d" + integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g== + dependencies: + loose-envify "^1.1.0" + scheduler "^0.23.0" + +react-dropzone@^11.2.0: + version "11.5.3" + resolved "https://registry.yarnpkg.com/react-dropzone/-/react-dropzone-11.5.3.tgz#757d4980fcae839896a894e41d3e72df04981f86" + integrity sha512-68+T6sWW5L89qJnn3SD1aRazhuRBhTT9JOI1W8vI5YWsfegM4C7tlGbPH1AgEbmZY5s8E8L0QhX0e3VdAa0KWA== + dependencies: + attr-accept "^2.2.1" + file-selector "^0.2.2" + prop-types "^15.7.2" + +react-focus-lock@^2.6.0: + version "2.7.1" + resolved "https://registry.yarnpkg.com/react-focus-lock/-/react-focus-lock-2.7.1.tgz#a9fbb3fa4efaee32162406e5eb96ae658964193b" + integrity sha512-ImSeVmcrLKNMqzUsIdqOkXwTVltj79OPu43oT8tVun7eIckA4VdM7UmYUFo3H/UC2nRVgagMZGFnAOQEDiDYcA== + dependencies: + "@babel/runtime" "^7.0.0" + focus-lock "^0.10.1" + prop-types "^15.6.2" + react-clientside-effect "^1.2.5" + use-callback-ref "^1.2.5" + use-sidecar "^1.0.5" + +react-focus-on@^3.5.0: + version "3.5.4" + resolved "https://registry.yarnpkg.com/react-focus-on/-/react-focus-on-3.5.4.tgz#be45a9d0495f3bb6f5249704c85362df94980ecf" + integrity sha512-HnU0YGKhNSUsC4k6K8L+2wk8mC/qdg+CsS7A1bWLMgK7UuBphdECs2esnS6cLmBoVNjsFnCm/vMypeezKOdK3A== + dependencies: + aria-hidden "^1.1.3" + react-focus-lock "^2.6.0" + react-remove-scroll "^2.4.1" + react-style-singleton "^2.1.1" + tslib "^2.3.1" + use-callback-ref "^1.2.5" + use-sidecar "^1.0.5" + +react-input-autosize@^2.2.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/react-input-autosize/-/react-input-autosize-2.2.2.tgz#fcaa7020568ec206bc04be36f4eb68e647c4d8c2" + integrity sha512-jQJgYCA3S0j+cuOwzuCd1OjmBmnZLdqQdiLKRYrsMMzbjUrVDS5RvJUDwJqA7sKuksDuzFtm6hZGKFu7Mjk5aw== + dependencies: + prop-types "^15.5.8" + +react-is@^16.13.1, react-is@^16.7.0: + version "16.13.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" + integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== + +react-is@^17.0.2: + version "17.0.2" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" + integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== + +react-is@~16.3.0: + version "16.3.2" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.3.2.tgz#f4d3d0e2f5fbb6ac46450641eb2e25bf05d36b22" + integrity sha512-ybEM7YOr4yBgFd6w8dJqwxegqZGJNBZl6U27HnGKuTZmDvVrD5quWOK/wAnMywiZzW+Qsk+l4X2c70+thp/A8Q== + +react-redux@^7.2.0: + version "7.2.6" + resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.6.tgz#49633a24fe552b5f9caf58feb8a138936ddfe9aa" + integrity sha512-10RPdsz0UUrRL1NZE0ejTkucnclYSgXp5q+tB5SWx2qeG2ZJQJyymgAhwKy73yiL/13btfB6fPr+rgbMAaZIAQ== + dependencies: + "@babel/runtime" "^7.15.4" + "@types/react-redux" "^7.1.20" + hoist-non-react-statics "^3.3.2" + loose-envify "^1.4.0" + prop-types "^15.7.2" + react-is "^17.0.2" + +react-refresh@^0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.9.0.tgz#71863337adc3e5c2f8a6bfddd12ae3bfe32aafbf" + integrity sha512-Gvzk7OZpiqKSkxsQvO/mbTN1poglhmAV7gR/DdIrRrSMXraRQQlfikRJOr3Nb9GTMPC5kof948Zy6jJZIFtDvQ== + +react-remove-scroll-bar@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/react-remove-scroll-bar/-/react-remove-scroll-bar-2.2.0.tgz#d4d545a7df024f75d67e151499a6ab5ac97c8cdd" + integrity sha512-UU9ZBP1wdMR8qoUs7owiVcpaPwsQxUDC2lypP6mmixaGlARZa7ZIBx1jcuObLdhMOvCsnZcvetOho0wzPa9PYg== + dependencies: + react-style-singleton "^2.1.0" + tslib "^1.0.0" + +react-remove-scroll@^2.4.1: + version "2.4.3" + resolved "https://registry.yarnpkg.com/react-remove-scroll/-/react-remove-scroll-2.4.3.tgz#83d19b02503b04bd8141ed6e0b9e6691a2e935a6" + integrity sha512-lGWYXfV6jykJwbFpsuPdexKKzp96f3RbvGapDSIdcyGvHb7/eqyn46C7/6h+rUzYar1j5mdU+XECITHXCKBk9Q== + dependencies: + react-remove-scroll-bar "^2.1.0" + react-style-singleton "^2.1.0" + tslib "^1.0.0" + use-callback-ref "^1.2.3" + use-sidecar "^1.0.1" + +react-style-singleton@^2.1.0, react-style-singleton@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/react-style-singleton/-/react-style-singleton-2.1.1.tgz#ce7f90b67618be2b6b94902a30aaea152ce52e66" + integrity sha512-jNRp07Jza6CBqdRKNgGhT3u9umWvils1xsuMOjZlghBDH2MU0PL2WZor4PGYjXpnRCa9DQSlHMs/xnABWOwYbA== + dependencies: + get-nonce "^1.0.0" + invariant "^2.2.4" + tslib "^1.0.0" + +react-virtualized-auto-sizer@^1.0.2: + version "1.0.6" + resolved "https://registry.yarnpkg.com/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.6.tgz#66c5b1c9278064c5ef1699ed40a29c11518f97ca" + integrity sha512-7tQ0BmZqfVF6YYEWcIGuoR3OdYe8I/ZFbNclFlGOC3pMqunkYF/oL30NCjSGl9sMEb17AnzixDz98Kqc3N76HQ== + +react-window@^1.8.5: + version "1.8.6" + resolved "https://registry.yarnpkg.com/react-window/-/react-window-1.8.6.tgz#d011950ac643a994118632665aad0c6382e2a112" + integrity sha512-8VwEEYyjz6DCnGBsd+MgkD0KJ2/OXFULyDtorIiTz+QzwoP94tBoA7CnbtyXMm+cCeAUER5KJcPtWl9cpKbOBg== + dependencies: + "@babel/runtime" "^7.0.0" + memoize-one ">=3.1.1 <6" + +react@^18.2.0: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5" + integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ== + dependencies: + loose-envify "^1.1.0" + +redisinsight-plugin-sdk@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/redisinsight-plugin-sdk/-/redisinsight-plugin-sdk-1.0.0.tgz#1519cfcf41a0ede8b0f3e491bc5560e33e2df040" + integrity sha512-myF7hrUPx31kWdr8EcXCTR5925ZC68Sfu5WSEqFWXH8CoDqgILkx8AQVg8P5Q0WGfRllgSY0SH2zhC4TD9me4w== + +redux@^4.0.0, redux@^4.0.4: + version "4.1.2" + resolved "https://registry.yarnpkg.com/redux/-/redux-4.1.2.tgz#140f35426d99bb4729af760afcf79eaaac407104" + integrity sha512-SH8PglcebESbd/shgf6mii6EIoRM0zrQyjcuQ+ojmfxjTtE0z9Y8pa62iA/OJ58qjP6j27uyW4kUF4jl/jd6sw== + dependencies: + "@babel/runtime" "^7.9.2" + +refractor@^3.4.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/refractor/-/refractor-3.5.0.tgz#334586f352dda4beaf354099b48c2d18e0819aec" + integrity sha512-QwPJd3ferTZ4cSPPjdP5bsYHMytwWYnAN5EEnLtGvkqp/FCCnGsBgxrm9EuIDnjUC3Uc/kETtvVi7fSIVC74Dg== + dependencies: + hastscript "^6.0.0" + parse-entities "^2.0.0" + prismjs "~1.25.0" + +regenerator-runtime@^0.13.4, regenerator-runtime@^0.13.7: + version "0.13.9" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52" + integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA== + +rehype-raw@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/rehype-raw/-/rehype-raw-5.1.0.tgz#66d5e8d7188ada2d31bc137bc19a1000cf2c6b7e" + integrity sha512-MDvHAb/5mUnif2R+0IPCYJU8WjHa9UzGtM/F4AVy5GixPlDZ1z3HacYy4xojDU+uBa+0X/3PIfyQI26/2ljJNA== + dependencies: + hast-util-raw "^6.1.0" + +rehype-react@^6.0.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/rehype-react/-/rehype-react-6.2.1.tgz#9b9bf188451ad6f63796b784fe1f51165c67b73a" + integrity sha512-f9KIrjktvLvmbGc7si25HepocOg4z0MuNOtweigKzBcDjiGSTGhyz6VSgaV5K421Cq1O+z4/oxRJ5G9owo0KVg== + dependencies: + "@mapbox/hast-util-table-cell-style" "^0.2.0" + hast-to-hyperscript "^9.0.0" + +rehype-stringify@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/rehype-stringify/-/rehype-stringify-8.0.0.tgz#9b6afb599bcf3165f10f93fc8548f9a03d2ec2ba" + integrity sha512-VkIs18G0pj2xklyllrPSvdShAV36Ff3yE5PUO9u36f6+2qJFnn22Z5gKwBOwgXviux4UC7K+/j13AnZfPICi/g== + dependencies: + hast-util-to-html "^7.1.1" + +remark-emoji@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/remark-emoji/-/remark-emoji-2.2.0.tgz#1c702090a1525da5b80e15a8f963ef2c8236cac7" + integrity sha512-P3cj9s5ggsUvWw5fS2uzCHJMGuXYRb0NnZqYlNecewXt8QBU9n5vW3DUUKOhepS8F9CwdMx9B8a3i7pqFWAI5w== + dependencies: + emoticon "^3.2.0" + node-emoji "^1.10.0" + unist-util-visit "^2.0.3" + +remark-parse@^8.0.3: + version "8.0.3" + resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-8.0.3.tgz#9c62aa3b35b79a486454c690472906075f40c7e1" + integrity sha512-E1K9+QLGgggHxCQtLt++uXltxEprmWzNfg+MxpfHsZlrddKzZ/hZyWHDbK3/Ap8HJQqYJRXP+jHczdL6q6i85Q== + dependencies: + ccount "^1.0.0" + collapse-white-space "^1.0.2" + is-alphabetical "^1.0.0" + is-decimal "^1.0.0" + is-whitespace-character "^1.0.0" + is-word-character "^1.0.0" + markdown-escapes "^1.0.0" + parse-entities "^2.0.0" + repeat-string "^1.5.4" + state-toggle "^1.0.0" + trim "0.0.1" + trim-trailing-lines "^1.0.0" + unherit "^1.0.4" + unist-util-remove-position "^2.0.0" + vfile-location "^3.0.0" + xtend "^4.0.1" + +remark-rehype@^8.0.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/remark-rehype/-/remark-rehype-8.1.0.tgz#610509a043484c1e697437fa5eb3fd992617c945" + integrity sha512-EbCu9kHgAxKmW1yEYjx3QafMyGY3q8noUbNUI5xyKbaFP89wbhDrKxyIQNukNYthzjNHZu6J7hwFg7hRm1svYA== + dependencies: + mdast-util-to-hast "^10.2.0" + +repeat-string@^1.5.4: + version "1.6.1" + resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" + integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= + +requires-port@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" + integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8= + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +rxjs@^6.6.3: + version "6.6.7" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9" + integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ== + dependencies: + tslib "^1.9.0" + +safe-buffer@^5.0.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +"safer-buffer@>= 2.1.2 < 3": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +sax@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" + integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== + +scheduler@^0.23.0: + version "0.23.0" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.0.tgz#ba8041afc3d30eb206a487b6b384002e4e61fdfe" + integrity sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw== + dependencies: + loose-envify "^1.1.0" + +semver@^5.6.0, semver@^5.7.0, semver@^5.7.1: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +source-map-js@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" + integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== + +source-map-support@~0.5.20: + version "0.5.21" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" + integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +source-map@~0.7.2: + version "0.7.3" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" + integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== + +space-separated-tokens@^1.0.0: + version "1.1.5" + resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz#85f32c3d10d9682007e917414ddc5c26d1aa6899" + integrity sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA== + +spawn-command@^0.0.2-1: + version "0.0.2-1" + resolved "https://registry.yarnpkg.com/spawn-command/-/spawn-command-0.0.2-1.tgz#62f5e9466981c1b796dc5929937e11c9c6921bd0" + integrity sha1-YvXpRmmBwbeW3Fkpk34RycaSG9A= + +stable@^0.1.8: + version "0.1.8" + resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" + integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== + +state-toggle@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/state-toggle/-/state-toggle-1.0.3.tgz#e123b16a88e143139b09c6852221bc9815917dfe" + integrity sha512-d/5Z4/2iiCnHw6Xzghyhb+GcmF89bxwgXG60wjIiZaxnymbyOmI8Hk4VqHXiVVp6u2ysaskFfXg3ekCj4WNftQ== + +string-width@^4.1.0, string-width@^4.2.0: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +stringify-entities@^3.0.1: + version "3.1.0" + resolved "https://registry.yarnpkg.com/stringify-entities/-/stringify-entities-3.1.0.tgz#b8d3feac256d9ffcc9fa1fefdcf3ca70576ee903" + integrity sha512-3FP+jGMmMV/ffZs86MoghGqAoqXAdxLrJP4GUdrDN1aIScYih5tuIO3eF4To5AJZ79KDZ8Fpdy7QJnK8SsL1Vg== + dependencies: + character-entities-html4 "^1.0.0" + character-entities-legacy "^1.0.0" + xtend "^4.0.0" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +style-to-object@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/style-to-object/-/style-to-object-0.3.0.tgz#b1b790d205991cc783801967214979ee19a76e46" + integrity sha512-CzFnRRXhzWIdItT3OmF8SQfWyahHhjq3HwcMNCNLn+N7klOOqPjMeG/4JSu77D7ypZdGvSzvkrbyeTMizz2VrA== + dependencies: + inline-style-parser "0.1.1" + +stylehacks@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-5.0.3.tgz#2ef3de567bfa2be716d29a93bf3d208c133e8d04" + integrity sha512-ENcUdpf4yO0E1rubu8rkxI+JGQk4CgjchynZ4bDBJDfqdy+uhTRSWb8/F3Jtu+Bw5MW45Po3/aQGeIyyxgQtxg== + dependencies: + browserslist "^4.16.6" + postcss-selector-parser "^6.0.4" + +stylis@4.0.13: + version "4.0.13" + resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.0.13.tgz#f5db332e376d13cc84ecfe5dace9a2a51d954c91" + integrity sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag== + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-color@^8.1.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + +svgo@^2.4.0, svgo@^2.7.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/svgo/-/svgo-2.8.0.tgz#4ff80cce6710dc2795f0c7c74101e6764cfccd24" + integrity sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg== + dependencies: + "@trysound/sax" "0.2.0" + commander "^7.2.0" + css-select "^4.1.3" + css-tree "^1.1.3" + csso "^4.2.0" + picocolors "^1.0.0" + stable "^0.1.8" + +tabbable@^3.0.0: + version "3.1.2" + resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-3.1.2.tgz#f2d16cccd01f400e38635c7181adfe0ad965a4a2" + integrity sha512-wjB6puVXTYO0BSFtCmWQubA/KIn7Xvajw0x0l6eJUudMG/EAiJvIUnyNX6xO4NpGrJ16lbD0eUseB9WxW0vlpQ== + +terser@^5.2.0, terser@^5.9.0: + version "5.10.0" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.10.0.tgz#b86390809c0389105eb0a0b62397563096ddafcc" + integrity sha512-AMmF99DMfEDiRJfxfY5jj5wNH/bYO09cniSqhfoyxc8sFoYIgkJy86G04UoZU5VjlpnplVu0K6Tx6E9b5+DlHA== + dependencies: + commander "^2.20.0" + source-map "~0.7.2" + source-map-support "~0.5.20" + +text-diff@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/text-diff/-/text-diff-1.0.1.tgz#6c105905435e337857375c9d2f6ca63e453ff565" + integrity sha1-bBBZBUNeM3hXN1ydL2ymPkU/9WU= + +timsort@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4" + integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q= + +tiny-invariant@^1.0.6: + version "1.2.0" + resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.2.0.tgz#a1141f86b672a9148c72e978a19a73b9b94a15a9" + integrity sha512-1Uhn/aqw5C6RI4KejVeTg6mIS7IqxnLJ8Mv2tV5rTc0qWobay7pDUz6Wi392Cnc8ak1H0F2cjoRzb2/AW4+Fvg== + +tree-kill@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" + integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== + +trim-trailing-lines@^1.0.0: + version "1.1.4" + resolved "https://registry.yarnpkg.com/trim-trailing-lines/-/trim-trailing-lines-1.1.4.tgz#bd4abbec7cc880462f10b2c8b5ce1d8d1ec7c2c0" + integrity sha512-rjUWSqnfTNrjbB9NQWfPMH/xRK1deHeGsHoVfpxJ++XeYXE0d6B1En37AHfw3jtfTU7dzMzZL2jjpe8Qb5gLIQ== + +trim@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/trim/-/trim-0.0.1.tgz#5858547f6b290757ee95cccc666fb50084c460dd" + integrity sha1-WFhUf2spB1fulczMZm+1AITEYN0= + +trough@^1.0.0: + version "1.0.5" + resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.5.tgz#b8b639cefad7d0bb2abd37d433ff8293efa5f406" + integrity sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA== + +tslib@^1.0.0, tslib@^1.9.0, tslib@^1.9.3: + version "1.14.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" + integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== + +tslib@^2.0.3, tslib@^2.3.0, tslib@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" + integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== + +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== + +unherit@^1.0.4: + version "1.1.3" + resolved "https://registry.yarnpkg.com/unherit/-/unherit-1.1.3.tgz#6c9b503f2b41b262330c80e91c8614abdaa69c22" + integrity sha512-Ft16BJcnapDKp0+J/rqFC3Rrk6Y/Ng4nzsC028k2jdDII/rdZ7Wd3pPT/6+vIIxRagwRc9K0IUX0Ra4fKvw+WQ== + dependencies: + inherits "^2.0.0" + xtend "^4.0.0" + +unified@^9.2.0: + version "9.2.2" + resolved "https://registry.yarnpkg.com/unified/-/unified-9.2.2.tgz#67649a1abfc3ab85d2969502902775eb03146975" + integrity sha512-Sg7j110mtefBD+qunSLO1lqOEKdrwBFBrR6Qd8f4uwkhWNlbkaqwHse6e7QvD3AP/MNoJdEDLaf8OxYyoWgorQ== + dependencies: + bail "^1.0.0" + extend "^3.0.0" + is-buffer "^2.0.0" + is-plain-obj "^2.0.0" + trough "^1.0.0" + vfile "^4.0.0" + +unist-builder@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/unist-builder/-/unist-builder-2.0.3.tgz#77648711b5d86af0942f334397a33c5e91516436" + integrity sha512-f98yt5pnlMWlzP539tPc4grGMsFaQQlP/vM396b00jngsiINumNmsY8rkXjfoi1c6QaM8nQ3vaGDuoKWbe/1Uw== + +unist-util-generated@^1.0.0: + version "1.1.6" + resolved "https://registry.yarnpkg.com/unist-util-generated/-/unist-util-generated-1.1.6.tgz#5ab51f689e2992a472beb1b35f2ce7ff2f324d4b" + integrity sha512-cln2Mm1/CZzN5ttGK7vkoGw+RZ8VcUH6BtGbq98DDtRGquAAOXig1mrBQYelOwMXYS8rK+vZDyyojSjp7JX+Lg== + +unist-util-is@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-3.0.0.tgz#d9e84381c2468e82629e4a5be9d7d05a2dd324cd" + integrity sha512-sVZZX3+kspVNmLWBPAB6r+7D9ZgAFPNWm66f7YNb420RlQSbn+n8rG8dGZSkrER7ZIXGQYNm5pqC3v3HopH24A== + +unist-util-is@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-4.1.0.tgz#976e5f462a7a5de73d94b706bac1b90671b57797" + integrity sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg== + +unist-util-position@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/unist-util-position/-/unist-util-position-3.1.0.tgz#1c42ee6301f8d52f47d14f62bbdb796571fa2d47" + integrity sha512-w+PkwCbYSFw8vpgWD0v7zRCl1FpY3fjDSQ3/N/wNd9Ffa4gPi8+4keqt99N3XW6F99t/mUzp2xAhNmfKWp95QA== + +unist-util-remove-position@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/unist-util-remove-position/-/unist-util-remove-position-2.0.1.tgz#5d19ca79fdba712301999b2b73553ca8f3b352cc" + integrity sha512-fDZsLYIe2uT+oGFnuZmy73K6ZxOPG/Qcm+w7jbEjaFcJgbQ6cqjs/eSPzXhsmGpAsWPkqZM9pYjww5QTn3LHMA== + dependencies: + unist-util-visit "^2.0.0" + +unist-util-stringify-position@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz#cce3bfa1cdf85ba7375d1d5b17bdc4cada9bd9da" + integrity sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g== + dependencies: + "@types/unist" "^2.0.2" + +unist-util-stringify-position@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-3.0.0.tgz#d517d2883d74d0daa0b565adc3d10a02b4a8cde9" + integrity sha512-SdfAl8fsDclywZpfMDTVDxA2V7LjtRDTOFd44wUJamgl6OlVngsqWjxvermMYf60elWHbxhuRCZml7AnuXCaSA== + dependencies: + "@types/unist" "^2.0.0" + +unist-util-visit-parents@^2.0.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-2.1.2.tgz#25e43e55312166f3348cae6743588781d112c1e9" + integrity sha512-DyN5vD4NE3aSeB+PXYNKxzGsfocxp6asDc2XXE3b0ekO2BaRUpBicbbUygfSvYfUz1IkmjFR1YF7dPklraMZ2g== + dependencies: + unist-util-is "^3.0.0" + +unist-util-visit-parents@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-3.1.1.tgz#65a6ce698f78a6b0f56aa0e88f13801886cdaef6" + integrity sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg== + dependencies: + "@types/unist" "^2.0.0" + unist-util-is "^4.0.0" + +unist-util-visit@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-1.4.1.tgz#4724aaa8486e6ee6e26d7ff3c8685960d560b1e3" + integrity sha512-AvGNk7Bb//EmJZyhtRUnNMEpId/AZ5Ph/KUpTI09WHQuDZHKovQ1oEv3mfmKpWKtoMzyMC4GLBm1Zy5k12fjIw== + dependencies: + unist-util-visit-parents "^2.0.0" + +unist-util-visit@^2.0.0, unist-util-visit@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-2.0.3.tgz#c3703893146df47203bb8a9795af47d7b971208c" + integrity sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q== + dependencies: + "@types/unist" "^2.0.0" + unist-util-is "^4.0.0" + unist-util-visit-parents "^3.0.0" + +url-parse@^1.5.0: + version "1.5.10" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1" + integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ== + dependencies: + querystringify "^2.1.1" + requires-port "^1.0.0" + +use-callback-ref@^1.2.3, use-callback-ref@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/use-callback-ref/-/use-callback-ref-1.2.5.tgz#6115ed242cfbaed5915499c0a9842ca2912f38a5" + integrity sha512-gN3vgMISAgacF7sqsLPByqoePooY3n2emTH59Ur5d/M8eg4WTWu1xp8i8DHjohftIyEx0S08RiYxbffr4j8Peg== + +use-memo-one@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/use-memo-one/-/use-memo-one-1.1.2.tgz#0c8203a329f76e040047a35a1197defe342fab20" + integrity sha512-u2qFKtxLsia/r8qG0ZKkbytbztzRb317XCkT7yP8wxL0tZ/CzK2G+WWie5vWvpyeP7+YoPIwbJoIHJ4Ba4k0oQ== + +use-sidecar@^1.0.1, use-sidecar@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/use-sidecar/-/use-sidecar-1.0.5.tgz#ffff2a17c1df42e348624b699ba6e5c220527f2b" + integrity sha512-k9jnrjYNwN6xYLj1iaGhonDghfvmeTmYjAiGvOr7clwKfPjMXJf4/HOr7oT5tJwYafgp2tG2l3eZEOfoELiMcA== + dependencies: + detect-node-es "^1.1.0" + tslib "^1.9.3" + +util-deprecate@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= + +utility-types@^3.10.0: + version "3.10.0" + resolved "https://registry.yarnpkg.com/utility-types/-/utility-types-3.10.0.tgz#ea4148f9a741015f05ed74fd615e1d20e6bed82b" + integrity sha512-O11mqxmi7wMKCo6HKFt5AhO4BwY3VV68YU07tgxfz8zJTIxr4BpsezN49Ffwy9j3ZpwwJp4fkRwjRzq3uWE6Rg== + +uuid@^8.3.0: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + +uuid@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.0.tgz#592f550650024a38ceb0c562f2f6aa435761efb5" + integrity sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg== + +v8-compile-cache@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" + integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== + +vfile-location@^3.0.0, vfile-location@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/vfile-location/-/vfile-location-3.2.0.tgz#d8e41fbcbd406063669ebf6c33d56ae8721d0f3c" + integrity sha512-aLEIZKv/oxuCDZ8lkJGhuhztf/BW4M+iHdCwglA/eWc+vtuRFJj8EtgceYFX4LRjOhCAAiNHsKGssC6onJ+jbA== + +vfile-message@*: + version "3.1.0" + resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-3.1.0.tgz#5437035aa43185ff4b9210d32fada6c640e59143" + integrity sha512-4QJbBk+DkPEhBXq3f260xSaWtjE4gPKOfulzfMFF8ZNwaPZieWsg3iVlcmF04+eebzpcpeXOOFMfrYzJHVYg+g== + dependencies: + "@types/unist" "^2.0.0" + unist-util-stringify-position "^3.0.0" + +vfile-message@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-2.0.4.tgz#5b43b88171d409eae58477d13f23dd41d52c371a" + integrity sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ== + dependencies: + "@types/unist" "^2.0.0" + unist-util-stringify-position "^2.0.0" + +vfile@^4.0.0, vfile@^4.2.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/vfile/-/vfile-4.2.1.tgz#03f1dce28fc625c625bc6514350fbdb00fa9e624" + integrity sha512-O6AE4OskCG5S1emQ/4gl8zK586RqA3srz3nfK/Viy0UPToBc5Trp9BVFb1u0CjsKrAWwnpr4ifM/KBXPWwJbCA== + dependencies: + "@types/unist" "^2.0.0" + is-buffer "^2.0.0" + unist-util-stringify-position "^2.0.0" + vfile-message "^2.0.0" + +weak-lru-cache@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/weak-lru-cache/-/weak-lru-cache-1.2.2.tgz#fdbb6741f36bae9540d12f480ce8254060dccd19" + integrity sha512-DEAoo25RfSYMuTGc9vPJzZcZullwIqRDSI9LOy+fkCJPi6hykCnfKaXTuPBDuXAUcqHXyOgFtHNp/kB2FjYHbw== + +web-namespaces@^1.0.0: + version "1.1.4" + resolved "https://registry.yarnpkg.com/web-namespaces/-/web-namespaces-1.1.4.tgz#bc98a3de60dadd7faefc403d1076d529f5e030ec" + integrity sha512-wYxSGajtmoP4WxfejAPIr4l0fVh+jeMXZb08wNc0tMg6xsfZXj3cECqIK0G7ZAqUq0PP8WlMDtaOGVBTAWztNw== + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + +xtend@^4.0.0, xtend@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" + integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== + +xxhash-wasm@^0.4.2: + version "0.4.2" + resolved "https://registry.yarnpkg.com/xxhash-wasm/-/xxhash-wasm-0.4.2.tgz#752398c131a4dd407b5132ba62ad372029be6f79" + integrity sha512-/eyHVRJQCirEkSZ1agRSCwriMhwlyUcFkXD5TPVSLP+IPzjsqMVzZwdoczLp1SoQU0R3dxz1RpIK+4YNQbCVOA== + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yaml@^1.10.0, yaml@^1.10.2: + version "1.10.2" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" + integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== + +yargs-parser@^20.2.2: + version "20.2.9" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" + integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== + +yargs@^16.2.0: + version "16.2.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" + integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.0" + y18n "^5.0.5" + yargs-parser "^20.2.2" + +zwitch@^1.0.0: + version "1.0.5" + resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-1.0.5.tgz#d11d7381ffed16b742f6af7b3f223d5cd9fe9920" + integrity sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw== From 2878b5596b9b604d1d8e6217faf30b7545cfd739 Mon Sep 17 00:00:00 2001 From: Gnanesh Date: Thu, 29 Dec 2022 20:04:09 +0530 Subject: [PATCH 02/29] Add support for ft.profile (plain version) and scrollbar functionality as well. --- .../ui/src/packages/ri-explain/package.json | 11 +- .../ui/src/packages/ri-explain/src/App.tsx | 6 +- .../src/packages/ri-explain/src/Explain.tsx | 79 ++++++----- .../ui/src/packages/ri-explain/src/Node.tsx | 71 +++++++++- .../ui/src/packages/ri-explain/src/main.tsx | 8 +- .../ui/src/packages/ri-explain/src/parser.ts | 131 +++++++++++------- .../ri-explain/src/styles/styles.less | 10 ++ 7 files changed, 217 insertions(+), 99 deletions(-) diff --git a/redisinsight/ui/src/packages/ri-explain/package.json b/redisinsight/ui/src/packages/ri-explain/package.json index 34f9ed9c59..ee627ca2b8 100644 --- a/redisinsight/ui/src/packages/ri-explain/package.json +++ b/redisinsight/ui/src/packages/ri-explain/package.json @@ -29,14 +29,15 @@ }, "visualizations": [ { - "id": "explain-viz", - "name": "Explain", - "activationMethod": "renderSearchExplain", + "id": "profile-explain-viz", + "name": "Profile-Explain", + "activationMethod": "renderCore", "matchCommands": [ "FT.EXPLAIN", - "FT.EXPLAINCLI" + "FT.EXPLAINCLI", + "FT.PROFILE" ], - "description": "Example of FT.EXPLAIN plugin", + "description": "Profile/Explain plugin Visualization", "default": true } ], diff --git a/redisinsight/ui/src/packages/ri-explain/src/App.tsx b/redisinsight/ui/src/packages/ri-explain/src/App.tsx index aaa38ea353..e4a9875b55 100644 --- a/redisinsight/ui/src/packages/ri-explain/src/App.tsx +++ b/redisinsight/ui/src/packages/ri-explain/src/App.tsx @@ -3,15 +3,15 @@ import Explain from './Explain' const isDarkTheme = document.body.classList.contains('theme_DARK') -export function ExplainApp(props: { command?: string, data: any }) { +export function App(props: { command?: string, data: any }) { const ErrorResponse = HandleError(props) if (ErrorResponse !== null) return ErrorResponse return ( -
- +
+
) } diff --git a/redisinsight/ui/src/packages/ri-explain/src/Explain.tsx b/redisinsight/ui/src/packages/ri-explain/src/Explain.tsx index 30c0be2c40..b92e05d928 100644 --- a/redisinsight/ui/src/packages/ri-explain/src/Explain.tsx +++ b/redisinsight/ui/src/packages/ri-explain/src/Explain.tsx @@ -3,36 +3,62 @@ import { Model, Graph } from '@antv/x6' import { register} from '@antv/x6-react-shape'; import Hierarchy from '@antv/hierarchy'; -import { AntHierarchyInput, ASTToJson } from './parser' -import { ExplainNode } from './Node'; +import { ParseExplain, ParseProfile } from './parser' +import { ExplainNode, ProfileNode } from './Node'; interface IExplain { - data: [{response: string[] | string}] + command: string + data: [{response: string[] | string | any}] +} + +enum CoreType { + Profile, + Explain, } export default function Explain(props: IExplain): JSX.Element { + const command = props.command.split(' ')[0] + if (command.toLowerCase() == 'ft.profile') { + const info = props.data[0].response[1]; + const resp = ParseProfile(info) + return ( + + ) + } const resp = props.data[0].response + const data = ParseExplain( + Array.isArray(resp) ? resp.join('\n') : resp.split('\\n').join('\n') + ); return ( ) } register({ - shape: 'react-node', + shape: 'react-explain-node', width: 100, height: 100, component: ExplainNode as any }) +register({ + shape: 'react-profile-node', + width: 100, + height: 100, + component: ProfileNode as any, +}) + const isDarkTheme = document.body.classList.contains('theme_DARK') -function ExplainDraw(props: {data: any}): JSX.Element { +function ExplainDraw({data, type}: {data: any, type: CoreType}): JSX.Element { const [done, setDone] = useState(false) const container = useRef(null) @@ -59,23 +85,22 @@ function ExplainDraw(props: {data: any}): JSX.Element { virtual: true, }) - graph.on("resize", () => { - graph.centerContent() - }) + graph.on("resize", () => graph.centerContent()) function resize() { const isFullScreen = parent.document.body.getElementsByClassName('fullscreen').length > 0 + const b = graph.getAllCellsBBox(); + const width = Math.max(b?.width || 1080, document.body.offsetWidth) + 100 if (isFullScreen) { - graph.resize(document.body.offsetWidth, parent.document.body.offsetHeight) + const height = Math.max(b?.height || 585, parent.document.body.offsetHeight) + 100 + graph.resize(width, height) } else { - graph.resize(document.body.offsetWidth, 585) + graph.resize(width, b?.height || 585) } } window.addEventListener('resize', resize); - let data = ASTToJson(props.data); - const result = Hierarchy.dendrogram(data, { direction: 'BT', getHeight() { @@ -98,16 +123,15 @@ function ExplainDraw(props: {data: any}): JSX.Element { const model: Model.FromJSONData = { nodes: [], edges: [] } const traverse = (data: any) => { if (data) { - const myData = data.data + const info = data.data model.nodes?.push({ id: data.id, x: (data.x || 0) + document.body.clientWidth / 2, y: (data.y || 0) + document.body.clientHeight, - shape: 'react-node', + shape: 'react-explain-node', width: 240, - height: (myData.snippet ? 64 : 42), - label: data.id.toString(), - data: {...myData, label: myData.data.data, type: myData.data.type}, + height: (info.snippet ? 64 : 42), + data: info, attrs: { body: { fill: isDarkTheme ? '#5F95FF' : '#8992B3', @@ -151,26 +175,13 @@ function ExplainDraw(props: {data: any}): JSX.Element { traverse(result) graph.fromJSON(model) - // graph.centerContent() graph.centerContent() - // scroller.enableAutoResize() - // scroller.center() - // scroller.scrollToContent() - - // scroller.enableAutoResize() - // scroller.center() - // scroller.resize(1636) - // scroller.centerContent() - // graph.center() - // graph.centerContent() - - }, [done]) return ( -
+
) } diff --git a/redisinsight/ui/src/packages/ri-explain/src/Node.tsx b/redisinsight/ui/src/packages/ri-explain/src/Node.tsx index d439cdde22..92489da25e 100644 --- a/redisinsight/ui/src/packages/ri-explain/src/Node.tsx +++ b/redisinsight/ui/src/packages/ri-explain/src/Node.tsx @@ -1,5 +1,6 @@ import React from 'react' -import { AntHierarchyInput } from './parser'; +import { EuiToolTip, EuiIcon } from '@elastic/eui' +import { EntityInfo, EntityType } from './parser'; interface INodeProps { label: string @@ -10,15 +11,14 @@ interface INodeProps { export function ExplainNode(props: INodeProps) { - const data = (props as any).node.getData(); - const label = data ? data.label : ''; - const snippet = data ? data.snippet : ''; + const propData: EntityInfo = (props as any).node.getData(); + const { type, data, snippet } = propData return (
-
{label}
- {data.type === 'Expr' &&
text
} +
{data ? data : type}
+ {type === EntityType.Expr &&
text
}
{ snippet && @@ -29,3 +29,62 @@ export function ExplainNode(props: INodeProps) {
) } + + + +interface INodeToolTip { + content: string +} + +function NodeToolTipContent(props: INodeToolTip) { + return ( +
+ {props.content} +
+ ) +} +function Box() { + return
+} + +export function ProfileNode(props: INodeProps) { + const info: EntityInfo = (props as any).node.getData(); + const {data, type, snippet, time, counter} = info + return ( +
+
+
+
{data && type}
+
text
+
+
+
{snippet}
+
+
100
+ {[...Array(4)].map(() => )} +
+
+
+
+ }> +
+
+ +
+
{time} ms
+
+
+ }> +
+
+ {counter} +
+
+ +
+
+
+
+
+ ) +} diff --git a/redisinsight/ui/src/packages/ri-explain/src/main.tsx b/redisinsight/ui/src/packages/ri-explain/src/main.tsx index e02488b028..cb3d1765e4 100644 --- a/redisinsight/ui/src/packages/ri-explain/src/main.tsx +++ b/redisinsight/ui/src/packages/ri-explain/src/main.tsx @@ -1,7 +1,7 @@ /* eslint-disable react/jsx-filename-extension */ import React from 'react' import { render } from 'react-dom' -import { ExplainApp } from './App' +import { App } from './App' interface Props { command?: string @@ -38,9 +38,9 @@ const renderApp = (element: JSX.Element) => render( document.getElementById('app') ) -const renderSearchExplain = (props: Props) => renderApp( - +const renderCore = (props: Props) => renderApp( + ) // This is a required action - export the main function for execution of the visualization -export default { renderSearchExplain } +export default { renderCore } diff --git a/redisinsight/ui/src/packages/ri-explain/src/parser.ts b/redisinsight/ui/src/packages/ri-explain/src/parser.ts index c65d60607c..b8295f943d 100644 --- a/redisinsight/ui/src/packages/ri-explain/src/parser.ts +++ b/redisinsight/ui/src/packages/ri-explain/src/parser.ts @@ -191,38 +191,48 @@ class Lexer { } } -type TNode = 'Expr' | 'UNION' | 'INTERSECT' | 'NUMERIC' +export enum EntityType { + Expr = 'Expr', + UNION = 'UNION', + INTERSECT = 'INTERSECT', + NUMERIC = 'NUMERIC', + + Index = 'Index', + Scorer = 'Scorer', + Sorter = 'Sorter', + Loader = 'Loader', +} -export interface AntHierarchyInput { +export interface EntityInfo { id: string - x?: number - y?: number - data?: { - type: TNode, - data: string - } + type: EntityType, + data?: string snippet?: string - children: AntHierarchyInput[] + children: EntityInfo[] + time?: string + counter?: string + size?: string } class Expr { Core: string + Type?: string + Time?: string constructor(expr: string) { this.Core = expr } - toJSON(): AntHierarchyInput { + toJSON(): EntityInfo { return { id: uuidv4(), // data: 'Expr', // snippet: this.Core, - data: { - type: 'Expr', - data: this.Core - }, + type: EntityType.Expr, + data: this.Core, children: [], + time: this.Time, } } } @@ -245,13 +255,11 @@ class NumericExpr { this.RSign = rsign; } - toJSON(): AntHierarchyInput { + toJSON(): EntityInfo { return { id: uuidv4(), - data: { - type: 'NUMERIC', - data: 'Numeric', - }, + type: EntityType.NUMERIC, + data: 'Numeric', snippet: `${this.Left.toString()} ${this.LSign.Data} ${this.Identifier.Data} ${this.RSign.Data} ${this.Right.toString()}`, children: [], } @@ -269,14 +277,11 @@ class IntersectExpr { this.Core = e } - toJSON(): AntHierarchyInput { + toJSON(): EntityInfo { return { id: uuidv4(), - data: { - type: 'INTERSECT', - data: 'INTERSECT', - }, - children: this.Core.map(x => x.toJSON()) + type: EntityType.INTERSECT, + children: this.Core.map(x => x.toJSON()), } } } @@ -288,45 +293,26 @@ class UnionExpr { this.Core = e } - toJSON(): AntHierarchyInput { + toJSON(): EntityInfo { return { id: uuidv4(), - data: { - type: 'UNION', - data: 'UNION', - }, + type: EntityType.UNION, children: this.Core.map(x => x.toJSON()) } } } -class SearchResult { - Core: IntersectExpr | UnionExpr - - constructor(e: IntersectExpr | UnionExpr) { - this.Core = e - } -} - -enum PRECEDENCE { - CALL -} - - -type PrefixFunction = (T: Token, p: PRECEDENCE) => void class Parser { private L: Lexer CurrentToken: Token PeekToken: Token Errors: string[] - PrefixFunctions: Map constructor(l: Lexer) { this.L = l; this.Errors = []; - this.PrefixFunctions = new Map() this.CurrentToken = new Token(TokenType.INIT, '') this.PeekToken = new Token(TokenType.INIT, '') @@ -517,7 +503,7 @@ function Parse(data: string): SearchExpr { } } -export function ASTToJson(output: string) { +export function ParseExplain(output: string) { return Parse(output).toJSON() } @@ -546,3 +532,54 @@ function assertToken(expected: TokenType, actual: TokenType | undefined) { assert(expected === actual, `Expected ${expected}, Actual: ${actual}`) } + +export function ParseProfile(info: any[][]): EntityInfo { + const parserData: any = info[info.length - 2] + let resp = ParseIteratorProfile(parserData[1]) + + const processorsProfile: string[][] = info[info.length - 1].slice(1); + + for (let i = 0; i < processorsProfile.length; i++) { + const e = processorsProfile[i] + resp = { + id: uuidv4(), + type: e[1] as EntityType, + time: e[3], + counter: e[5], + children: [resp], + } + } + + return resp; +} + +export function ParseIteratorProfile(data: any[]): EntityInfo { + const t: EntityType = data[1]; + if ([EntityType.UNION, EntityType.INTERSECT].includes(t)) { + const l = data.length; + return { + id: uuidv4(), + type: t, + time: data[5], + counter: data[7], + children: data.slice(l - 2).map(x => ParseIteratorProfile(x)), + } + } else if (t === EntityType.NUMERIC) { + return { + id: uuidv4(), + type: EntityType.NUMERIC, + snippet: 'Numeric', + children: [], + } + } else { + return { + id: uuidv4(), + type: EntityType.Expr, + data: data[3], + time: data[5], + counter: data[7], + size: data[9], + children: [], + } + } +} diff --git a/redisinsight/ui/src/packages/ri-explain/src/styles/styles.less b/redisinsight/ui/src/packages/ri-explain/src/styles/styles.less index 0de3aa99b7..302d13f705 100644 --- a/redisinsight/ui/src/packages/ri-explain/src/styles/styles.less +++ b/redisinsight/ui/src/packages/ri-explain/src/styles/styles.less @@ -6,6 +6,16 @@ padding: 0px; } +#mainApp::-webkit-scrollbar { + width: 2em; +} + +#mainApp::-webkit-scrollbar-thumb { + background-color: rgba(105, 112, 125, 0.5); + border: 6px solid transparent; + background-clip: content-box; +} + .theme_DARK { --info-background: #20222B; --info-color: white; From 3c92f20e07e79b61b8e2f4e035ccfaca24b882d4 Mon Sep 17 00:00:00 2001 From: Gnanesh Date: Fri, 30 Dec 2022 17:12:16 +0530 Subject: [PATCH 03/29] Add support for ft.profile (non-clustered version) visualization --- .../src/packages/ri-explain/src/Explain.tsx | 71 ++++++++-- .../ui/src/packages/ri-explain/src/Node.tsx | 123 ++++++++++++------ .../ri-explain/src/icons/arrow_down.js | 28 ---- .../ri-explain/src/icons/arrow_left.js | 28 ---- .../ri-explain/src/icons/arrow_right.js | 33 ----- .../packages/ri-explain/src/icons/check.js | 28 ---- .../src/packages/ri-explain/src/icons/copy.js | 29 ----- .../packages/ri-explain/src/icons/cross.js | 27 ---- .../packages/ri-explain/src/icons/empty.js | 23 ---- .../ui/src/packages/ri-explain/src/index.html | 2 +- .../ui/src/packages/ri-explain/src/main.tsx | 24 +--- .../ui/src/packages/ri-explain/src/parser.ts | 99 ++++++++++---- .../ri-explain/src/styles/styles.less | 121 +++++++++++++++-- .../ui/src/packages/ri-explain/src/utils.ts | 56 -------- 14 files changed, 333 insertions(+), 359 deletions(-) delete mode 100644 redisinsight/ui/src/packages/ri-explain/src/icons/arrow_down.js delete mode 100644 redisinsight/ui/src/packages/ri-explain/src/icons/arrow_left.js delete mode 100644 redisinsight/ui/src/packages/ri-explain/src/icons/arrow_right.js delete mode 100644 redisinsight/ui/src/packages/ri-explain/src/icons/check.js delete mode 100644 redisinsight/ui/src/packages/ri-explain/src/icons/copy.js delete mode 100644 redisinsight/ui/src/packages/ri-explain/src/icons/cross.js delete mode 100644 redisinsight/ui/src/packages/ri-explain/src/icons/empty.js delete mode 100644 redisinsight/ui/src/packages/ri-explain/src/utils.ts diff --git a/redisinsight/ui/src/packages/ri-explain/src/Explain.tsx b/redisinsight/ui/src/packages/ri-explain/src/Explain.tsx index b92e05d928..6173a42f57 100644 --- a/redisinsight/ui/src/packages/ri-explain/src/Explain.tsx +++ b/redisinsight/ui/src/packages/ri-explain/src/Explain.tsx @@ -20,11 +20,18 @@ export default function Explain(props: IExplain): JSX.Element { const command = props.command.split(' ')[0] if (command.toLowerCase() == 'ft.profile') { const info = props.data[0].response[1]; - const resp = ParseProfile(info) + + const profilingTime: IProfilingTime = { + profile: info[0][1], + parsing: info[1][1], + pipelineCreation: info[2][1], + } + return ( ) } @@ -58,7 +65,13 @@ register({ const isDarkTheme = document.body.classList.contains('theme_DARK') -function ExplainDraw({data, type}: {data: any, type: CoreType}): JSX.Element { +interface IProfilingTime { + profile: string + parsing: string + pipelineCreation: string +} + +function ExplainDraw({data, type, profilingTime}: {data: any, type: CoreType, profilingTime?: IProfilingTime}): JSX.Element { const [done, setDone] = useState(false) const container = useRef(null) @@ -90,14 +103,16 @@ function ExplainDraw({data, type}: {data: any, type: CoreType}): JSX.Element { function resize() { const isFullScreen = parent.document.body.getElementsByClassName('fullscreen').length > 0 const b = graph.getAllCellsBBox(); - const width = Math.max(b?.width || 1080, document.body.offsetWidth) + 100 + const width = Math.max((b?.width || 1080) + 100, document.body.offsetWidth) if (isFullScreen) { - const height = Math.max(b?.height || 585, parent.document.body.offsetHeight) + 100 + const height = Math.max((b?.height || 585) + 100, parent.document.body.offsetHeight) graph.resize(width, height) } else { - graph.resize(width, b?.height || 585) + graph.resize(width, (b?.height || 585) + 100) } } + + resize() window.addEventListener('resize', resize); @@ -115,7 +130,7 @@ function ExplainDraw({data, type}: {data: any, type: CoreType}): JSX.Element { getVGap() { return 0 }, - nodeSep: 250, + nodeSep: type === CoreType.Explain ? 250 : 350, rankSep: 150, subTreeSep: 0, }) @@ -124,13 +139,26 @@ function ExplainDraw({data, type}: {data: any, type: CoreType}): JSX.Element { const traverse = (data: any) => { if (data) { const info = data.data + + let nodeProps = { + shape: 'react-explain-node', + width: 240, + height: (info.snippet ? 64 : 42), + } + if (type === CoreType.Profile) { + nodeProps = { + shape: 'react-profile-node', + width: 320, + height: 84, + } + } + + model.nodes?.push({ id: data.id, x: (data.x || 0) + document.body.clientWidth / 2, y: (data.y || 0) + document.body.clientHeight, - shape: 'react-explain-node', - width: 240, - height: (info.snippet ? 64 : 42), + ...nodeProps, data: info, attrs: { body: { @@ -162,7 +190,7 @@ function ExplainDraw({data, type}: {data: any, type: CoreType}): JSX.Element { }, attrs: { line: { - stroke: '#6B6B6B', + stroke: isDarkTheme ? '#6B6B6B' : '#8992B3', strokeWidth: 1, targetMarker: null, }, @@ -181,7 +209,24 @@ function ExplainDraw({data, type}: {data: any, type: CoreType}): JSX.Element { }, [done]) return ( -
+
+
+ { profilingTime && ( +
+
+
{profilingTime.profile}
+
Total Profile Time
+
+
+
{profilingTime.parsing}
+
Parsing Time
+
+
+
{profilingTime.pipelineCreation}
+
Pipeline Creation Time
+
+
+ )} +
) - } diff --git a/redisinsight/ui/src/packages/ri-explain/src/Node.tsx b/redisinsight/ui/src/packages/ri-explain/src/Node.tsx index 92489da25e..53b87f6404 100644 --- a/redisinsight/ui/src/packages/ri-explain/src/Node.tsx +++ b/redisinsight/ui/src/packages/ri-explain/src/Node.tsx @@ -33,55 +33,102 @@ export function ExplainNode(props: INodeProps) { interface INodeToolTip { - content: string + content?: string + items?: {[key: string]: string} } function NodeToolTipContent(props: INodeToolTip) { - return ( -
- {props.content} -
- ) -} -function Box() { - return
+ + if (props.content !== undefined) { + return
{props.content}
+ } + + if (props.items !== undefined) { + let items = props.items + return ( +
+ { + Object.keys(items).map(k => ( +
{k}: {items[k]}
+ )) + } +
+ ) + } + + return null; } + export function ProfileNode(props: INodeProps) { const info: EntityInfo = (props as any).node.getData(); - const {data, type, snippet, time, counter} = info + const {data, type, snippet, time, counter, size} = info + // const oldRender = ( + //
+ //
+ //
+ //
{data ? data : type}
+ //
text
+ //
+ //
+ //
{snippet}
+ // {/*
+ //
100
+ // {[...Array(4)].map(() => )} + //
*/} + //
+ //
+ //
+ // }> + //
+ //
+ // + //
+ //
{time} ms
+ //
+ //
+ // }> + //
+ //
+ // {counter} + //
+ //
+ // + //
+ //
+ //
+ //
+ //
+ // ) + + let items = {} + + if (counter !== undefined) { + items['Counter'] = counter; + } + + if (size !== undefined) { + items['Size'] = size; + } + + return ( -
-
-
-
{data && type}
-
text
-
-
-
{snippet}
-
-
100
- {[...Array(4)].map(() => )} -
-
+
+
+
{data ? data : type}
+
{[EntityType.GEO, EntityType.NUMERIC, EntityType.TEXT, EntityType.TAG].includes(type) ? type : ''}
-
- }> -
-
- -
-
{time} ms
+
+ }> +
+
+
{time}
- }> -
-
- {counter} -
-
- -
+ }> +
+
{counter}
+
diff --git a/redisinsight/ui/src/packages/ri-explain/src/icons/arrow_down.js b/redisinsight/ui/src/packages/ri-explain/src/icons/arrow_down.js deleted file mode 100644 index 9fc44fc582..0000000000 --- a/redisinsight/ui/src/packages/ri-explain/src/icons/arrow_down.js +++ /dev/null @@ -1,28 +0,0 @@ -function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } - -function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; } - -function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; } - -import * as React from 'react'; - -var EuiIconArrowDown = function EuiIconArrowDown(_ref) { - var title = _ref.title, - titleId = _ref.titleId, - props = _objectWithoutProperties(_ref, ["title", "titleId"]); - - return /*#__PURE__*/React.createElement("svg", _extends({ - width: 16, - height: 16, - viewBox: "0 0 16 16", - xmlns: "http://www.w3.org/2000/svg", - "aria-labelledby": titleId - }, props), title ? /*#__PURE__*/React.createElement("title", { - id: titleId - }, title) : null, /*#__PURE__*/React.createElement("path", { - fillRule: "non-zero", - d: "M13.069 5.157L8.384 9.768a.546.546 0 01-.768 0L2.93 5.158a.552.552 0 00-.771 0 .53.53 0 000 .759l4.684 4.61c.641.631 1.672.63 2.312 0l4.684-4.61a.53.53 0 000-.76.552.552 0 00-.771 0z" - })); -}; - -export var icon = EuiIconArrowDown; diff --git a/redisinsight/ui/src/packages/ri-explain/src/icons/arrow_left.js b/redisinsight/ui/src/packages/ri-explain/src/icons/arrow_left.js deleted file mode 100644 index 9acedc3e12..0000000000 --- a/redisinsight/ui/src/packages/ri-explain/src/icons/arrow_left.js +++ /dev/null @@ -1,28 +0,0 @@ -function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } - -function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; } - -function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; } - -import * as React from 'react'; - -var EuiIconArrowLeft = function EuiIconArrowLeft(_ref) { - var title = _ref.title, - titleId = _ref.titleId, - props = _objectWithoutProperties(_ref, ["title", "titleId"]); - - return /*#__PURE__*/React.createElement("svg", _extends({ - width: 16, - height: 16, - viewBox: "0 0 16 16", - xmlns: "http://www.w3.org/2000/svg", - "aria-labelledby": titleId - }, props), title ? /*#__PURE__*/React.createElement("title", { - id: titleId - }, title) : null, /*#__PURE__*/React.createElement("path", { - fillRule: "nonzero", - d: "M10.843 13.069L6.232 8.384a.546.546 0 010-.768l4.61-4.685a.552.552 0 000-.771.53.53 0 00-.759 0l-4.61 4.684a1.65 1.65 0 000 2.312l4.61 4.684a.53.53 0 00.76 0 .552.552 0 000-.771z" - })); -}; - -export var icon = EuiIconArrowLeft; diff --git a/redisinsight/ui/src/packages/ri-explain/src/icons/arrow_right.js b/redisinsight/ui/src/packages/ri-explain/src/icons/arrow_right.js deleted file mode 100644 index 0de7006630..0000000000 --- a/redisinsight/ui/src/packages/ri-explain/src/icons/arrow_right.js +++ /dev/null @@ -1,33 +0,0 @@ -import * as React from 'react' - -function _extends() { _extends = Object.assign || function (target) { for (let i = 1; i < arguments.length; i++) { const source = arguments[i]; for (const key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key] } } } return target }; return _extends.apply(this, arguments) } - -function _objectWithoutProperties(source, excluded) { - if (source == null) return {}; const target = _objectWithoutPropertiesLoose(source, excluded); let key; let - i; if (Object.getOwnPropertySymbols) { const sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key] } } return target -} - -function _objectWithoutPropertiesLoose(source, excluded) { - if (source == null) return {}; const target = {}; const sourceKeys = Object.keys(source); let key; let - i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key] } return target -} - -const EuiIconArrowRight = function EuiIconArrowRight(_ref) { - const { title } = _ref - const { titleId } = _ref - const props = _objectWithoutProperties(_ref, ['title', 'titleId']) - - return /* #__PURE__ */React.createElement('svg', { width: 16, - height: 16, - viewBox: '0 0 16 16', - xmlns: 'http://www.w3.org/2000/svg', - 'aria-labelledby': titleId, - ...props }, title ? /* #__PURE__ */React.createElement('title', { - id: titleId - }, title) : null, /* #__PURE__ */React.createElement('path', { - fillRule: 'nonzero', - d: 'M5.157 13.069l4.611-4.685a.546.546 0 000-.768L5.158 2.93a.552.552 0 010-.771.53.53 0 01.759 0l4.61 4.684c.631.641.63 1.672 0 2.312l-4.61 4.684a.53.53 0 01-.76 0 .552.552 0 010-.771z' - })) -} - -export var icon = EuiIconArrowRight diff --git a/redisinsight/ui/src/packages/ri-explain/src/icons/check.js b/redisinsight/ui/src/packages/ri-explain/src/icons/check.js deleted file mode 100644 index 4c0144cc33..0000000000 --- a/redisinsight/ui/src/packages/ri-explain/src/icons/check.js +++ /dev/null @@ -1,28 +0,0 @@ -function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } - -function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; } - -function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; } - -import * as React from 'react'; - -var EuiIconCheck = function EuiIconCheck(_ref) { - var title = _ref.title, - titleId = _ref.titleId, - props = _objectWithoutProperties(_ref, ["title", "titleId"]); - - return /*#__PURE__*/React.createElement("svg", _extends({ - width: 16, - height: 16, - viewBox: "0 0 16 16", - xmlns: "http://www.w3.org/2000/svg", - "aria-labelledby": titleId - }, props), title ? /*#__PURE__*/React.createElement("title", { - id: titleId - }, title) : null, /*#__PURE__*/React.createElement("path", { - fillRule: "evenodd", - d: "M6.5 12a.502.502 0 01-.354-.146l-4-4a.502.502 0 01.708-.708L6.5 10.793l6.646-6.647a.502.502 0 01.708.708l-7 7A.502.502 0 016.5 12" - })); -}; - -export var icon = EuiIconCheck; diff --git a/redisinsight/ui/src/packages/ri-explain/src/icons/copy.js b/redisinsight/ui/src/packages/ri-explain/src/icons/copy.js deleted file mode 100644 index 73146e9dea..0000000000 --- a/redisinsight/ui/src/packages/ri-explain/src/icons/copy.js +++ /dev/null @@ -1,29 +0,0 @@ -function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } - -function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; } - -function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; } - -import * as React from 'react'; - -var EuiIconCopy = function EuiIconCopy(_ref) { - var title = _ref.title, - titleId = _ref.titleId, - props = _objectWithoutProperties(_ref, ["title", "titleId"]); - - return /*#__PURE__*/React.createElement("svg", _extends({ - width: 16, - height: 16, - viewBox: "0 0 16 16", - xmlns: "http://www.w3.org/2000/svg", - "aria-labelledby": titleId - }, props), title ? /*#__PURE__*/React.createElement("title", { - id: titleId - }, title) : null, /*#__PURE__*/React.createElement("path", { - d: "M11.4 0c.235 0 .46.099.622.273l2.743 3c.151.162.235.378.235.602v9.25a.867.867 0 01-.857.875H3.857A.867.867 0 013 13.125V.875C3 .392 3.384 0 3.857 0H11.4zM14 4h-2.6a.4.4 0 01-.4-.4V1H4v12h10V4z" - }), /*#__PURE__*/React.createElement("path", { - d: "M3 1H2a1 1 0 00-1 1v13a1 1 0 001 1h10a1 1 0 001-1v-1h-1v1H2V2h1V1z" - })); -}; - -export var icon = EuiIconCopy; diff --git a/redisinsight/ui/src/packages/ri-explain/src/icons/cross.js b/redisinsight/ui/src/packages/ri-explain/src/icons/cross.js deleted file mode 100644 index 80f56b4b98..0000000000 --- a/redisinsight/ui/src/packages/ri-explain/src/icons/cross.js +++ /dev/null @@ -1,27 +0,0 @@ -function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } - -function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; } - -function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; } - -import * as React from 'react'; - -var EuiIconCross = function EuiIconCross(_ref) { - var title = _ref.title, - titleId = _ref.titleId, - props = _objectWithoutProperties(_ref, ["title", "titleId"]); - - return /*#__PURE__*/React.createElement("svg", _extends({ - width: 16, - height: 16, - viewBox: "0 0 16 16", - xmlns: "http://www.w3.org/2000/svg", - "aria-labelledby": titleId - }, props), title ? /*#__PURE__*/React.createElement("title", { - id: titleId - }, title) : null, /*#__PURE__*/React.createElement("path", { - d: "M7.293 8L3.146 3.854a.5.5 0 11.708-.708L8 7.293l4.146-4.147a.5.5 0 01.708.708L8.707 8l4.147 4.146a.5.5 0 01-.708.708L8 8.707l-4.146 4.147a.5.5 0 01-.708-.708L7.293 8z" - })); -}; - -export var icon = EuiIconCross; diff --git a/redisinsight/ui/src/packages/ri-explain/src/icons/empty.js b/redisinsight/ui/src/packages/ri-explain/src/icons/empty.js deleted file mode 100644 index 9eb9b8ba43..0000000000 --- a/redisinsight/ui/src/packages/ri-explain/src/icons/empty.js +++ /dev/null @@ -1,23 +0,0 @@ -function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } - -function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; } - -function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; } - -import * as React from 'react'; - -var EuiIconEmpty = function EuiIconEmpty(_ref) { - var title = _ref.title, - titleId = _ref.titleId, - props = _objectWithoutProperties(_ref, ["title", "titleId"]); - - return /*#__PURE__*/React.createElement("svg", _extends({ - width: 16, - height: 16, - viewBox: "0 0 16 16", - xmlns: "http://www.w3.org/2000/svg", - "aria-labelledby": titleId - }, props)); -}; - -export var icon = EuiIconEmpty; diff --git a/redisinsight/ui/src/packages/ri-explain/src/index.html b/redisinsight/ui/src/packages/ri-explain/src/index.html index 8090672fce..ddb4447fc8 100644 --- a/redisinsight/ui/src/packages/ri-explain/src/index.html +++ b/redisinsight/ui/src/packages/ri-explain/src/index.html @@ -4,7 +4,7 @@ - Client list plugin + Profile/Explain Plugin Visualization diff --git a/redisinsight/ui/src/packages/ri-explain/src/main.tsx b/redisinsight/ui/src/packages/ri-explain/src/main.tsx index cb3d1765e4..f74351e871 100644 --- a/redisinsight/ui/src/packages/ri-explain/src/main.tsx +++ b/redisinsight/ui/src/packages/ri-explain/src/main.tsx @@ -9,28 +9,12 @@ interface Props { } import { appendIconComponentCache } from '@elastic/eui/es/components/icon/icon'; -import { icon as EuiIconMagnifyWithPlus } from '@elastic/eui/es/components/icon/assets/magnifyWithPlus'; -import { icon as EuiIconMagnifyWithMinus } from '@elastic/eui/es/components/icon/assets/magnifyWithMinus'; -import { icon as EuiIconBullsEye } from '@elastic/eui/es/components/icon/assets/bullseye'; -import { icon as EuiIconEditorItemAlignLeft } from '@elastic/eui/es/components/icon/assets/editorItemAlignLeft'; -import { icon as EuiIconEditorItemAlignRight } from '@elastic/eui/es/components/icon/assets/editorItemAlignRight'; -import { icon as EuiIconEditorItemAlignCenter } from '@elastic/eui/es/components/icon/assets/editorItemAlignCenter'; -import { icon as EuiIconArrowLeft } from '@elastic/eui/es/components/icon/assets/arrow_left'; -import { icon as EuiIconArrowRight } from '@elastic/eui/es/components/icon/assets/arrow_right'; -import { icon as EuiIconArrowDown } from '@elastic/eui/es/components/icon/assets/arrow_down'; -import { icon as EuiIconCross } from '@elastic/eui/es/components/icon/assets/cross'; +import { icon as EuiIconClock } from '@elastic/eui/es/components/icon/assets/clock'; +import { icon as EuiIconIInCircle } from '@elastic/eui/es/components/icon/assets/iInCircle'; appendIconComponentCache({ - magnifyWithPlus: EuiIconMagnifyWithPlus, - magnifyWithMinus: EuiIconMagnifyWithMinus, - bullseye: EuiIconBullsEye, - editorItemAlignLeft: EuiIconEditorItemAlignLeft, - editorItemAlignRight: EuiIconEditorItemAlignRight, - editorItemAlignCenter: EuiIconEditorItemAlignCenter, - arrowLeft: EuiIconArrowLeft, - arrowRight: EuiIconArrowRight, - arrowDown: EuiIconArrowDown, - cross: EuiIconCross, + clock: EuiIconClock, + iInCircle: EuiIconIInCircle, }) const renderApp = (element: JSX.Element) => render( diff --git a/redisinsight/ui/src/packages/ri-explain/src/parser.ts b/redisinsight/ui/src/packages/ri-explain/src/parser.ts index b8295f943d..1e7b77089f 100644 --- a/redisinsight/ui/src/packages/ri-explain/src/parser.ts +++ b/redisinsight/ui/src/packages/ri-explain/src/parser.ts @@ -197,6 +197,11 @@ export enum EntityType { INTERSECT = 'INTERSECT', NUMERIC = 'NUMERIC', + // These are used exclusively in FT.PROFILE + GEO = 'GEO', + TEXT = 'TEXT', + TAG = 'TAG', + Index = 'Index', Scorer = 'Scorer', Sorter = 'Sorter', @@ -554,32 +559,76 @@ export function ParseProfile(info: any[][]): EntityInfo { } export function ParseIteratorProfile(data: any[]): EntityInfo { - const t: EntityType = data[1]; - if ([EntityType.UNION, EntityType.INTERSECT].includes(t)) { - const l = data.length; - return { - id: uuidv4(), - type: t, - time: data[5], - counter: data[7], - children: data.slice(l - 2).map(x => ParseIteratorProfile(x)), - } - } else if (t === EntityType.NUMERIC) { - return { - id: uuidv4(), - type: EntityType.NUMERIC, - snippet: 'Numeric', - children: [], + + let props: {[key: string]: any} = {} + + // Parse items with the following format [key1, value1, key2, value2, null, key3, value3, key4, value4_1[], value4_2[]] + for (let x = 0; x < data.length; x += 2) { + let key = data[x] + if (key === null) { + + while (data[x] === null) { + x = x + 1; + } + key = data[x]; } - } else { - return { - id: uuidv4(), - type: EntityType.Expr, - data: data[3], - time: data[5], - counter: data[7], - size: data[9], - children: [], + + let val = data[x + 1]; + + while (data[x + 1] === null) x = x + 1; + val = data[x + 1]; + + if (Array.isArray(val)) { + let arr: any[] = [] + while ((x + 1) < data.length && Array.isArray(data[x + 1])) { + arr.push(data[x + 1]) + x = x + 1; + } + props[key] = arr; + } else { + props[key] = val; } } + + let childrens = props['Child iterators'] || []; + + return { + id: uuidv4(), + type: props['Type'], + time: props['Time'], + counter: props['Counter'], + size: props['Size'], + data: props['Term'], + children: childrens.map(ParseIteratorProfile), + } + + // const t: EntityType = props['Type']; + // if ([EntityType.UNION, EntityType.INTERSECT].includes(t)) { + // const l = data.length; + + // return { + // id: uuidv4(), + // type: t, + // time: data[5], + // counter: data[7], + // children: props['Child iterators'].map(x => ParseIteratorProfile(x)), + // } + // // } else if (t === EntityType.NUMERIC) { + // // return { + // // id: uuidv4(), + // // type: EntityType.NUMERIC, + // // snippet: 'Numeric', + // // children: [], + // // } + // } else { + // return { + // id: uuidv4(), + // type: data[1], + // data: data[3], + // time: data[5], + // counter: data[7], + // size: data[9], + // children: [], + // } + // } } diff --git a/redisinsight/ui/src/packages/ri-explain/src/styles/styles.less b/redisinsight/ui/src/packages/ri-explain/src/styles/styles.less index 302d13f705..7aac6c80ad 100644 --- a/redisinsight/ui/src/packages/ri-explain/src/styles/styles.less +++ b/redisinsight/ui/src/packages/ri-explain/src/styles/styles.less @@ -76,6 +76,72 @@ background-color: var(--tooltip-background) !important; } +.ProfileContainer { + width: 320px; + max-height: 84px; + + padding-left: 18px !important; + padding-right: 18px !important; + + box-shadow: 0px 3px 12px var(--node-border-shadow); + border: 1px solid var(--node-border-color) !important; + border-radius: 4px; + opacity: 1; + + display: flex; + flex-direction: column; + justify-content: space-around; + + background-color: var(--node-background); + color: var(--text-color); + .Main { + + display: flex; + justify-content: space-between; + line-height: 18px; + + font-size: 13px; + color: var(--text-color); + + padding-top: 12px; + padding-bottom: 12px; + + .Type { + color: #CE915B; + text-transform: lowercase; + } + } + + .MetaData { + display: flex; + justify-content: space-between; + line-height: 18px; + color: #B5B6C0; + + padding-top: 12px; + padding-bottom: 12px; + + height: 42px; //FIXME: fixed height? + border-top: 0.5px solid #3C3C3D; + + .Time { + display: flex; + align-items: center; + font-size: 12px; + } + + .Size { + display: flex; + align-items: center; + font-size: 13px; + } + + .IconContainer { + align-self: end; + } + } +} + .ExplainContainer { width: 240px; min-height: 42px; @@ -138,6 +204,7 @@ flex-direction: column; justify-content: space-between; font-size: 13px; + height: 18px; .NodeHeaderInfo { display: flex; @@ -162,6 +229,50 @@ } + .NodeIcon { + height: 13px !important; + width: 13px !important; + } + + + .NodeToolTip { + display: flex; + flex-direction: column; + background-color: #333D4F; + padding: 10px; + + .NodeToolTipItem { + background-color: inherit; + } + + } + +} + +.ProfileTimeInfo { + display: flex; + justify-content: center; + color: var(--text-color); + + font-family: 'Graphik' Light, sans-serif !important; + padding-top: 6px !important; + padding-bottom: 13px !important; + + .Item { + + padding-left: 18px; + padding-right: 18px; + + .Key { + font-size: 13px; + line-height: 18px; + } + + .Value { + font-size: 18px; + line-height: 24px; + } + } } .NodeType { @@ -175,11 +286,6 @@ color: darkgrey; } -.NodeIcon { - height: 13px; - width: 13px; -} - .NodeInfoDivider { width: 284px; height: 29px; @@ -200,11 +306,6 @@ } -.NodeToolTip { - background-color: #333D4F; - padding: 10px; -} - .NodeTimeIcon { padding-right: 5px !important; } diff --git a/redisinsight/ui/src/packages/ri-explain/src/utils.ts b/redisinsight/ui/src/packages/ri-explain/src/utils.ts deleted file mode 100644 index cf48c629a4..0000000000 --- a/redisinsight/ui/src/packages/ri-explain/src/utils.ts +++ /dev/null @@ -1,56 +0,0 @@ -export const truncateText = (str = '', length = 100) => { - const ending = '...' - - if (str.length > length) { - return str.substring(0, length - ending.length) + ending - } - - return str -} - -function charCodeSum(str: string | undefined) { - if (str === undefined) return 0 - let sum = 0 - for (let i = 0; i < str.length; i++) { - sum += str.charCodeAt(i) - } - return sum -} - -export function invertColor(hex: string) { - if (hex.indexOf('#') === 0) { - hex = hex.slice(1) - } - // convert 3-digit hex to 6-digits. - if (hex.length === 3) { - hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2] - } - if (hex.length !== 6) { - throw new Error('Invalid HEX color.') - } - // invert color components - var r = (255 - parseInt(hex.slice(0, 2), 16)).toString(16), - g = (255 - parseInt(hex.slice(2, 4), 16)).toString(16), - b = (255 - parseInt(hex.slice(4, 6), 16)).toString(16) - // pad each with zeros and return - return '#' + padZero(r) + padZero(g) + padZero(b) -} - -function padZero(str) { - let len = str.length || 2 - var zeros = new Array(len).join('0') - return (zeros + str).slice(-len) -} - - -export function wrapText(s: string, w: number) { - return s.replace( - new RegExp(`(?![^\\n]{1,${w}}$)([^\\n]{1,${w}})\\s`, 'g'), - '$1\n' - ) -} - - -export function commandIsSuccess(resp: [{ response: any, status: string }]) { - return Array.isArray(resp) && resp.length >= 1 || resp[0].status === 'success' -} From 57604efdaa945677aebbb41f481e028cc7e86718 Mon Sep 17 00:00:00 2001 From: Gnanesh Date: Fri, 30 Dec 2022 17:17:10 +0530 Subject: [PATCH 04/29] Remove semicolons --- .../src/packages/ri-explain/src/Explain.tsx | 14 +- .../ui/src/packages/ri-explain/src/Node.tsx | 49 +------ .../ui/src/packages/ri-explain/src/main.tsx | 6 +- .../ui/src/packages/ri-explain/src/parser.ts | 130 +++++++++--------- 4 files changed, 81 insertions(+), 118 deletions(-) diff --git a/redisinsight/ui/src/packages/ri-explain/src/Explain.tsx b/redisinsight/ui/src/packages/ri-explain/src/Explain.tsx index 6173a42f57..ec210b8094 100644 --- a/redisinsight/ui/src/packages/ri-explain/src/Explain.tsx +++ b/redisinsight/ui/src/packages/ri-explain/src/Explain.tsx @@ -1,10 +1,10 @@ import React, { useEffect, useState, useRef } from 'react' import { Model, Graph } from '@antv/x6' -import { register} from '@antv/x6-react-shape'; -import Hierarchy from '@antv/hierarchy'; +import { register} from '@antv/x6-react-shape' +import Hierarchy from '@antv/hierarchy' import { ParseExplain, ParseProfile } from './parser' -import { ExplainNode, ProfileNode } from './Node'; +import { ExplainNode, ProfileNode } from './Node' interface IExplain { command: string @@ -19,7 +19,7 @@ enum CoreType { export default function Explain(props: IExplain): JSX.Element { const command = props.command.split(' ')[0] if (command.toLowerCase() == 'ft.profile') { - const info = props.data[0].response[1]; + const info = props.data[0].response[1] const profilingTime: IProfilingTime = { profile: info[0][1], @@ -40,7 +40,7 @@ export default function Explain(props: IExplain): JSX.Element { const data = ParseExplain( Array.isArray(resp) ? resp.join('\n') : resp.split('\\n').join('\n') - ); + ) return ( 0 - const b = graph.getAllCellsBBox(); + const b = graph.getAllCellsBBox() const width = Math.max((b?.width || 1080) + 100, document.body.offsetWidth) if (isFullScreen) { const height = Math.max((b?.height || 585) + 100, parent.document.body.offsetHeight) @@ -114,7 +114,7 @@ function ExplainDraw({data, type, profilingTime}: {data: any, type: CoreType, pr resize() - window.addEventListener('resize', resize); + window.addEventListener('resize', resize) const result = Hierarchy.dendrogram(data, { direction: 'BT', diff --git a/redisinsight/ui/src/packages/ri-explain/src/Node.tsx b/redisinsight/ui/src/packages/ri-explain/src/Node.tsx index 53b87f6404..d3d1a42c94 100644 --- a/redisinsight/ui/src/packages/ri-explain/src/Node.tsx +++ b/redisinsight/ui/src/packages/ri-explain/src/Node.tsx @@ -1,6 +1,6 @@ import React from 'react' import { EuiToolTip, EuiIcon } from '@elastic/eui' -import { EntityInfo, EntityType } from './parser'; +import { EntityInfo, EntityType } from './parser' interface INodeProps { label: string @@ -11,7 +11,7 @@ interface INodeProps { export function ExplainNode(props: INodeProps) { - const propData: EntityInfo = (props as any).node.getData(); + const propData: EntityInfo = (props as any).node.getData() const { type, data, snippet } = propData return (
@@ -56,59 +56,22 @@ function NodeToolTipContent(props: INodeToolTip) { ) } - return null; + return null } export function ProfileNode(props: INodeProps) { - const info: EntityInfo = (props as any).node.getData(); + const info: EntityInfo = (props as any).node.getData() const {data, type, snippet, time, counter, size} = info - // const oldRender = ( - //
- //
- //
- //
{data ? data : type}
- //
text
- //
- //
- //
{snippet}
- // {/*
- //
100
- // {[...Array(4)].map(() => )} - //
*/} - //
- //
- //
- // }> - //
- //
- // - //
- //
{time} ms
- //
- //
- // }> - //
- //
- // {counter} - //
- //
- // - //
- //
- //
- //
- //
- // ) let items = {} if (counter !== undefined) { - items['Counter'] = counter; + items['Counter'] = counter } if (size !== undefined) { - items['Size'] = size; + items['Size'] = size } diff --git a/redisinsight/ui/src/packages/ri-explain/src/main.tsx b/redisinsight/ui/src/packages/ri-explain/src/main.tsx index f74351e871..07f5caacf3 100644 --- a/redisinsight/ui/src/packages/ri-explain/src/main.tsx +++ b/redisinsight/ui/src/packages/ri-explain/src/main.tsx @@ -8,9 +8,9 @@ interface Props { data?: { response: any, status: string }[] } -import { appendIconComponentCache } from '@elastic/eui/es/components/icon/icon'; -import { icon as EuiIconClock } from '@elastic/eui/es/components/icon/assets/clock'; -import { icon as EuiIconIInCircle } from '@elastic/eui/es/components/icon/assets/iInCircle'; +import { appendIconComponentCache } from '@elastic/eui/es/components/icon/icon' +import { icon as EuiIconClock } from '@elastic/eui/es/components/icon/assets/clock' +import { icon as EuiIconIInCircle } from '@elastic/eui/es/components/icon/assets/iInCircle' appendIconComponentCache({ clock: EuiIconClock, diff --git a/redisinsight/ui/src/packages/ri-explain/src/parser.ts b/redisinsight/ui/src/packages/ri-explain/src/parser.ts index 1e7b77089f..055e869bfa 100644 --- a/redisinsight/ui/src/packages/ri-explain/src/parser.ts +++ b/redisinsight/ui/src/packages/ri-explain/src/parser.ts @@ -1,4 +1,4 @@ -import { v4 as uuidv4 } from 'uuid'; +import { v4 as uuidv4 } from 'uuid' enum TokenType { @@ -35,8 +35,8 @@ class Token { Data: string constructor(t: TokenType, data: string) { - this.T = t; - this.Data = data; + this.T = t + this.Data = data } } @@ -60,26 +60,26 @@ class Lexer { C?: string constructor(input: string) { - this.Input = input; - this.Position = 0; - this.ReadPosition = 0; - this.C = undefined; + this.Input = input + this.Position = 0 + this.ReadPosition = 0 + this.C = undefined - this.ReadChar(); + this.ReadChar() } ReadChar() { if (this.ReadPosition >= this.Input.length) { - this.C = undefined; + this.C = undefined } else { - this.C = this.Input[this.ReadPosition]; + this.C = this.Input[this.ReadPosition] } - this.Position = this.ReadPosition++; + this.Position = this.ReadPosition++ } PeekChar() { if (this.ReadPosition >= this.Input.length) { - return null; + return null } else { return this.Input[this.ReadPosition] } @@ -92,40 +92,40 @@ class Lexer { } ReadIdentifier(): string { - let str = ''; + let str = '' while (this.C !== undefined && isLetter(this.C)) { - str = str + this.C; + str = str + this.C this.ReadChar() } - return str; + return str } ReadNumber(): string { - let str = ''; + let str = '' while (this.C !== undefined && (isDigit(this.C) || this.C === '.') && parseFloat(str + this.C) != NaN) { - str = str + this.C; - this.ReadChar(); + str = str + this.C + this.ReadChar() } - return str; + return str } NextToken() { - let t: Token | null = null; + let t: Token | null = null - this.SkipWhitespace(); + this.SkipWhitespace() switch (this.C) { case '\n': - t = new Token(TokenType.NEW_LINE, this.C); + t = new Token(TokenType.NEW_LINE, this.C) break case '{': - t = new Token(TokenType.LBRACE, this.C); - break; + t = new Token(TokenType.LBRACE, this.C) + break case '}': - t = new Token(TokenType.RBRACE, this.C); - break; + t = new Token(TokenType.RBRACE, this.C) + break case '(': t = new Token(TokenType.LPAREN, this.C) break @@ -170,24 +170,24 @@ class Lexer { } break case undefined: - t = new Token(TokenType.EOF, ''); - break; + t = new Token(TokenType.EOF, '') + break default: if (this.C !== undefined && isLetter(this.C)) { - const literal = this.ReadIdentifier(); + const literal = this.ReadIdentifier() let tokenType = KEYWORDS[literal] || TokenType.IDENTIFIER - t = new Token(tokenType, literal); - return t; + t = new Token(tokenType, literal) + return t } else if (this.C !== undefined && isDigit(this.C)) { - const n = this.ReadNumber(); - t = new Token(TokenType.NUMBER, n); - return t; + const n = this.ReadNumber() + t = new Token(TokenType.NUMBER, n) + return t } else { - t = new Token(TokenType.ILLEGAL, this.C); + t = new Token(TokenType.ILLEGAL, this.C) } } - this.ReadChar(); - return t; + this.ReadChar() + return t } } @@ -253,11 +253,11 @@ class NumericExpr { constructor(left: number, lsign: Token, identifier: Token, rsign: Token, right: number) { - this.Left = left; - this.LSign = lsign; - this.Identifier = identifier; - this.Right = right; - this.RSign = rsign; + this.Left = left + this.LSign = lsign + this.Identifier = identifier + this.Right = right + this.RSign = rsign } toJSON(): EntityInfo { @@ -315,9 +315,9 @@ class Parser { Errors: string[] constructor(l: Lexer) { - this.L = l; + this.L = l - this.Errors = []; + this.Errors = [] this.CurrentToken = new Token(TokenType.INIT, '') this.PeekToken = new Token(TokenType.INIT, '') @@ -424,7 +424,7 @@ class Parser { assertToken(TokenType.IDENTIFIER, this.CurrentToken.T) - let str = ''; + let str = '' while (this.CurrentToken.T !== TokenType.NEW_LINE) { str = str + this.CurrentToken.Data @@ -445,17 +445,17 @@ class Parser { assertToken(TokenType.NUMBER, this.CurrentToken?.T) - let left = this.CurrentToken?.Data; + let left = this.CurrentToken?.Data this.nextToken() - let lsign = this.CurrentToken; // TODO: Check sign + let lsign = this.CurrentToken // TODO: Check sign this.nextToken() assertToken(TokenType.IDENTIFIER, this.CurrentToken?.T) - let identifier = this.CurrentToken; + let identifier = this.CurrentToken this.nextToken() @@ -465,14 +465,14 @@ class Parser { } - let rsign = this.CurrentToken; + let rsign = this.CurrentToken this.nextToken() assertToken(TokenType.NUMBER, this.CurrentToken?.T) - let right = this.CurrentToken?.Data; + let right = this.CurrentToken?.Data this.nextToken() @@ -493,7 +493,7 @@ class Parser { function Parse(data: string): SearchExpr { - const l = new Lexer(data); + const l = new Lexer(data) let p = new Parser(l) @@ -518,7 +518,7 @@ function isLetter(str: string): boolean { } function isDigit(str: string): boolean { - return str >='0' && str <= '9'; + return str >='0' && str <= '9' } @@ -542,7 +542,7 @@ export function ParseProfile(info: any[][]): EntityInfo { const parserData: any = info[info.length - 2] let resp = ParseIteratorProfile(parserData[1]) - const processorsProfile: string[][] = info[info.length - 1].slice(1); + const processorsProfile: string[][] = info[info.length - 1].slice(1) for (let i = 0; i < processorsProfile.length; i++) { const e = processorsProfile[i] @@ -555,7 +555,7 @@ export function ParseProfile(info: any[][]): EntityInfo { } } - return resp; + return resp } export function ParseIteratorProfile(data: any[]): EntityInfo { @@ -568,29 +568,29 @@ export function ParseIteratorProfile(data: any[]): EntityInfo { if (key === null) { while (data[x] === null) { - x = x + 1; + x = x + 1 } - key = data[x]; + key = data[x] } - let val = data[x + 1]; + let val = data[x + 1] - while (data[x + 1] === null) x = x + 1; - val = data[x + 1]; + while (data[x + 1] === null) x = x + 1 + val = data[x + 1] if (Array.isArray(val)) { let arr: any[] = [] while ((x + 1) < data.length && Array.isArray(data[x + 1])) { arr.push(data[x + 1]) - x = x + 1; + x = x + 1 } - props[key] = arr; + props[key] = arr } else { - props[key] = val; + props[key] = val } } - let childrens = props['Child iterators'] || []; + let childrens = props['Child iterators'] || [] return { id: uuidv4(), @@ -602,9 +602,9 @@ export function ParseIteratorProfile(data: any[]): EntityInfo { children: childrens.map(ParseIteratorProfile), } - // const t: EntityType = props['Type']; + // const t: EntityType = props['Type'] // if ([EntityType.UNION, EntityType.INTERSECT].includes(t)) { - // const l = data.length; + // const l = data.length // return { // id: uuidv4(), From 843df8a0ebdc593cd673cf2c392852c2bda3dfc3 Mon Sep 17 00:00:00 2001 From: Gnanesh Date: Fri, 30 Dec 2022 22:52:08 +0530 Subject: [PATCH 05/29] Center profile info --- redisinsight/ui/src/packages/ri-explain/src/Explain.tsx | 7 +++++-- .../ui/src/packages/ri-explain/src/styles/styles.less | 8 ++++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/redisinsight/ui/src/packages/ri-explain/src/Explain.tsx b/redisinsight/ui/src/packages/ri-explain/src/Explain.tsx index ec210b8094..3b0ec7259a 100644 --- a/redisinsight/ui/src/packages/ri-explain/src/Explain.tsx +++ b/redisinsight/ui/src/packages/ri-explain/src/Explain.tsx @@ -72,9 +72,11 @@ interface IProfilingTime { } function ExplainDraw({data, type, profilingTime}: {data: any, type: CoreType, profilingTime?: IProfilingTime}): JSX.Element { - const [done, setDone] = useState(false) const container = useRef(null) + const [done, setDone] = useState(false) + const [infoWidth, setInfoWidth] = useState(document.body.offsetWidth) + useEffect(() => { if (done) return @@ -110,6 +112,7 @@ function ExplainDraw({data, type, profilingTime}: {data: any, type: CoreType, pr } else { graph.resize(width, (b?.height || 585) + 100) } + setInfoWidth(width) } resize() @@ -212,7 +215,7 @@ function ExplainDraw({data, type, profilingTime}: {data: any, type: CoreType, pr
{ profilingTime && ( -
+
{profilingTime.profile}
Total Profile Time
diff --git a/redisinsight/ui/src/packages/ri-explain/src/styles/styles.less b/redisinsight/ui/src/packages/ri-explain/src/styles/styles.less index 7aac6c80ad..8939d8c317 100644 --- a/redisinsight/ui/src/packages/ri-explain/src/styles/styles.less +++ b/redisinsight/ui/src/packages/ri-explain/src/styles/styles.less @@ -24,6 +24,8 @@ --node-border-color: #3D3D3D; --node-border-shadow: #17336952; + --node-metadata-border-color: #3C3C3C; + --node-metadata-color: #B5B6C0; --node-background: #212021; --edge-background: #6B6B6B; --text-color: #DFE5EF; @@ -37,6 +39,8 @@ --node-border-color: #E4EAF2; --node-border-shadow: #17336926; + --node-metadata-border-color: #CCD7E7; + --node-metadata-color: #415681; --node-background: #FFF; --edge-background: #8992B3; --text-color: #173369; @@ -116,13 +120,13 @@ display: flex; justify-content: space-between; line-height: 18px; - color: #B5B6C0; + color: var(--node-metadata-color); padding-top: 12px; padding-bottom: 12px; height: 42px; //FIXME: fixed height? - border-top: 0.5px solid #3C3C3D; + border-top: 0.5px solid var(--node-metadata-border-color); .Time { display: flex; From 26f7264fadda8094f33046c1f80fee7d12fc2772 Mon Sep 17 00:00:00 2001 From: Zalenski Egor <63463140+zalenskiSofteq@users.noreply.github.com> Date: Thu, 5 Jan 2023 14:57:29 +0800 Subject: [PATCH 06/29] #RI-3726 - Prepare for vizualizations for Graph and FT profile and explain commands --- redisinsight/ui/src/packages/ri-explain/package.json | 5 ++++- .../ri-explain/src/assets/table_view_icon_dark.svg | 6 ++++++ .../ri-explain/src/assets/table_view_icon_light.svg | 6 ++++++ scripts/build-statics.cmd | 9 +++++++++ scripts/build-statics.sh | 7 +++++++ 5 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 redisinsight/ui/src/packages/ri-explain/src/assets/table_view_icon_dark.svg create mode 100644 redisinsight/ui/src/packages/ri-explain/src/assets/table_view_icon_light.svg diff --git a/redisinsight/ui/src/packages/ri-explain/package.json b/redisinsight/ui/src/packages/ri-explain/package.json index ee627ca2b8..cd93fbb92c 100644 --- a/redisinsight/ui/src/packages/ri-explain/package.json +++ b/redisinsight/ui/src/packages/ri-explain/package.json @@ -15,10 +15,11 @@ "version": "0.0.1", "scripts": { "start": "cross-env NODE_ENV=development parcel serve src/index.html", - "build": "rimraf dist && cross-env NODE_ENV=production concurrently \"yarn build:js && yarn minify:js\" \"yarn build:css\"", + "build": "rimraf dist && cross-env NODE_ENV=production concurrently \"yarn build:js && yarn minify:js\" \"yarn build:css\" \"yarn build:assets\"", "build-lite": "rm dist/*.js && cross-env NODE_ENV=production concurrently \"yarn build:js && yarn minify:js\"", "build:js": "parcel build src/main.tsx --dist-dir dist", "build:css": "parcel build src/styles/styles.less --dist-dir dist", + "build:assets": "parcel build src/assets/**/* --dist-dir dist", "minify:js": "terser -- dist/main.js > dist/index.js && rimraf dist/main.js" }, "targets": { @@ -37,6 +38,8 @@ "FT.EXPLAINCLI", "FT.PROFILE" ], + "iconDark": "./dist/table_view_icon_dark.svg", + "iconLight": "./dist/table_view_icon_light.svg", "description": "Profile/Explain plugin Visualization", "default": true } diff --git a/redisinsight/ui/src/packages/ri-explain/src/assets/table_view_icon_dark.svg b/redisinsight/ui/src/packages/ri-explain/src/assets/table_view_icon_dark.svg new file mode 100644 index 0000000000..5831ce0e11 --- /dev/null +++ b/redisinsight/ui/src/packages/ri-explain/src/assets/table_view_icon_dark.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/redisinsight/ui/src/packages/ri-explain/src/assets/table_view_icon_light.svg b/redisinsight/ui/src/packages/ri-explain/src/assets/table_view_icon_light.svg new file mode 100644 index 0000000000..7a880c88f3 --- /dev/null +++ b/redisinsight/ui/src/packages/ri-explain/src/assets/table_view_icon_light.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/scripts/build-statics.cmd b/scripts/build-statics.cmd index c85c7d3bf0..68c321f954 100644 --- a/scripts/build-statics.cmd +++ b/scripts/build-statics.cmd @@ -38,3 +38,12 @@ if not exist "%PLUGINS_DIR%\redistimeseries-app" mkdir "%PLUGINS_DIR%\redistimes if not exist "%PLUGINS_DIR%\redistimeseries-app\dist" mkdir "%PLUGINS_DIR%\redistimeseries-app\dist" xcopy "%REDISTIMESERSIES_DIR%\dist" "%PLUGINS_DIR%\redistimeseries-app\dist\" /s /e /y copy "%REDISTIMESERSIES_DIR%\package.json" "%PLUGINS_DIR%\redistimeseries-app\" + +:: Build ri-explain plugin +set RI_EXPLIAIN_DIR=".\redisinsight\ui\src\packages\ri-explain" +call yarn --cwd "%RI_EXPLIAIN_DIR%" +call yarn --cwd "%RI_EXPLIAIN_DIR%" build +if not exist "%PLUGINS_DIR%\ri-explain" mkdir "%PLUGINS_DIR%\ri-explain" +if not exist "%PLUGINS_DIR%\ri-explain\dist" mkdir "%PLUGINS_DIR%\ri-explain\dist" +xcopy "%RI_EXPLIAIN_DIR%\dist" "%PLUGINS_DIR%\ri-explain\dist\" /s /e /y +copy "%RI_EXPLIAIN_DIR%\package.json" "%PLUGINS_DIR%\ri-explain\" diff --git a/scripts/build-statics.sh b/scripts/build-statics.sh index 73eff63518..2f6db15e19 100644 --- a/scripts/build-statics.sh +++ b/scripts/build-statics.sh @@ -33,3 +33,10 @@ yarn --cwd "${REDISTIMESERIES_DIR}" yarn --cwd "${REDISTIMESERIES_DIR}" build mkdir -p "${PLUGINS_DIR}/redistimeseries-app" cp -R "${REDISTIMESERIES_DIR}/dist" "${REDISTIMESERIES_DIR}/package.json" "${PLUGINS_DIR}/redistimeseries-app" + +# Build ri-explain plugin +RI_EXPLIAIN_DIR="./redisinsight/ui/src/packages/ri-explain" +yarn --cwd "${RI_EXPLIAIN_DIR}" +yarn --cwd "${RI_EXPLIAIN_DIR}" build +mkdir -p "${PLUGINS_DIR}/ri-explain" +cp -R "${RI_EXPLIAIN_DIR}/dist" "${RI_EXPLIAIN_DIR}/package.json" "${PLUGINS_DIR}/ri-explain" From 9e47c5cd80d6b199747ec3a1453c1134bb6d6710 Mon Sep 17 00:00:00 2001 From: Zalenski Egor <63463140+zalenskiSofteq@users.noreply.github.com> Date: Thu, 5 Jan 2023 18:23:16 +0800 Subject: [PATCH 07/29] #RI-3726 - Prepare for vizualizations for Graph and FT profile and explain commands --- redisinsight/ui/src/packages/ri-explain/package.json | 4 ++-- .../packages/ri-explain/src/assets/profile_icon_dark.svg | 7 +++++++ .../packages/ri-explain/src/assets/profile_icon_light.svg | 7 +++++++ .../ri-explain/src/assets/table_view_icon_dark.svg | 6 ------ .../ri-explain/src/assets/table_view_icon_light.svg | 6 ------ 5 files changed, 16 insertions(+), 14 deletions(-) create mode 100644 redisinsight/ui/src/packages/ri-explain/src/assets/profile_icon_dark.svg create mode 100644 redisinsight/ui/src/packages/ri-explain/src/assets/profile_icon_light.svg delete mode 100644 redisinsight/ui/src/packages/ri-explain/src/assets/table_view_icon_dark.svg delete mode 100644 redisinsight/ui/src/packages/ri-explain/src/assets/table_view_icon_light.svg diff --git a/redisinsight/ui/src/packages/ri-explain/package.json b/redisinsight/ui/src/packages/ri-explain/package.json index cd93fbb92c..0a94b08ef5 100644 --- a/redisinsight/ui/src/packages/ri-explain/package.json +++ b/redisinsight/ui/src/packages/ri-explain/package.json @@ -38,8 +38,8 @@ "FT.EXPLAINCLI", "FT.PROFILE" ], - "iconDark": "./dist/table_view_icon_dark.svg", - "iconLight": "./dist/table_view_icon_light.svg", + "iconDark": "./dist/profile_icon_dark.svg", + "iconLight": "./dist/profile_icon_light.svg", "description": "Profile/Explain plugin Visualization", "default": true } diff --git a/redisinsight/ui/src/packages/ri-explain/src/assets/profile_icon_dark.svg b/redisinsight/ui/src/packages/ri-explain/src/assets/profile_icon_dark.svg new file mode 100644 index 0000000000..f335f3f6cb --- /dev/null +++ b/redisinsight/ui/src/packages/ri-explain/src/assets/profile_icon_dark.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/redisinsight/ui/src/packages/ri-explain/src/assets/profile_icon_light.svg b/redisinsight/ui/src/packages/ri-explain/src/assets/profile_icon_light.svg new file mode 100644 index 0000000000..c092a49e88 --- /dev/null +++ b/redisinsight/ui/src/packages/ri-explain/src/assets/profile_icon_light.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/redisinsight/ui/src/packages/ri-explain/src/assets/table_view_icon_dark.svg b/redisinsight/ui/src/packages/ri-explain/src/assets/table_view_icon_dark.svg deleted file mode 100644 index 5831ce0e11..0000000000 --- a/redisinsight/ui/src/packages/ri-explain/src/assets/table_view_icon_dark.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/redisinsight/ui/src/packages/ri-explain/src/assets/table_view_icon_light.svg b/redisinsight/ui/src/packages/ri-explain/src/assets/table_view_icon_light.svg deleted file mode 100644 index 7a880c88f3..0000000000 --- a/redisinsight/ui/src/packages/ri-explain/src/assets/table_view_icon_light.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - From 405ac842dcf01d497691956d434f034a53a9a0ba Mon Sep 17 00:00:00 2001 From: Gnanesh Date: Sat, 7 Jan 2023 22:40:36 +0530 Subject: [PATCH 08/29] Add support for redisearch cluster profile. src/Explain.tsx - Detect redisearch cluster profile and select the appropriate parser. src/parser.ts - Add redisearch cluster profile parsing and also track parentIds which can be used to add hover support later. --- .../src/packages/ri-explain/src/Explain.tsx | 47 +++++++------- .../ui/src/packages/ri-explain/src/parser.ts | 61 +++++++++++++++++-- 2 files changed, 81 insertions(+), 27 deletions(-) diff --git a/redisinsight/ui/src/packages/ri-explain/src/Explain.tsx b/redisinsight/ui/src/packages/ri-explain/src/Explain.tsx index 3b0ec7259a..193fdeaa2e 100644 --- a/redisinsight/ui/src/packages/ri-explain/src/Explain.tsx +++ b/redisinsight/ui/src/packages/ri-explain/src/Explain.tsx @@ -3,7 +3,7 @@ import { Model, Graph } from '@antv/x6' import { register} from '@antv/x6-react-shape' import Hierarchy from '@antv/hierarchy' -import { ParseExplain, ParseProfile } from './parser' +import { EntityInfo, ParseExplain, ParseProfile, ParseProfileCluster } from './parser' import { ExplainNode, ProfileNode } from './Node' interface IExplain { @@ -21,15 +21,25 @@ export default function Explain(props: IExplain): JSX.Element { if (command.toLowerCase() == 'ft.profile') { const info = props.data[0].response[1] - const profilingTime: IProfilingTime = { - profile: info[0][1], - parsing: info[1][1], - pipelineCreation: info[2][1], + let data: EntityInfo; + let profilingTime: IProfilingTime = {}; + + if (info.length > 5 && typeof info[0] === 'string' && info[0].toLowerCase().startsWith('shard')) { + let cluster: Object; + [cluster, data] = ParseProfileCluster(info) + cluster['Coordinator'].forEach((kv: [string, string]) => profilingTime[kv[0]] = kv[1]); + } else { + data = ParseProfile(info) + profilingTime = { + 'Total Profile Time': info[0][1], + 'Parsing Time': info[1][1], + 'Pipeline Creation Time': info[2][1], + } } return ( @@ -66,9 +76,7 @@ register({ const isDarkTheme = document.body.classList.contains('theme_DARK') interface IProfilingTime { - profile: string - parsing: string - pipelineCreation: string + [key: string]: string } function ExplainDraw({data, type, profilingTime}: {data: any, type: CoreType, profilingTime?: IProfilingTime}): JSX.Element { @@ -174,6 +182,7 @@ function ExplainDraw({data, type, profilingTime}: {data: any, type: CoreType, pr if (data.children) { data.children.forEach((item: any) => { model.edges?.push({ + id: `${data.id}-${item.id}`, source: data.id, target: item.id, router: { @@ -216,18 +225,14 @@ function ExplainDraw({data, type, profilingTime}: {data: any, type: CoreType, pr
{ profilingTime && (
-
-
{profilingTime.profile}
-
Total Profile Time
-
-
-
{profilingTime.parsing}
-
Parsing Time
-
-
-
{profilingTime.pipelineCreation}
-
Pipeline Creation Time
-
+ { + Object.keys(profilingTime).map(key => ( +
+
{profilingTime[key]}
+
{key}
+
+ )) + }
)}
diff --git a/redisinsight/ui/src/packages/ri-explain/src/parser.ts b/redisinsight/ui/src/packages/ri-explain/src/parser.ts index 055e869bfa..8ca69c485b 100644 --- a/redisinsight/ui/src/packages/ri-explain/src/parser.ts +++ b/redisinsight/ui/src/packages/ri-explain/src/parser.ts @@ -206,6 +206,8 @@ export enum EntityType { Scorer = 'Scorer', Sorter = 'Sorter', Loader = 'Loader', + + CLUSTER_MERGE = 'CLUSTER MERGE' } @@ -218,6 +220,7 @@ export interface EntityInfo { time?: string counter?: string size?: string + parentId?: string } class Expr { @@ -538,6 +541,50 @@ function assertToken(expected: TokenType, actual: TokenType | undefined) { assert(expected === actual, `Expected ${expected}, Actual: ${actual}`) } + +export function ParseProfileCluster(info: any[]): [Object, EntityInfo] { + + let i = 0; + let clusterInfo: {[key: string]: any[]} = {} + let key: string = ''; + while (i < info.length) { + if (Array.isArray(info[i])) { + clusterInfo[key].push(info[i]); + } else if (typeof(info[i]) === 'string') { + key = info[i]; + clusterInfo[key] = [] + } else { + throw new Error("Expected array or string - " + JSON.stringify(info)) + } + i++; + } + + let shards: EntityInfo[] = [] + + Object.keys(clusterInfo).map(k => { + if (k.toLowerCase().startsWith('shard')) { + let shardProfileInfo = ParseProfile(clusterInfo[k]); + shards.push({ + id: uuidv4(), + type: k as EntityType, + children: [shardProfileInfo], + }) + } + }) + + return [ + clusterInfo, + { + id: uuidv4(), + type: EntityType.CLUSTER_MERGE, + // children: shards, + children: Object.keys(clusterInfo).filter(k => k.toLowerCase().startsWith('shard')).map(k => + ParseProfile(clusterInfo[k]) + ) + } + ] +} + export function ParseProfile(info: any[][]): EntityInfo { const parserData: any = info[info.length - 2] let resp = ParseIteratorProfile(parserData[1]) @@ -546,12 +593,13 @@ export function ParseProfile(info: any[][]): EntityInfo { for (let i = 0; i < processorsProfile.length; i++) { const e = processorsProfile[i] + let id = uuidv4() resp = { - id: uuidv4(), + id, type: e[1] as EntityType, time: e[3], counter: e[5], - children: [resp], + children: [{...resp, parentId: id}], } } @@ -590,16 +638,17 @@ export function ParseIteratorProfile(data: any[]): EntityInfo { } } - let childrens = props['Child iterators'] || [] + let childrens = props['Child iterators'] || props['Child Iterators'] || [] + let id = uuidv4() return { - id: uuidv4(), - type: props['Type'], + id, + type: props['Type'] || props['TYPE'], time: props['Time'], counter: props['Counter'], size: props['Size'], data: props['Term'], - children: childrens.map(ParseIteratorProfile), + children: childrens.map(ParseIteratorProfile).map((d: EntityInfo) => ({...d, parentId: id})), } // const t: EntityType = props['Type'] From 398e6e89f1f15ebcb36d52cd225b2484d2367fce Mon Sep 17 00:00:00 2001 From: Gnanesh Date: Sun, 8 Jan 2023 11:17:25 +0530 Subject: [PATCH 09/29] Add support for GRAPH.EXPLAIN and GRAPH.PROFILE --- .../ui/src/packages/ri-explain/package.json | 7 +-- .../src/packages/ri-explain/src/Explain.tsx | 30 +++++++++--- .../ui/src/packages/ri-explain/src/parser.ts | 49 ++++++++++++++++++- .../ui/src/packages/ri-explain/yarn.lock | 5 -- 4 files changed, 74 insertions(+), 17 deletions(-) diff --git a/redisinsight/ui/src/packages/ri-explain/package.json b/redisinsight/ui/src/packages/ri-explain/package.json index 0a94b08ef5..585713f5f0 100644 --- a/redisinsight/ui/src/packages/ri-explain/package.json +++ b/redisinsight/ui/src/packages/ri-explain/package.json @@ -31,12 +31,14 @@ "visualizations": [ { "id": "profile-explain-viz", - "name": "Profile-Explain", + "name": "Visualization", "activationMethod": "renderCore", "matchCommands": [ "FT.EXPLAIN", "FT.EXPLAINCLI", - "FT.PROFILE" + "FT.PROFILE", + "GRAPH.EXPLAIN", + "GRAPH.PROFILE" ], "iconDark": "./dist/profile_icon_dark.svg", "iconLight": "./dist/profile_icon_light.svg", @@ -64,7 +66,6 @@ "prop-types": "^15.8.1", "react": "^18.2.0", "react-dom": "^18.2.0", - "redisinsight-plugin-sdk": "^1.0.0", "uuid": "^9.0.0" } } diff --git a/redisinsight/ui/src/packages/ri-explain/src/Explain.tsx b/redisinsight/ui/src/packages/ri-explain/src/Explain.tsx index 193fdeaa2e..83a78df24a 100644 --- a/redisinsight/ui/src/packages/ri-explain/src/Explain.tsx +++ b/redisinsight/ui/src/packages/ri-explain/src/Explain.tsx @@ -3,7 +3,14 @@ import { Model, Graph } from '@antv/x6' import { register} from '@antv/x6-react-shape' import Hierarchy from '@antv/hierarchy' -import { EntityInfo, ParseExplain, ParseProfile, ParseProfileCluster } from './parser' +import { + CoreType, + EntityInfo, + ParseExplain, + ParseGraph, + ParseProfile, + ParseProfileCluster, +} from './parser' import { ExplainNode, ProfileNode } from './Node' interface IExplain { @@ -11,14 +18,21 @@ interface IExplain { data: [{response: string[] | string | any}] } -enum CoreType { - Profile, - Explain, -} - export default function Explain(props: IExplain): JSX.Element { - const command = props.command.split(' ')[0] - if (command.toLowerCase() == 'ft.profile') { + const command = props.command.split(' ')[0].toLowerCase() + + if (command.startsWith('graph')) { + const info = props.data[0].response + const resp = ParseGraph(info); + return ( + + ) + } + + if (command == 'ft.profile') { const info = props.data[0].response[1] let data: EntityInfo; diff --git a/redisinsight/ui/src/packages/ri-explain/src/parser.ts b/redisinsight/ui/src/packages/ri-explain/src/parser.ts index 8ca69c485b..639e174f5e 100644 --- a/redisinsight/ui/src/packages/ri-explain/src/parser.ts +++ b/redisinsight/ui/src/packages/ri-explain/src/parser.ts @@ -640,7 +640,7 @@ export function ParseIteratorProfile(data: any[]): EntityInfo { let childrens = props['Child iterators'] || props['Child Iterators'] || [] - let id = uuidv4() + const id = uuidv4() return { id, type: props['Type'] || props['TYPE'], @@ -681,3 +681,50 @@ export function ParseIteratorProfile(data: any[]): EntityInfo { // } // } } + +export enum CoreType { + Profile, + Explain, +} + +export function ParseGraph(output: string[]) : EntityInfo { + + const entities = [...output].reverse() + + const first = entities.pop() as string + + function ParseEntity(entity: string, children: EntityInfo[]): EntityInfo { + const info = entity.trim().split('|') + + let time: string | undefined = '', size: string | undefined = '' + + const metaData = info.slice(-1)[0].trim() + + // Is GRAPH.PROFILE output + if (metaData.startsWith('Records produced')) { + const sizeAndTime = metaData.trim().match( + /^Records produced: (?[0-9]*), Execution time: (?
- { snippet && -
- {snippet} -
+ { + snippet && ( +
+ {snippet} +
+ ) }
) diff --git a/redisinsight/ui/src/packages/ri-explain/src/parser.ts b/redisinsight/ui/src/packages/ri-explain/src/parser.ts index f20a376d0d..f34e8d2dea 100644 --- a/redisinsight/ui/src/packages/ri-explain/src/parser.ts +++ b/redisinsight/ui/src/packages/ri-explain/src/parser.ts @@ -544,26 +544,26 @@ function assertToken(expected: TokenType, actual: TokenType | undefined) { export function ParseProfileCluster(info: any[]): [Object, EntityInfo] { - let i = 0; let clusterInfo: {[key: string]: any[]} = {} - let key: string = ''; + let key: string = '' + let i = 0 while (i < info.length) { if (Array.isArray(info[i])) { - clusterInfo[key].push(info[i]); + clusterInfo[key].push(info[i]) } else if (typeof(info[i]) === 'string') { - key = info[i]; + key = info[i] clusterInfo[key] = [] } else { throw new Error("Expected array or string - " + JSON.stringify(info)) } - i++; + i++ } let shards: EntityInfo[] = [] Object.keys(clusterInfo).map(k => { if (k.toLowerCase().startsWith('shard')) { - let shardProfileInfo = ParseProfile(clusterInfo[k]); + let shardProfileInfo = ParseProfile(clusterInfo[k]) shards.push({ id: uuidv4(), type: k as EntityType, diff --git a/redisinsight/ui/src/packages/ri-explain/types/@elastic/index.d.ts b/redisinsight/ui/src/packages/ri-explain/types/@elastic/index.d.ts deleted file mode 100644 index 7b41812218..0000000000 --- a/redisinsight/ui/src/packages/ri-explain/types/@elastic/index.d.ts +++ /dev/null @@ -1,60 +0,0 @@ -// yourLibrary.d.ts - -declare module '@elastic/eui/es/components/icon/icon' { - export function appendIconComponentCache(args: any): any -} - -declare module '@elastic/eui/es/components/icon/assets/magnifyWithPlus' { - export { - icon - } -} - -declare module '@elastic/eui/es/components/icon/assets/magnifyWithMinus' { - export { - icon - } -} - -declare module '@elastic/eui/es/components/icon/assets/bullseye' { - export { - icon - } -} - -declare module '@elastic/eui/es/components/icon/assets/editorItemAlignLeft' { - export { - icon - } -} - -declare module '@elastic/eui/es/components/icon/assets/editorItemAlignRight' { - export { - icon - } -} - -declare module '@elastic/eui/es/components/icon/assets/editorItemAlignCenter' { - export { - icon - } -} - -declare module '@elastic/eui/es/components/icon/assets/arrow_left' { - export { - icon - } -} - -declare module '@elastic/eui/es/components/icon/assets/arrow_right' { - export { - icon - } -} - - -declare module '@elastic/eui/es/components/icon/assets/arrow_down' { - export { - icon - } -} From fd4e1516eba79d42ca63a4679b7161cf442dfefe Mon Sep 17 00:00:00 2001 From: Gnanesh Date: Wed, 11 Jan 2023 18:22:09 +0530 Subject: [PATCH 12/29] Add support for hovering for search queries --- .../src/packages/ri-explain/src/Explain.tsx | 24 +++++++++++ .../ui/src/packages/ri-explain/src/parser.ts | 40 +++++++++++++++++-- .../ri-explain/src/styles/styles.less | 5 +++ 3 files changed, 65 insertions(+), 4 deletions(-) diff --git a/redisinsight/ui/src/packages/ri-explain/src/Explain.tsx b/redisinsight/ui/src/packages/ri-explain/src/Explain.tsx index 5c9b4d1bbf..c1b02819b6 100644 --- a/redisinsight/ui/src/packages/ri-explain/src/Explain.tsx +++ b/redisinsight/ui/src/packages/ri-explain/src/Explain.tsx @@ -10,6 +10,7 @@ import { ParseGraph, ParseProfile, ParseProfileCluster, + GetAncestors, } from './parser' import { ExplainNode, ProfileNode } from './Node' @@ -123,6 +124,29 @@ function ExplainDraw({data, type, profilingTime}: {data: any, type: CoreType, pr }) graph.on("resize", () => graph.centerContent()) + graph.on("node:mouseenter", x => { + const {id} = x.node.getData() + const ancestors = GetAncestors(data, id, {found: false, pairs: []}) + if (ancestors.found) { + ancestors.pairs.forEach(p => { + document.querySelector(`[data-cell-id='${p[0]}-${p[1]}']`)?.childNodes.forEach(k => { + (k as any).setAttribute("style", "stroke: #85A2FE; stroke-linecap: butt; stroke-width: 2px") + }) + }) + } + }) + + graph.on("node:mouseleave", x => { + const {id} = x.node.getData() + const ancestors = GetAncestors(data, id, {found: false, pairs: []}) + if (ancestors.found) { + ancestors.pairs.forEach(p => { + document.querySelector(`[data-cell-id='${p[0]}-${p[1]}']`)?.childNodes.forEach(k => { + (k as any).setAttribute("style", "") + }) + }) + } + }) function resize() { const isFullScreen = parent.document.body.getElementsByClassName('fullscreen').length > 0 diff --git a/redisinsight/ui/src/packages/ri-explain/src/parser.ts b/redisinsight/ui/src/packages/ri-explain/src/parser.ts index f34e8d2dea..031e2b73fa 100644 --- a/redisinsight/ui/src/packages/ri-explain/src/parser.ts +++ b/redisinsight/ui/src/packages/ri-explain/src/parser.ts @@ -223,6 +223,36 @@ export interface EntityInfo { parentId?: string } +interface IAncestors { + found: boolean + pairs: [string, string][] +} + +export function GetAncestors(info: EntityInfo, searchId: string, a: IAncestors): IAncestors { + if (searchId === info.id) { + return { + found: true, + pairs: info.parentId ? [[info.parentId, info.id]] : [] + } + } else { + let r: IAncestors = {...a} + for (let i = 0; i < info.children.length; i++) { + let c = info.children[i] + let ci = GetAncestors(c, searchId, a) + if (ci.found) { + r.found = true + r.pairs = [...a.pairs, ...ci.pairs] + if (info.parentId) { + r.pairs = [...r.pairs, [info.parentId, info.id]] + } + return r + } + } + return r + } +} + + class Expr { Core: string Type?: string @@ -286,10 +316,11 @@ class IntersectExpr { } toJSON(): EntityInfo { + const id = uuidv4() return { - id: uuidv4(), + id, type: EntityType.INTERSECT, - children: this.Core.map(x => x.toJSON()), + children: this.Core.map(x => x.toJSON()).map((d: EntityInfo) => ({...d, parentId: id})), } } } @@ -302,10 +333,11 @@ class UnionExpr { } toJSON(): EntityInfo { + const id = uuidv4() return { - id: uuidv4(), + id, type: EntityType.UNION, - children: this.Core.map(x => x.toJSON()) + children: this.Core.map(x => x.toJSON()).map((d: EntityInfo) => ({...d, parentId: id})) } } } diff --git a/redisinsight/ui/src/packages/ri-explain/src/styles/styles.less b/redisinsight/ui/src/packages/ri-explain/src/styles/styles.less index 8939d8c317..258f1eaa33 100644 --- a/redisinsight/ui/src/packages/ri-explain/src/styles/styles.less +++ b/redisinsight/ui/src/packages/ri-explain/src/styles/styles.less @@ -98,6 +98,11 @@ background-color: var(--node-background); color: var(--text-color); + + &:hover { + border: 1px solid #85A2FE !important; + } + .Main { display: flex; From 70853754efe27d22134e648b675e42a36514ade1 Mon Sep 17 00:00:00 2001 From: Gnanesh Date: Sat, 14 Jan 2023 07:33:41 +0530 Subject: [PATCH 13/29] Add proper recursive redisgraph PROFILE/EXPLAIN level parser --- .../src/packages/ri-explain/src/Explain.tsx | 12 ++- .../ui/src/packages/ri-explain/src/Node.tsx | 14 ++- .../ui/src/packages/ri-explain/src/main.tsx | 4 +- .../ui/src/packages/ri-explain/src/parser.ts | 91 +++++++++++++------ .../ri-explain/src/styles/styles.less | 36 +++++++- 5 files changed, 119 insertions(+), 38 deletions(-) diff --git a/redisinsight/ui/src/packages/ri-explain/src/Explain.tsx b/redisinsight/ui/src/packages/ri-explain/src/Explain.tsx index c1b02819b6..64a81e4731 100644 --- a/redisinsight/ui/src/packages/ri-explain/src/Explain.tsx +++ b/redisinsight/ui/src/packages/ri-explain/src/Explain.tsx @@ -7,7 +7,7 @@ import { CoreType, EntityInfo, ParseExplain, - ParseGraph, + ParseGraphV2, ParseProfile, ParseProfileCluster, GetAncestors, @@ -24,7 +24,9 @@ export default function Explain(props: IExplain): JSX.Element { if (command.startsWith('graph')) { const info = props.data[0].response - const resp = ParseGraph(info) + + const resp = ParseGraphV2(info) + return ( { + const parentNode = document.querySelector(`#node-${p[0]}`) + parentNode?.classList.add('ProfileContainerHover') document.querySelector(`[data-cell-id='${p[0]}-${p[1]}']`)?.childNodes.forEach(k => { (k as any).setAttribute("style", "stroke: #85A2FE; stroke-linecap: butt; stroke-width: 2px") }) @@ -141,6 +145,8 @@ function ExplainDraw({data, type, profilingTime}: {data: any, type: CoreType, pr const ancestors = GetAncestors(data, id, {found: false, pairs: []}) if (ancestors.found) { ancestors.pairs.forEach(p => { + const parentNode = document.querySelector(`#node-${p[0]}`) + parentNode?.classList.remove('ProfileContainerHover') document.querySelector(`[data-cell-id='${p[0]}-${p[1]}']`)?.childNodes.forEach(k => { (k as any).setAttribute("style", "") }) @@ -198,7 +204,7 @@ function ExplainDraw({data, type, profilingTime}: {data: any, type: CoreType, pr nodeProps = { shape: 'react-profile-node', width: 320, - height: 84, + height: (info.snippet ? 114 : 84), } } diff --git a/redisinsight/ui/src/packages/ri-explain/src/Node.tsx b/redisinsight/ui/src/packages/ri-explain/src/Node.tsx index 90bf38eea0..c26d965261 100644 --- a/redisinsight/ui/src/packages/ri-explain/src/Node.tsx +++ b/redisinsight/ui/src/packages/ri-explain/src/Node.tsx @@ -64,7 +64,7 @@ function NodeToolTipContent(props: INodeToolTip) { export function ProfileNode(props: INodeProps) { const info: EntityInfo = (props as any).node.getData() - const {data, type, snippet, time, counter, size} = info + const {id, data, type, snippet, time, counter, size} = info let items = {} @@ -76,13 +76,19 @@ export function ProfileNode(props: INodeProps) { items['Size'] = size } - return ( -
+
{data ? data : type}
{[EntityType.GEO, EntityType.NUMERIC, EntityType.TEXT, EntityType.TAG].includes(type) ? type : ''}
+ { + snippet && ( +
+ {snippet} +
+ ) + }
}>
@@ -93,7 +99,7 @@ export function ProfileNode(props: INodeProps) { }>
{counter}
-
+
diff --git a/redisinsight/ui/src/packages/ri-explain/src/main.tsx b/redisinsight/ui/src/packages/ri-explain/src/main.tsx index 07f5caacf3..ab5f51b16b 100644 --- a/redisinsight/ui/src/packages/ri-explain/src/main.tsx +++ b/redisinsight/ui/src/packages/ri-explain/src/main.tsx @@ -10,11 +10,11 @@ interface Props { import { appendIconComponentCache } from '@elastic/eui/es/components/icon/icon' import { icon as EuiIconClock } from '@elastic/eui/es/components/icon/assets/clock' -import { icon as EuiIconIInCircle } from '@elastic/eui/es/components/icon/assets/iInCircle' +import { icon as EuiIconReportingApp } from '@elastic/eui/es/components/icon/assets/app_reporting' appendIconComponentCache({ clock: EuiIconClock, - iInCircle: EuiIconIInCircle, + reportingApp: EuiIconReportingApp, }) const renderApp = (element: JSX.Element) => render( diff --git a/redisinsight/ui/src/packages/ri-explain/src/parser.ts b/redisinsight/ui/src/packages/ri-explain/src/parser.ts index 031e2b73fa..a6b1c735db 100644 --- a/redisinsight/ui/src/packages/ri-explain/src/parser.ts +++ b/redisinsight/ui/src/packages/ri-explain/src/parser.ts @@ -221,6 +221,7 @@ export interface EntityInfo { counter?: string size?: string parentId?: string + level?: number } interface IAncestors { @@ -719,44 +720,80 @@ export enum CoreType { Explain, } -export function ParseGraph(output: string[]) : EntityInfo { +export function getOutputLevel(output: string) { + let i = 0 + while (output[i] == ' ' && i < output.length) { + i++ + } + return (i > 0 ? i / 4 : 0) + 1 +} - const entities = [...output].reverse() +function ParseEntity(entity: string, children: EntityInfo[]): EntityInfo { + const info = entity.trim().split('|') - const first = entities.pop() as string + let time: string | undefined = '', size: string | undefined = '' - function ParseEntity(entity: string, children: EntityInfo[]): EntityInfo { - const info = entity.trim().split('|') + const metaData = info.slice(-1)[0].trim() - let time: string | undefined = '', size: string | undefined = '' + // Is GRAPH.PROFILE output + if (metaData.startsWith('Records produced')) { - const metaData = info.slice(-1)[0].trim() + [size, time] = metaData.trim().split(',') - // Is GRAPH.PROFILE output - if (metaData.startsWith('Records produced')) { + size = size.split(': ')[1] + time = time.split(': ')[1].split(' ')[0] + info.pop() + } - [size, time] = metaData.trim().split(',') + const snippet = [...info.slice(1)].join('|').trim() - size = size.split(': ')[1] - time = time.split(': ')[1].split(' ')[0] - info.pop() - } + return { + id: uuidv4(), + type: info[0] as EntityType, + snippet, + children, + time, + size, + counter: size, + level: getOutputLevel(entity), + } +} - const snippet = [...info.slice(1)].join('|').trim() - return { - id: uuidv4(), - type: info[0] as EntityType, - snippet, - children, - time, - size, - counter: size, +export function ParseGraphV2(output: string[]) { + + const level = getOutputLevel(output[0]) + 1 + + let entity = ParseEntity(output[0], []) + let children: EntityInfo[] = [] + + let pairs: [number, number][] = [] + + let s: number | null = null, e: number | null = null + let i = 1 + + while (i < output.length) { + let l = getOutputLevel(output[i]) + if (l === level) { + if (s == null) { + s = i + } else if (s != null) { + pairs.push([s, i]) + s = i + } } + i++ + } + + if (s !== null) { + pairs.push([s, i]) + } + + for (let k = 0; k < pairs.length; k++) { + let p = pairs[k] + children.push({...ParseGraphV2(output.slice(p[0], p[1])), parentId: entity.id}) } - return entities.reduce( - (a, c) => ParseEntity(c, [a]), - ParseEntity(first, []) - ) + entity.children = children + return entity } diff --git a/redisinsight/ui/src/packages/ri-explain/src/styles/styles.less b/redisinsight/ui/src/packages/ri-explain/src/styles/styles.less index 258f1eaa33..0f37d8c698 100644 --- a/redisinsight/ui/src/packages/ri-explain/src/styles/styles.less +++ b/redisinsight/ui/src/packages/ri-explain/src/styles/styles.less @@ -80,9 +80,15 @@ background-color: var(--tooltip-background) !important; } +.ProfileContainerHover { + border: 1px solid #85A2FE !important; + + width: 50px; +} + .ProfileContainer { width: 320px; - max-height: 84px; + min-height: 84px; padding-left: 18px !important; padding-right: 18px !important; @@ -121,6 +127,14 @@ } } + .Footer{ + font-size: 12px; + padding-top: 4px; + padding-bottom: 12px; + height: auto; + color: #B5B6C0; + } + .MetaData { display: flex; justify-content: space-between; @@ -137,16 +151,30 @@ display: flex; align-items: center; font-size: 12px; + + div:first-child { + padding-right: 5px; + } + } .Size { display: flex; align-items: center; font-size: 13px; + + div:first-child { + padding-right: 5px; + } + + } .IconContainer { - align-self: end; + > svg { + width: 12px !important; + height: 13px !important; + } } } } @@ -168,6 +196,10 @@ background-color: var(--node-background); + &:hover { + border: 1px solid #85A2FE !important; + } + .Main { // height: 42px; font-size: 13px; From 7569daf46c960026c59cd562e8331035ad054bc4 Mon Sep 17 00:00:00 2001 From: Gnanesh Date: Sat, 14 Jan 2023 09:07:06 +0530 Subject: [PATCH 14/29] - Suffix time with 'ms' - Use "Records Produced" for graph --- .../ui/src/packages/ri-explain/src/Explain.tsx | 17 +++++++++++++++-- .../ui/src/packages/ri-explain/src/Node.tsx | 13 +++++++++---- .../ui/src/packages/ri-explain/src/parser.ts | 6 ++++++ 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/redisinsight/ui/src/packages/ri-explain/src/Explain.tsx b/redisinsight/ui/src/packages/ri-explain/src/Explain.tsx index 64a81e4731..cb61100eb9 100644 --- a/redisinsight/ui/src/packages/ri-explain/src/Explain.tsx +++ b/redisinsight/ui/src/packages/ri-explain/src/Explain.tsx @@ -5,6 +5,7 @@ import Hierarchy from '@antv/hierarchy' import { CoreType, + ModuleType, EntityInfo, ParseExplain, ParseGraphV2, @@ -30,11 +31,14 @@ export default function Explain(props: IExplain): JSX.Element { return ( ) } + const module = ModuleType.Search + if (command == 'ft.profile') { const info = props.data[0].response[1] @@ -57,6 +61,7 @@ export default function Explain(props: IExplain): JSX.Element { return ( @@ -71,6 +76,7 @@ export default function Explain(props: IExplain): JSX.Element { return ( ) @@ -96,7 +102,7 @@ interface IProfilingTime { [key: string]: string } -function ExplainDraw({data, type, profilingTime}: {data: any, type: CoreType, profilingTime?: IProfilingTime}): JSX.Element { +function ExplainDraw({data, type, module, profilingTime}: {data: any, type: CoreType, module: ModuleType, profilingTime?: IProfilingTime}): JSX.Element { const container = useRef(null) const [done, setDone] = useState(false) @@ -193,7 +199,14 @@ function ExplainDraw({data, type, profilingTime}: {data: any, type: CoreType, pr const model: Model.FromJSONData = { nodes: [], edges: [] } const traverse = (data: any) => { if (data) { - const info = data.data + const info = data.data as EntityInfo + + if (module === ModuleType.Graph) { + info.recordsProduced = info.counter + delete info.counter + delete info.size + + } let nodeProps = { shape: 'react-explain-node', diff --git a/redisinsight/ui/src/packages/ri-explain/src/Node.tsx b/redisinsight/ui/src/packages/ri-explain/src/Node.tsx index c26d965261..0c296bf392 100644 --- a/redisinsight/ui/src/packages/ri-explain/src/Node.tsx +++ b/redisinsight/ui/src/packages/ri-explain/src/Node.tsx @@ -61,10 +61,9 @@ function NodeToolTipContent(props: INodeToolTip) { return null } - export function ProfileNode(props: INodeProps) { const info: EntityInfo = (props as any).node.getData() - const {id, data, type, snippet, time, counter, size} = info + const {id, data, type, snippet, time, counter, size, recordsProduced} = info let items = {} @@ -76,6 +75,10 @@ export function ProfileNode(props: INodeProps) { items['Size'] = size } + if (recordsProduced !== undefined) { + items['Records Produced'] = recordsProduced + } + return (
@@ -93,12 +96,14 @@ export function ProfileNode(props: INodeProps) { }>
-
{time}
+
{time} ms
}>
-
{counter}
+
{ + counter !== undefined ? counter : + size !== undefined ? size : recordsProduced}
diff --git a/redisinsight/ui/src/packages/ri-explain/src/parser.ts b/redisinsight/ui/src/packages/ri-explain/src/parser.ts index a6b1c735db..8428a49b2a 100644 --- a/redisinsight/ui/src/packages/ri-explain/src/parser.ts +++ b/redisinsight/ui/src/packages/ri-explain/src/parser.ts @@ -222,6 +222,7 @@ export interface EntityInfo { size?: string parentId?: string level?: number + recordsProduced?: string } interface IAncestors { @@ -715,6 +716,11 @@ export function ParseIteratorProfile(data: any[]): EntityInfo { // } } +export enum ModuleType { + Graph, + Search, +} + export enum CoreType { Profile, Explain, From 5b2b181f11a79da6437cb59fca44c988a8650f7f Mon Sep 17 00:00:00 2001 From: Gnanesh Date: Sat, 14 Jan 2023 20:12:34 +0530 Subject: [PATCH 15/29] - Edge width should depend on record size. - Highlight time based on execution time --- .../src/packages/ri-explain/src/Explain.tsx | 17 ++++++++++---- .../ui/src/packages/ri-explain/src/Node.tsx | 22 ++++++++++++++----- 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/redisinsight/ui/src/packages/ri-explain/src/Explain.tsx b/redisinsight/ui/src/packages/ri-explain/src/Explain.tsx index cb61100eb9..f1636eebc4 100644 --- a/redisinsight/ui/src/packages/ri-explain/src/Explain.tsx +++ b/redisinsight/ui/src/packages/ri-explain/src/Explain.tsx @@ -20,6 +20,10 @@ interface IExplain { data: [{response: string[] | string | any}] } +function getEdgeSize(c: number) { + return (Math.log(c || 1) / Math.log(3)) + 1 +} + export default function Explain(props: IExplain): JSX.Element { const command = props.command.split(' ')[0].toLowerCase() @@ -139,8 +143,12 @@ function ExplainDraw({data, type, module, profilingTime}: {data: any, type: Core ancestors.pairs.forEach(p => { const parentNode = document.querySelector(`#node-${p[0]}`) parentNode?.classList.add('ProfileContainerHover') - document.querySelector(`[data-cell-id='${p[0]}-${p[1]}']`)?.childNodes.forEach(k => { - (k as any).setAttribute("style", "stroke: #85A2FE; stroke-linecap: butt; stroke-width: 2px") + document.querySelector(`[data-cell-id='${p[0]}-${p[1]}']`)?.childNodes.forEach((k: any) => { + + const entityNode = document.querySelector(`#node-${p[1]}`) as any + + const strokeSize = getEdgeSize(parseInt(entityNode?.dataset?.size)) + 1; + k.setAttribute("style", `stroke: #85A2FE; stroke-linecap: butt; stroke-width: ${strokeSize}px`) }) }) } @@ -205,7 +213,6 @@ function ExplainDraw({data, type, module, profilingTime}: {data: any, type: Core info.recordsProduced = info.counter delete info.counter delete info.size - } let nodeProps = { @@ -238,6 +245,8 @@ function ExplainDraw({data, type, module, profilingTime}: {data: any, type: Core } if (data.children) { data.children.forEach((item: any) => { + const itemRecords = parseInt(item.data.counter || 0) + model.edges?.push({ id: `${data.id}-${item.id}`, source: data.id, @@ -260,7 +269,7 @@ function ExplainDraw({data, type, module, profilingTime}: {data: any, type: Core attrs: { line: { stroke: isDarkTheme ? '#6B6B6B' : '#8992B3', - strokeWidth: 1, + strokeWidth: getEdgeSize(itemRecords), targetMarker: null, }, }, diff --git a/redisinsight/ui/src/packages/ri-explain/src/Node.tsx b/redisinsight/ui/src/packages/ri-explain/src/Node.tsx index 0c296bf392..5251730703 100644 --- a/redisinsight/ui/src/packages/ri-explain/src/Node.tsx +++ b/redisinsight/ui/src/packages/ri-explain/src/Node.tsx @@ -75,12 +75,15 @@ export function ProfileNode(props: INodeProps) { items['Size'] = size } - if (recordsProduced !== undefined) { - items['Records Produced'] = recordsProduced + const timeInFloat = parseFloat(time || '') + const timeStyles = { + fontWeight: 'bold', + color: 'white', + backgroundColor: (timeInFloat > 45 ? 'red' : timeInFloat > 25 ? 'yellow' : ''), } return ( -
+
{data ? data : type}
{[EntityType.GEO, EntityType.NUMERIC, EntityType.TEXT, EntityType.TAG].includes(type) ? type : ''}
@@ -94,12 +97,21 @@ export function ProfileNode(props: INodeProps) { }
}> -
+
25 ? timeStyles : {}}>
{time} ms
- }> + + } + >
{ counter !== undefined ? counter : From 7c38d919c68932ce899db3fb64fa74b685ad5000 Mon Sep 17 00:00:00 2001 From: Gnanesh Date: Sun, 15 Jan 2023 07:56:59 +0530 Subject: [PATCH 16/29] Highlight ancestors on child node hover --- .../src/packages/ri-explain/src/Explain.tsx | 41 +++++++++---------- .../ui/src/packages/ri-explain/src/Node.tsx | 4 +- .../ri-explain/src/styles/styles.less | 12 ++---- 3 files changed, 25 insertions(+), 32 deletions(-) diff --git a/redisinsight/ui/src/packages/ri-explain/src/Explain.tsx b/redisinsight/ui/src/packages/ri-explain/src/Explain.tsx index f1636eebc4..0a08535470 100644 --- a/redisinsight/ui/src/packages/ri-explain/src/Explain.tsx +++ b/redisinsight/ui/src/packages/ri-explain/src/Explain.tsx @@ -138,34 +138,31 @@ function ExplainDraw({data, type, module, profilingTime}: {data: any, type: Core graph.on("resize", () => graph.centerContent()) graph.on("node:mouseenter", x => { const {id} = x.node.getData() + // Find ancestors of a node const ancestors = GetAncestors(data, id, {found: false, pairs: []}) - if (ancestors.found) { - ancestors.pairs.forEach(p => { - const parentNode = document.querySelector(`#node-${p[0]}`) - parentNode?.classList.add('ProfileContainerHover') - document.querySelector(`[data-cell-id='${p[0]}-${p[1]}']`)?.childNodes.forEach((k: any) => { - - const entityNode = document.querySelector(`#node-${p[1]}`) as any - - const strokeSize = getEdgeSize(parseInt(entityNode?.dataset?.size)) + 1; - k.setAttribute("style", `stroke: #85A2FE; stroke-linecap: butt; stroke-width: ${strokeSize}px`) - }) - }) - } + ancestors.pairs.forEach(p => { + // Highlight ancestor and their ancestor + document.querySelector(`#node-${p[0]}`)?.setAttribute("style", "border: 1px solid #85A2FE !important;") + + // Get edge size of parent ancestor to apply the right edge stroke + const strokeSize = getEdgeSize(parseInt((document.querySelector(`#node-${p[1]}`) as HTMLElement)?.dataset?.size || '')) + 1 + document.querySelector(`[data-cell-id='${p[0]}-${p[1]}']`)?.childNodes.forEach(k => + (k as HTMLElement) + .setAttribute( + "style", + `stroke: #85A2FE; stroke-linecap: butt; stroke-width: ${strokeSize}px` + ) + ) + }) }) graph.on("node:mouseleave", x => { const {id} = x.node.getData() const ancestors = GetAncestors(data, id, {found: false, pairs: []}) - if (ancestors.found) { - ancestors.pairs.forEach(p => { - const parentNode = document.querySelector(`#node-${p[0]}`) - parentNode?.classList.remove('ProfileContainerHover') - document.querySelector(`[data-cell-id='${p[0]}-${p[1]}']`)?.childNodes.forEach(k => { - (k as any).setAttribute("style", "") - }) - }) - } + ancestors.pairs.forEach(p => { + document.querySelector(`#node-${p[0]}`)?.setAttribute("style", "") + document.querySelector(`[data-cell-id='${p[0]}-${p[1]}']`)?.childNodes.forEach(k => (k as HTMLElement).setAttribute("style", "")) + }) }) function resize() { diff --git a/redisinsight/ui/src/packages/ri-explain/src/Node.tsx b/redisinsight/ui/src/packages/ri-explain/src/Node.tsx index 5251730703..dfee88d657 100644 --- a/redisinsight/ui/src/packages/ri-explain/src/Node.tsx +++ b/redisinsight/ui/src/packages/ri-explain/src/Node.tsx @@ -12,9 +12,9 @@ interface INodeProps { export function ExplainNode(props: INodeProps) { const propData: EntityInfo = (props as any).node.getData() - const { type, data, snippet } = propData + const { id, type, data, snippet } = propData return ( -
+
{data ? data : type}
diff --git a/redisinsight/ui/src/packages/ri-explain/src/styles/styles.less b/redisinsight/ui/src/packages/ri-explain/src/styles/styles.less index 0f37d8c698..d1efd47d99 100644 --- a/redisinsight/ui/src/packages/ri-explain/src/styles/styles.less +++ b/redisinsight/ui/src/packages/ri-explain/src/styles/styles.less @@ -80,12 +80,6 @@ background-color: var(--tooltip-background) !important; } -.ProfileContainerHover { - border: 1px solid #85A2FE !important; - - width: 50px; -} - .ProfileContainer { width: 320px; min-height: 84px; @@ -106,7 +100,8 @@ color: var(--text-color); &:hover { - border: 1px solid #85A2FE !important; + border: 2px solid #85A2FE !important; + box-sizing: border-box; } .Main { @@ -197,7 +192,8 @@ background-color: var(--node-background); &:hover { - border: 1px solid #85A2FE !important; + border: 2px solid #85A2FE !important; + box-sizing: border-box; } .Main { From 4732c09e716c668d145af57d81fd406422057c8b Mon Sep 17 00:00:00 2001 From: Gnanesh Date: Sun, 15 Jan 2023 22:58:32 +0530 Subject: [PATCH 17/29] Calculate total execution time for redisgraph profile --- .../ui/src/packages/ri-explain/src/Explain.tsx | 15 ++++++++++++--- .../ui/src/packages/ri-explain/src/parser.ts | 5 +++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/redisinsight/ui/src/packages/ri-explain/src/Explain.tsx b/redisinsight/ui/src/packages/ri-explain/src/Explain.tsx index 0a08535470..15c29df62d 100644 --- a/redisinsight/ui/src/packages/ri-explain/src/Explain.tsx +++ b/redisinsight/ui/src/packages/ri-explain/src/Explain.tsx @@ -12,6 +12,7 @@ import { ParseProfile, ParseProfileCluster, GetAncestors, + GetTotalExecutionTime, } from './parser' import { ExplainNode, ProfileNode } from './Node' @@ -21,7 +22,7 @@ interface IExplain { } function getEdgeSize(c: number) { - return (Math.log(c || 1) / Math.log(3)) + 1 + return (Math.log(c || 1) / Math.log(10)) + 1 } export default function Explain(props: IExplain): JSX.Element { @@ -29,14 +30,22 @@ export default function Explain(props: IExplain): JSX.Element { if (command.startsWith('graph')) { const info = props.data[0].response - const resp = ParseGraphV2(info) + let profilingTime: IProfilingTime = {} + let t = command.endsWith('explain') ? CoreType.Explain : CoreType.Profile + if (t === CoreType.Profile) { + profilingTime = { + 'Total Execution Time': GetTotalExecutionTime(resp) + } + } + return ( ) } diff --git a/redisinsight/ui/src/packages/ri-explain/src/parser.ts b/redisinsight/ui/src/packages/ri-explain/src/parser.ts index 8428a49b2a..1e60b9bbea 100644 --- a/redisinsight/ui/src/packages/ri-explain/src/parser.ts +++ b/redisinsight/ui/src/packages/ri-explain/src/parser.ts @@ -803,3 +803,8 @@ export function ParseGraphV2(output: string[]) { entity.children = children return entity } + + +export function GetTotalExecutionTime(g: EntityInfo) { + return parseFloat(g.time || '') + g.children.reduce((a, c) => a + GetTotalExecutionTime(c), 0) +} From d8b3889195ebcf9ec19b034a8600d4e46f780db1 Mon Sep 17 00:00:00 2001 From: Gnanesh Date: Tue, 17 Jan 2023 18:53:18 +0530 Subject: [PATCH 18/29] Parse tags and display them in snippet region if available --- .../src/packages/ri-explain/src/Explain.tsx | 5 + .../ui/src/packages/ri-explain/src/Node.tsx | 4 +- .../ui/src/packages/ri-explain/src/parser.ts | 119 +++++++++++++++--- .../ri-explain/src/styles/styles.less | 3 +- 4 files changed, 111 insertions(+), 20 deletions(-) diff --git a/redisinsight/ui/src/packages/ri-explain/src/Explain.tsx b/redisinsight/ui/src/packages/ri-explain/src/Explain.tsx index 15c29df62d..bee966e729 100644 --- a/redisinsight/ui/src/packages/ri-explain/src/Explain.tsx +++ b/redisinsight/ui/src/packages/ri-explain/src/Explain.tsx @@ -215,6 +215,11 @@ function ExplainDraw({data, type, module, profilingTime}: {data: any, type: Core if (data) { const info = data.data as EntityInfo + if (!info.snippet && info.parentSnippet && info.data?.startsWith(info.parentSnippet)) { + info.data = info.data.substr(info.parentSnippet.length) + info.snippet = info.parentSnippet + } + if (module === ModuleType.Graph) { info.recordsProduced = info.counter delete info.counter diff --git a/redisinsight/ui/src/packages/ri-explain/src/Node.tsx b/redisinsight/ui/src/packages/ri-explain/src/Node.tsx index dfee88d657..05f93bb735 100644 --- a/redisinsight/ui/src/packages/ri-explain/src/Node.tsx +++ b/redisinsight/ui/src/packages/ri-explain/src/Node.tsx @@ -12,13 +12,13 @@ interface INodeProps { export function ExplainNode(props: INodeProps) { const propData: EntityInfo = (props as any).node.getData() - const { id, type, data, snippet } = propData + const { id, type, data, snippet, subType } = propData return (
{data ? data : type}
- {type === EntityType.Expr &&
text
} + {subType && [EntityType.GEO, EntityType.NUMERIC, EntityType.TEXT, EntityType.TAG].includes(subType) &&
{subType}
}
{ diff --git a/redisinsight/ui/src/packages/ri-explain/src/parser.ts b/redisinsight/ui/src/packages/ri-explain/src/parser.ts index 1e60b9bbea..bfb5a6162b 100644 --- a/redisinsight/ui/src/packages/ri-explain/src/parser.ts +++ b/redisinsight/ui/src/packages/ri-explain/src/parser.ts @@ -9,6 +9,7 @@ enum TokenType { UNION = 'UNION', INTERSECT = 'INTERSECT', + TAG_EXPR = 'TAG_EXPR', NUMERIC = 'NUMERIC', LBRACE = 'LBRACE', RBRACE = 'RBRACE', @@ -46,6 +47,7 @@ const KEYWORDS = { [TokenType.ILLEGAL.toString()]: TokenType.ILLEGAL, [TokenType.UNION.toString()]: TokenType.UNION, + [TokenType.TAG_EXPR.toString()]: TokenType.TAG_EXPR, [TokenType.INTERSECT.toString()]: TokenType.INTERSECT, [TokenType.NUMERIC.toString()]: TokenType.NUMERIC, @@ -94,7 +96,7 @@ class Lexer { ReadIdentifier(): string { let str = '' - while (this.C !== undefined && isLetter(this.C)) { + while (this.C !== undefined && (isLetter(this.C) || ['@', ':'].includes(this.C))) { str = str + this.C this.ReadChar() } @@ -138,9 +140,6 @@ class Lexer { case '-':// TODO: This should be MINUS token t = new Token(TokenType.IDENTIFIER, this.C) break - case '@': - t = new Token(TokenType.IDENTIFIER, this.C) - break case '<': let lPeekChar = this.PeekChar() if (lPeekChar !== null && lPeekChar === '=') { @@ -173,9 +172,17 @@ class Lexer { t = new Token(TokenType.EOF, '') break default: - if (this.C !== undefined && isLetter(this.C)) { + if (this.C !== undefined && (isLetter(this.C) || ['@', ':'].includes(this.C))) { const literal = this.ReadIdentifier() let tokenType = KEYWORDS[literal] || TokenType.IDENTIFIER + + if (literal.startsWith('TAG:')) { + tokenType = TokenType.TAG_EXPR + } else if (literal.startsWith('@') && literal.endsWith(':UNION')) { + tokenType = TokenType.UNION + } else if (literal.startsWith('@') && literal.endsWith(':INTERSECT')) { + tokenType = TokenType.INTERSECT + } t = new Token(tokenType, literal) return t } else if (this.C !== undefined && isDigit(this.C)) { @@ -214,6 +221,7 @@ export enum EntityType { export interface EntityInfo { id: string type: EntityType, + subType?: EntityType, data?: string snippet?: string children: EntityInfo[] @@ -221,6 +229,7 @@ export interface EntityInfo { counter?: string size?: string parentId?: string + parentSnippet?: string level?: number recordsProduced?: string } @@ -257,19 +266,32 @@ export function GetAncestors(info: EntityInfo, searchId: string, a: IAncestors): class Expr { Core: string - Type?: string + Type: EntityType + SubType: EntityType Time?: string + Info?: string - constructor(expr: string) { + constructor(expr: string, subType: EntityType, info: string | undefined = undefined) { this.Core = expr + this.SubType = subType + this.Info = info } toJSON(): EntityInfo { + + let snippet: string | undefined; + + if (this.SubType === EntityType.TAG && this.Info?.startsWith('TAG:')) { + snippet = this.Info?.substr(4) + } + return { id: uuidv4(), // data: 'Expr', // snippet: this.Core, type: EntityType.Expr, + subType: this.SubType, + snippet: snippet, data: this.Core, children: [], time: this.Time, @@ -312,34 +334,54 @@ type ExprTuple2 = SearchExpr[] class IntersectExpr { Core: ExprTuple2 + Info?: string - constructor(e: ExprTuple2) { + constructor(e: ExprTuple2, info?: string) { this.Core = e + this.Info = info } toJSON(): EntityInfo { const id = uuidv4() + + let snippet: string | undefined; + + if (!this.Info?.startsWith('INTERSECT')) { + snippet = this.Info?.substring(0, this.Info.indexOf(':INTERSECT')) + } + return { id, type: EntityType.INTERSECT, - children: this.Core.map(x => x.toJSON()).map((d: EntityInfo) => ({...d, parentId: id})), + snippet, + children: this.Core.map(x => x.toJSON()).map((d: EntityInfo) => ({...d, parentId: id, parentSnippet: snippet})), } } } class UnionExpr { + Info?: string Core: ExprTuple2 - constructor(e: ExprTuple2) { + constructor(e: ExprTuple2, info?: string) { this.Core = e + this.Info = info } toJSON(): EntityInfo { const id = uuidv4() + + let snippet: string | undefined; + + if (!this.Info?.startsWith('UNION')) { + snippet = this.Info?.substring(0, this.Info.indexOf(':UNION')) + } + return { id, type: EntityType.UNION, - children: this.Core.map(x => x.toJSON()).map((d: EntityInfo) => ({...d, parentId: id})) + snippet, + children: this.Core.map(x => x.toJSON()).map((d: EntityInfo) => ({...d, parentId: id, parentSnippet: snippet})) } } } @@ -374,6 +416,8 @@ class Parser { this.CurrentToken = this.PeekToken this.PeekToken = this.L.NextToken() + console.log("NEXT TOKEN", this.CurrentToken) + if (this.CurrentToken.T === TokenType.EOF) { throw new Error("Didn't expect EOF token") } @@ -383,6 +427,8 @@ class Parser { assertToken(TokenType.INTERSECT, this.CurrentToken?.T) + let intersectData = this.CurrentToken.Data + this.nextToken() assertToken(TokenType.LBRACE, this.CurrentToken?.T) @@ -409,12 +455,14 @@ class Parser { Exprs.push(this.parseUnionExpr()) } else if (this.CurrentToken.T === TokenType.INTERSECT) { Exprs.push(this.parseIntersectExpr()) + } else if (this.CurrentToken.T === TokenType.TAG_EXPR) { + Exprs.push(this.parseTagExpr()) } this.nextToken() } - return new IntersectExpr(Exprs) + return new IntersectExpr(Exprs, intersectData) } @@ -422,6 +470,8 @@ class Parser { assertToken(TokenType.UNION, this.CurrentToken?.T) + let unionData = this.CurrentToken.Data + this.nextToken() assertToken(TokenType.LBRACE, this.CurrentToken.T) @@ -449,12 +499,14 @@ class Parser { Exprs.push(this.parseUnionExpr()) } else if (this.CurrentToken.T === TokenType.INTERSECT) { Exprs.push(this.parseIntersectExpr()) + } else if (this.CurrentToken.T === TokenType.TAG_EXPR) { + Exprs.push(this.parseTagExpr()) } this.nextToken() } - return new UnionExpr(Exprs) + return new UnionExpr(Exprs, unionData) } parseExpr() { @@ -468,7 +520,40 @@ class Parser { this.nextToken() } - return new Expr(str) + return new Expr(str, EntityType.TEXT) + } + + parseTagExpr() { + assertToken(TokenType.TAG_EXPR, this.CurrentToken.T) + + let tagData = this.CurrentToken.Data + + this.nextToken() + + assertToken(TokenType.LBRACE, this.CurrentToken.T) + + this.nextToken() + + assertToken(TokenType.NEW_LINE, this.CurrentToken?.T) + + this.nextToken() + + assertToken(TokenType.IDENTIFIER, this.CurrentToken?.T) + + let identifier = this.CurrentToken.Data + + this.nextToken() + + assertToken(TokenType.NEW_LINE, this.CurrentToken?.T) + + this.nextToken() + + assertToken(TokenType.RBRACE, this.CurrentToken?.T) + + this.nextToken() + + return new Expr(identifier, EntityType.TAG, tagData) + } parseNumericExpr() { @@ -489,7 +574,7 @@ class Parser { let lsign = this.CurrentToken // TODO: Check sign this.nextToken() - + assertToken(TokenType.IDENTIFIER, this.CurrentToken?.T) let identifier = this.CurrentToken @@ -524,8 +609,6 @@ class Parser { return new NumericExpr(left !== 'inf' ? parseFloat(left) : Infinity, lsign, identifier, rsign, right !== 'inf' ? parseFloat(right) : Infinity) } - - } @@ -540,6 +623,8 @@ function Parse(data: string): SearchExpr { return p.parseNumericExpr() } else if (p.CurrentToken.T === TokenType.UNION) { return p.parseUnionExpr() + } else if (p.CurrentToken.T === TokenType.TAG_EXPR) { + return p.parseTagExpr() } else { return p.parseExpr() } diff --git a/redisinsight/ui/src/packages/ri-explain/src/styles/styles.less b/redisinsight/ui/src/packages/ri-explain/src/styles/styles.less index d1efd47d99..dc07400da4 100644 --- a/redisinsight/ui/src/packages/ri-explain/src/styles/styles.less +++ b/redisinsight/ui/src/packages/ri-explain/src/styles/styles.less @@ -205,7 +205,8 @@ display: flex; justify-content: space-between; .Type { - color: #CE915B + color: #CE915B; + text-transform: lowercase; } } From 5d54e8779742c406c4a555c54957e4c01caf3d3f Mon Sep 17 00:00:00 2001 From: Gnanesh Date: Sat, 21 Jan 2023 16:16:31 +0530 Subject: [PATCH 19/29] - Calculate edge size using log. - Use outline instead of border to apply the box edge styling outside the box. - Add different profiling time for redisgraph PROFILE. --- .../src/packages/ri-explain/src/Explain.tsx | 34 +++--- .../ui/src/packages/ri-explain/src/Node.tsx | 41 ++++--- .../ui/src/packages/ri-explain/src/parser.ts | 2 - .../ri-explain/src/styles/styles.less | 105 ++++++------------ 4 files changed, 81 insertions(+), 101 deletions(-) diff --git a/redisinsight/ui/src/packages/ri-explain/src/Explain.tsx b/redisinsight/ui/src/packages/ri-explain/src/Explain.tsx index bee966e729..43452bc22f 100644 --- a/redisinsight/ui/src/packages/ri-explain/src/Explain.tsx +++ b/redisinsight/ui/src/packages/ri-explain/src/Explain.tsx @@ -22,7 +22,7 @@ interface IExplain { } function getEdgeSize(c: number) { - return (Math.log(c || 1) / Math.log(10)) + 1 + return Math.floor(Math.log(c || 1) + 1) } export default function Explain(props: IExplain): JSX.Element { @@ -151,7 +151,7 @@ function ExplainDraw({data, type, module, profilingTime}: {data: any, type: Core const ancestors = GetAncestors(data, id, {found: false, pairs: []}) ancestors.pairs.forEach(p => { // Highlight ancestor and their ancestor - document.querySelector(`#node-${p[0]}`)?.setAttribute("style", "border: 1px solid #85A2FE !important;") + document.querySelector(`#node-${p[0]}`)?.setAttribute("style", "outline: 1px solid #85A2FE !important;") // Get edge size of parent ancestor to apply the right edge stroke const strokeSize = getEdgeSize(parseInt((document.querySelector(`#node-${p[1]}`) as HTMLElement)?.dataset?.size || '')) + 1 @@ -300,18 +300,26 @@ function ExplainDraw({data, type, module, profilingTime}: {data: any, type: Core return (
- { profilingTime && ( -
- { - Object.keys(profilingTime).map(key => ( -
-
{profilingTime[key]}
-
{key}
+ { profilingTime && + ( + module === ModuleType.Search ? + ( +
+ { + Object.keys(profilingTime).map(key => ( +
+
{profilingTime[key]}
+
{key}
+
+ )) + }
- )) - } -
- )} + ) + : + type === CoreType.Profile && ( +
Total execution time: {profilingTime['Total Execution Time']} ms
+ ) + )}
) } diff --git a/redisinsight/ui/src/packages/ri-explain/src/Node.tsx b/redisinsight/ui/src/packages/ri-explain/src/Node.tsx index 05f93bb735..b88068d0b0 100644 --- a/redisinsight/ui/src/packages/ri-explain/src/Node.tsx +++ b/redisinsight/ui/src/packages/ri-explain/src/Node.tsx @@ -10,6 +10,14 @@ interface INodeProps { } +function Snippet({content}: {content: string}) { + return ( +
+ {content} +
+ ) +} + export function ExplainNode(props: INodeProps) { const propData: EntityInfo = (props as any).node.getData() const { id, type, data, snippet, subType } = propData @@ -22,11 +30,7 @@ export function ExplainNode(props: INodeProps) {
{ - snippet && ( -
- {snippet} -
- ) + snippet && }
) @@ -76,12 +80,25 @@ export function ProfileNode(props: INodeProps) { } const timeInFloat = parseFloat(time || '') - const timeStyles = { - fontWeight: 'bold', + let timeStyles: React.CSSProperties = { color: 'white', - backgroundColor: (timeInFloat > 45 ? 'red' : timeInFloat > 25 ? 'yellow' : ''), } + if (timeInFloat > 250) { + timeStyles = { + ...timeStyles, + backgroundColor: 'red', + paddingRight: '4px', + paddingLeft: '2px', + borderRadius: '5px', + } + } else if (timeInFloat > 49) { + timeStyles['color'] = 'red' + } else if (timeInFloat > 24) { + timeStyles['color'] = 'yellow' + } + + return (
@@ -89,15 +106,11 @@ export function ProfileNode(props: INodeProps) {
{[EntityType.GEO, EntityType.NUMERIC, EntityType.TEXT, EntityType.TAG].includes(type) ? type : ''}
{ - snippet && ( -
- {snippet} -
- ) + snippet && }
}> -
25 ? timeStyles : {}}> +
{time} ms
diff --git a/redisinsight/ui/src/packages/ri-explain/src/parser.ts b/redisinsight/ui/src/packages/ri-explain/src/parser.ts index bfb5a6162b..5004a79d84 100644 --- a/redisinsight/ui/src/packages/ri-explain/src/parser.ts +++ b/redisinsight/ui/src/packages/ri-explain/src/parser.ts @@ -416,8 +416,6 @@ class Parser { this.CurrentToken = this.PeekToken this.PeekToken = this.L.NextToken() - console.log("NEXT TOKEN", this.CurrentToken) - if (this.CurrentToken.T === TokenType.EOF) { throw new Error("Didn't expect EOF token") } diff --git a/redisinsight/ui/src/packages/ri-explain/src/styles/styles.less b/redisinsight/ui/src/packages/ri-explain/src/styles/styles.less index dc07400da4..8de7f9597a 100644 --- a/redisinsight/ui/src/packages/ri-explain/src/styles/styles.less +++ b/redisinsight/ui/src/packages/ri-explain/src/styles/styles.less @@ -80,6 +80,28 @@ background-color: var(--tooltip-background) !important; } + +.euiToolTipAnchor { + display: unset !important; +} + +.FooterCommon { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + + color: var(--node-metadata-color); + font-size: 12px; + + + // Make text selectable + -moz-user-select: text; + -khtml-user-select: text; + -webkit-user-select: text; + -ms-user-select: text; + user-select: text; +} + .ProfileContainer { width: 320px; min-height: 84px; @@ -100,7 +122,7 @@ color: var(--text-color); &:hover { - border: 2px solid #85A2FE !important; + outline: 2px solid #85A2FE !important; box-sizing: border-box; } @@ -123,11 +145,9 @@ } .Footer{ - font-size: 12px; padding-top: 4px; padding-bottom: 12px; height: auto; - color: #B5B6C0; } .MetaData { @@ -192,7 +212,7 @@ background-color: var(--node-background); &:hover { - border: 2px solid #85A2FE !important; + outline: 2px solid #85A2FE !important; box-sizing: border-box; } @@ -214,87 +234,24 @@ .Footer{ height: 24px; - font-size: 12px; padding-top: 6px; - color: #B5B6C0; } } -.NodeContainer { - width: 320px; - height: 114px; - - box-shadow: 0px 3px 12px #17336952; - border: 1px solid #3D3D3D !important; - border-radius: 4px; - opacity: 1; - - padding: 12px 18px 12px 18px !important; - +.ProfileInfo { display: flex; - flex-direction: column; - justify-content: space-around; -} + justify-content: center; + color: var(--text-color); -.NodeHeader { - display: flex; - flex-direction: column; - justify-content: space-between; + font-family: 'Graphik', sans-serif !important; font-size: 13px; - height: 18px; - - .NodeHeaderInfo { - display: flex; - justify-content: space-between; - } - - .NodeHeaderMetadata{ - display: flex; - justify-content: space-between; - - padding-top: 10px; - padding-bottom: 10px; - - .MetadataSnippet { - font-family: monospace !important; - } - - .MetadataCount { - display: flex; - } - - } - - - .NodeIcon { - height: 13px !important; - width: 13px !important; - } - - - .NodeToolTip { - display: flex; - flex-direction: column; - background-color: #333D4F; - padding: 10px; - - .NodeToolTipItem { - background-color: inherit; - } - - } + padding-bottom: 13px !important; } .ProfileTimeInfo { - display: flex; - justify-content: center; - color: var(--text-color); - - font-family: 'Graphik' Light, sans-serif !important; padding-top: 6px !important; - padding-bottom: 13px !important; .Item { @@ -313,6 +270,10 @@ } } +.ProfileTimeMini { + background-color: var(--svg-background); +} + .NodeType { color: #CE915B; } From 3e20c4f1d82f23a311d9807d6a6a5842d2f15ff5 Mon Sep 17 00:00:00 2001 From: Gnanesh Date: Sun, 22 Jan 2023 17:08:33 +0530 Subject: [PATCH 20/29] Add ability to run a profile/explain right from the query card tab of graph/search query. --- .../assets/img/workbench/vis_tag_cloud.svg | 3 ++ .../src/components/query-card/QueryCard.tsx | 5 +- .../QueryCardHeader/QueryCardHeader.tsx | 48 ++++++++++++++++++- .../QueryCardHeader/styles.module.scss | 21 ++++++++ .../wb-results/WBResults/WBResults.tsx | 12 +++++ .../ui/src/pages/workbench/constants.ts | 34 +++++++++++++ redisinsight/ui/src/pages/workbench/utils.ts | 30 ++++++++++++ 7 files changed, 151 insertions(+), 2 deletions(-) create mode 100644 redisinsight/ui/src/assets/img/workbench/vis_tag_cloud.svg create mode 100644 redisinsight/ui/src/pages/workbench/utils.ts diff --git a/redisinsight/ui/src/assets/img/workbench/vis_tag_cloud.svg b/redisinsight/ui/src/assets/img/workbench/vis_tag_cloud.svg new file mode 100644 index 0000000000..22259f5eab --- /dev/null +++ b/redisinsight/ui/src/assets/img/workbench/vis_tag_cloud.svg @@ -0,0 +1,3 @@ + + + diff --git a/redisinsight/ui/src/components/query-card/QueryCard.tsx b/redisinsight/ui/src/components/query-card/QueryCard.tsx index f656480b7b..28330cec51 100644 --- a/redisinsight/ui/src/components/query-card/QueryCard.tsx +++ b/redisinsight/ui/src/components/query-card/QueryCard.tsx @@ -4,7 +4,7 @@ import cx from 'classnames' import { EuiLoadingContent, keys } from '@elastic/eui' import { useParams } from 'react-router-dom' -import { WBQueryType } from 'uiSrc/pages/workbench/constants' +import { WBQueryType, ProfileQueryType } from 'uiSrc/pages/workbench/constants' import { RunQueryMode, ResultsMode, ResultsSummary } from 'uiSrc/slices/interfaces/workbench' import { getWBQueryType, @@ -43,6 +43,7 @@ export interface Props { onQueryDelete: () => void onQueryReRun: () => void onQueryOpen: () => void + onQueryProfile: (type: ProfileQueryType) => void } const getDefaultPlugin = (views: IPluginVisualization[], query: string) => @@ -74,6 +75,7 @@ const QueryCard = (props: Props) => { createdAt, onQueryOpen, onQueryDelete, + onQueryProfile, onQueryReRun, loading, emptyCommand, @@ -183,6 +185,7 @@ const QueryCard = (props: Props) => { setSelectedValue={changeViewTypeSelected} onQueryDelete={onQueryDelete} onQueryReRun={onQueryReRun} + onQueryProfile={onQueryProfile} /> {isOpen && ( <> diff --git a/redisinsight/ui/src/components/query-card/QueryCardHeader/QueryCardHeader.tsx b/redisinsight/ui/src/components/query-card/QueryCardHeader/QueryCardHeader.tsx index e0ab98707c..d059ea7e19 100644 --- a/redisinsight/ui/src/components/query-card/QueryCardHeader/QueryCardHeader.tsx +++ b/redisinsight/ui/src/components/query-card/QueryCardHeader/QueryCardHeader.tsx @@ -32,7 +32,7 @@ import { numberWithSpaces } from 'uiSrc/utils/numbers' import { ThemeContext } from 'uiSrc/contexts/themeContext' import { appPluginsSelector } from 'uiSrc/slices/app/plugins' import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry' -import { getViewTypeOptions, WBQueryType } from 'uiSrc/pages/workbench/constants' +import { getViewTypeOptions, WBQueryType, getProfileViewTypeOptions, ProfileQueryType, isCommandAllowedForProfile } from 'uiSrc/pages/workbench/constants' import { IPluginVisualization } from 'uiSrc/slices/interfaces' import { RunQueryMode, ResultsMode, ResultsSummary } from 'uiSrc/slices/interfaces/workbench' import { appRedisCommandsSelector } from 'uiSrc/slices/app/redis-commands' @@ -69,6 +69,7 @@ export interface Props { setSelectedValue: (type: WBQueryType, value: string) => void onQueryDelete: () => void onQueryReRun: () => void + onQueryProfile: (type: ProfileQueryType) => void } const getExecutionTimeString = (value: number): string => { @@ -108,6 +109,7 @@ const QueryCardHeader = (props: Props) => { setSelectedValue, onQueryDelete, onQueryReRun, + onQueryProfile, } = props const { visualizations = [] } = useSelector(appPluginsSelector) @@ -235,6 +237,30 @@ const QueryCardHeader = (props: Props) => { } }) + const profileOptions: EuiSuperSelectOption[] = (getProfileViewTypeOptions() as any[]).map((item) => { + const { value, id, text } = item + return { + value: id ?? value, + inputDisplay: ( +
+ +
+ ), + dropdownDisplay: ( +
+ {truncateText(text, 20)} +
+ ), + 'data-test-subj': `profile-type-option-${value}-${id}`, + } + }) + + const canCommandProfile = isCommandAllowedForProfile(query.split(' ')[0].toLowerCase()) + const indexForSeparator = findIndex(pluginsOptions, (option) => !option.internal) if (indexForSeparator > -1) { modifiedOptions.splice(indexForSeparator + 1, 0, { @@ -318,6 +344,26 @@ const QueryCardHeader = (props: Props) => { )} + + {isOpen && profileOptions.length > 1 && canCommandProfile && !summaryText && ( +
+
+ onQueryProfile(value)} + data-testid="run-profile-type" + /> +
+
+ )} +
{ activeResultsMode={activeResultsMode} resultsMode={resultsMode} onQueryOpen={() => onQueryOpen(id)} + onQueryProfile={(profileType: ProfileQueryType) => { + const profileQuery = generateProfileQueryForCommand(command, profileType) + if (profileQuery) { + return onQueryReRun( + profileQuery, + null, + { mode, results: resultsMode, clearEditor: false, }, + ) + } + }} onQueryReRun={() => onQueryReRun( command, null, diff --git a/redisinsight/ui/src/pages/workbench/constants.ts b/redisinsight/ui/src/pages/workbench/constants.ts index 0166a6d403..5b9cc70a97 100644 --- a/redisinsight/ui/src/pages/workbench/constants.ts +++ b/redisinsight/ui/src/pages/workbench/constants.ts @@ -27,6 +27,40 @@ export const VIEW_TYPE_OPTIONS = [ export const getViewTypeOptions = () => [...VIEW_TYPE_OPTIONS] + +export const SEARCH_COMMANDS = ['ft.search', 'ft.aggregate'] +export const GRAPH_COMMANDS = ['graph.query'] + +const ALLOWED_PROFILE_COMMANDS = [...SEARCH_COMMANDS, ...GRAPH_COMMANDS] + +export function isCommandAllowedForProfile(cmd: string) { + return ALLOWED_PROFILE_COMMANDS.includes(cmd) +} + +export enum ProfileQueryType { + Profile = 'Profile', + Explain = 'Explain' +} + +const PROFILE_VIEW_TYPE_OPTIONS = [ + { + id: ProfileQueryType.Profile, + text: 'Profile the command', + name: 'Profile', + value: WBQueryType.Text, + }, + { + id: ProfileQueryType.Explain, + text: 'Explain the command', + name: 'Explain', + value: WBQueryType.Text, + }, +] + +export const getProfileViewTypeOptions = () => + [...PROFILE_VIEW_TYPE_OPTIONS] + + export enum ModuleCommandPrefix { RediSearch = 'FT.', } diff --git a/redisinsight/ui/src/pages/workbench/utils.ts b/redisinsight/ui/src/pages/workbench/utils.ts new file mode 100644 index 0000000000..445a2abe1a --- /dev/null +++ b/redisinsight/ui/src/pages/workbench/utils.ts @@ -0,0 +1,30 @@ +import { ProfileQueryType, SEARCH_COMMANDS, GRAPH_COMMANDS } from './constants' + +function generateGraphProfileQuery(query: string, type: ProfileQueryType) { + return [`graph.${type}`, ...query.split(' ').slice(1)].join(' ') +} + +function generateSearchProfileQuery(query: string, type: ProfileQueryType) { + const commandSplit = query.split(' ') + const key = commandSplit[0].toLowerCase() + + if (type === ProfileQueryType.Explain) { + return [`ft.${type}`, ...commandSplit.slice(1)].join(' ') + } else { + const index = commandSplit[1] + const queryType = key.split('.')[1] // SEARCH / AGGREGATE + return [`ft.${type}`, index, queryType, 'QUERY', ...commandSplit.slice(2)].join(' ') + } +} + +export function generateProfileQueryForCommand(query: string, type: ProfileQueryType) { + const cmd = query.split(' ')[0].toLowerCase() + + if (GRAPH_COMMANDS.includes(cmd)) { + return generateGraphProfileQuery(query, type) + } else if (SEARCH_COMMANDS.includes(cmd)) { + return generateSearchProfileQuery(query, type) + } + + return null +} From fc31b4190b0c9c5f1f47e3d30b832e4a6043f7ac Mon Sep 17 00:00:00 2001 From: Gnanesh Date: Mon, 23 Jan 2023 19:24:20 +0530 Subject: [PATCH 21/29] Fix comments - https://github.com/RedisInsight/RedisInsight/pull/1537#pullrequestreview-1265311626 - Add unit tests for profile/explain commands generator - Use arrow functions --- .../QueryCardHeader/QueryCardHeader.tsx | 6 +- .../ui/src/pages/workbench/constants.ts | 4 +- .../ui/src/pages/workbench/utils.spec.ts | 79 +++++++++++++++++++ redisinsight/ui/src/pages/workbench/utils.ts | 17 ++-- 4 files changed, 93 insertions(+), 13 deletions(-) create mode 100644 redisinsight/ui/src/pages/workbench/utils.spec.ts diff --git a/redisinsight/ui/src/components/query-card/QueryCardHeader/QueryCardHeader.tsx b/redisinsight/ui/src/components/query-card/QueryCardHeader/QueryCardHeader.tsx index d059ea7e19..b3b2f5d8f3 100644 --- a/redisinsight/ui/src/components/query-card/QueryCardHeader/QueryCardHeader.tsx +++ b/redisinsight/ui/src/components/query-card/QueryCardHeader/QueryCardHeader.tsx @@ -245,7 +245,7 @@ const QueryCardHeader = (props: Props) => {
@@ -259,7 +259,7 @@ const QueryCardHeader = (props: Props) => { } }) - const canCommandProfile = isCommandAllowedForProfile(query.split(' ')[0].toLowerCase()) + const canCommandProfile = isCommandAllowedForProfile(query) const indexForSeparator = findIndex(pluginsOptions, (option) => !option.internal) if (indexForSeparator > -1) { @@ -349,7 +349,7 @@ const QueryCardHeader = (props: Props) => { className={cx(styles.buttonIcon, styles.viewTypeIcon)} onClick={onDropDownViewClick} > - {isOpen && profileOptions.length > 1 && canCommandProfile && !summaryText && ( + {isOpen && canCommandProfile && !summaryText && (
{ + return ALLOWED_PROFILE_COMMANDS.includes(query.split(' ')[0].toLowerCase()) } export enum ProfileQueryType { diff --git a/redisinsight/ui/src/pages/workbench/utils.spec.ts b/redisinsight/ui/src/pages/workbench/utils.spec.ts new file mode 100644 index 0000000000..20c7efe465 --- /dev/null +++ b/redisinsight/ui/src/pages/workbench/utils.spec.ts @@ -0,0 +1,79 @@ +import { ProfileQueryType, SEARCH_COMMANDS, GRAPH_COMMANDS } from './constants' + +import { + generateGraphProfileQuery, + generateSearchProfileQuery, + generateProfileQueryForCommand, +} from './utils' + + +const generateGraphProfileQueryTests: Record[] = [ + { input: 'GRAPH.QUERY key "MATCH (n) RETURN n"', output: 'graph.profile key "MATCH (n) RETURN n"', type: ProfileQueryType.Profile }, + { input: 'GRAPH.QUERY key "MATCH (n) RETURN n"', output: 'graph.explain key "MATCH (n) RETURN n"', type: ProfileQueryType.Explain }, + { input: 'graph.query key "MATCH (n) RETURN n"', output: 'graph.profile key "MATCH (n) RETURN n"', type: ProfileQueryType.Profile }, + { input: 'graph.query key "MATCH (n) RETURN n"', output: 'graph.explain key "MATCH (n) RETURN n"', type: ProfileQueryType.Explain }, +] + +describe('generateGraphProfileQuery', () => { + + generateGraphProfileQueryTests.forEach(test => { + it(`should be output: ${test.output} for input: ${test.input} and type: ${test.type}`, () => { + const result = generateGraphProfileQuery(test.input, test.type); + + expect(result).toEqual(test.output); + }); + }) +}); + + +const generateSearchProfileQueryTests: Record[] = [ + { input: 'FT.SEARCH index tomatoes', output: 'ft.profile index SEARCH QUERY tomatoes', type: ProfileQueryType.Profile }, + { input: 'FT.AGGREGATE index tomatoes', output: 'ft.profile index AGGREGATE QUERY tomatoes', type: ProfileQueryType.Profile }, +{ input: 'FT.SEARCH index tomatoes', output: 'ft.explain index tomatoes', type: ProfileQueryType.Explain }, + { input: 'FT.AGGREGATE index tomatoes', output: 'ft.explain index tomatoes', type: ProfileQueryType.Explain }, +{ input: 'ft.search index tomatoes', output: 'ft.profile index search QUERY tomatoes', type: ProfileQueryType.Profile }, + { input: 'ft.aggregate index tomatoes', output: 'ft.profile index aggregate QUERY tomatoes', type: ProfileQueryType.Profile }, +{ input: 'ft.search index tomatoes', output: 'ft.explain index tomatoes', type: ProfileQueryType.Explain }, + { input: 'ft.aggregate index tomatoes', output: 'ft.explain index tomatoes', type: ProfileQueryType.Explain }, +{ input: 'ft.SEARCH index tomatoes', output: 'ft.profile index SEARCH QUERY tomatoes', type: ProfileQueryType.Profile }, + { input: 'ft.AGGREGATE index tomatoes', output: 'ft.profile index AGGREGATE QUERY tomatoes', type: ProfileQueryType.Profile }, +{ input: 'ft.SEARCH index tomatoes', output: 'ft.explain index tomatoes', type: ProfileQueryType.Explain }, + { input: 'ft.AGGREGATE index tomatoes', output: 'ft.explain index tomatoes', type: ProfileQueryType.Explain }, +] + +describe('generateSearchProfileQuery', () => { + + generateSearchProfileQueryTests.forEach(test => { + it(`should be output: ${test.output} for input: ${test.input} and type: ${test.type}`, () => { + const result = generateSearchProfileQuery(test.input, test.type); + + expect(result).toEqual(test.output); + }); + }) +}); + +const generateProfileQueryForCommandTests: Record[] = [ + ...generateGraphProfileQueryTests, + ...generateSearchProfileQueryTests, + { input: 'GRAPH.LIST', output: null, type: ProfileQueryType.Profile }, + { input: 'GRAPH.LIST', output: null, type: ProfileQueryType.Explain }, + { input: 'GRAPH.PROFILE key "MATCH (n) RETURN n"', output: null, type: ProfileQueryType.Profile }, + { input: 'GRAPH.PROFILE key "MATCH (n) RETURN n"', output: null, type: ProfileQueryType.Explain }, + { input: 'GRAPH.EXPLAIN key "MATCH (n) RETURN n"', output: null, type: ProfileQueryType.Profile }, + { input: 'GRAPH.EXPLAIN key "MATCH (n) RETURN n"', output: null, type: ProfileQueryType.Explain }, + { input: 'ft._LIST', output: null, type: ProfileQueryType.Profile }, + { input: 'ft._LIST', output: null, type: ProfileQueryType.Explain }, +{ input: 'ft.profile index SEARCH QUERY tomatoes', output: null, type: ProfileQueryType.Profile }, + { input: 'ft.profile index AGGREGATE QUERY tomatoes', output: null, type: ProfileQueryType.Explain }, +{ input: 'ft.explain index tomatoes', output: null, type: ProfileQueryType.Profile }, + { input: 'ft.explain index tomatoes', output: null, type: ProfileQueryType.Explain }, +] +describe('generateProfileQueryForCommand', () => { + generateProfileQueryForCommandTests.forEach(test => { + it(`should be output: ${test.output} for input: ${test.input} and type: ${test.type}`, () => { + const result = generateProfileQueryForCommand(test.input, test.type); + + expect(result).toEqual(test.output); + }); + }) +}); diff --git a/redisinsight/ui/src/pages/workbench/utils.ts b/redisinsight/ui/src/pages/workbench/utils.ts index 445a2abe1a..3a697d1ae4 100644 --- a/redisinsight/ui/src/pages/workbench/utils.ts +++ b/redisinsight/ui/src/pages/workbench/utils.ts @@ -1,19 +1,20 @@ import { ProfileQueryType, SEARCH_COMMANDS, GRAPH_COMMANDS } from './constants' -function generateGraphProfileQuery(query: string, type: ProfileQueryType) { - return [`graph.${type}`, ...query.split(' ').slice(1)].join(' ') +export function generateGraphProfileQuery(query: string, type: ProfileQueryType) { + return [`graph.${type.toLowerCase()}`, ...query.split(' ').slice(1)].join(' ') } -function generateSearchProfileQuery(query: string, type: ProfileQueryType) { +export function generateSearchProfileQuery(query: string, type: ProfileQueryType) { const commandSplit = query.split(' ') - const key = commandSplit[0].toLowerCase() + const cmd = commandSplit[0] if (type === ProfileQueryType.Explain) { - return [`ft.${type}`, ...commandSplit.slice(1)].join(' ') + return [`ft.${type.toLowerCase()}`, ...commandSplit.slice(1)].join(' ') } else { - const index = commandSplit[1] - const queryType = key.split('.')[1] // SEARCH / AGGREGATE - return [`ft.${type}`, index, queryType, 'QUERY', ...commandSplit.slice(2)].join(' ') + let index = commandSplit[1] + + const queryType = cmd.split('.')[1] // SEARCH / AGGREGATE + return [`ft.${type.toLowerCase()}`, index, queryType, 'QUERY', ...commandSplit.slice(2)].join(' ') } } From f770a43c6f4f5665b4028434dfa0fa99deb34050 Mon Sep 17 00:00:00 2001 From: Gnanesh Date: Mon, 23 Jan 2023 19:39:45 +0530 Subject: [PATCH 22/29] - Move queryProfile to parent and create a new telement event on the same. --- .../workbench/components/wb-results/WBResults/WBResults.tsx | 4 +++- .../workbench/components/wb-results/WBResultsWrapper.tsx | 1 + .../pages/workbench/components/wb-view/WBView/WBView.tsx | 6 ++++++ redisinsight/ui/src/pages/workbench/utils.ts | 6 +++--- redisinsight/ui/src/telemetry/events.ts | 1 + 5 files changed, 14 insertions(+), 4 deletions(-) diff --git a/redisinsight/ui/src/pages/workbench/components/wb-results/WBResults/WBResults.tsx b/redisinsight/ui/src/pages/workbench/components/wb-results/WBResults/WBResults.tsx index bd1b1d7769..4b121fcabf 100644 --- a/redisinsight/ui/src/pages/workbench/components/wb-results/WBResults/WBResults.tsx +++ b/redisinsight/ui/src/pages/workbench/components/wb-results/WBResults/WBResults.tsx @@ -23,6 +23,7 @@ export interface Props { onQueryReRun: (query: string, commandId?: Nullable, executeParams?: CodeButtonParams) => void onQueryDelete: (commandId: string) => void onQueryOpen: (commandId: string) => void + onQueryProfile: (query: string, commandId?: Nullable, executeParams?: CodeButtonParams) => void } const WBResults = (props: Props) => { const { @@ -30,6 +31,7 @@ const WBResults = (props: Props) => { activeMode, activeResultsMode, onQueryReRun, + onQueryProfile, onQueryDelete, onQueryOpen, scrollDivRef @@ -89,7 +91,7 @@ const WBResults = (props: Props) => { onQueryProfile={(profileType: ProfileQueryType) => { const profileQuery = generateProfileQueryForCommand(command, profileType) if (profileQuery) { - return onQueryReRun( + return onQueryProfile( profileQuery, null, { mode, results: resultsMode, clearEditor: false, }, diff --git a/redisinsight/ui/src/pages/workbench/components/wb-results/WBResultsWrapper.tsx b/redisinsight/ui/src/pages/workbench/components/wb-results/WBResultsWrapper.tsx index 9a23f7d2a7..ec91f806e5 100644 --- a/redisinsight/ui/src/pages/workbench/components/wb-results/WBResultsWrapper.tsx +++ b/redisinsight/ui/src/pages/workbench/components/wb-results/WBResultsWrapper.tsx @@ -13,6 +13,7 @@ export interface Props { onQueryReRun: (query: string, commandId?: Nullable, executeParams?: CodeButtonParams) => void onQueryOpen: (commandId: string) => void onQueryDelete: (commandId: string) => void + onQueryProfile: (query: string, commandId?: Nullable, executeParams?: CodeButtonParams) => void } const WBResultsWrapper = (props: Props) => ( diff --git a/redisinsight/ui/src/pages/workbench/components/wb-view/WBView/WBView.tsx b/redisinsight/ui/src/pages/workbench/components/wb-view/WBView/WBView.tsx index c2691a1502..f52f5cc8f5 100644 --- a/redisinsight/ui/src/pages/workbench/components/wb-view/WBView/WBView.tsx +++ b/redisinsight/ui/src/pages/workbench/components/wb-view/WBView/WBView.tsx @@ -117,6 +117,11 @@ const WBView = (props: Props) => { onSubmit(query, commandId, executeParams) } + const handleProfile = (query?: string, commandId?: Nullable, executeParams: CodeButtonParams = {}) => { + sendEventSubmitTelemetry(TelemetryEvent.WORKBENCH_COMMAND_PROFILE, query, executeParams) + onSubmit(query, commandId, executeParams) + } + const sendEventSubmitTelemetry = ( event: TelemetryEvent, commandInit = script, @@ -236,6 +241,7 @@ const WBView = (props: Props) => { activeResultsMode={resultsMode} scrollDivRef={scrollDivRef} onQueryReRun={handleReRun} + onQueryProfile={handleProfile} onQueryOpen={onQueryOpen} onQueryDelete={onQueryDelete} /> diff --git a/redisinsight/ui/src/pages/workbench/utils.ts b/redisinsight/ui/src/pages/workbench/utils.ts index 3a697d1ae4..ac67671938 100644 --- a/redisinsight/ui/src/pages/workbench/utils.ts +++ b/redisinsight/ui/src/pages/workbench/utils.ts @@ -1,10 +1,10 @@ import { ProfileQueryType, SEARCH_COMMANDS, GRAPH_COMMANDS } from './constants' -export function generateGraphProfileQuery(query: string, type: ProfileQueryType) { +export const generateGraphProfileQuery = (query: string, type: ProfileQueryType) => { return [`graph.${type.toLowerCase()}`, ...query.split(' ').slice(1)].join(' ') } -export function generateSearchProfileQuery(query: string, type: ProfileQueryType) { +export const generateSearchProfileQuery = (query: string, type: ProfileQueryType) => { const commandSplit = query.split(' ') const cmd = commandSplit[0] @@ -18,7 +18,7 @@ export function generateSearchProfileQuery(query: string, type: ProfileQueryType } } -export function generateProfileQueryForCommand(query: string, type: ProfileQueryType) { +export const generateProfileQueryForCommand = (query: string, type: ProfileQueryType) => { const cmd = query.split(' ')[0].toLowerCase() if (GRAPH_COMMANDS.includes(cmd)) { diff --git a/redisinsight/ui/src/telemetry/events.ts b/redisinsight/ui/src/telemetry/events.ts index 345dcb5471..65657799ae 100644 --- a/redisinsight/ui/src/telemetry/events.ts +++ b/redisinsight/ui/src/telemetry/events.ts @@ -98,6 +98,7 @@ export enum TelemetryEvent { WORKBENCH_COMMAND_COPIED = 'WORKBENCH_COMMAND_COPIED', WORKBENCH_COMMAND_RUN_AGAIN = 'WORKBENCH_COMMAND_RUN_AGAIN', + WORKBENCH_COMMAND_PROFILE = 'WORKBENCH_COMMAND_PROFILE', WORKBENCH_COMMAND_DELETE_COMMAND = 'WORKBENCH_COMMAND_DELETE_COMMAND', WORKBENCH_RESULTS_IN_FULL_SCREEN = 'WORKBENCH_RESULTS_IN_FULL_SCREEN', WORKBENCH_RESULTS_COLLAPSED = 'WORKBENCH_RESULTS_COLLAPSED', From 9d2e65c1754f3bd7ac810fa1028ebf5f3350c9d2 Mon Sep 17 00:00:00 2001 From: Gnanesh Date: Mon, 23 Jan 2023 21:41:57 +0530 Subject: [PATCH 23/29] - Add null checks for generate profile command functions - https://github.com/RedisInsight/RedisInsight/pull/1537#discussion_r1084103676 --- .../ui/src/pages/workbench/constants.ts | 2 +- .../ui/src/pages/workbench/utils.spec.ts | 22 ++++++++-------- redisinsight/ui/src/pages/workbench/utils.ts | 26 +++++++++++++------ 3 files changed, 30 insertions(+), 20 deletions(-) diff --git a/redisinsight/ui/src/pages/workbench/constants.ts b/redisinsight/ui/src/pages/workbench/constants.ts index 46a642848c..ba9e2d954a 100644 --- a/redisinsight/ui/src/pages/workbench/constants.ts +++ b/redisinsight/ui/src/pages/workbench/constants.ts @@ -34,7 +34,7 @@ export const GRAPH_COMMANDS = ['graph.query'] const ALLOWED_PROFILE_COMMANDS = [...SEARCH_COMMANDS, ...GRAPH_COMMANDS] export const isCommandAllowedForProfile = (query: string) => { - return ALLOWED_PROFILE_COMMANDS.includes(query.split(' ')[0].toLowerCase()) + return ALLOWED_PROFILE_COMMANDS.includes(query?.split(' ')?.[0]?.toLowerCase()) } export enum ProfileQueryType { diff --git a/redisinsight/ui/src/pages/workbench/utils.spec.ts b/redisinsight/ui/src/pages/workbench/utils.spec.ts index 20c7efe465..3d91dad1d8 100644 --- a/redisinsight/ui/src/pages/workbench/utils.spec.ts +++ b/redisinsight/ui/src/pages/workbench/utils.spec.ts @@ -12,14 +12,14 @@ const generateGraphProfileQueryTests: Record[] = [ { input: 'GRAPH.QUERY key "MATCH (n) RETURN n"', output: 'graph.explain key "MATCH (n) RETURN n"', type: ProfileQueryType.Explain }, { input: 'graph.query key "MATCH (n) RETURN n"', output: 'graph.profile key "MATCH (n) RETURN n"', type: ProfileQueryType.Profile }, { input: 'graph.query key "MATCH (n) RETURN n"', output: 'graph.explain key "MATCH (n) RETURN n"', type: ProfileQueryType.Explain }, + { input: null, output: null, type: ProfileQueryType.Profile }, + { input: null, output: null, type: ProfileQueryType.Explain }, ] describe('generateGraphProfileQuery', () => { - generateGraphProfileQueryTests.forEach(test => { it(`should be output: ${test.output} for input: ${test.input} and type: ${test.type}`, () => { const result = generateGraphProfileQuery(test.input, test.type); - expect(result).toEqual(test.output); }); }) @@ -29,24 +29,24 @@ describe('generateGraphProfileQuery', () => { const generateSearchProfileQueryTests: Record[] = [ { input: 'FT.SEARCH index tomatoes', output: 'ft.profile index SEARCH QUERY tomatoes', type: ProfileQueryType.Profile }, { input: 'FT.AGGREGATE index tomatoes', output: 'ft.profile index AGGREGATE QUERY tomatoes', type: ProfileQueryType.Profile }, -{ input: 'FT.SEARCH index tomatoes', output: 'ft.explain index tomatoes', type: ProfileQueryType.Explain }, + { input: 'FT.SEARCH index tomatoes', output: 'ft.explain index tomatoes', type: ProfileQueryType.Explain }, { input: 'FT.AGGREGATE index tomatoes', output: 'ft.explain index tomatoes', type: ProfileQueryType.Explain }, -{ input: 'ft.search index tomatoes', output: 'ft.profile index search QUERY tomatoes', type: ProfileQueryType.Profile }, + { input: 'ft.search index tomatoes', output: 'ft.profile index search QUERY tomatoes', type: ProfileQueryType.Profile }, { input: 'ft.aggregate index tomatoes', output: 'ft.profile index aggregate QUERY tomatoes', type: ProfileQueryType.Profile }, -{ input: 'ft.search index tomatoes', output: 'ft.explain index tomatoes', type: ProfileQueryType.Explain }, + { input: 'ft.search index tomatoes', output: 'ft.explain index tomatoes', type: ProfileQueryType.Explain }, { input: 'ft.aggregate index tomatoes', output: 'ft.explain index tomatoes', type: ProfileQueryType.Explain }, -{ input: 'ft.SEARCH index tomatoes', output: 'ft.profile index SEARCH QUERY tomatoes', type: ProfileQueryType.Profile }, + { input: 'ft.SEARCH index tomatoes', output: 'ft.profile index SEARCH QUERY tomatoes', type: ProfileQueryType.Profile }, { input: 'ft.AGGREGATE index tomatoes', output: 'ft.profile index AGGREGATE QUERY tomatoes', type: ProfileQueryType.Profile }, -{ input: 'ft.SEARCH index tomatoes', output: 'ft.explain index tomatoes', type: ProfileQueryType.Explain }, + { input: 'ft.SEARCH index tomatoes', output: 'ft.explain index tomatoes', type: ProfileQueryType.Explain }, { input: 'ft.AGGREGATE index tomatoes', output: 'ft.explain index tomatoes', type: ProfileQueryType.Explain }, + { input: null, output: null, type: ProfileQueryType.Profile }, + { input: null, output: null, type: ProfileQueryType.Explain }, ] describe('generateSearchProfileQuery', () => { - generateSearchProfileQueryTests.forEach(test => { it(`should be output: ${test.output} for input: ${test.input} and type: ${test.type}`, () => { const result = generateSearchProfileQuery(test.input, test.type); - expect(result).toEqual(test.output); }); }) @@ -63,9 +63,9 @@ const generateProfileQueryForCommandTests: Record[] = [ { input: 'GRAPH.EXPLAIN key "MATCH (n) RETURN n"', output: null, type: ProfileQueryType.Explain }, { input: 'ft._LIST', output: null, type: ProfileQueryType.Profile }, { input: 'ft._LIST', output: null, type: ProfileQueryType.Explain }, -{ input: 'ft.profile index SEARCH QUERY tomatoes', output: null, type: ProfileQueryType.Profile }, + { input: 'ft.profile index SEARCH QUERY tomatoes', output: null, type: ProfileQueryType.Profile }, { input: 'ft.profile index AGGREGATE QUERY tomatoes', output: null, type: ProfileQueryType.Explain }, -{ input: 'ft.explain index tomatoes', output: null, type: ProfileQueryType.Profile }, + { input: 'ft.explain index tomatoes', output: null, type: ProfileQueryType.Profile }, { input: 'ft.explain index tomatoes', output: null, type: ProfileQueryType.Explain }, ] describe('generateProfileQueryForCommand', () => { diff --git a/redisinsight/ui/src/pages/workbench/utils.ts b/redisinsight/ui/src/pages/workbench/utils.ts index ac67671938..2bca2b87c2 100644 --- a/redisinsight/ui/src/pages/workbench/utils.ts +++ b/redisinsight/ui/src/pages/workbench/utils.ts @@ -1,25 +1,35 @@ import { ProfileQueryType, SEARCH_COMMANDS, GRAPH_COMMANDS } from './constants' export const generateGraphProfileQuery = (query: string, type: ProfileQueryType) => { - return [`graph.${type.toLowerCase()}`, ...query.split(' ').slice(1)].join(' ') + const q = query?.split(' ')?.slice(1) + + if (q) { + return [`graph.${type.toLowerCase()}`, ...q].join(' ') + } + + return null } export const generateSearchProfileQuery = (query: string, type: ProfileQueryType) => { - const commandSplit = query.split(' ') - const cmd = commandSplit[0] + const commandSplit = query?.split(' ') + const cmd = commandSplit?.[0] + + if (!commandSplit || !cmd) { + return null + } if (type === ProfileQueryType.Explain) { - return [`ft.${type.toLowerCase()}`, ...commandSplit.slice(1)].join(' ') + return [`ft.${type.toLowerCase()}`, ...commandSplit?.slice(1)].join(' ') } else { - let index = commandSplit[1] + let index = commandSplit?.[1] - const queryType = cmd.split('.')[1] // SEARCH / AGGREGATE - return [`ft.${type.toLowerCase()}`, index, queryType, 'QUERY', ...commandSplit.slice(2)].join(' ') + const queryType = cmd.split('.')?.[1] // SEARCH / AGGREGATE + return [`ft.${type.toLowerCase()}`, index, queryType, 'QUERY', ...commandSplit?.slice(2)].join(' ') } } export const generateProfileQueryForCommand = (query: string, type: ProfileQueryType) => { - const cmd = query.split(' ')[0].toLowerCase() + const cmd = query?.split(' ')?.[0]?.toLowerCase() if (GRAPH_COMMANDS.includes(cmd)) { return generateGraphProfileQuery(query, type) From c05bddef89933465d26b27a3b159b80b8fef36c2 Mon Sep 17 00:00:00 2001 From: Gnanesh Date: Wed, 25 Jan 2023 07:32:25 +0530 Subject: [PATCH 24/29] Switch back to compactBox layout --- redisinsight/ui/src/packages/ri-explain/src/Explain.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/redisinsight/ui/src/packages/ri-explain/src/Explain.tsx b/redisinsight/ui/src/packages/ri-explain/src/Explain.tsx index 43452bc22f..2a03f20532 100644 --- a/redisinsight/ui/src/packages/ri-explain/src/Explain.tsx +++ b/redisinsight/ui/src/packages/ri-explain/src/Explain.tsx @@ -191,7 +191,7 @@ function ExplainDraw({data, type, module, profilingTime}: {data: any, type: Core window.addEventListener('resize', resize) - const result = Hierarchy.dendrogram(data, { + const result = Hierarchy.compactBox(data, { direction: 'BT', getHeight() { return 200 @@ -200,7 +200,7 @@ function ExplainDraw({data, type, module, profilingTime}: {data: any, type: Core return 250 }, getHGap() { - return 0 + return 50 }, getVGap() { return 0 From eb75d62d33cbbf941faf7997e579839ae65d6f07 Mon Sep 17 00:00:00 2001 From: Gnanesh Date: Wed, 25 Jan 2023 18:05:36 +0530 Subject: [PATCH 25/29] Wrap info data for large texts --- .../ui/src/packages/ri-explain/src/Node.tsx | 13 +++++++++++-- .../src/packages/ri-explain/src/styles/styles.less | 13 +++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/redisinsight/ui/src/packages/ri-explain/src/Node.tsx b/redisinsight/ui/src/packages/ri-explain/src/Node.tsx index b88068d0b0..4b3ad21f12 100644 --- a/redisinsight/ui/src/packages/ri-explain/src/Node.tsx +++ b/redisinsight/ui/src/packages/ri-explain/src/Node.tsx @@ -21,11 +21,16 @@ function Snippet({content}: {content: string}) { export function ExplainNode(props: INodeProps) { const propData: EntityInfo = (props as any).node.getData() const { id, type, data, snippet, subType } = propData + + const infoData = data ? data : type + return (
-
{data ? data : type}
+
+ {infoData} +
{subType && [EntityType.GEO, EntityType.NUMERIC, EntityType.TEXT, EntityType.TAG].includes(subType) &&
{subType}
}
@@ -99,10 +104,14 @@ export function ProfileNode(props: INodeProps) { } + const infoData = data ? data : type + return (
-
{data ? data : type}
+
+ {infoData} +
{[EntityType.GEO, EntityType.NUMERIC, EntityType.TEXT, EntityType.TAG].includes(type) ? type : ''}
{ diff --git a/redisinsight/ui/src/packages/ri-explain/src/styles/styles.less b/redisinsight/ui/src/packages/ri-explain/src/styles/styles.less index 8de7f9597a..a859cef5d5 100644 --- a/redisinsight/ui/src/packages/ri-explain/src/styles/styles.less +++ b/redisinsight/ui/src/packages/ri-explain/src/styles/styles.less @@ -85,6 +85,19 @@ display: unset !important; } +.InfoData { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + + // Make text selectable + -moz-user-select: text; + -khtml-user-select: text; + -webkit-user-select: text; + -ms-user-select: text; + user-select: text; +} + .FooterCommon { white-space: nowrap; overflow: hidden; From 47913b19371b14d045e0dc0f2c4559b412129442 Mon Sep 17 00:00:00 2001 From: Gnanesh Date: Tue, 31 Jan 2023 17:12:37 +0530 Subject: [PATCH 26/29] Revert "- Add null checks for generate profile command functions" This reverts commit 9d2e65c1754f3bd7ac810fa1028ebf5f3350c9d2. --- .../ui/src/pages/workbench/constants.ts | 2 +- .../ui/src/pages/workbench/utils.spec.ts | 22 ++++++++-------- redisinsight/ui/src/pages/workbench/utils.ts | 26 ++++++------------- 3 files changed, 20 insertions(+), 30 deletions(-) diff --git a/redisinsight/ui/src/pages/workbench/constants.ts b/redisinsight/ui/src/pages/workbench/constants.ts index ba9e2d954a..46a642848c 100644 --- a/redisinsight/ui/src/pages/workbench/constants.ts +++ b/redisinsight/ui/src/pages/workbench/constants.ts @@ -34,7 +34,7 @@ export const GRAPH_COMMANDS = ['graph.query'] const ALLOWED_PROFILE_COMMANDS = [...SEARCH_COMMANDS, ...GRAPH_COMMANDS] export const isCommandAllowedForProfile = (query: string) => { - return ALLOWED_PROFILE_COMMANDS.includes(query?.split(' ')?.[0]?.toLowerCase()) + return ALLOWED_PROFILE_COMMANDS.includes(query.split(' ')[0].toLowerCase()) } export enum ProfileQueryType { diff --git a/redisinsight/ui/src/pages/workbench/utils.spec.ts b/redisinsight/ui/src/pages/workbench/utils.spec.ts index 3d91dad1d8..20c7efe465 100644 --- a/redisinsight/ui/src/pages/workbench/utils.spec.ts +++ b/redisinsight/ui/src/pages/workbench/utils.spec.ts @@ -12,14 +12,14 @@ const generateGraphProfileQueryTests: Record[] = [ { input: 'GRAPH.QUERY key "MATCH (n) RETURN n"', output: 'graph.explain key "MATCH (n) RETURN n"', type: ProfileQueryType.Explain }, { input: 'graph.query key "MATCH (n) RETURN n"', output: 'graph.profile key "MATCH (n) RETURN n"', type: ProfileQueryType.Profile }, { input: 'graph.query key "MATCH (n) RETURN n"', output: 'graph.explain key "MATCH (n) RETURN n"', type: ProfileQueryType.Explain }, - { input: null, output: null, type: ProfileQueryType.Profile }, - { input: null, output: null, type: ProfileQueryType.Explain }, ] describe('generateGraphProfileQuery', () => { + generateGraphProfileQueryTests.forEach(test => { it(`should be output: ${test.output} for input: ${test.input} and type: ${test.type}`, () => { const result = generateGraphProfileQuery(test.input, test.type); + expect(result).toEqual(test.output); }); }) @@ -29,24 +29,24 @@ describe('generateGraphProfileQuery', () => { const generateSearchProfileQueryTests: Record[] = [ { input: 'FT.SEARCH index tomatoes', output: 'ft.profile index SEARCH QUERY tomatoes', type: ProfileQueryType.Profile }, { input: 'FT.AGGREGATE index tomatoes', output: 'ft.profile index AGGREGATE QUERY tomatoes', type: ProfileQueryType.Profile }, - { input: 'FT.SEARCH index tomatoes', output: 'ft.explain index tomatoes', type: ProfileQueryType.Explain }, +{ input: 'FT.SEARCH index tomatoes', output: 'ft.explain index tomatoes', type: ProfileQueryType.Explain }, { input: 'FT.AGGREGATE index tomatoes', output: 'ft.explain index tomatoes', type: ProfileQueryType.Explain }, - { input: 'ft.search index tomatoes', output: 'ft.profile index search QUERY tomatoes', type: ProfileQueryType.Profile }, +{ input: 'ft.search index tomatoes', output: 'ft.profile index search QUERY tomatoes', type: ProfileQueryType.Profile }, { input: 'ft.aggregate index tomatoes', output: 'ft.profile index aggregate QUERY tomatoes', type: ProfileQueryType.Profile }, - { input: 'ft.search index tomatoes', output: 'ft.explain index tomatoes', type: ProfileQueryType.Explain }, +{ input: 'ft.search index tomatoes', output: 'ft.explain index tomatoes', type: ProfileQueryType.Explain }, { input: 'ft.aggregate index tomatoes', output: 'ft.explain index tomatoes', type: ProfileQueryType.Explain }, - { input: 'ft.SEARCH index tomatoes', output: 'ft.profile index SEARCH QUERY tomatoes', type: ProfileQueryType.Profile }, +{ input: 'ft.SEARCH index tomatoes', output: 'ft.profile index SEARCH QUERY tomatoes', type: ProfileQueryType.Profile }, { input: 'ft.AGGREGATE index tomatoes', output: 'ft.profile index AGGREGATE QUERY tomatoes', type: ProfileQueryType.Profile }, - { input: 'ft.SEARCH index tomatoes', output: 'ft.explain index tomatoes', type: ProfileQueryType.Explain }, +{ input: 'ft.SEARCH index tomatoes', output: 'ft.explain index tomatoes', type: ProfileQueryType.Explain }, { input: 'ft.AGGREGATE index tomatoes', output: 'ft.explain index tomatoes', type: ProfileQueryType.Explain }, - { input: null, output: null, type: ProfileQueryType.Profile }, - { input: null, output: null, type: ProfileQueryType.Explain }, ] describe('generateSearchProfileQuery', () => { + generateSearchProfileQueryTests.forEach(test => { it(`should be output: ${test.output} for input: ${test.input} and type: ${test.type}`, () => { const result = generateSearchProfileQuery(test.input, test.type); + expect(result).toEqual(test.output); }); }) @@ -63,9 +63,9 @@ const generateProfileQueryForCommandTests: Record[] = [ { input: 'GRAPH.EXPLAIN key "MATCH (n) RETURN n"', output: null, type: ProfileQueryType.Explain }, { input: 'ft._LIST', output: null, type: ProfileQueryType.Profile }, { input: 'ft._LIST', output: null, type: ProfileQueryType.Explain }, - { input: 'ft.profile index SEARCH QUERY tomatoes', output: null, type: ProfileQueryType.Profile }, +{ input: 'ft.profile index SEARCH QUERY tomatoes', output: null, type: ProfileQueryType.Profile }, { input: 'ft.profile index AGGREGATE QUERY tomatoes', output: null, type: ProfileQueryType.Explain }, - { input: 'ft.explain index tomatoes', output: null, type: ProfileQueryType.Profile }, +{ input: 'ft.explain index tomatoes', output: null, type: ProfileQueryType.Profile }, { input: 'ft.explain index tomatoes', output: null, type: ProfileQueryType.Explain }, ] describe('generateProfileQueryForCommand', () => { diff --git a/redisinsight/ui/src/pages/workbench/utils.ts b/redisinsight/ui/src/pages/workbench/utils.ts index 2bca2b87c2..ac67671938 100644 --- a/redisinsight/ui/src/pages/workbench/utils.ts +++ b/redisinsight/ui/src/pages/workbench/utils.ts @@ -1,35 +1,25 @@ import { ProfileQueryType, SEARCH_COMMANDS, GRAPH_COMMANDS } from './constants' export const generateGraphProfileQuery = (query: string, type: ProfileQueryType) => { - const q = query?.split(' ')?.slice(1) - - if (q) { - return [`graph.${type.toLowerCase()}`, ...q].join(' ') - } - - return null + return [`graph.${type.toLowerCase()}`, ...query.split(' ').slice(1)].join(' ') } export const generateSearchProfileQuery = (query: string, type: ProfileQueryType) => { - const commandSplit = query?.split(' ') - const cmd = commandSplit?.[0] - - if (!commandSplit || !cmd) { - return null - } + const commandSplit = query.split(' ') + const cmd = commandSplit[0] if (type === ProfileQueryType.Explain) { - return [`ft.${type.toLowerCase()}`, ...commandSplit?.slice(1)].join(' ') + return [`ft.${type.toLowerCase()}`, ...commandSplit.slice(1)].join(' ') } else { - let index = commandSplit?.[1] + let index = commandSplit[1] - const queryType = cmd.split('.')?.[1] // SEARCH / AGGREGATE - return [`ft.${type.toLowerCase()}`, index, queryType, 'QUERY', ...commandSplit?.slice(2)].join(' ') + const queryType = cmd.split('.')[1] // SEARCH / AGGREGATE + return [`ft.${type.toLowerCase()}`, index, queryType, 'QUERY', ...commandSplit.slice(2)].join(' ') } } export const generateProfileQueryForCommand = (query: string, type: ProfileQueryType) => { - const cmd = query?.split(' ')?.[0]?.toLowerCase() + const cmd = query.split(' ')[0].toLowerCase() if (GRAPH_COMMANDS.includes(cmd)) { return generateGraphProfileQuery(query, type) From efaa72025cf15405a4802234c9261389d612dc18 Mon Sep 17 00:00:00 2001 From: Gnanesh Date: Tue, 31 Jan 2023 17:12:39 +0530 Subject: [PATCH 27/29] Revert "- Move queryProfile to parent and create a new telement event on the same." This reverts commit f770a43c6f4f5665b4028434dfa0fa99deb34050. --- .../workbench/components/wb-results/WBResults/WBResults.tsx | 4 +--- .../workbench/components/wb-results/WBResultsWrapper.tsx | 1 - .../pages/workbench/components/wb-view/WBView/WBView.tsx | 6 ------ redisinsight/ui/src/pages/workbench/utils.ts | 6 +++--- redisinsight/ui/src/telemetry/events.ts | 1 - 5 files changed, 4 insertions(+), 14 deletions(-) diff --git a/redisinsight/ui/src/pages/workbench/components/wb-results/WBResults/WBResults.tsx b/redisinsight/ui/src/pages/workbench/components/wb-results/WBResults/WBResults.tsx index 6c8c5c65c9..5297b1eb7b 100644 --- a/redisinsight/ui/src/pages/workbench/components/wb-results/WBResults/WBResults.tsx +++ b/redisinsight/ui/src/pages/workbench/components/wb-results/WBResults/WBResults.tsx @@ -23,7 +23,6 @@ export interface Props { onQueryReRun: (query: string, commandId?: Nullable, executeParams?: CodeButtonParams) => void onQueryDelete: (commandId: string) => void onQueryOpen: (commandId: string) => void - onQueryProfile: (query: string, commandId?: Nullable, executeParams?: CodeButtonParams) => void } const WBResults = (props: Props) => { const { @@ -31,7 +30,6 @@ const WBResults = (props: Props) => { activeMode, activeResultsMode, onQueryReRun, - onQueryProfile, onQueryDelete, onQueryOpen, scrollDivRef @@ -93,7 +91,7 @@ const WBResults = (props: Props) => { onQueryProfile={(profileType: ProfileQueryType) => { const profileQuery = generateProfileQueryForCommand(command, profileType) if (profileQuery) { - return onQueryProfile( + return onQueryReRun( profileQuery, null, { mode, results: resultsMode, clearEditor: false, }, diff --git a/redisinsight/ui/src/pages/workbench/components/wb-results/WBResultsWrapper.tsx b/redisinsight/ui/src/pages/workbench/components/wb-results/WBResultsWrapper.tsx index ec91f806e5..9a23f7d2a7 100644 --- a/redisinsight/ui/src/pages/workbench/components/wb-results/WBResultsWrapper.tsx +++ b/redisinsight/ui/src/pages/workbench/components/wb-results/WBResultsWrapper.tsx @@ -13,7 +13,6 @@ export interface Props { onQueryReRun: (query: string, commandId?: Nullable, executeParams?: CodeButtonParams) => void onQueryOpen: (commandId: string) => void onQueryDelete: (commandId: string) => void - onQueryProfile: (query: string, commandId?: Nullable, executeParams?: CodeButtonParams) => void } const WBResultsWrapper = (props: Props) => ( diff --git a/redisinsight/ui/src/pages/workbench/components/wb-view/WBView/WBView.tsx b/redisinsight/ui/src/pages/workbench/components/wb-view/WBView/WBView.tsx index f52f5cc8f5..c2691a1502 100644 --- a/redisinsight/ui/src/pages/workbench/components/wb-view/WBView/WBView.tsx +++ b/redisinsight/ui/src/pages/workbench/components/wb-view/WBView/WBView.tsx @@ -117,11 +117,6 @@ const WBView = (props: Props) => { onSubmit(query, commandId, executeParams) } - const handleProfile = (query?: string, commandId?: Nullable, executeParams: CodeButtonParams = {}) => { - sendEventSubmitTelemetry(TelemetryEvent.WORKBENCH_COMMAND_PROFILE, query, executeParams) - onSubmit(query, commandId, executeParams) - } - const sendEventSubmitTelemetry = ( event: TelemetryEvent, commandInit = script, @@ -241,7 +236,6 @@ const WBView = (props: Props) => { activeResultsMode={resultsMode} scrollDivRef={scrollDivRef} onQueryReRun={handleReRun} - onQueryProfile={handleProfile} onQueryOpen={onQueryOpen} onQueryDelete={onQueryDelete} /> diff --git a/redisinsight/ui/src/pages/workbench/utils.ts b/redisinsight/ui/src/pages/workbench/utils.ts index ac67671938..3a697d1ae4 100644 --- a/redisinsight/ui/src/pages/workbench/utils.ts +++ b/redisinsight/ui/src/pages/workbench/utils.ts @@ -1,10 +1,10 @@ import { ProfileQueryType, SEARCH_COMMANDS, GRAPH_COMMANDS } from './constants' -export const generateGraphProfileQuery = (query: string, type: ProfileQueryType) => { +export function generateGraphProfileQuery(query: string, type: ProfileQueryType) { return [`graph.${type.toLowerCase()}`, ...query.split(' ').slice(1)].join(' ') } -export const generateSearchProfileQuery = (query: string, type: ProfileQueryType) => { +export function generateSearchProfileQuery(query: string, type: ProfileQueryType) { const commandSplit = query.split(' ') const cmd = commandSplit[0] @@ -18,7 +18,7 @@ export const generateSearchProfileQuery = (query: string, type: ProfileQueryType } } -export const generateProfileQueryForCommand = (query: string, type: ProfileQueryType) => { +export function generateProfileQueryForCommand(query: string, type: ProfileQueryType) { const cmd = query.split(' ')[0].toLowerCase() if (GRAPH_COMMANDS.includes(cmd)) { diff --git a/redisinsight/ui/src/telemetry/events.ts b/redisinsight/ui/src/telemetry/events.ts index 65657799ae..345dcb5471 100644 --- a/redisinsight/ui/src/telemetry/events.ts +++ b/redisinsight/ui/src/telemetry/events.ts @@ -98,7 +98,6 @@ export enum TelemetryEvent { WORKBENCH_COMMAND_COPIED = 'WORKBENCH_COMMAND_COPIED', WORKBENCH_COMMAND_RUN_AGAIN = 'WORKBENCH_COMMAND_RUN_AGAIN', - WORKBENCH_COMMAND_PROFILE = 'WORKBENCH_COMMAND_PROFILE', WORKBENCH_COMMAND_DELETE_COMMAND = 'WORKBENCH_COMMAND_DELETE_COMMAND', WORKBENCH_RESULTS_IN_FULL_SCREEN = 'WORKBENCH_RESULTS_IN_FULL_SCREEN', WORKBENCH_RESULTS_COLLAPSED = 'WORKBENCH_RESULTS_COLLAPSED', From 58a0ccc30fb0730acc3b9faa33844473343cba54 Mon Sep 17 00:00:00 2001 From: Gnanesh Date: Tue, 31 Jan 2023 17:12:40 +0530 Subject: [PATCH 28/29] Revert "Fix comments - https://github.com/RedisInsight/RedisInsight/pull/1537#pullrequestreview-1265311626" This reverts commit fc31b4190b0c9c5f1f47e3d30b832e4a6043f7ac. --- .../QueryCardHeader/QueryCardHeader.tsx | 6 +- .../ui/src/pages/workbench/constants.ts | 4 +- .../ui/src/pages/workbench/utils.spec.ts | 79 ------------------- redisinsight/ui/src/pages/workbench/utils.ts | 17 ++-- 4 files changed, 13 insertions(+), 93 deletions(-) delete mode 100644 redisinsight/ui/src/pages/workbench/utils.spec.ts diff --git a/redisinsight/ui/src/components/query-card/QueryCardHeader/QueryCardHeader.tsx b/redisinsight/ui/src/components/query-card/QueryCardHeader/QueryCardHeader.tsx index a6af198d35..85243f6bc2 100644 --- a/redisinsight/ui/src/components/query-card/QueryCardHeader/QueryCardHeader.tsx +++ b/redisinsight/ui/src/components/query-card/QueryCardHeader/QueryCardHeader.tsx @@ -247,7 +247,7 @@ const QueryCardHeader = (props: Props) => {
@@ -261,7 +261,7 @@ const QueryCardHeader = (props: Props) => { } }) - const canCommandProfile = isCommandAllowedForProfile(query) + const canCommandProfile = isCommandAllowedForProfile(query.split(' ')[0].toLowerCase()) const indexForSeparator = findIndex(pluginsOptions, (option) => !option.internal) if (indexForSeparator > -1) { @@ -351,7 +351,7 @@ const QueryCardHeader = (props: Props) => { className={cx(styles.buttonIcon, styles.viewTypeIcon)} onClick={onDropDownViewClick} > - {isOpen && canCommandProfile && !summaryText && ( + {isOpen && profileOptions.length > 1 && canCommandProfile && !summaryText && (
{ - return ALLOWED_PROFILE_COMMANDS.includes(query.split(' ')[0].toLowerCase()) +export function isCommandAllowedForProfile(cmd: string) { + return ALLOWED_PROFILE_COMMANDS.includes(cmd) } export enum ProfileQueryType { diff --git a/redisinsight/ui/src/pages/workbench/utils.spec.ts b/redisinsight/ui/src/pages/workbench/utils.spec.ts deleted file mode 100644 index 20c7efe465..0000000000 --- a/redisinsight/ui/src/pages/workbench/utils.spec.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { ProfileQueryType, SEARCH_COMMANDS, GRAPH_COMMANDS } from './constants' - -import { - generateGraphProfileQuery, - generateSearchProfileQuery, - generateProfileQueryForCommand, -} from './utils' - - -const generateGraphProfileQueryTests: Record[] = [ - { input: 'GRAPH.QUERY key "MATCH (n) RETURN n"', output: 'graph.profile key "MATCH (n) RETURN n"', type: ProfileQueryType.Profile }, - { input: 'GRAPH.QUERY key "MATCH (n) RETURN n"', output: 'graph.explain key "MATCH (n) RETURN n"', type: ProfileQueryType.Explain }, - { input: 'graph.query key "MATCH (n) RETURN n"', output: 'graph.profile key "MATCH (n) RETURN n"', type: ProfileQueryType.Profile }, - { input: 'graph.query key "MATCH (n) RETURN n"', output: 'graph.explain key "MATCH (n) RETURN n"', type: ProfileQueryType.Explain }, -] - -describe('generateGraphProfileQuery', () => { - - generateGraphProfileQueryTests.forEach(test => { - it(`should be output: ${test.output} for input: ${test.input} and type: ${test.type}`, () => { - const result = generateGraphProfileQuery(test.input, test.type); - - expect(result).toEqual(test.output); - }); - }) -}); - - -const generateSearchProfileQueryTests: Record[] = [ - { input: 'FT.SEARCH index tomatoes', output: 'ft.profile index SEARCH QUERY tomatoes', type: ProfileQueryType.Profile }, - { input: 'FT.AGGREGATE index tomatoes', output: 'ft.profile index AGGREGATE QUERY tomatoes', type: ProfileQueryType.Profile }, -{ input: 'FT.SEARCH index tomatoes', output: 'ft.explain index tomatoes', type: ProfileQueryType.Explain }, - { input: 'FT.AGGREGATE index tomatoes', output: 'ft.explain index tomatoes', type: ProfileQueryType.Explain }, -{ input: 'ft.search index tomatoes', output: 'ft.profile index search QUERY tomatoes', type: ProfileQueryType.Profile }, - { input: 'ft.aggregate index tomatoes', output: 'ft.profile index aggregate QUERY tomatoes', type: ProfileQueryType.Profile }, -{ input: 'ft.search index tomatoes', output: 'ft.explain index tomatoes', type: ProfileQueryType.Explain }, - { input: 'ft.aggregate index tomatoes', output: 'ft.explain index tomatoes', type: ProfileQueryType.Explain }, -{ input: 'ft.SEARCH index tomatoes', output: 'ft.profile index SEARCH QUERY tomatoes', type: ProfileQueryType.Profile }, - { input: 'ft.AGGREGATE index tomatoes', output: 'ft.profile index AGGREGATE QUERY tomatoes', type: ProfileQueryType.Profile }, -{ input: 'ft.SEARCH index tomatoes', output: 'ft.explain index tomatoes', type: ProfileQueryType.Explain }, - { input: 'ft.AGGREGATE index tomatoes', output: 'ft.explain index tomatoes', type: ProfileQueryType.Explain }, -] - -describe('generateSearchProfileQuery', () => { - - generateSearchProfileQueryTests.forEach(test => { - it(`should be output: ${test.output} for input: ${test.input} and type: ${test.type}`, () => { - const result = generateSearchProfileQuery(test.input, test.type); - - expect(result).toEqual(test.output); - }); - }) -}); - -const generateProfileQueryForCommandTests: Record[] = [ - ...generateGraphProfileQueryTests, - ...generateSearchProfileQueryTests, - { input: 'GRAPH.LIST', output: null, type: ProfileQueryType.Profile }, - { input: 'GRAPH.LIST', output: null, type: ProfileQueryType.Explain }, - { input: 'GRAPH.PROFILE key "MATCH (n) RETURN n"', output: null, type: ProfileQueryType.Profile }, - { input: 'GRAPH.PROFILE key "MATCH (n) RETURN n"', output: null, type: ProfileQueryType.Explain }, - { input: 'GRAPH.EXPLAIN key "MATCH (n) RETURN n"', output: null, type: ProfileQueryType.Profile }, - { input: 'GRAPH.EXPLAIN key "MATCH (n) RETURN n"', output: null, type: ProfileQueryType.Explain }, - { input: 'ft._LIST', output: null, type: ProfileQueryType.Profile }, - { input: 'ft._LIST', output: null, type: ProfileQueryType.Explain }, -{ input: 'ft.profile index SEARCH QUERY tomatoes', output: null, type: ProfileQueryType.Profile }, - { input: 'ft.profile index AGGREGATE QUERY tomatoes', output: null, type: ProfileQueryType.Explain }, -{ input: 'ft.explain index tomatoes', output: null, type: ProfileQueryType.Profile }, - { input: 'ft.explain index tomatoes', output: null, type: ProfileQueryType.Explain }, -] -describe('generateProfileQueryForCommand', () => { - generateProfileQueryForCommandTests.forEach(test => { - it(`should be output: ${test.output} for input: ${test.input} and type: ${test.type}`, () => { - const result = generateProfileQueryForCommand(test.input, test.type); - - expect(result).toEqual(test.output); - }); - }) -}); diff --git a/redisinsight/ui/src/pages/workbench/utils.ts b/redisinsight/ui/src/pages/workbench/utils.ts index 3a697d1ae4..445a2abe1a 100644 --- a/redisinsight/ui/src/pages/workbench/utils.ts +++ b/redisinsight/ui/src/pages/workbench/utils.ts @@ -1,20 +1,19 @@ import { ProfileQueryType, SEARCH_COMMANDS, GRAPH_COMMANDS } from './constants' -export function generateGraphProfileQuery(query: string, type: ProfileQueryType) { - return [`graph.${type.toLowerCase()}`, ...query.split(' ').slice(1)].join(' ') +function generateGraphProfileQuery(query: string, type: ProfileQueryType) { + return [`graph.${type}`, ...query.split(' ').slice(1)].join(' ') } -export function generateSearchProfileQuery(query: string, type: ProfileQueryType) { +function generateSearchProfileQuery(query: string, type: ProfileQueryType) { const commandSplit = query.split(' ') - const cmd = commandSplit[0] + const key = commandSplit[0].toLowerCase() if (type === ProfileQueryType.Explain) { - return [`ft.${type.toLowerCase()}`, ...commandSplit.slice(1)].join(' ') + return [`ft.${type}`, ...commandSplit.slice(1)].join(' ') } else { - let index = commandSplit[1] - - const queryType = cmd.split('.')[1] // SEARCH / AGGREGATE - return [`ft.${type.toLowerCase()}`, index, queryType, 'QUERY', ...commandSplit.slice(2)].join(' ') + const index = commandSplit[1] + const queryType = key.split('.')[1] // SEARCH / AGGREGATE + return [`ft.${type}`, index, queryType, 'QUERY', ...commandSplit.slice(2)].join(' ') } } From dee1683d71b83ca238540308db708a49628da01f Mon Sep 17 00:00:00 2001 From: Gnanesh Date: Tue, 31 Jan 2023 17:13:01 +0530 Subject: [PATCH 29/29] Revert "Add ability to run a profile/explain right from the query card tab of" This reverts commit 3e20c4f1d82f23a311d9807d6a6a5842d2f15ff5. --- .../assets/img/workbench/vis_tag_cloud.svg | 3 -- .../src/components/query-card/QueryCard.tsx | 5 +- .../QueryCardHeader/QueryCardHeader.tsx | 47 +------------------ .../QueryCardHeader/styles.module.scss | 21 --------- .../wb-results/WBResults/WBResults.tsx | 12 ----- .../ui/src/pages/workbench/constants.ts | 34 -------------- redisinsight/ui/src/pages/workbench/utils.ts | 30 ------------ 7 files changed, 2 insertions(+), 150 deletions(-) delete mode 100644 redisinsight/ui/src/assets/img/workbench/vis_tag_cloud.svg delete mode 100644 redisinsight/ui/src/pages/workbench/utils.ts diff --git a/redisinsight/ui/src/assets/img/workbench/vis_tag_cloud.svg b/redisinsight/ui/src/assets/img/workbench/vis_tag_cloud.svg deleted file mode 100644 index 22259f5eab..0000000000 --- a/redisinsight/ui/src/assets/img/workbench/vis_tag_cloud.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/redisinsight/ui/src/components/query-card/QueryCard.tsx b/redisinsight/ui/src/components/query-card/QueryCard.tsx index 694ed6187b..166fe60835 100644 --- a/redisinsight/ui/src/components/query-card/QueryCard.tsx +++ b/redisinsight/ui/src/components/query-card/QueryCard.tsx @@ -4,7 +4,7 @@ import cx from 'classnames' import { EuiLoadingContent, keys } from '@elastic/eui' import { useParams } from 'react-router-dom' -import { WBQueryType, ProfileQueryType } from 'uiSrc/pages/workbench/constants' +import { WBQueryType } from 'uiSrc/pages/workbench/constants' import { RunQueryMode, ResultsMode, ResultsSummary } from 'uiSrc/slices/interfaces/workbench' import { getWBQueryType, @@ -44,7 +44,6 @@ export interface Props { onQueryDelete: () => void onQueryReRun: () => void onQueryOpen: () => void - onQueryProfile: (type: ProfileQueryType) => void } const getDefaultPlugin = (views: IPluginVisualization[], query: string) => @@ -76,7 +75,6 @@ const QueryCard = (props: Props) => { createdAt, onQueryOpen, onQueryDelete, - onQueryProfile, onQueryReRun, loading, emptyCommand, @@ -188,7 +186,6 @@ const QueryCard = (props: Props) => { setSelectedValue={changeViewTypeSelected} onQueryDelete={onQueryDelete} onQueryReRun={onQueryReRun} - onQueryProfile={onQueryProfile} /> {isOpen && ( <> diff --git a/redisinsight/ui/src/components/query-card/QueryCardHeader/QueryCardHeader.tsx b/redisinsight/ui/src/components/query-card/QueryCardHeader/QueryCardHeader.tsx index 85243f6bc2..12eedf3f4c 100644 --- a/redisinsight/ui/src/components/query-card/QueryCardHeader/QueryCardHeader.tsx +++ b/redisinsight/ui/src/components/query-card/QueryCardHeader/QueryCardHeader.tsx @@ -32,7 +32,7 @@ import { numberWithSpaces } from 'uiSrc/utils/numbers' import { ThemeContext } from 'uiSrc/contexts/themeContext' import { appPluginsSelector } from 'uiSrc/slices/app/plugins' import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry' -import { getViewTypeOptions, WBQueryType, getProfileViewTypeOptions, ProfileQueryType, isCommandAllowedForProfile } from 'uiSrc/pages/workbench/constants' +import { getViewTypeOptions, WBQueryType } from 'uiSrc/pages/workbench/constants' import { IPluginVisualization } from 'uiSrc/slices/interfaces' import { RunQueryMode, ResultsMode, ResultsSummary } from 'uiSrc/slices/interfaces/workbench' import { appRedisCommandsSelector } from 'uiSrc/slices/app/redis-commands' @@ -70,7 +70,6 @@ export interface Props { setSelectedValue: (type: WBQueryType, value: string) => void onQueryDelete: () => void onQueryReRun: () => void - onQueryProfile: (type: ProfileQueryType) => void } const getExecutionTimeString = (value: number): string => { @@ -239,30 +238,6 @@ const QueryCardHeader = (props: Props) => { } }) - const profileOptions: EuiSuperSelectOption[] = (getProfileViewTypeOptions() as any[]).map((item) => { - const { value, id, text } = item - return { - value: id ?? value, - inputDisplay: ( -
- -
- ), - dropdownDisplay: ( -
- {truncateText(text, 20)} -
- ), - 'data-test-subj': `profile-type-option-${value}-${id}`, - } - }) - - const canCommandProfile = isCommandAllowedForProfile(query.split(' ')[0].toLowerCase()) - const indexForSeparator = findIndex(pluginsOptions, (option) => !option.internal) if (indexForSeparator > -1) { modifiedOptions.splice(indexForSeparator + 1, 0, { @@ -346,26 +321,6 @@ const QueryCardHeader = (props: Props) => { )} - - {isOpen && profileOptions.length > 1 && canCommandProfile && !summaryText && ( -
-
- onQueryProfile(value)} - data-testid="run-profile-type" - /> -
-
- )} -
{ resultsMode={resultsMode} db={db} onQueryOpen={() => onQueryOpen(id)} - onQueryProfile={(profileType: ProfileQueryType) => { - const profileQuery = generateProfileQueryForCommand(command, profileType) - if (profileQuery) { - return onQueryReRun( - profileQuery, - null, - { mode, results: resultsMode, clearEditor: false, }, - ) - } - }} onQueryReRun={() => onQueryReRun( command, null, diff --git a/redisinsight/ui/src/pages/workbench/constants.ts b/redisinsight/ui/src/pages/workbench/constants.ts index 5b9cc70a97..0166a6d403 100644 --- a/redisinsight/ui/src/pages/workbench/constants.ts +++ b/redisinsight/ui/src/pages/workbench/constants.ts @@ -27,40 +27,6 @@ export const VIEW_TYPE_OPTIONS = [ export const getViewTypeOptions = () => [...VIEW_TYPE_OPTIONS] - -export const SEARCH_COMMANDS = ['ft.search', 'ft.aggregate'] -export const GRAPH_COMMANDS = ['graph.query'] - -const ALLOWED_PROFILE_COMMANDS = [...SEARCH_COMMANDS, ...GRAPH_COMMANDS] - -export function isCommandAllowedForProfile(cmd: string) { - return ALLOWED_PROFILE_COMMANDS.includes(cmd) -} - -export enum ProfileQueryType { - Profile = 'Profile', - Explain = 'Explain' -} - -const PROFILE_VIEW_TYPE_OPTIONS = [ - { - id: ProfileQueryType.Profile, - text: 'Profile the command', - name: 'Profile', - value: WBQueryType.Text, - }, - { - id: ProfileQueryType.Explain, - text: 'Explain the command', - name: 'Explain', - value: WBQueryType.Text, - }, -] - -export const getProfileViewTypeOptions = () => - [...PROFILE_VIEW_TYPE_OPTIONS] - - export enum ModuleCommandPrefix { RediSearch = 'FT.', } diff --git a/redisinsight/ui/src/pages/workbench/utils.ts b/redisinsight/ui/src/pages/workbench/utils.ts deleted file mode 100644 index 445a2abe1a..0000000000 --- a/redisinsight/ui/src/pages/workbench/utils.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { ProfileQueryType, SEARCH_COMMANDS, GRAPH_COMMANDS } from './constants' - -function generateGraphProfileQuery(query: string, type: ProfileQueryType) { - return [`graph.${type}`, ...query.split(' ').slice(1)].join(' ') -} - -function generateSearchProfileQuery(query: string, type: ProfileQueryType) { - const commandSplit = query.split(' ') - const key = commandSplit[0].toLowerCase() - - if (type === ProfileQueryType.Explain) { - return [`ft.${type}`, ...commandSplit.slice(1)].join(' ') - } else { - const index = commandSplit[1] - const queryType = key.split('.')[1] // SEARCH / AGGREGATE - return [`ft.${type}`, index, queryType, 'QUERY', ...commandSplit.slice(2)].join(' ') - } -} - -export function generateProfileQueryForCommand(query: string, type: ProfileQueryType) { - const cmd = query.split(' ')[0].toLowerCase() - - if (GRAPH_COMMANDS.includes(cmd)) { - return generateGraphProfileQuery(query, type) - } else if (SEARCH_COMMANDS.includes(cmd)) { - return generateSearchProfileQuery(query, type) - } - - return null -}