diff --git a/jest.config.js b/jest.config.js index 4ebfb1bba9..2756fd4cf7 100644 --- a/jest.config.js +++ b/jest.config.js @@ -16,6 +16,7 @@ module.exports = { 'rehype-stringify': '/redisinsight/__mocks__/rehypeStringify.js', 'unist-util-visit': '/redisinsight/__mocks__/unistUtilsVisit.js', 'react-children-utilities': '/redisinsight/__mocks__/react-children-utilities.js', + d3: '/node_modules/d3/dist/d3.min.js', }, setupFiles: [ '/redisinsight/ui/src/setup-env.ts', @@ -38,6 +39,11 @@ module.exports = { transformIgnorePatterns: [ 'node_modules/(?!(monaco-editor|react-monaco-editor)/)', ], + // TODO: add tests for plugins + modulePathIgnorePatterns: [ + '/redisinsight/ui/src/packages', + '/redisinsight/ui/src/mocks', + ], coverageThreshold: { global: { statements: 70, diff --git a/package.json b/package.json index 8c9ee379cb..743eed262a 100644 --- a/package.json +++ b/package.json @@ -110,6 +110,7 @@ "@testing-library/user-event": "^14.4.3", "@types/axios": "^0.14.0", "@types/classnames": "^2.2.11", + "@types/d3": "^7.4.0", "@types/date-fns": "^2.6.0", "@types/detect-port": "^1.3.0", "@types/electron-store": "^3.2.0", @@ -216,6 +217,7 @@ "buffer": "^6.0.3", "classnames": "^2.3.1", "connection-string": "^4.3.2", + "d3": "^7.6.1", "date-fns": "^2.16.1", "detect-port": "^1.3.0", "electron-context-menu": "^3.1.0", diff --git a/redisinsight/ui/src/components/charts/donut-chart/DonutChart.spec.tsx b/redisinsight/ui/src/components/charts/donut-chart/DonutChart.spec.tsx new file mode 100644 index 0000000000..1e3c866231 --- /dev/null +++ b/redisinsight/ui/src/components/charts/donut-chart/DonutChart.spec.tsx @@ -0,0 +1,66 @@ +import React from 'react' +import { render, screen, fireEvent } from 'uiSrc/utils/test-utils' + +import DonutChart, { ChartData } from './DonutChart' + +const mockData: ChartData[] = [ + { value: 1, name: 'A', color: [0, 0, 0] }, + { value: 5, name: 'B', color: [10, 10, 10] }, + { value: 10, name: 'C', color: [20, 20, 20] }, + { value: 2, name: 'D', color: [30, 30, 30] }, + { value: 30, name: 'E', color: [40, 40, 40] }, + { value: 15, name: 'F', color: [50, 50, 50] }, +] + +describe('DonutChart', () => { + it('should render with empty data', () => { + expect(render()).toBeTruthy() + }) + + it('should render with data', () => { + expect(render()).toBeTruthy() + }) + + it('should render svg', () => { + render() + expect(screen.getByTestId('donut-test')).toBeInTheDocument() + }) + + it('should render arcs and labels', () => { + render() + mockData.forEach(({ value, name }) => { + expect(screen.getByTestId(`arc-${name}-${value}`)).toBeInTheDocument() + expect(screen.getByTestId(`label-${name}-${value}`)).toBeInTheDocument() + }) + }) + + it('should do not render label value if value less than 5%', () => { + render() + expect(screen.getByTestId('label-A-1')).toHaveTextContent('') + }) + + it('should render label value if value more than 5%', () => { + render() + expect(screen.getByTestId('label-E-30')).toHaveTextContent('E: 30') + }) + + it('should call render tooltip and label methods', () => { + const renderLabel = jest.fn() + const renderTooltip = jest.fn() + render() + expect(renderLabel).toBeCalled() + + fireEvent.mouseEnter(screen.getByTestId('arc-A-1')) + expect(renderTooltip).toBeCalled() + }) + + it('should set tooltip as visible on hover and hidden on leave', () => { + render() + + fireEvent.mouseEnter(screen.getByTestId('arc-A-1')) + expect(screen.getByTestId('chart-value-tooltip')).toBeVisible() + + fireEvent.mouseLeave(screen.getByTestId('arc-A-1')) + expect(screen.getByTestId('chart-value-tooltip')).not.toBeVisible() + }) +}) diff --git a/redisinsight/ui/src/components/charts/donut-chart/DonutChart.tsx b/redisinsight/ui/src/components/charts/donut-chart/DonutChart.tsx new file mode 100644 index 0000000000..c036c5e19c --- /dev/null +++ b/redisinsight/ui/src/components/charts/donut-chart/DonutChart.tsx @@ -0,0 +1,171 @@ +import cx from 'classnames' +import * as d3 from 'd3' +import React, { useEffect, useRef } from 'react' +import { truncateNumberToRange } from 'uiSrc/utils' +import { rgb, RGBColor } from 'uiSrc/utils/colors' + +import styles from './styles.module.scss' + +export interface ChartData { + value: number + name: string + color: RGBColor +} + +interface IProps { + name?: string + data: ChartData[] + width?: number + height?: number + title?: React.ReactElement | string + config?: { + percentToShowLabel?: number + arcWidth?: number + margin?: number + radius?: number + } + classNames?: { + chart?: string + arc?: string + arcLabel?: string + arcLabelValue?: string + tooltip?: string + } + renderLabel?: (value: number) => string + renderTooltip?: (value: number) => string +} + +const ANIMATION_DURATION_MS = 100 + +const DonutChart = (props: IProps) => { + const { + name = '', + data, + width = 328, + height = 300, + title, + config, + classNames, + renderLabel, + renderTooltip = (v) => v, + } = props + + const margin = config?.margin || 72 + const radius = config?.radius || (width / 2 - margin) + const arcWidth = config?.arcWidth || 8 + const percentToShowLabel = config?.percentToShowLabel || 5 + + const svgRef = useRef(null) + const tooltipRef = useRef(null) + + const arc = d3.arc>() + .outerRadius(radius) + .innerRadius(radius - arcWidth) + + const arcHover = d3.arc>() + .outerRadius(radius + 4) + .innerRadius(radius - arcWidth) + + const onMouseEnterSlice = (e: MouseEvent, d: d3.PieArcDatum) => { + d3 + .select>(e.target as SVGPathElement) + .transition() + .duration(ANIMATION_DURATION_MS) + .attr('d', arcHover) + + if (tooltipRef.current) { + tooltipRef.current.innerHTML = `${d.data.name}: ${renderTooltip(d.value)}` + tooltipRef.current.style.visibility = 'visible' + tooltipRef.current.style.top = `${e.pageY + 15}px` + tooltipRef.current.style.left = `${e.pageX + 15}px` + } + } + + const onMouseLeaveSlice = (e: MouseEvent) => { + d3 + .select>(e.target as SVGPathElement) + .transition() + .duration(ANIMATION_DURATION_MS) + .attr('d', arc) + + if (tooltipRef.current) { + tooltipRef.current.style.visibility = 'hidden' + } + } + + const isShowLabel = (d: d3.PieArcDatum) => + d.endAngle - d.startAngle > (Math.PI * 2) / (100 / percentToShowLabel) + + const getLabelPosition = (d: d3.PieArcDatum) => { + const [x, y] = arc.centroid(d) + const h = Math.sqrt(x * x + y * y) + return `translate(${(x / h) * (radius + 16)}, ${((y + 4) / h) * (radius + 16)})` + } + + useEffect(() => { + const pie = d3.pie().value((d: ChartData) => d.value).sort(null) + const dataReady = pie(data) + + d3 + .select(svgRef.current) + .select('g') + .remove() + + const svg = d3 + .select(svgRef.current) + .attr('width', width) + .attr('height', height) + .attr('data-testid', `donut-${name}`) + .attr('class', cx(classNames?.chart)) + .append('g') + .attr('transform', `translate(${width / 2},${height / 2})`) + + // add arcs + svg + .selectAll() + .data(dataReady) + .enter() + .append('path') + .attr('data-testid', (d) => `arc-${d.data.name}-${d.data.value}`) + .attr('d', arc) + .attr('fill', (d) => rgb(d.data.color)) + .attr('class', cx(styles.arc, classNames?.arc)) + .on('mouseenter mousemove', onMouseEnterSlice) + .on('mouseleave', onMouseLeaveSlice) + + // add labels + svg + .selectAll() + .data(dataReady) + .enter() + .append('text') + .attr('class', cx(styles.chartLabel, classNames?.arcLabel)) + .attr('transform', getLabelPosition) + .text((d) => (isShowLabel(d) ? d.data.name : '')) + .attr('data-testid', (d) => `label-${d.data.name}-${d.data.value}`) + .style('text-anchor', (d) => ((d.endAngle + d.startAngle) / 2 > Math.PI ? 'end' : 'start')) + .on('mouseenter mousemove', onMouseEnterSlice) + .on('mouseleave', onMouseLeaveSlice) + .append('tspan') + .text((d) => (isShowLabel(d) ? `: ${renderLabel ? renderLabel(d.value) : truncateNumberToRange(d.value)}` : '')) + .attr('class', cx(styles.chartLabelValue, classNames?.arcLabelValue)) + }, [data]) + + return ( +
+ +
+ {title && ( +
+ {title} +
+ )} +
+ ) +} + +export default DonutChart diff --git a/redisinsight/ui/src/components/charts/donut-chart/index.ts b/redisinsight/ui/src/components/charts/donut-chart/index.ts new file mode 100644 index 0000000000..bdfb67bbc5 --- /dev/null +++ b/redisinsight/ui/src/components/charts/donut-chart/index.ts @@ -0,0 +1,3 @@ +import DonutChart from './DonutChart' + +export default DonutChart diff --git a/redisinsight/ui/src/components/charts/donut-chart/styles.module.scss b/redisinsight/ui/src/components/charts/donut-chart/styles.module.scss new file mode 100644 index 0000000000..2fda31fb86 --- /dev/null +++ b/redisinsight/ui/src/components/charts/donut-chart/styles.module.scss @@ -0,0 +1,36 @@ +.wrapper { + position: relative; +} + +.innerTextContainer { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); +} + +.tooltip { + position: fixed; + background: var(--separatorColor); + color: var(--htmlColor); + padding: 10px; + visibility: hidden; + border-radius: 4px; + z-index: 5; +} + +.chartLabel { + fill: var(--euiTextSubduedColor); + font-size: 12px; + font-weight: bold; + + .chartLabelValue { + font-weight: normal; + } +} + +.arc { + stroke: var(--euiColorLightestShade); + stroke-width: 2px; + cursor: pointer; +} diff --git a/redisinsight/ui/src/components/charts/index.ts b/redisinsight/ui/src/components/charts/index.ts new file mode 100644 index 0000000000..0dc80344b0 --- /dev/null +++ b/redisinsight/ui/src/components/charts/index.ts @@ -0,0 +1,5 @@ +import DonutChart from './donut-chart' + +export { + DonutChart +} diff --git a/redisinsight/ui/src/mocks/handlers/analytics/clusterDetailsHandlers.ts b/redisinsight/ui/src/mocks/handlers/analytics/clusterDetailsHandlers.ts index bc38e80320..b328a8e685 100644 --- a/redisinsight/ui/src/mocks/handlers/analytics/clusterDetailsHandlers.ts +++ b/redisinsight/ui/src/mocks/handlers/analytics/clusterDetailsHandlers.ts @@ -1,5 +1,6 @@ import { DatabaseInstanceResponse } from 'apiSrc/modules/instances/dto/database-instance.dto' import { rest, RestHandler } from 'msw' +import { ClusterDetails, HealthStatus, NodeRole } from 'apiSrc/modules/cluster-monitor/models' import { ApiEndpoints } from 'uiSrc/constants' import { getUrl } from 'uiSrc/utils' import { getMswURL } from 'uiSrc/utils/test-utils' @@ -9,13 +10,13 @@ export const INSTANCE_ID_MOCK = 'instanceId' const handlers: RestHandler[] = [ // useGetClusterDetailsQuery rest.get(getMswURL(getUrl(INSTANCE_ID_MOCK, ApiEndpoints.CLUSTER_DETAILS)), - async (req, res, ctx) => res( + async (_req, res, ctx) => res( ctx.status(200), ctx.json(CLUSTER_DETAILS_DATA_MOCK), )) ] -export const CLUSTER_DETAILS_DATA_MOCK = { +export const CLUSTER_DETAILS_DATA_MOCK: ClusterDetails = { state: 'ok', slotsAssigned: 16384, slotsOk: 16384, @@ -34,11 +35,11 @@ export const CLUSTER_DETAILS_DATA_MOCK = { id: '3', host: '3.93.234.244', port: 12511, - role: 'primary', + role: 'primary' as NodeRole, slots: [ '10923-16383' ], - health: 'online', + health: 'online' as HealthStatus, totalKeys: 0, usedMemory: 38448896, opsPerSecond: 0, @@ -47,24 +48,22 @@ export const CLUSTER_DETAILS_DATA_MOCK = { commandsProcessed: 114, networkInKbps: 0.35, networkOutKbps: 3.62, - cacheHitRatio: null, + cacheHitRatio: 0, replicationOffset: 0, uptimeSec: 1661931600, version: '6.2.6', mode: 'standalone', - replicas: [ - - ] + replicas: [] }, { id: '4', host: '44.202.117.57', port: 12511, - role: 'primary', + role: 'primary' as NodeRole, slots: [ '0-5460' ], - health: 'online', + health: 'online' as HealthStatus, totalKeys: 0, usedMemory: 38448896, opsPerSecond: 0, @@ -73,24 +72,22 @@ export const CLUSTER_DETAILS_DATA_MOCK = { commandsProcessed: 114, networkInKbps: 0.35, networkOutKbps: 3.62, - cacheHitRatio: null, + cacheHitRatio: 0, replicationOffset: 0, uptimeSec: 1661931600, version: '6.2.6', mode: 'standalone', - replicas: [ - - ] + replicas: [] }, { id: '5', host: '44.210.115.34', port: 12511, - role: 'primary', + role: 'primary' as NodeRole, slots: [ '5461-10922' ], - health: 'online', + health: 'online' as HealthStatus, totalKeys: 0, usedMemory: 38448896, opsPerSecond: 0, @@ -99,14 +96,12 @@ export const CLUSTER_DETAILS_DATA_MOCK = { commandsProcessed: 114, networkInKbps: 0.35, networkOutKbps: 3.62, - cacheHitRatio: null, + cacheHitRatio: 0, replicationOffset: 0, uptimeSec: 1661931600, version: '6.2.6', mode: 'standalone', - replicas: [ - - ] + replicas: [] } ], version: '6.2.6', diff --git a/redisinsight/ui/src/mocks/handlers/index.ts b/redisinsight/ui/src/mocks/handlers/index.ts index 603bbcdf1e..519ee1acf3 100644 --- a/redisinsight/ui/src/mocks/handlers/index.ts +++ b/redisinsight/ui/src/mocks/handlers/index.ts @@ -2,5 +2,6 @@ import { MockedRequest, RestHandler } from 'msw' import instances from './instances' import content from './content' import app from './app' +import analytics from './analytics' -export const handlers: RestHandler[] = [].concat(instances, content, app) +export const handlers: RestHandler[] = [].concat(instances, content, app, analytics) diff --git a/redisinsight/ui/src/pages/clusterDetails/ClusterDetailsPage.spec.tsx b/redisinsight/ui/src/pages/clusterDetails/ClusterDetailsPage.spec.tsx index 44d05666ca..f7c081ce28 100644 --- a/redisinsight/ui/src/pages/clusterDetails/ClusterDetailsPage.spec.tsx +++ b/redisinsight/ui/src/pages/clusterDetails/ClusterDetailsPage.spec.tsx @@ -1,31 +1,36 @@ +import { cloneDeep } from 'lodash' import React from 'react' -import { fetchClusterDetailsAction } from 'uiSrc/slices/analytics/clusterDetails' -import { render } from 'uiSrc/utils/test-utils' +import { CLUSTER_DETAILS_DATA_MOCK } from 'uiSrc/mocks/handlers/analytics/clusterDetailsHandlers' +import { + getClusterDetails, + getClusterDetailsSuccess +} from 'uiSrc/slices/analytics/clusterDetails' +import { act, cleanup, mockedStore, render } from 'uiSrc/utils/test-utils' import ClusterDetailsPage from './ClusterDetailsPage' -jest.mock('uiSrc/slices/analytics/clusterDetails', () => ({ - ...jest.requireActual('uiSrc/slices/analytics/clusterDetails'), - fetchClusterDetailsAction: jest.fn(), - clusterDetailsSelector: jest.fn().mockReturnValue({ - data: [], - loading: false, - error: '', - }), -})) +let store: typeof mockedStore describe('ClusterDetailsPage', () => { - it('should render', () => { - const fetchClusterDetailsActionMock = jest.fn(); - (fetchClusterDetailsAction as jest.Mock).mockImplementation(() => fetchClusterDetailsActionMock) - expect(render()).toBeTruthy() + beforeEach(() => { + cleanup() + store = cloneDeep(mockedStore) + store.clearActions() + }) + + it('should render', async () => { + await act(() => { + expect(render()) + .toBeTruthy() + }) }) it('should call fetchClusterDetailsAction after rendering', async () => { - const fetchClusterDetailsActionMock = jest.fn(); - (fetchClusterDetailsAction as jest.Mock).mockImplementation(() => fetchClusterDetailsActionMock) + await act(() => { + render() + }) - render() - expect(fetchClusterDetailsActionMock).toBeCalled() + const expectedActions = [getClusterDetails(), getClusterDetailsSuccess(CLUSTER_DETAILS_DATA_MOCK)] + expect(store.getActions()).toEqual([...expectedActions]) }) }) diff --git a/redisinsight/ui/src/pages/clusterDetails/ClusterDetailsPage.tsx b/redisinsight/ui/src/pages/clusterDetails/ClusterDetailsPage.tsx index 3a1487bbdc..121729f742 100644 --- a/redisinsight/ui/src/pages/clusterDetails/ClusterDetailsPage.tsx +++ b/redisinsight/ui/src/pages/clusterDetails/ClusterDetailsPage.tsx @@ -1,11 +1,12 @@ import { orderBy } from 'lodash' -import React, { useEffect, useState } from 'react' +import React, { useContext, useEffect, useState } from 'react' import { useSelector, useDispatch } from 'react-redux' import { useParams } from 'react-router-dom' import { ClusterNodeDetails } from 'src/modules/cluster-monitor/models' import InstanceHeader from 'uiSrc/components/instance-header' -import ClusterNodesTable from 'uiSrc/pages/clusterDetails/components/cluser-nodes-table' +import { Theme } from 'uiSrc/constants' +import { ThemeContext } from 'uiSrc/contexts/themeContext' import { clusterDetailsSelector, fetchClusterDetailsAction } from 'uiSrc/slices/analytics/clusterDetails' import { analyticsSettingsSelector, setAnalyticsViewTab } from 'uiSrc/slices/analytics/settings' import { appAnalyticsInfoSelector } from 'uiSrc/slices/app/info' @@ -13,13 +14,16 @@ import { connectedInstanceSelector } from 'uiSrc/slices/instances/instances' import { AnalyticsViewTab } from 'uiSrc/slices/interfaces/analytics' import { sendPageViewTelemetry, TelemetryPageView } from 'uiSrc/telemetry' import { formatLongName, getDbIndex, getLetterByIndex, Nullable, setTitle, } from 'uiSrc/utils' +import { ColorScheme, getRGBColorByScheme, RGBColor } from 'uiSrc/utils/colors' + +import { ClusterDetailsHeader, ClusterDetailsGraphics, ClusterNodesTable } from './components' -import ClusterDetailsHeader from './components/cluster-details-header' import styles from './styles.module.scss' export interface ModifiedClusterNodes extends ClusterNodeDetails { letter: string index: number + color: RGBColor } const POLLING_INTERVAL = 5_000 @@ -39,10 +43,18 @@ const ClusterDetailsPage = () => { const [nodes, setNodes] = useState>(null) const dispatch = useDispatch() + const { theme } = useContext(ThemeContext) const dbName = `${formatLongName(connectedInstanceName, 33, 0, '...')} ${getDbIndex(db)}` setTitle(`${dbName} - Overview`) + const colorScheme: ColorScheme = { + cHueStart: 180, + cHueRange: 140, + cSaturation: 55, + cLightness: theme === Theme.Dark ? 45 : 55 + } + useEffect(() => { dispatch(fetchClusterDetailsAction( instanceId, @@ -73,7 +85,13 @@ const ClusterDetailsPage = () => { useEffect(() => { if (data) { const nodes = orderBy(data.nodes, ['asc', 'host']) - const modifiedNodes = nodes.map((d, index) => ({ ...d, letter: getLetterByIndex(index), index })) + const shift = colorScheme.cHueRange / nodes.length + const modifiedNodes = nodes.map((d, index) => ({ + ...d, + letter: getLetterByIndex(index), + index, + color: getRGBColorByScheme(index, shift, colorScheme) + })) setNodes(modifiedNodes) } }, [data]) @@ -97,7 +115,10 @@ const ClusterDetailsPage = () => {
- +
+ + +
) diff --git a/redisinsight/ui/src/pages/clusterDetails/components/cluser-nodes-table/ClusterNodesTable.spec.tsx b/redisinsight/ui/src/pages/clusterDetails/components/cluser-nodes-table/ClusterNodesTable.spec.tsx index d36ad39d16..971b263610 100644 --- a/redisinsight/ui/src/pages/clusterDetails/components/cluser-nodes-table/ClusterNodesTable.spec.tsx +++ b/redisinsight/ui/src/pages/clusterDetails/components/cluser-nodes-table/ClusterNodesTable.spec.tsx @@ -74,7 +74,7 @@ const mockNodes = [ mode: 'cluster', replicas: [] } -].map((d, index) => ({ ...d, letter: getLetterByIndex(index), index })) as ModifiedClusterNodes[] +].map((d, index) => ({ ...d, letter: getLetterByIndex(index), index, color: [0, 0, 0] })) as ModifiedClusterNodes[] describe('ClusterNodesTable', () => { it('should render', () => { diff --git a/redisinsight/ui/src/pages/clusterDetails/components/cluser-nodes-table/styles.module.scss b/redisinsight/ui/src/pages/clusterDetails/components/cluser-nodes-table/styles.module.scss index 44dd432781..80c3bd83bd 100644 --- a/redisinsight/ui/src/pages/clusterDetails/components/cluser-nodes-table/styles.module.scss +++ b/redisinsight/ui/src/pages/clusterDetails/components/cluser-nodes-table/styles.module.scss @@ -5,7 +5,6 @@ $breakpoint-table: 1232px; .wrapper { - height: calc(100% - 140px); max-width: 1920px; .loading { diff --git a/redisinsight/ui/src/pages/clusterDetails/components/cluster-details-graphics/ClusterDetailsGraphics.spec.tsx b/redisinsight/ui/src/pages/clusterDetails/components/cluster-details-graphics/ClusterDetailsGraphics.spec.tsx new file mode 100644 index 0000000000..c666092bca --- /dev/null +++ b/redisinsight/ui/src/pages/clusterDetails/components/cluster-details-graphics/ClusterDetailsGraphics.spec.tsx @@ -0,0 +1,100 @@ +import React from 'react' +import { ModifiedClusterNodes } from 'uiSrc/pages/clusterDetails/ClusterDetailsPage' +import { render, screen } from 'uiSrc/utils/test-utils' + +import ClusterDetailsGraphics from './ClusterDetailsGraphics' + +const mockNodes = [ + { + id: '1', + host: '0.0.0.1', + port: 6379, + role: 'primary', + slots: ['10923-16383'], + health: 'online', + totalKeys: 1, + usedMemory: 2867968, + opsPerSecond: 1, + connectionsReceived: 13, + connectedClients: 6, + commandsProcessed: 5678, + networkInKbps: 0.02, + networkOutKbps: 0, + cacheHitRatio: 1, + replicationOffset: 6924, + uptimeSec: 5614, + version: '6.2.6', + mode: 'cluster', + replicas: [] + }, + { + id: '2', + host: '0.0.0.2', + port: 6379, + role: 'primary', + slots: ['0-5460'], + health: 'online', + totalKeys: 4, + usedMemory: 2825880, + opsPerSecond: 1, + connectionsReceived: 15, + connectedClients: 4, + commandsProcessed: 5667, + networkInKbps: 0.04, + networkOutKbps: 0, + cacheHitRatio: 1, + replicationOffset: 6910, + uptimeSec: 5609, + version: '6.2.6', + mode: 'cluster', + replicas: [] + }, + { + id: '3', + host: '0.0.0.3', + port: 6379, + role: 'primary', + slots: [ + '5461-10922' + ], + health: 'online', + totalKeys: 10, + usedMemory: 2886960, + opsPerSecond: 0, + connectionsReceived: 18, + connectedClients: 7, + commandsProcessed: 5697, + networkInKbps: 0.02, + networkOutKbps: 0, + cacheHitRatio: 0, + replicationOffset: 6991, + uptimeSec: 5609, + version: '6.2.6', + mode: 'cluster', + replicas: [] + } +].map((d, index) => ({ ...d, letter: 'A', index, color: [0, 0, 0] })) as ModifiedClusterNodes[] + +describe('ClusterDetailsGraphics', () => { + it('should render', () => { + expect(render()).toBeTruthy() + }) + + it('should render nothing without nodes', () => { + render() + expect(screen.queryByTestId('cluster-details-graphics-loading')).not.toBeInTheDocument() + expect(screen.queryByTestId('cluster-details-charts')).not.toBeInTheDocument() + }) + + it('should render loading content', () => { + render() + expect(screen.getByTestId('cluster-details-graphics-loading')).toBeInTheDocument() + expect(screen.queryByTestId('cluster-details-charts')).not.toBeInTheDocument() + }) + + it('should render donuts', () => { + render() + expect(screen.getByTestId('donut-memory')).toBeInTheDocument() + expect(screen.queryByTestId('donut-keys')).toBeInTheDocument() + }) +}) diff --git a/redisinsight/ui/src/pages/clusterDetails/components/cluster-details-graphics/ClusterDetailsGraphics.tsx b/redisinsight/ui/src/pages/clusterDetails/components/cluster-details-graphics/ClusterDetailsGraphics.tsx new file mode 100644 index 0000000000..9ca13fe647 --- /dev/null +++ b/redisinsight/ui/src/pages/clusterDetails/components/cluster-details-graphics/ClusterDetailsGraphics.tsx @@ -0,0 +1,73 @@ +import { EuiIcon, EuiTitle } from '@elastic/eui' +import cx from 'classnames' +import React, { useEffect, useState } from 'react' +import { DonutChart } from 'uiSrc/components/charts' +import { ChartData } from 'uiSrc/components/charts/donut-chart/DonutChart' +import { KeyIconSvg, MemoryIconSvg } from 'uiSrc/components/database-overview/components/icons' +import { ModifiedClusterNodes } from 'uiSrc/pages/clusterDetails/ClusterDetailsPage' +import { formatBytes, Nullable } from 'uiSrc/utils' +import { numberWithSpaces } from 'uiSrc/utils/numbers' + +import styles from './styles.module.scss' + +const ClusterDetailsGraphics = ({ nodes, loading }: { nodes: Nullable, loading: boolean }) => { + const [memoryData, setMemoryData] = useState([]) + const [keysData, setKeysData] = useState([]) + + const renderMemoryLabel = (value: number) => formatBytes(value, 1, false) as string + const renderMemoryTooltip = (value: number) => `${numberWithSpaces(value)} B` + + useEffect(() => { + if (nodes) { + setMemoryData(nodes.map((n) => ({ value: n.usedMemory, name: n.letter, color: n.color })) as ChartData[]) + setKeysData(nodes.map((n) => ({ value: n.totalKeys, name: n.letter, color: n.color })) as ChartData[]) + } + }, [nodes]) + + if (loading && !nodes?.length) { + return ( +
+
+
+
+ ) + } + + if (!nodes || nodes.length === 0) { + return null + } + + return ( +
+ + + + Memory + +
+ )} + /> + + + + Keys + +
+ )} + /> +
+ ) +} + +export default ClusterDetailsGraphics diff --git a/redisinsight/ui/src/pages/clusterDetails/components/cluster-details-graphics/index.ts b/redisinsight/ui/src/pages/clusterDetails/components/cluster-details-graphics/index.ts new file mode 100644 index 0000000000..316d8707fc --- /dev/null +++ b/redisinsight/ui/src/pages/clusterDetails/components/cluster-details-graphics/index.ts @@ -0,0 +1,3 @@ +import ClusterDetailsGraphics from './ClusterDetailsGraphics' + +export default ClusterDetailsGraphics diff --git a/redisinsight/ui/src/pages/clusterDetails/components/cluster-details-graphics/styles.module.scss b/redisinsight/ui/src/pages/clusterDetails/components/cluster-details-graphics/styles.module.scss new file mode 100644 index 0000000000..3b587cd5b4 --- /dev/null +++ b/redisinsight/ui/src/pages/clusterDetails/components/cluster-details-graphics/styles.module.scss @@ -0,0 +1,30 @@ +.wrapper { + background-color: var(--euiColorLightestShade); + border-radius: 16px; + + display: flex; + align-items: center; + justify-content: space-around; + margin-bottom: 24px; + + &.loadingWrapper { + margin-top: 36px; + } + + .chartTitle { + display: flex; + align-items: center; + + .icon { + margin-right: 10px; + } + } + + .preloaderCircle { + width: 180px; + height: 180px; + margin: 60px 0; + border-radius: 100%; + background-color: var(--separatorColor); + } +} diff --git a/redisinsight/ui/src/pages/clusterDetails/components/cluster-details-header/ClusterDetailsHeader.tsx b/redisinsight/ui/src/pages/clusterDetails/components/cluster-details-header/ClusterDetailsHeader.tsx index 03dd1d4285..5a4d0c986c 100644 --- a/redisinsight/ui/src/pages/clusterDetails/components/cluster-details-header/ClusterDetailsHeader.tsx +++ b/redisinsight/ui/src/pages/clusterDetails/components/cluster-details-header/ClusterDetailsHeader.tsx @@ -11,7 +11,7 @@ import { capitalize } from 'lodash' import { truncateNumberToFirstUnit, formatLongName, - truncateTTLToDuration, + truncateNumberToDuration, } from 'uiSrc/utils' import { nullableNumberWithSpaces } from 'uiSrc/utils/numbers' import { connectedInstanceSelector } from 'uiSrc/slices/instances/instances' @@ -76,7 +76,7 @@ const ClusterDetailsHeader = () => { <> {`${nullableNumberWithSpaces(data?.uptimeSec) || 0} s`}
- {`(${truncateTTLToDuration(data?.uptimeSec || 0)})`} + {`(${truncateNumberToDuration(data?.uptimeSec || 0)})`} )} > diff --git a/redisinsight/ui/src/pages/clusterDetails/components/index.ts b/redisinsight/ui/src/pages/clusterDetails/components/index.ts new file mode 100644 index 0000000000..a3332921e8 --- /dev/null +++ b/redisinsight/ui/src/pages/clusterDetails/components/index.ts @@ -0,0 +1,9 @@ +import ClusterDetailsHeader from './cluster-details-header' +import ClusterDetailsGraphics from './cluster-details-graphics' +import ClusterNodesTable from './cluser-nodes-table' + +export { + ClusterDetailsHeader, + ClusterDetailsGraphics, + ClusterNodesTable +} diff --git a/redisinsight/ui/src/pages/clusterDetails/styles.module.scss b/redisinsight/ui/src/pages/clusterDetails/styles.module.scss index d011665959..8bd111291a 100644 --- a/redisinsight/ui/src/pages/clusterDetails/styles.module.scss +++ b/redisinsight/ui/src/pages/clusterDetails/styles.module.scss @@ -1,6 +1,19 @@ +@import "@elastic/eui/src/global_styling/mixins/helpers"; +@import "@elastic/eui/src/components/table/mixins"; +@import "@elastic/eui/src/global_styling/index"; + .main { margin: 0 16px 0; height: calc(100% - 70px); background-color: var(--euiColorEmptyShade); padding: 18px 24px; } + +.wrapper { + @include euiScrollBar; + overflow-y: auto; + overflow-x: hidden; + max-height: calc(100% - 134px); + + max-width: 1920px; +} diff --git a/redisinsight/ui/src/utils/colors.ts b/redisinsight/ui/src/utils/colors.ts new file mode 100644 index 0000000000..d7886092fd --- /dev/null +++ b/redisinsight/ui/src/utils/colors.ts @@ -0,0 +1,48 @@ +export type RGBColor = [number, number, number] + +export interface ColorScheme { + cHueStart: number + cHueRange: number + cSaturation: number + cLightness: number +} + +const HSLToRGB = (h: number, sI: number, lI: number): RGBColor => { + const s = sI / 100 + const l = lI / 100 + const k = (n: number) => (n + h / 30) % 12 + const a = s * Math.min(l, 1 - l) + const f = (n: number) => l - a * Math.max(-1, Math.min(k(n) - 3, Math.min(9 - k(n), 1))) + + return [255 * f(0), 255 * f(8), 255 * f(4)] +} + +const PBC = (r: number, g: number, b: number): number => + Math.sqrt(0.299 * (r * r) + 0.587 * (g * g) + 0.114 * (b * b)) / 255 + +const correctBrightness = (rgb: RGBColor, cLightness: number) => 1 / ((PBC(...rgb) * 100) / cLightness) + +const applyBrightnessToRGB = (rgb: RGBColor, cLightness: number): RGBColor => { + const [r, g, b] = rgb + return [ + Math.round(r * correctBrightness([r, g, b], cLightness)), + Math.round(g * correctBrightness([r, g, b], cLightness)), + Math.round(b * correctBrightness([r, g, b], cLightness)) + ] as RGBColor +} + +const getRGBColorByScheme = (index: number, shift: number, colorScheme: ColorScheme): RGBColor => { + const nc = index * shift + colorScheme.cHueStart + const rgb: RGBColor = HSLToRGB(nc, colorScheme.cSaturation, colorScheme.cLightness) + return applyBrightnessToRGB(rgb, colorScheme.cLightness) +} + +const rgb = (rgb: RGBColor) => `rgb(${rgb.join(', ')})` + +export { + HSLToRGB, + correctBrightness, + applyBrightnessToRGB, + getRGBColorByScheme, + rgb +} diff --git a/redisinsight/ui/src/utils/tests/colors.spec.ts b/redisinsight/ui/src/utils/tests/colors.spec.ts new file mode 100644 index 0000000000..42618f637a --- /dev/null +++ b/redisinsight/ui/src/utils/tests/colors.spec.ts @@ -0,0 +1,37 @@ +import { ColorScheme, getRGBColorByScheme, rgb } from 'uiSrc/utils/colors' + +const colorScheme: ColorScheme = { + cHueStart: 180, + cHueRange: 140, + cSaturation: 55, + cLightness: 45 +} + +const RGBColorsTests: any[] = [ + // colors for length 3 + [0, 0, [39, 135, 135]], + [1, 140 / 3, [66, 101, 226]], + [2, 140 / 3, [143, 60, 208]], + + // other colors + [1, 140 / 3, [66, 101, 226]], + [2, 140 / 4, [101, 72, 248]], + [3, 140 / 5, [129, 65, 224]], + [4, 140 / 6, [143, 60, 208]], + [5, 140 / 7, [151, 57, 197]], +] + +describe('getRGBColorByScheme', () => { + it.each(RGBColorsTests)('for input: %s (index), %s (shift), should be output: %s', + (index, shift, expected) => { + const result = getRGBColorByScheme(index, shift, colorScheme) + expect(result).toEqual(expected) + }) +}) + +describe('rgb', () => { + it('should return proper rgb string color', () => { + expect(rgb([0, 0, 0])).toEqual('rgb(0, 0, 0)') + expect(rgb([100, 30, 10])).toEqual('rgb(100, 30, 10)') + }) +}) diff --git a/yarn.lock b/yarn.lock index d34ccfab99..63b273bc9b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2141,6 +2141,216 @@ resolved "https://registry.yarnpkg.com/@types/cookiejar/-/cookiejar-2.1.2.tgz#66ad9331f63fe8a3d3d9d8c6e3906dd10f6446e8" integrity sha512-t73xJJrvdTjXrn4jLS9VSGRbz0nUY3cl2DMGDU48lKl+HR9dbbjW2A9r3g40VA++mQpy6uuHg33gy7du2BKpog== +"@types/d3-array@*": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/d3-array/-/d3-array-3.0.3.tgz#87d990bf504d14ad6b16766979d04e943c046dac" + integrity sha512-Reoy+pKnvsksN0lQUlcH6dOGjRZ/3WRwXR//m+/8lt1BXeI4xyaUZoqULNjyXXRuh0Mj4LNpkCvhUpQlY3X5xQ== + +"@types/d3-axis@*": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/d3-axis/-/d3-axis-3.0.1.tgz#6afc20744fa5cc0cbc3e2bd367b140a79ed3e7a8" + integrity sha512-zji/iIbdd49g9WN0aIsGcwcTBUkgLsCSwB+uH+LPVDAiKWENMtI3cJEWt+7/YYwelMoZmbBfzA3qCdrZ2XFNnw== + dependencies: + "@types/d3-selection" "*" + +"@types/d3-brush@*": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/d3-brush/-/d3-brush-3.0.1.tgz#ae5f17ce391935ca88b29000e60ee20452c6357c" + integrity sha512-B532DozsiTuQMHu2YChdZU0qsFJSio3Q6jmBYGYNp3gMDzBmuFFgPt9qKA4VYuLZMp4qc6eX7IUFUEsvHiXZAw== + dependencies: + "@types/d3-selection" "*" + +"@types/d3-chord@*": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/d3-chord/-/d3-chord-3.0.1.tgz#54c8856c19c8e4ab36a53f73ba737de4768ad248" + integrity sha512-eQfcxIHrg7V++W8Qxn6QkqBNBokyhdWSAS73AbkbMzvLQmVVBviknoz2SRS/ZJdIOmhcmmdCRE/NFOm28Z1AMw== + +"@types/d3-color@*": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@types/d3-color/-/d3-color-3.1.0.tgz#6594da178ded6c7c3842f3cc0ac84b156f12f2d4" + integrity sha512-HKuicPHJuvPgCD+np6Se9MQvS6OCbJmOjGvylzMJRlDwUXjKTTXs6Pwgk79O09Vj/ho3u1ofXnhFOaEWWPrlwA== + +"@types/d3-contour@*": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/d3-contour/-/d3-contour-3.0.1.tgz#9ff4e2fd2a3910de9c5097270a7da8a6ef240017" + integrity sha512-C3zfBrhHZvrpAAK3YXqLWVAGo87A4SvJ83Q/zVJ8rFWJdKejUnDYaWZPkA8K84kb2vDA/g90LTQAz7etXcgoQQ== + dependencies: + "@types/d3-array" "*" + "@types/geojson" "*" + +"@types/d3-delaunay@*": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@types/d3-delaunay/-/d3-delaunay-6.0.1.tgz#006b7bd838baec1511270cb900bf4fc377bbbf41" + integrity sha512-tLxQ2sfT0p6sxdG75c6f/ekqxjyYR0+LwPrsO1mbC9YDBzPJhs2HbJJRrn8Ez1DBoHRo2yx7YEATI+8V1nGMnQ== + +"@types/d3-dispatch@*": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/d3-dispatch/-/d3-dispatch-3.0.1.tgz#a1b18ae5fa055a6734cb3bd3cbc6260ef19676e3" + integrity sha512-NhxMn3bAkqhjoxabVJWKryhnZXXYYVQxaBnbANu0O94+O/nX9qSjrA1P1jbAQJxJf+VC72TxDX/YJcKue5bRqw== + +"@types/d3-drag@*": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/d3-drag/-/d3-drag-3.0.1.tgz#fb1e3d5cceeee4d913caa59dedf55c94cb66e80f" + integrity sha512-o1Va7bLwwk6h03+nSM8dpaGEYnoIG19P0lKqlic8Un36ymh9NSkNFX1yiXMKNMx8rJ0Kfnn2eovuFaL6Jvj0zA== + dependencies: + "@types/d3-selection" "*" + +"@types/d3-dsv@*": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/d3-dsv/-/d3-dsv-3.0.0.tgz#f3c61fb117bd493ec0e814856feb804a14cfc311" + integrity sha512-o0/7RlMl9p5n6FQDptuJVMxDf/7EDEv2SYEO/CwdG2tr1hTfUVi0Iavkk2ax+VpaQ/1jVhpnj5rq1nj8vwhn2A== + +"@types/d3-ease@*": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/d3-ease/-/d3-ease-3.0.0.tgz#c29926f8b596f9dadaeca062a32a45365681eae0" + integrity sha512-aMo4eaAOijJjA6uU+GIeW018dvy9+oH5Y2VPPzjjfxevvGQ/oRDs+tfYC9b50Q4BygRR8yE2QCLsrT0WtAVseA== + +"@types/d3-fetch@*": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/d3-fetch/-/d3-fetch-3.0.1.tgz#f9fa88b81aa2eea5814f11aec82ecfddbd0b8fe0" + integrity sha512-toZJNOwrOIqz7Oh6Q7l2zkaNfXkfR7mFSJvGvlD/Ciq/+SQ39d5gynHJZ/0fjt83ec3WL7+u3ssqIijQtBISsw== + dependencies: + "@types/d3-dsv" "*" + +"@types/d3-force@*": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/d3-force/-/d3-force-3.0.3.tgz#76cb20d04ae798afede1ea6e41750763ff5a9c82" + integrity sha512-z8GteGVfkWJMKsx6hwC3SiTSLspL98VNpmvLpEFJQpZPq6xpA1I8HNBDNSpukfK0Vb0l64zGFhzunLgEAcBWSA== + +"@types/d3-format@*": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/d3-format/-/d3-format-3.0.1.tgz#194f1317a499edd7e58766f96735bdc0216bb89d" + integrity sha512-5KY70ifCCzorkLuIkDe0Z9YTf9RR2CjBX1iaJG+rgM/cPP+sO+q9YdQ9WdhQcgPj1EQiJ2/0+yUkkziTG6Lubg== + +"@types/d3-geo@*": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@types/d3-geo/-/d3-geo-3.0.2.tgz#e7ec5f484c159b2c404c42d260e6d99d99f45d9a" + integrity sha512-DbqK7MLYA8LpyHQfv6Klz0426bQEf7bRTvhMy44sNGVyZoWn//B0c+Qbeg8Osi2Obdc9BLLXYAKpyWege2/7LQ== + dependencies: + "@types/geojson" "*" + +"@types/d3-hierarchy@*": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@types/d3-hierarchy/-/d3-hierarchy-3.1.0.tgz#4561bb7ace038f247e108295ef77b6a82193ac25" + integrity sha512-g+sey7qrCa3UbsQlMZZBOHROkFqx7KZKvUpRzI/tAp/8erZWpYq7FgNKvYwebi2LaEiVs1klhUfd3WCThxmmWQ== + +"@types/d3-interpolate@*": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/d3-interpolate/-/d3-interpolate-3.0.1.tgz#e7d17fa4a5830ad56fe22ce3b4fac8541a9572dc" + integrity sha512-jx5leotSeac3jr0RePOH1KdR9rISG91QIE4Q2PYTu4OymLTZfA3SrnURSLzKH48HmXVUru50b8nje4E79oQSQw== + dependencies: + "@types/d3-color" "*" + +"@types/d3-path@*": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/d3-path/-/d3-path-3.0.0.tgz#939e3a784ae4f80b1fde8098b91af1776ff1312b" + integrity sha512-0g/A+mZXgFkQxN3HniRDbXMN79K3CdTpLsevj+PXiTcb2hVyvkZUBg37StmgCQkaD84cUJ4uaDAWq7UJOQy2Tg== + +"@types/d3-polygon@*": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/d3-polygon/-/d3-polygon-3.0.0.tgz#5200a3fa793d7736fa104285fa19b0dbc2424b93" + integrity sha512-D49z4DyzTKXM0sGKVqiTDTYr+DHg/uxsiWDAkNrwXYuiZVd9o9wXZIo+YsHkifOiyBkmSWlEngHCQme54/hnHw== + +"@types/d3-quadtree@*": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@types/d3-quadtree/-/d3-quadtree-3.0.2.tgz#433112a178eb7df123aab2ce11c67f51cafe8ff5" + integrity sha512-QNcK8Jguvc8lU+4OfeNx+qnVy7c0VrDJ+CCVFS9srBo2GL9Y18CnIxBdTF3v38flrGy5s1YggcoAiu6s4fLQIw== + +"@types/d3-random@*": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/d3-random/-/d3-random-3.0.1.tgz#5c8d42b36cd4c80b92e5626a252f994ca6bfc953" + integrity sha512-IIE6YTekGczpLYo/HehAy3JGF1ty7+usI97LqraNa8IiDur+L44d0VOjAvFQWJVdZOJHukUJw+ZdZBlgeUsHOQ== + +"@types/d3-scale-chromatic@*": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz#103124777e8cdec85b20b51fd3397c682ee1e954" + integrity sha512-dsoJGEIShosKVRBZB0Vo3C8nqSDqVGujJU6tPznsBJxNJNwMF8utmS83nvCBKQYPpjCzaaHcrf66iTRpZosLPw== + +"@types/d3-scale@*": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@types/d3-scale/-/d3-scale-4.0.2.tgz#41be241126af4630524ead9cb1008ab2f0f26e69" + integrity sha512-Yk4htunhPAwN0XGlIwArRomOjdoBFXC3+kCxK2Ubg7I9shQlVSJy/pG/Ht5ASN+gdMIalpk8TJ5xV74jFsetLA== + dependencies: + "@types/d3-time" "*" + +"@types/d3-selection@*": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/d3-selection/-/d3-selection-3.0.3.tgz#57be7da68e7d9c9b29efefd8ea5a9ef1171e42ba" + integrity sha512-Mw5cf6nlW1MlefpD9zrshZ+DAWL4IQ5LnWfRheW6xwsdaWOb6IRRu2H7XPAQcyXEx1D7XQWgdoKR83ui1/HlEA== + +"@types/d3-shape@*": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@types/d3-shape/-/d3-shape-3.1.0.tgz#1d87a6ddcf28285ef1e5c278ca4bdbc0658f3505" + integrity sha512-jYIYxFFA9vrJ8Hd4Se83YI6XF+gzDL1aC5DCsldai4XYYiVNdhtpGbA/GM6iyQ8ayhSp3a148LY34hy7A4TxZA== + dependencies: + "@types/d3-path" "*" + +"@types/d3-time-format@*": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@types/d3-time-format/-/d3-time-format-4.0.0.tgz#ee7b6e798f8deb2d9640675f8811d0253aaa1946" + integrity sha512-yjfBUe6DJBsDin2BMIulhSHmr5qNR5Pxs17+oW4DoVPyVIXZ+m6bs7j1UVKP08Emv6jRmYrYqxYzO63mQxy1rw== + +"@types/d3-time@*": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/d3-time/-/d3-time-3.0.0.tgz#e1ac0f3e9e195135361fa1a1d62f795d87e6e819" + integrity sha512-sZLCdHvBUcNby1cB6Fd3ZBrABbjz3v1Vm90nysCQ6Vt7vd6e/h9Lt7SiJUoEX0l4Dzc7P5llKyhqSi1ycSf1Hg== + +"@types/d3-timer@*": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/d3-timer/-/d3-timer-3.0.0.tgz#e2505f1c21ec08bda8915238e397fb71d2fc54ce" + integrity sha512-HNB/9GHqu7Fo8AQiugyJbv6ZxYz58wef0esl4Mv828w1ZKpAshw/uFWVDUcIB9KKFeFKoxS3cHY07FFgtTRZ1g== + +"@types/d3-transition@*": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@types/d3-transition/-/d3-transition-3.0.2.tgz#393dc3e3d55009a43cc6f252e73fccab6d78a8a4" + integrity sha512-jo5o/Rf+/u6uerJ/963Dc39NI16FQzqwOc54bwvksGAdVfvDrqDpVeq95bEvPtBwLCVZutAEyAtmSyEMxN7vxQ== + dependencies: + "@types/d3-selection" "*" + +"@types/d3-zoom@*": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/d3-zoom/-/d3-zoom-3.0.1.tgz#4bfc7e29625c4f79df38e2c36de52ec3e9faf826" + integrity sha512-7s5L9TjfqIYQmQQEUcpMAcBOahem7TRoSO/+Gkz02GbMVuULiZzjF2BOdw291dbO2aNon4m2OdFsRGaCq2caLQ== + dependencies: + "@types/d3-interpolate" "*" + "@types/d3-selection" "*" + +"@types/d3@^7.4.0": + version "7.4.0" + resolved "https://registry.yarnpkg.com/@types/d3/-/d3-7.4.0.tgz#fc5cac5b1756fc592a3cf1f3dc881bf08225f515" + integrity sha512-jIfNVK0ZlxcuRDKtRS/SypEyOQ6UHaFQBKv032X45VvxSJ6Yi5G9behy9h6tNTHTDGh5Vq+KbmBjUWLgY4meCA== + dependencies: + "@types/d3-array" "*" + "@types/d3-axis" "*" + "@types/d3-brush" "*" + "@types/d3-chord" "*" + "@types/d3-color" "*" + "@types/d3-contour" "*" + "@types/d3-delaunay" "*" + "@types/d3-dispatch" "*" + "@types/d3-drag" "*" + "@types/d3-dsv" "*" + "@types/d3-ease" "*" + "@types/d3-fetch" "*" + "@types/d3-force" "*" + "@types/d3-format" "*" + "@types/d3-geo" "*" + "@types/d3-hierarchy" "*" + "@types/d3-interpolate" "*" + "@types/d3-path" "*" + "@types/d3-polygon" "*" + "@types/d3-quadtree" "*" + "@types/d3-random" "*" + "@types/d3-scale" "*" + "@types/d3-scale-chromatic" "*" + "@types/d3-selection" "*" + "@types/d3-shape" "*" + "@types/d3-time" "*" + "@types/d3-time-format" "*" + "@types/d3-timer" "*" + "@types/d3-transition" "*" + "@types/d3-zoom" "*" + "@types/date-fns@^2.6.0": version "2.6.0" resolved "https://registry.yarnpkg.com/@types/date-fns/-/date-fns-2.6.0.tgz#b062ca46562002909be0c63a6467ed173136acc1" @@ -2219,6 +2429,11 @@ dependencies: "@types/node" "*" +"@types/geojson@*": + version "7946.0.10" + resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.10.tgz#6dfbf5ea17142f7f9a043809f1cd4c448cb68249" + integrity sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA== + "@types/glob@^7.1.1": version "7.1.4" resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.4.tgz#ea59e21d2ee5c517914cb4bc8e4153b99e566672" @@ -4919,6 +5134,11 @@ commander@4.1.1, commander@^4.1.1: resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== +commander@7, 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== + commander@^2.20.0: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" @@ -4934,11 +5154,6 @@ commander@^6.1.0, commander@^6.2.0: resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c" integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA== -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== - commondir@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" @@ -5492,6 +5707,250 @@ csstype@^3.0.2: resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.5.tgz#7fdec6a28a67ae18647c51668a9ff95bb2fa7bb8" integrity sha512-uVDi8LpBUKQj6sdxNaTetL6FpeCqTjOvAQuQUa/qAqq8oOd4ivkbhgnqayl0dnPal8Tb/yB1tF+gOvCBiicaiQ== +"d3-array@2 - 3", "d3-array@2.10.0 - 3", "d3-array@2.5.0 - 3", d3-array@3, d3-array@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-3.2.0.tgz#15bf96cd9b7333e02eb8de8053d78962eafcff14" + integrity sha512-3yXFQo0oG3QCxbF06rMPFyGRMGJNS7NvsV1+2joOjbBE+9xvWQ8+GcMJAjRCzw06zQ3/arXeJgbPYcjUCuC+3g== + dependencies: + internmap "1 - 2" + +d3-axis@3: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-axis/-/d3-axis-3.0.0.tgz#c42a4a13e8131d637b745fc2973824cfeaf93322" + integrity sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw== + +d3-brush@3: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-brush/-/d3-brush-3.0.0.tgz#6f767c4ed8dcb79de7ede3e1c0f89e63ef64d31c" + integrity sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ== + dependencies: + d3-dispatch "1 - 3" + d3-drag "2 - 3" + d3-interpolate "1 - 3" + d3-selection "3" + d3-transition "3" + +d3-chord@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-chord/-/d3-chord-3.0.1.tgz#d156d61f485fce8327e6abf339cb41d8cbba6966" + integrity sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g== + dependencies: + d3-path "1 - 3" + +"d3-color@1 - 3", d3-color@3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-3.1.0.tgz#395b2833dfac71507f12ac2f7af23bf819de24e2" + integrity sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA== + +d3-contour@4: + version "4.0.0" + resolved "https://registry.yarnpkg.com/d3-contour/-/d3-contour-4.0.0.tgz#5a1337c6da0d528479acdb5db54bc81a0ff2ec6b" + integrity sha512-7aQo0QHUTu/Ko3cP9YK9yUTxtoDEiDGwnBHyLxG5M4vqlBkO/uixMRele3nfsfj6UXOcuReVpVXzAboGraYIJw== + dependencies: + d3-array "^3.2.0" + +d3-delaunay@6: + version "6.0.2" + resolved "https://registry.yarnpkg.com/d3-delaunay/-/d3-delaunay-6.0.2.tgz#7fd3717ad0eade2fc9939f4260acfb503f984e92" + integrity sha512-IMLNldruDQScrcfT+MWnazhHbDJhcRJyOEBAJfwQnHle1RPh6WDuLvxNArUju2VSMSUuKlY5BGHRJ2cYyoFLQQ== + dependencies: + delaunator "5" + +"d3-dispatch@1 - 3", d3-dispatch@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-dispatch/-/d3-dispatch-3.0.1.tgz#5fc75284e9c2375c36c839411a0cf550cbfc4d5e" + integrity sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg== + +"d3-drag@2 - 3", d3-drag@3: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-drag/-/d3-drag-3.0.0.tgz#994aae9cd23c719f53b5e10e3a0a6108c69607ba" + integrity sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg== + dependencies: + d3-dispatch "1 - 3" + d3-selection "3" + +"d3-dsv@1 - 3", d3-dsv@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-dsv/-/d3-dsv-3.0.1.tgz#c63af978f4d6a0d084a52a673922be2160789b73" + integrity sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q== + dependencies: + commander "7" + iconv-lite "0.6" + rw "1" + +"d3-ease@1 - 3", d3-ease@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-3.0.1.tgz#9658ac38a2140d59d346160f1f6c30fda0bd12f4" + integrity sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w== + +d3-fetch@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-fetch/-/d3-fetch-3.0.1.tgz#83141bff9856a0edb5e38de89cdcfe63d0a60a22" + integrity sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw== + dependencies: + d3-dsv "1 - 3" + +d3-force@3: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-force/-/d3-force-3.0.0.tgz#3e2ba1a61e70888fe3d9194e30d6d14eece155c4" + integrity sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg== + dependencies: + d3-dispatch "1 - 3" + d3-quadtree "1 - 3" + d3-timer "1 - 3" + +"d3-format@1 - 3", d3-format@3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-3.1.0.tgz#9260e23a28ea5cb109e93b21a06e24e2ebd55641" + integrity sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA== + +d3-geo@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-geo/-/d3-geo-3.0.1.tgz#4f92362fd8685d93e3b1fae0fd97dc8980b1ed7e" + integrity sha512-Wt23xBych5tSy9IYAM1FR2rWIBFWa52B/oF/GYe5zbdHrg08FU8+BuI6X4PvTwPDdqdAdq04fuWJpELtsaEjeA== + dependencies: + d3-array "2.5.0 - 3" + +d3-hierarchy@3: + version "3.1.2" + resolved "https://registry.yarnpkg.com/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz#b01cd42c1eed3d46db77a5966cf726f8c09160c6" + integrity sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA== + +"d3-interpolate@1 - 3", "d3-interpolate@1.2.0 - 3", d3-interpolate@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz#3c47aa5b32c5b3dfb56ef3fd4342078a632b400d" + integrity sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g== + dependencies: + d3-color "1 - 3" + +"d3-path@1 - 3", d3-path@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-3.0.1.tgz#f09dec0aaffd770b7995f1a399152bf93052321e" + integrity sha512-gq6gZom9AFZby0YLduxT1qmrp4xpBA1YZr19OI717WIdKE2OM5ETq5qrHLb301IgxhLwcuxvGZVLeeWc/k1I6w== + +d3-polygon@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-polygon/-/d3-polygon-3.0.1.tgz#0b45d3dd1c48a29c8e057e6135693ec80bf16398" + integrity sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg== + +"d3-quadtree@1 - 3", d3-quadtree@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-quadtree/-/d3-quadtree-3.0.1.tgz#6dca3e8be2b393c9a9d514dabbd80a92deef1a4f" + integrity sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw== + +d3-random@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-random/-/d3-random-3.0.1.tgz#d4926378d333d9c0bfd1e6fa0194d30aebaa20f4" + integrity sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ== + +d3-scale-chromatic@3: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz#15b4ceb8ca2bb0dcb6d1a641ee03d59c3b62376a" + integrity sha512-Lx9thtxAKrO2Pq6OO2Ua474opeziKr279P/TKZsMAhYyNDD3EnCffdbgeSYN5O7m2ByQsxtuP2CSDczNUIZ22g== + dependencies: + d3-color "1 - 3" + d3-interpolate "1 - 3" + +d3-scale@4: + version "4.0.2" + resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-4.0.2.tgz#82b38e8e8ff7080764f8dcec77bd4be393689396" + integrity sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ== + dependencies: + d3-array "2.10.0 - 3" + d3-format "1 - 3" + d3-interpolate "1.2.0 - 3" + d3-time "2.1.1 - 3" + d3-time-format "2 - 4" + +"d3-selection@2 - 3", d3-selection@3: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-3.0.0.tgz#c25338207efa72cc5b9bd1458a1a41901f1e1b31" + integrity sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ== + +d3-shape@3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-3.1.0.tgz#c8a495652d83ea6f524e482fca57aa3f8bc32556" + integrity sha512-tGDh1Muf8kWjEDT/LswZJ8WF85yDZLvVJpYU9Nq+8+yW1Z5enxrmXOhTArlkaElU+CTn0OTVNli+/i+HP45QEQ== + dependencies: + d3-path "1 - 3" + +"d3-time-format@2 - 4", d3-time-format@4: + version "4.1.0" + resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-4.1.0.tgz#7ab5257a5041d11ecb4fe70a5c7d16a195bb408a" + integrity sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg== + dependencies: + d3-time "1 - 3" + +"d3-time@1 - 3", "d3-time@2.1.1 - 3", d3-time@3: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-3.0.0.tgz#65972cb98ae2d4954ef5c932e8704061335d4975" + integrity sha512-zmV3lRnlaLI08y9IMRXSDshQb5Nj77smnfpnd2LrBa/2K281Jijactokeak14QacHs/kKq0AQ121nidNYlarbQ== + dependencies: + d3-array "2 - 3" + +"d3-timer@1 - 3", d3-timer@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-3.0.1.tgz#6284d2a2708285b1abb7e201eda4380af35e63b0" + integrity sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA== + +"d3-transition@2 - 3", d3-transition@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-transition/-/d3-transition-3.0.1.tgz#6869fdde1448868077fdd5989200cb61b2a1645f" + integrity sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w== + dependencies: + d3-color "1 - 3" + d3-dispatch "1 - 3" + d3-ease "1 - 3" + d3-interpolate "1 - 3" + d3-timer "1 - 3" + +d3-zoom@3: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-zoom/-/d3-zoom-3.0.0.tgz#d13f4165c73217ffeaa54295cd6969b3e7aee8f3" + integrity sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw== + dependencies: + d3-dispatch "1 - 3" + d3-drag "2 - 3" + d3-interpolate "1 - 3" + d3-selection "2 - 3" + d3-transition "2 - 3" + +d3@^7.6.1: + version "7.6.1" + resolved "https://registry.yarnpkg.com/d3/-/d3-7.6.1.tgz#b21af9563485ed472802f8c611cc43be6c37c40c" + integrity sha512-txMTdIHFbcpLx+8a0IFhZsbp+PfBBPt8yfbmukZTQFroKuFqIwqswF0qE5JXWefylaAVpSXFoKm3yP+jpNLFLw== + dependencies: + d3-array "3" + d3-axis "3" + d3-brush "3" + d3-chord "3" + d3-color "3" + d3-contour "4" + d3-delaunay "6" + d3-dispatch "3" + d3-drag "3" + d3-dsv "3" + d3-ease "3" + d3-fetch "3" + d3-force "3" + d3-format "3" + d3-geo "3" + d3-hierarchy "3" + d3-interpolate "3" + d3-path "3" + d3-polygon "3" + d3-quadtree "3" + d3-random "3" + d3-scale "4" + d3-scale-chromatic "3" + d3-selection "3" + d3-shape "3" + d3-time "3" + d3-time-format "4" + d3-timer "3" + d3-transition "3" + d3-zoom "3" + damerau-levenshtein@^1.0.6: version "1.0.7" resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.7.tgz#64368003512a1a6992593741a09a9d31a836f55d" @@ -5695,6 +6154,13 @@ del@^4.1.1: pify "^4.0.1" rimraf "^2.6.3" +delaunator@5: + version "5.0.0" + resolved "https://registry.yarnpkg.com/delaunator/-/delaunator-5.0.0.tgz#60f052b28bd91c9b4566850ebf7756efe821d81b" + integrity sha512-AyLvtyJdbv/U1GkiS6gUUzclRoAY4Gs75qkMygJJhU75LW4DNuSF2RMzpxs9jw9Oz1BobHjTdkG3zdP55VxAqw== + dependencies: + robust-predicates "^3.0.0" + delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" @@ -8369,7 +8835,7 @@ iconv-lite@0.4.24, iconv-lite@^0.4.24: dependencies: safer-buffer ">= 2.1.2 < 3" -iconv-lite@^0.6.2: +iconv-lite@0.6, iconv-lite@^0.6.2: version "0.6.3" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== @@ -8565,6 +9031,11 @@ internal-slot@^1.0.3: has "^1.0.3" side-channel "^1.0.4" +"internmap@1 - 2": + version "2.0.3" + resolved "https://registry.yarnpkg.com/internmap/-/internmap-2.0.3.tgz#6685f23755e43c524e251d29cbc97248e3061009" + integrity sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg== + interpret@^1.0.0, interpret@^1.2.0: version "1.4.0" resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" @@ -13662,6 +14133,11 @@ roarr@^2.15.3: semver-compare "^1.0.0" sprintf-js "^1.1.2" +robust-predicates@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/robust-predicates/-/robust-predicates-3.0.1.tgz#ecde075044f7f30118682bd9fb3f123109577f9a" + integrity sha512-ndEIpszUHiG4HtDsQLeIuMvRsDnn8c8rYStabochtUeCvfuvNptb5TUbVD68LRAILPX7p9nqQGh4xJgn3EHS/g== + run-async@^2.4.0: version "2.4.1" resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" @@ -13674,6 +14150,11 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" +rw@1: + version "1.3.3" + resolved "https://registry.yarnpkg.com/rw/-/rw-1.3.3.tgz#3f862dfa91ab766b14885ef4d01124bfda074fb4" + integrity sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ== + rxjs@6.6.3, rxjs@^6.5.2, rxjs@^6.6.0, rxjs@^6.6.3: version "6.6.3" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.3.tgz#8ca84635c4daa900c0d3967a6ee7ac60271ee552"