From ab9313463fd8b62392b41a42c1b98f863a2e4e6d Mon Sep 17 00:00:00 2001 From: Kishore Kumaar Natarajan Date: Thu, 1 May 2025 13:30:48 -0700 Subject: [PATCH 01/26] Live Queries Initial dash Signed-off-by: Kishore Kumaar Natarajan --- .../pages/InflightQueries/InflightQueries.tsx | 659 ++++++++++++++++++ server/clusters/queryInsightsPlugin.ts | 7 + server/routes/index.ts | 46 ++ types/types.ts | 20 + 4 files changed, 732 insertions(+) create mode 100644 public/pages/InflightQueries/InflightQueries.tsx diff --git a/public/pages/InflightQueries/InflightQueries.tsx b/public/pages/InflightQueries/InflightQueries.tsx new file mode 100644 index 00000000..81c033d8 --- /dev/null +++ b/public/pages/InflightQueries/InflightQueries.tsx @@ -0,0 +1,659 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React, {useEffect, useContext, useState, useRef} from 'react'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiPanel, + EuiLink, + EuiText, + EuiTitle, + EuiTextAlign, + EuiIcon, + EuiButtonGroup, + EuiHorizontalRule, + EuiSpacer, +} from '@elastic/eui'; +import embed, { VisualizationSpec } from 'vega-embed'; +import { useHistory, useLocation } from 'react-router-dom'; +import { AppMountParameters, CoreStart } from 'opensearch-dashboards/public'; +import { DataSourceManagementPluginSetup } from 'src/plugins/data_source_management/public'; +import { + QUERY_INSIGHTS, + DataSourceContext, +} from '../TopNQueries/TopNQueries'; +import { QueryInsightsDataSourceMenu } from '../../components/DataSourcePicker'; +import { QueryInsightsDashboardsPluginStartDependencies } from '../../types'; +import {LiveSearchQueryResponse} from "../../../types/types"; +import {retrieveLiveQueries} from "../../../common/utils/QueryUtils"; +const inflightQueries = ({ + core, + depsStart, + params, + dataSourceManagement, + }: { + core: CoreStart; + params: AppMountParameters; + dataSourceManagement?: DataSourceManagementPluginSetup; + depsStart: QueryInsightsDashboardsPluginStartDependencies; +}) => { + const history = useHistory(); + const location = useLocation(); + const [query, setQuery] = useState(null); + + const { dataSource, setDataSource } = useContext(DataSourceContext)!; + + const fetchliveQueries = async () => { + const retrievedQueries = await retrieveLiveQueries(core); + setQuery(retrievedQueries); + }; + + useEffect(() => { + fetchliveQueries(); + }, []); + + useEffect(() => { + core.chrome.setBreadcrumbs([ + { + text: 'Query insights', + href: QUERY_INSIGHTS, + onClick: (e) => { + e.preventDefault(); + history.push(QUERY_INSIGHTS); + }, + }, + ]); + }, [core.chrome, history, location]); + + const chartOptions = [ + { id: 'donut', label: 'Donut', iconType: 'visPie' }, + { id: 'bar', label: 'Bar', iconType: 'visBarHorizontal' }, + ]; + + const [selectedChartIdByIndex, setSelectedChartIdByIndex] = useState('donut'); + const [selectedChartIdByUser, setSelectedChartIdByUser] = useState('donut'); + const [selectedChartIdByNode, setSelectedChartIdByNode] = useState('donut'); + + const onChartChangeByIndex = (optionId: string) => { + setSelectedChartIdByIndex(optionId); + console.log('Chart type changed to:', optionId); + }; + + const onChartChangeByUser = (optionId: string) => { + setSelectedChartIdByUser(optionId); + console.log('Chart type changed to:', optionId); + }; + + const onChartChangeByNode = (optionId: string) => { + setSelectedChartIdByNode(optionId); + console.log('Chart type changed to:', optionId); + }; + + + const metrics = React.useMemo(() => { + if (!query || !query.response?.live_queries?.length) return null; + + const queries = query.response.live_queries; + + const activeQueries = queries.length; + let totalLatency = 0; + let totalCPU = 0; + let totalMemory = 0; + let longestLatency = 0; + let longestQueryId = ''; + + queries.forEach(q => { + const latency = q.measurements?.latency?.number ?? 0; + const cpu = q.measurements?.cpu?.number ?? 0; + const memory = q.measurements?.memory?.number ?? 0; + + totalLatency += latency; + totalCPU += cpu; + totalMemory += memory; + + if (latency > longestLatency) { + longestLatency = latency; + longestQueryId = q.id; + } + }); + + return { + activeQueries, + avgElapsedSec: totalLatency / activeQueries / 1000000000, + longestElapsedSec: longestLatency / 1000000000, + longestQueryId, + totalCPUSec: totalCPU / 1000000000, + totalMemoryBytes: totalMemory, + }; + }, [query]); + + + const getChartSpec = (type: string): VisualizationSpec => { + const isDonut = type.includes('donut'); // Donut is at index 0 + + return { + width: 400, + height: 300, + mark: isDonut + ? { type: 'arc', innerRadius: 50 } // donut + : { type: 'bar' }, // bar + encoding: isDonut + ? { + theta: { field: 'b', type: 'quantitative' }, + color: { field: 'a', type: 'nominal' }, + } + : { + x: { field: 'a', type: 'ordinal', axis: { labelAngle: 0 } }, + y: { field: 'b', type: 'quantitative' }, + }, + data: { name: 'table' }, + }; + }; + + const data = { + table: [ + { a: 'A', b: 28 }, + { a: 'B', b: 55 }, + { a: 'C', b: 43 }, + { a: 'D', b: 91 }, + ], + }; + const chartRefByNode = useRef(null); + const chartRefByIndex = useRef(null); + const chartRefByUser = useRef(null); + + useEffect(() => { + if (chartRefByNode.current) { + embed( + chartRefByNode.current, + { + ...getChartSpec(selectedChartIdByNode), + data: { values: data.table }, + }, + { actions: false } + ).catch(console.error); + } + }, [data, selectedChartIdByNode]); // 🧠 key fix: add selectedChartIdByNode + + + useEffect(() => { + if (chartRefByIndex.current) { + embed(chartRefByIndex.current, { + ...getChartSpec(selectedChartIdByIndex), + data: { values: data.table }, + }, { actions: false }).catch(console.error); + } + }, [data, selectedChartIdByIndex]); + + + useEffect(() => { + if (chartRefByUser.current) { + embed(chartRefByUser.current, { + ...getChartSpec(selectedChartIdByUser), + data: { values: data.table }, + }, { actions: false }).catch(console.error); + } + }, [data, selectedChartIdByUser]); + + + // const getChartSpec = (type: string): VisualizationSpec => { + // const isDonut = type.includes('donut'); // Donut is at index 0 + // + // return { + // width: 400, + // height: 300, + // mark: isDonut + // ? { type: 'arc', innerRadius: 50 } // donut + // : { type: 'bar' }, // bar + // encoding: isDonut + // ? { + // theta: { field: 'b', type: 'quantitative' }, + // color: { field: 'a', type: 'nominal' }, + // } + // : { + // x: { field: 'a', type: 'ordinal', axis: { labelAngle: 0 } }, + // y: { field: 'b', type: 'quantitative' }, + // }, + // data: { name: 'table' }, + // }; + // }; + // + const buildChartData = (query, + groupBy: 'node_id' | 'user' | 'indices' + ): { a: string; b: number }[] => { + if (!query?.response?.live_queries) return []; + + const groups: Record = {}; + + for (const q of query.response.live_queries) { + let keys: string[] = []; + + if (groupBy === 'node_id') { + keys = [q.node_id ?? 'unknown'];} + + for (const key of keys) { + groups[key] = (groups[key] || 0) + 1; + } + } + + return Object.entries(groups).map(([a, b]) => ({ a, b })); + }; + + + // + // + // const chartRefByNode = useRef(null); + // const chartRefByIndex = useRef(null); + // const chartRefByUser = useRef(null); + // + // useEffect(() => { + // if (chartRefByNode.current) { + // const table = buildChartData('node_id'); + // console.log(table); + // embed( + // chartRefByNode.current, + // { + // ...getChartSpec(selectedChartIdByNode), + // data: [{ name: 'table', values: table }], + // }, + // { actions: false } + // ).catch(console.error); + // } + // }, [query, selectedChartIdByNode]); + // + // + // useEffect(() => { + // if (chartRefByIndex.current) { + // const table = buildChartData('node_id'); + // embed(chartRefByIndex.current, { + // ...getChartSpec(selectedChartIdByIndex), + // data: [{ name: 'table', values: table }], + // }, { actions: false }).catch(console.error); + // } + // }, [query, selectedChartIdByIndex]); + // + // + // useEffect(() => { + // if (chartRefByUser.current) { + // const table = buildChartData('node_id'); + // embed(chartRefByUser.current, { + // ...getChartSpec(selectedChartIdByUser), + // data: [{ name: 'table', values: table }], + // }, { actions: false }).catch(console.error); + // } + // }, [query, selectedChartIdByUser]); + + + + // const MyCollapsibleTablePanel = () => { + // const [isTableVisible, setIsTableVisible] = useState(false); + // + // const toggleContent = () => { + // setIsTableVisible(!isTableVisible); + // }; + // + // const items = [ + // { status: 'Running (agg)', Shard_ID: 'Shard-01', Phase_timeline: '' }, + // ]; + // + // const columns = [ + // { field: 'status', name: 'Status', truncateText: true }, + // { field: 'Shard_ID', name: 'Shard ID', truncateText: true }, + // { field: 'Phase_timeline', name: 'Phase timeline (Growing)', truncateText: true }, + // ]; + // + // + // return ( + // + // + // + // + // + // + // ID + // Index: + // archive_data + // Node: + // Node1 + // User: + // batch_processor + // + // + // + // + // {!isTableVisible && ( + // <> + // + // + // )} + // + // + // Shard Completion Progress + // 30% 20/80 + // + // + // + // + // + // + // + // + // + // + // + // Time Elapsed: + // + // + // 2m 30s + // + // + // + // + // + // + // + // CPU: + // + // + // 30% + // + // + // + // + // + // + // + // Memory Peak: + // + // + // 500MB + // + // + // + // + // + // + // + // Active Shards: + // + // + // 20 + // + // + // + // + // + // + // + // {isTableVisible && ( + // <> + // + // + // )} + // + // {/* Collapsible Table */} + // {isTableVisible && ( + //
+ // Active shard Details + // + // + // + // View Query Details + // + // + // Kill Query + // + // + // + //
+ // )} + //
+ // ); + // }; + + + return ( +
+ {}} + onSelectedDataSource= {fetchliveQueries} + dataSourcePickerReadOnly={true} + /> + + {/* Active Queries */} + + + + + +

Active queries

+
+ +

{metrics?.activeQueries ?? 'N/A'}

+
+ +
+
+
+
+ + {/* Avg. elapsed time */} + + + + + +

Avg. elapsed time

+
+ +

{metrics ? metrics.avgElapsedSec.toFixed(2) + ' s' : 'N/A'}

+
+ +

(Avg. across {metrics?.activeQueries ?? 'N/A'})

+
+
+
+
+
+ + {/* Longest running query */} + + + + + +

Longest running query

+
+ +

{metrics ? metrics.longestElapsedSec.toFixed(2) + ' s' : 'N/A'}

+
+ +

ID: {metrics?.longestQueryId ?? 'N/A'}

+
+
+
+
+
+ + {/* Total CPU usage */} + + + + + +

Total CPU usage

+
+ +

{metrics ? metrics.totalCPUSec.toFixed(2) + ' s' : 'N/A'}

+
+ +

(Sum across {metrics?.activeQueries ?? 'N/A'})

+
+
+
+
+
+ + {/* Total memory usage */} + + + + + +

Total memory usage

+
+ +

+ {metrics + ? (metrics.totalMemoryBytes / 1024 / 1024 / 1024).toFixed(2) + ' GB' + : 'N/A'} +

+
+ +

(Sum across {metrics?.activeQueries ?? 'N/A'})

+
+
+
+
+
+
+ + {/* Queries by Node */} + + + + +

Queries by Node

+
+ +
+ + +
+ + + + {/* Queries by Index */} + + + + +

Queries by Index

+
+ +
+ + +
+ + + + + + + {/* Queries by User */} + + + + +

Queries by User

+
+ +
+ + +
+ + + + + + + + + + + +

Succeeded

+
+ +

205

+
+ +

(Last 5 min*)

+
+
+
+
+
+ + + + + +

Failed

+
+ +

3

+
+ +

(Last 5 min*)

+
+
+
+
+
+
+ {/**/} + {/* */} + {/* */} + {/* */} + {/**/} +
+ ); +}; + + + + +// eslint-disable-next-line import/no-default-export +export default inflightQueries; + diff --git a/server/clusters/queryInsightsPlugin.ts b/server/clusters/queryInsightsPlugin.ts index 53672558..54b3ebf8 100644 --- a/server/clusters/queryInsightsPlugin.ts +++ b/server/clusters/queryInsightsPlugin.ts @@ -167,4 +167,11 @@ export const QueryInsightsPlugin = function (Client, config, components) { method: 'PUT', needBody: true, }); + + queryInsights.getLiveQueries = ca({ + url: { + fmt: `/_insights/live_queries`, + }, + method: 'GET', + }); }; diff --git a/server/routes/index.ts b/server/routes/index.ts index 081bcaa0..f5b8421d 100644 --- a/server/routes/index.ts +++ b/server/routes/index.ts @@ -356,4 +356,50 @@ export function defineRoutes(router: IRouter, dataSourceEnabled: boolean) { } } ); + router.get( + { + path: '/api/live_queries', + validate: { + query: schema.object({ + dataSourceId: schema.maybe(schema.string()), + }), + }, + }, + async (context, request, response) => { + try { + if (!dataSourceEnabled || !request.query?.dataSourceId) { + const client = context.queryInsights_plugin.queryInsightsClient.asScoped(request) + .callAsCurrentUser; + const res = await client('queryInsights.getLiveQueries'); + return response.custom({ + statusCode: 200, + body: { + ok: true, + response: res, + }, + }); + } else { + const client = context.dataSource.opensearch.legacy.getClient( + request.query?.dataSourceId + ); + const res = await client.callAPI('queryInsights.getLiveQueries', {}); + return response.custom({ + statusCode: 200, + body: { + ok: true, + response: res, + }, + }); + } + } catch (error) { + console.error('Unable to get top queries: ', error); + return response.ok({ + body: { + ok: false, + response: error.message, + }, + }); + } + } + ); } diff --git a/types/types.ts b/types/types.ts index 960c6ae2..15021d1d 100644 --- a/types/types.ts +++ b/types/types.ts @@ -66,3 +66,23 @@ export interface QueryInsightsSettingsResponse { group_by?: string; exporter?: ExporterSettingsResponse; } + +export interface LiveSearchQueryRecord { + timestamp: number; + id: string; + description: string; + measurements: { + latency?: Measurement; + cpu?: Measurement; + memory?: Measurement; + }; + node_id: string; +} + +export interface LiveSearchQueryResponse { + ok: boolean; + response: { + live_queries: LiveSearchQueryRecord[]; + }; +} + From b52897c9c8a64a70921e115478a54187b8072844 Mon Sep 17 00:00:00 2001 From: Kishore Kumaar Natarajan Date: Fri, 2 May 2025 12:09:48 -0700 Subject: [PATCH 02/26] added LiveQueriesResponse in QueryUlits Signed-off-by: Kishore Kumaar Natarajan --- common/utils/QueryUtils.ts | 22 +- cypress/e2e/5_live_queries.cy.js | 0 cypress/fixtures/stub.live_queries.json | 173 +++++++++ .../pages/InflightQueries/InflightQueries.tsx | 340 ++---------------- public/pages/TopNQueries/TopNQueries.tsx | 29 ++ 5 files changed, 251 insertions(+), 313 deletions(-) create mode 100644 cypress/e2e/5_live_queries.cy.js create mode 100644 cypress/fixtures/stub.live_queries.json diff --git a/common/utils/QueryUtils.ts b/common/utils/QueryUtils.ts index f84c2d63..bc23b21c 100644 --- a/common/utils/QueryUtils.ts +++ b/common/utils/QueryUtils.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { SearchQueryRecord } from '../../types/types'; +import { SearchQueryRecord, LiveSearchQueryResponse } from '../../types/types'; // Utility function to fetch query by id and time range export const retrieveQueryById = async ( @@ -63,3 +63,23 @@ export const retrieveQueryById = async ( return null; } }; + + +export const retrieveLiveQueries = async ( + core: { http: { get: (endpoint: string) => Promise } }, +): Promise => { + const nullResponse: LiveSearchQueryResponse = { + ok: false, + response: { live_queries: [] }, + }; + + try { + const response: LiveSearchQueryResponse = await core.http.get(`/api/live_queries`); + return response && Array.isArray(response.response?.live_queries) + ? response + : nullResponse; + } catch (error) { + console.error('Error retrieving live queries:', error); + return nullResponse; + } +}; diff --git a/cypress/e2e/5_live_queries.cy.js b/cypress/e2e/5_live_queries.cy.js new file mode 100644 index 00000000..e69de29b diff --git a/cypress/fixtures/stub.live_queries.json b/cypress/fixtures/stub.live_queries.json new file mode 100644 index 00000000..658dee1c --- /dev/null +++ b/cypress/fixtures/stub.live_queries.json @@ -0,0 +1,173 @@ +{ + "ok": true, + "response": { + "live_queries": [ + { + "timestamp": 1746147037494, + "id": "a23fvIkHTVuE3LkB_001", + "node_id": "node_1", + "description": "indices[customers], search_type[QUERY_THEN_FETCH], source[{\"size\":0,\"aggregations\":{\"heavy_terms\":{\"terms\":{\"field\":\"title.keyword\",\"size\":1000000}}}}]", + "measurements": { + "cpu": {"number": 657000, "count": 1, "aggregationType": "NONE"}, + "memory": {"number": 9256, "count": 1, "aggregationType": "NONE"}, + "latency": {"number": 5174478333, "count": 1, "aggregationType": "NONE"} + } + }, + { + "timestamp": 1746147037494, + "id": "b45fvIkHTVuE3LkB_002", + "node_id": "node_2", + "description": "indices[orders], search_type[QUERY_THEN_FETCH], source[{\"size\":0,\"aggregations\":{\"heavy_terms\":{\"terms\":{\"field\":\"title.keyword\",\"size\":500000}}}}]", + "measurements": { + "cpu": {"number": 957000, "count": 1, "aggregationType": "NONE"}, + "memory": {"number": 13256, "count": 1, "aggregationType": "NONE"}, + "latency": {"number": 8174478333, "count": 1, "aggregationType": "NONE"} + } + }, + { + "timestamp": 1746147037494, + "id": "c67fvIkHTVuE3LkB_003", + "node_id": "node_3", + "description": "indices[products], search_type[QUERY_THEN_FETCH], source[{\"size\":0,\"aggregations\":{\"heavy_terms\":{\"terms\":{\"field\":\"title.keyword\",\"size\":750000}}}}]", + "measurements": { + "cpu": {"number": 757000, "count": 1, "aggregationType": "NONE"}, + "memory": {"number": 15256, "count": 1, "aggregationType": "NONE"}, + "latency": {"number": 6174478333, "count": 1, "aggregationType": "NONE"} + } + }, + { + "timestamp": 1746147037494, + "id": "d89fvIkHTVuE3LkB_004", + "node_id": "node_4", + "description": "indices[inventory], search_type[QUERY_THEN_FETCH], source[{\"size\":0,\"aggregations\":{\"heavy_terms\":{\"terms\":{\"field\":\"title.keyword\",\"size\":600000}}}}]", + "measurements": { + "cpu": {"number": 857000, "count": 1, "aggregationType": "NONE"}, + "memory": {"number": 11256, "count": 1, "aggregationType": "NONE"}, + "latency": {"number": 7174478333, "count": 1, "aggregationType": "NONE"} + } + }, + { + "timestamp": 1746147037494, + "id": "e01fvIkHTVuE3LkB_005", + "node_id": "node_5", + "description": "indices[users], search_type[QUERY_THEN_FETCH], source[{\"size\":0,\"aggregations\":{\"heavy_terms\":{\"terms\":{\"field\":\"title.keyword\",\"size\":800000}}}}]", + "measurements": { + "cpu": {"number": 557000, "count": 1, "aggregationType": "NONE"}, + "memory": {"number": 17256, "count": 1, "aggregationType": "NONE"}, + "latency": {"number": 4174478333, "count": 1, "aggregationType": "NONE"} + } + }, + { + "timestamp": 1746147037494, + "id": "f12fvIkHTVuE3LkB_006", + "node_id": "node_1", + "description": "indices[sales], search_type[QUERY_THEN_FETCH], source[{\"size\":0,\"aggregations\":{\"heavy_terms\":{\"terms\":{\"field\":\"title.keyword\",\"size\":900000}}}}]", + "measurements": { + "cpu": {"number": 457000, "count": 1, "aggregationType": "NONE"}, + "memory": {"number": 19256, "count": 1, "aggregationType": "NONE"}, + "latency": {"number": 3174478333, "count": 1, "aggregationType": "NONE"} + } + }, + { + "timestamp": 1746147037494, + "id": "g23fvIkHTVuE3LkB_007", + "node_id": "node_2", + "description": "indices[reports], search_type[QUERY_THEN_FETCH], source[{\"size\":0,\"aggregations\":{\"heavy_terms\":{\"terms\":{\"field\":\"title.keyword\",\"size\":550000}}}}]", + "measurements": { + "cpu": {"number": 357000, "count": 1, "aggregationType": "NONE"}, + "memory": {"number": 21256, "count": 1, "aggregationType": "NONE"}, + "latency": {"number": 2174478333, "count": 1, "aggregationType": "NONE"} + } + }, + { + "timestamp": 1746147037494, + "id": "h34fvIkHTVuE3LkB_008", + "node_id": "node_3", + "description": "indices[analytics], search_type[QUERY_THEN_FETCH], source[{\"size\":0,\"aggregations\":{\"heavy_terms\":{\"terms\":{\"field\":\"title.keyword\",\"size\":650000}}}}]", + "measurements": { + "cpu": {"number": 257000, "count": 1, "aggregationType": "NONE"}, + "memory": {"number": 23256, "count": 1, "aggregationType": "NONE"}, + "latency": {"number": 1174478333, "count": 1, "aggregationType": "NONE"} + } + }, + { + "timestamp": 1746147037494, + "id": "i45fvIkHTVuE3LkB_009", + "node_id": "node_4", + "description": "indices[logs], search_type[QUERY_THEN_FETCH], source[{\"size\":0,\"aggregations\":{\"heavy_terms\":{\"terms\":{\"field\":\"title.keyword\",\"size\":450000}}}}]", + "measurements": { + "cpu": {"number": 157000, "count": 1, "aggregationType": "NONE"}, + "memory": {"number": 25256, "count": 1, "aggregationType": "NONE"}, + "latency": {"number": 9174478333, "count": 1, "aggregationType": "NONE"} + } + }, + { + "timestamp": 1746147037494, + "id": "j56fvIkHTVuE3LkB_010", + "node_id": "node_5", + "description": "indices[metrics], search_type[QUERY_THEN_FETCH], source[{\"size\":0,\"aggregations\":{\"heavy_terms\":{\"terms\":{\"field\":\"title.keyword\",\"size\":350000}}}}]", + "measurements": { + "cpu": {"number": 757000, "count": 1, "aggregationType": "NONE"}, + "memory": {"number": 27256, "count": 1, "aggregationType": "NONE"}, + "latency": {"number": 5574478333, "count": 1, "aggregationType": "NONE"} + } + }, + { + "timestamp": 1746147037494, + "id": "k67fvIkHTVuE3LkB_011", + "node_id": "node_1", + "description": "indices[events], search_type[QUERY_THEN_FETCH], source[{\"size\":0,\"aggregations\":{\"heavy_terms\":{\"terms\":{\"field\":\"title.keyword\",\"size\":250000}}}}]", + "measurements": { + "cpu": {"number": 857000, "count": 1, "aggregationType": "NONE"}, + "memory": {"number": 29256, "count": 1, "aggregationType": "NONE"}, + "latency": {"number": 6574478333, "count": 1, "aggregationType": "NONE"} + } + }, + { + "timestamp": 1746147037494, + "id": "l78fvIkHTVuE3LkB_012", + "node_id": "node_2", + "description": "indices[audit], search_type[QUERY_THEN_FETCH], source[{\"size\":0,\"aggregations\":{\"heavy_terms\":{\"terms\":{\"field\":\"title.keyword\",\"size\":150000}}}}]", + "measurements": { + "cpu": {"number": 957000, "count": 1, "aggregationType": "NONE"}, + "memory": {"number": 31256, "count": 1, "aggregationType": "NONE"}, + "latency": {"number": 7574478333, "count": 1, "aggregationType": "NONE"} + } + }, + { + "timestamp": 1746147037494, + "id": "m89fvIkHTVuE3LkB_013", + "node_id": "node_3", + "description": "indices[cache], search_type[QUERY_THEN_FETCH], source[{\"size\":0,\"aggregations\":{\"heavy_terms\":{\"terms\":{\"field\":\"title.keyword\",\"size\":950000}}}}]", + "measurements": { + "cpu": {"number": 657000, "count": 1, "aggregationType": "NONE"}, + "memory": {"number": 33256, "count": 1, "aggregationType": "NONE"}, + "latency": {"number": 8574478333, "count": 1, "aggregationType": "NONE"} + } + }, + { + "timestamp": 1746147037494, + "id": "n90fvIkHTVuE3LkB_014", + "node_id": "node_4", + "description": "indices[queue], search_type[QUERY_THEN_FETCH], source[{\"size\":0,\"aggregations\":{\"heavy_terms\":{\"terms\":{\"field\":\"title.keyword\",\"size\":850000}}}}]", + "measurements": { + "cpu": {"number": 557000, "count": 1, "aggregationType": "NONE"}, + "memory": {"number": 35256, "count": 1, "aggregationType": "NONE"}, + "latency": {"number": 9574478333, "count": 1, "aggregationType": "NONE"} + } + }, + { + "timestamp": 1746147037494, + "id": "o01fvIkHTVuE3LkB_015", + "node_id": "node_5", + "description": "indices[tasks], search_type[QUERY_THEN_FETCH], source[{\"size\":0,\"aggregations\":{\"heavy_terms\":{\"terms\":{\"field\":\"title.keyword\",\"size\":750000}}}}]", + "measurements": { + "cpu": {"number": 457000, "count": 1, "aggregationType": "NONE"}, + "memory": {"number": 37256, "count": 1, "aggregationType": "NONE"}, + "latency": {"number": 4574478333, "count": 1, "aggregationType": "NONE"} + } + } + ] + } +} + diff --git a/public/pages/InflightQueries/InflightQueries.tsx b/public/pages/InflightQueries/InflightQueries.tsx index 81c033d8..ee5e2b4e 100644 --- a/public/pages/InflightQueries/InflightQueries.tsx +++ b/public/pages/InflightQueries/InflightQueries.tsx @@ -45,10 +45,31 @@ const inflightQueries = ({ const [query, setQuery] = useState(null); const { dataSource, setDataSource } = useContext(DataSourceContext)!; + const [nodeCounts, setNodeCounts] = useState({}); + const [indexCounts, setIndexCounts] = useState({}); + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(null); const fetchliveQueries = async () => { const retrievedQueries = await retrieveLiveQueries(core); setQuery(retrievedQueries); + const nodeCount = {}; + const indexCount = {}; + + query.response.live_queries.forEach(query => { + // Count nodes + nodeCount[query.node_id] = (nodeCount[query.node_id] || 0) + 1; + + // Extract and count indices + const indexMatch = query.description.match(/indices\[(.*?)\]/); + if (indexMatch) { + const index = indexMatch[1]; + indexCount[index] = (indexCount[index] || 0) + 1; + } + }); + + setNodeCounts(nodeCount); + setIndexCounts(indexCount); }; useEffect(() => { @@ -74,26 +95,21 @@ const inflightQueries = ({ ]; const [selectedChartIdByIndex, setSelectedChartIdByIndex] = useState('donut'); - const [selectedChartIdByUser, setSelectedChartIdByUser] = useState('donut'); const [selectedChartIdByNode, setSelectedChartIdByNode] = useState('donut'); const onChartChangeByIndex = (optionId: string) => { setSelectedChartIdByIndex(optionId); - console.log('Chart type changed to:', optionId); - }; - - const onChartChangeByUser = (optionId: string) => { - setSelectedChartIdByUser(optionId); - console.log('Chart type changed to:', optionId); }; const onChartChangeByNode = (optionId: string) => { setSelectedChartIdByNode(optionId); - console.log('Chart type changed to:', optionId); }; + + const metrics = React.useMemo(() => { + console.log(query); if (!query || !query.response?.live_queries?.length) return null; const queries = query.response.live_queries; @@ -131,6 +147,9 @@ const inflightQueries = ({ }, [query]); + + + const getChartSpec = (type: string): VisualizationSpec => { const isDonut = type.includes('donut'); // Donut is at index 0 @@ -149,7 +168,6 @@ const inflightQueries = ({ x: { field: 'a', type: 'ordinal', axis: { labelAngle: 0 } }, y: { field: 'b', type: 'quantitative' }, }, - data: { name: 'table' }, }; }; @@ -163,7 +181,6 @@ const inflightQueries = ({ }; const chartRefByNode = useRef(null); const chartRefByIndex = useRef(null); - const chartRefByUser = useRef(null); useEffect(() => { if (chartRefByNode.current) { @@ -180,6 +197,7 @@ const inflightQueries = ({ useEffect(() => { + console.log([{ name: 'table', values: data.table }],); if (chartRefByIndex.current) { embed(chartRefByIndex.current, { ...getChartSpec(selectedChartIdByIndex), @@ -189,242 +207,6 @@ const inflightQueries = ({ }, [data, selectedChartIdByIndex]); - useEffect(() => { - if (chartRefByUser.current) { - embed(chartRefByUser.current, { - ...getChartSpec(selectedChartIdByUser), - data: { values: data.table }, - }, { actions: false }).catch(console.error); - } - }, [data, selectedChartIdByUser]); - - - // const getChartSpec = (type: string): VisualizationSpec => { - // const isDonut = type.includes('donut'); // Donut is at index 0 - // - // return { - // width: 400, - // height: 300, - // mark: isDonut - // ? { type: 'arc', innerRadius: 50 } // donut - // : { type: 'bar' }, // bar - // encoding: isDonut - // ? { - // theta: { field: 'b', type: 'quantitative' }, - // color: { field: 'a', type: 'nominal' }, - // } - // : { - // x: { field: 'a', type: 'ordinal', axis: { labelAngle: 0 } }, - // y: { field: 'b', type: 'quantitative' }, - // }, - // data: { name: 'table' }, - // }; - // }; - // - const buildChartData = (query, - groupBy: 'node_id' | 'user' | 'indices' - ): { a: string; b: number }[] => { - if (!query?.response?.live_queries) return []; - - const groups: Record = {}; - - for (const q of query.response.live_queries) { - let keys: string[] = []; - - if (groupBy === 'node_id') { - keys = [q.node_id ?? 'unknown'];} - - for (const key of keys) { - groups[key] = (groups[key] || 0) + 1; - } - } - - return Object.entries(groups).map(([a, b]) => ({ a, b })); - }; - - - // - // - // const chartRefByNode = useRef(null); - // const chartRefByIndex = useRef(null); - // const chartRefByUser = useRef(null); - // - // useEffect(() => { - // if (chartRefByNode.current) { - // const table = buildChartData('node_id'); - // console.log(table); - // embed( - // chartRefByNode.current, - // { - // ...getChartSpec(selectedChartIdByNode), - // data: [{ name: 'table', values: table }], - // }, - // { actions: false } - // ).catch(console.error); - // } - // }, [query, selectedChartIdByNode]); - // - // - // useEffect(() => { - // if (chartRefByIndex.current) { - // const table = buildChartData('node_id'); - // embed(chartRefByIndex.current, { - // ...getChartSpec(selectedChartIdByIndex), - // data: [{ name: 'table', values: table }], - // }, { actions: false }).catch(console.error); - // } - // }, [query, selectedChartIdByIndex]); - // - // - // useEffect(() => { - // if (chartRefByUser.current) { - // const table = buildChartData('node_id'); - // embed(chartRefByUser.current, { - // ...getChartSpec(selectedChartIdByUser), - // data: [{ name: 'table', values: table }], - // }, { actions: false }).catch(console.error); - // } - // }, [query, selectedChartIdByUser]); - - - - // const MyCollapsibleTablePanel = () => { - // const [isTableVisible, setIsTableVisible] = useState(false); - // - // const toggleContent = () => { - // setIsTableVisible(!isTableVisible); - // }; - // - // const items = [ - // { status: 'Running (agg)', Shard_ID: 'Shard-01', Phase_timeline: '' }, - // ]; - // - // const columns = [ - // { field: 'status', name: 'Status', truncateText: true }, - // { field: 'Shard_ID', name: 'Shard ID', truncateText: true }, - // { field: 'Phase_timeline', name: 'Phase timeline (Growing)', truncateText: true }, - // ]; - // - // - // return ( - // - // - // - // - // - // - // ID - // Index: - // archive_data - // Node: - // Node1 - // User: - // batch_processor - // - // - // - // - // {!isTableVisible && ( - // <> - // - // - // )} - // - // - // Shard Completion Progress - // 30% 20/80 - // - // - // - // - // - // - // - // - // - // - // - // Time Elapsed: - // - // - // 2m 30s - // - // - // - // - // - // - // - // CPU: - // - // - // 30% - // - // - // - // - // - // - // - // Memory Peak: - // - // - // 500MB - // - // - // - // - // - // - // - // Active Shards: - // - // - // 20 - // - // - // - // - // - // - // - // {isTableVisible && ( - // <> - // - // - // )} - // - // {/* Collapsible Table */} - // {isTableVisible && ( - //
- // Active shard Details - // - // - // - // View Query Details - // - // - // Kill Query - // - // - // - //
- // )} - //
- // ); - // }; - return (
@@ -583,77 +365,11 @@ const inflightQueries = ({ - {/* Queries by User */} - - - - -

Queries by User

-
- -
- - -
- - - - - - - - - - -

Succeeded

-
- -

205

-
- -

(Last 5 min*)

-
-
-
-
-
- - - - - -

Failed

-
- -

3

-
- -

(Last 5 min*)

-
-
-
-
-
-
- {/**/} - {/* */} - {/* */} - {/* */} - {/**/}
); }; - - - // eslint-disable-next-line import/no-default-export export default inflightQueries; diff --git a/public/pages/TopNQueries/TopNQueries.tsx b/public/pages/TopNQueries/TopNQueries.tsx index 630bca5f..2557298b 100644 --- a/public/pages/TopNQueries/TopNQueries.tsx +++ b/public/pages/TopNQueries/TopNQueries.tsx @@ -12,6 +12,7 @@ import { DataSourceOption } from 'src/plugins/data_source_management/public/comp import QueryInsights from '../QueryInsights/QueryInsights'; import Configuration from '../Configuration/Configuration'; import QueryDetails from '../QueryDetails/QueryDetails'; +import LiveQueries from '../InflightQueries/InflightQueries' import { SearchQueryRecord } from '../../../types/types'; import { QueryGroupDetails } from '../QueryGroupDetails/QueryGroupDetails'; import { QueryInsightsDashboardsPluginStartDependencies } from '../../types'; @@ -38,6 +39,7 @@ import { getDataSourceFromUrl } from '../../utils/datasource-utils'; export const QUERY_INSIGHTS = '/queryInsights'; export const CONFIGURATION = '/configuration'; +export const LIVE_QUERIES = '/InflightQueries' export interface MetricSettings { isEnabled: boolean; @@ -131,6 +133,11 @@ const TopNQueries = ({ const [queries, setQueries] = useState([]); const tabs: Array<{ id: string; name: string; route: string }> = [ + { + id: 'liveQueries', + name: 'Live queries', + route: LIVE_QUERIES, + }, { id: 'topNQueries', name: 'Top N queries', @@ -394,6 +401,28 @@ const TopNQueries = ({ ); }} + + + +

Query insights - In-flight queries scoreboard

+
+ + + } + /> + {tabs.map(renderTab)} + + +
Date: Wed, 14 May 2025 14:36:54 -0700 Subject: [PATCH 03/26] Live Queries Dashboard Signed-off-by: Kishore Kumaar Natarajan # Conflicts: # cypress/support/commands.js --- common/utils/QueryUtils.ts | 23 +- cypress/e2e/5_live_queries.cy.js | 324 ++++++++++++++++++ ...ve_queries.json => stub_live_queries.json} | 0 cypress/support/commands.js | 7 +- cypress/support/constants.js | 2 + .../InflightQueries/InflightQueries.test.tsx | 277 +++++++++++++++ .../pages/InflightQueries/InflightQueries.tsx | 310 ++++++++++------- public/pages/TopNQueries/TopNQueries.tsx | 2 +- 8 files changed, 809 insertions(+), 136 deletions(-) rename cypress/fixtures/{stub.live_queries.json => stub_live_queries.json} (100%) create mode 100644 public/pages/InflightQueries/InflightQueries.test.tsx diff --git a/common/utils/QueryUtils.ts b/common/utils/QueryUtils.ts index bc23b21c..0da42bf5 100644 --- a/common/utils/QueryUtils.ts +++ b/common/utils/QueryUtils.ts @@ -11,8 +11,7 @@ export const retrieveQueryById = async ( dataSourceId: string, start: string | null, end: string | null, - id: string | null, - verbose: boolean + id: string | null ): Promise => { const nullResponse = { response: { top_queries: [] } }; const params = { @@ -21,7 +20,6 @@ export const retrieveQueryById = async ( from: start, to: end, id, - verbose, }, }; @@ -45,19 +43,12 @@ export const retrieveQueryById = async ( }; try { - const endpoints = [ - `/api/top_queries/latency`, - `/api/top_queries/cpu`, - `/api/top_queries/memory`, - ]; - - for (const endpoint of endpoints) { - const result = await fetchMetric(endpoint); - if (result.response.top_queries.length > 0) { - return result.response.top_queries[0]; - } - } - return null; + const topQueriesResponse = await Promise.any([ + fetchMetric(`/api/top_queries/latency`), + fetchMetric(`/api/top_queries/cpu`), + fetchMetric(`/api/top_queries/memory`), + ]); + return topQueriesResponse.response.top_queries[0] || null; } catch (error) { console.error('Error retrieving query details:', error); return null; diff --git a/cypress/e2e/5_live_queries.cy.js b/cypress/e2e/5_live_queries.cy.js index e69de29b..604f1668 100644 --- a/cypress/e2e/5_live_queries.cy.js +++ b/cypress/e2e/5_live_queries.cy.js @@ -0,0 +1,324 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + + +describe('Inflight Queries Dashboard', () => { + beforeEach(() => { + cy.fixture('stub_live_queries.json').then((stubResponse) => { + cy.intercept('GET', '**/api/live_queries', { + statusCode: 200, + body: stubResponse, + }).as('getLiveQueries'); + }); + + cy.navigateToLiveQueries(); + cy.wait(1000); + cy.wait('@getLiveQueries'); + }); + + + it('displays the correct page title', () => { + cy.contains('Query insights - In-flight queries scoreboard').should('be.visible'); + }); + + it('displays metrics panels correctly', () => { + cy.get('.euiPanel').eq(0).within(() => { + cy.contains('Active queries'); + cy.get('h2').contains(15); // Matches any number + }); + + cy.get('.euiPanel').eq(1).within(() => { + cy.contains('Avg. elapsed time'); + cy.get('h2').contains("5.93 s"); // Matches number followed by 's' + }); + + cy.get('.euiPanel').eq(2).within(() => { + cy.contains('Longest running query'); + cy.get('h2').contains("9.57 s"); + cy.contains("ID: n90fvIkHTVuE3LkB_014"); + }); + + cy.get('.euiPanel').eq(3).within(() => { + cy.contains('Total CPU usage'); + cy.get('h2').contains("9.25 ms"); + }); + + cy.get('.euiPanel').eq(4).within(() => { + cy.contains('Total memory usage'); + cy.get('h2').contains("340.66 KB"); + }); + }); + + it('renders charts and allows switching between chart types', () => { + cy.contains('h3', 'Queries by Node').closest('.euiPanel').as('nodeChart'); + cy.contains('h3', 'Queries by Index').closest('.euiPanel').as('indexChart'); + + cy.get('@nodeChart').within(() => { + cy.get('.euiButtonGroup').should('exist'); + + cy.get('.euiButtonGroup').contains('Donut').should('exist'); + cy.get('.euiButtonGroup').contains('Bar').should('exist'); + + cy.get('.vega-embed').should('exist'); + + + // Test chart type switching + cy.get('.euiButtonGroup').contains('Bar').click(); + cy.wait(500); + + cy.get('.euiButtonGroup').contains('Donut').click(); + cy.wait(500); + }); + + cy.get('@indexChart').within(() => { + cy.get('.euiButtonGroup').should('exist'); + cy.get('.euiButtonGroup').contains('Donut').should('exist'); + cy.get('.euiButtonGroup').contains('Bar').should('exist'); + + cy.get('.vega-embed').should('exist'); + + cy.get('.euiButtonGroup').contains('Bar').click(); + cy.wait(500); + + cy.get('.euiButtonGroup').contains('Donut').click(); + cy.wait(500); + }); + }); + + + it('updates data periodically', () => { + cy.fixture('stub_live_queries.json').then((initialData) => { + let callCount = 0; + cy.intercept('GET', '**/api/live_queries', (req) => { + callCount++; + const modifiedData = { + ...initialData, + response: { + ...initialData.response, + live_queries: initialData.response.live_queries.map(query => ({ + ...query, + id: `query${callCount}_${query.id}` + })) + } + }; + req.reply(modifiedData); + }).as('getPeriodicQueries'); + }); + + cy.navigateToLiveQueries(); + + cy.wait('@getPeriodicQueries'); + cy.wait('@getPeriodicQueries'); + cy.wait('@getPeriodicQueries'); + + cy.get('@getPeriodicQueries.all').should('have.length.at.least', 3); + }); + + + it('handles empty response state', () => { + cy.intercept('GET', '**/api/live_queries', (req) => { + req.reply({ + statusCode: 200, + body: { + "ok": true, + "response": { + "live_queries": [] + } + } + }); + }).as('getEmptyQueries'); + + cy.navigateToLiveQueries(); + cy.wait('@getEmptyQueries'); + // Check all metrics panels show N/A + cy.get('.euiPanel').eq(0).within(() => { + cy.contains('Active queries'); + cy.get('h2').contains('N/A'); + }); + + cy.get('.euiPanel').eq(1).within(() => { + cy.contains('Avg. elapsed time'); + cy.get('h2').contains('N/A'); + }); + + cy.get('.euiPanel').eq(2).within(() => { + cy.contains('Longest running query'); + cy.get('h2').contains('N/A'); + }); + + cy.get('.euiPanel').eq(3).within(() => { + cy.contains('Total CPU usage'); + cy.get('h2').contains('N/A'); + }); + + cy.get('.euiPanel').eq(4).within(() => { + cy.contains('Total memory usage'); + cy.get('h2').contains('N/A'); + }); + + cy.contains('h3', 'Queries by Node') + .closest('.euiPanel') + .within(() => { + cy.contains('No data available').should('be.visible'); + }); + + cy.contains('h3', 'Queries by Index') + .closest('.euiPanel') + .within(() => { + cy.contains('No data available').should('be.visible'); + }); + }); + it('validates time unit conversions', () => { + // Test microseconds + cy.intercept('GET', '**/api/live_queries', { + statusCode: 200, + body: { + response: { + live_queries: [{ + measurements: { + latency: { number: 500 }, // 500 nanoseconds = 0.5 microseconds + cpu: { number: 100 }, + memory: { number: 1000 } + } + }] + } + } + }).as('getMicrosecondsData'); + + cy.wait('@getMicrosecondsData'); + cy.get('.euiPanel').eq(1).within(() => { + cy.get('h2').contains(/0\.50\s*µs/); + }); + + // Test milliseconds + cy.intercept('GET', '**/api/live_queries', { + statusCode: 200, + body: { + response: { + live_queries: [{ + measurements: { + latency: { number: 1000000 }, // 1 millisecond + cpu: { number: 100000 }, + memory: { number: 1000 } + } + }] + } + } + }).as('getMillisecondsData'); + + cy.wait('@getMillisecondsData'); + cy.get('.euiPanel').eq(1).within(() => { + cy.get('h2').contains(/1\.00\s*ms/); + }); + }); + + it('validates memory unit conversions', () => { + // Test KB display + cy.intercept('GET', '**/api/live_queries', { + statusCode: 200, + body: { + response: { + live_queries: [{ + measurements: { + memory: { number: 2048 } // 2KB + } + }] + } + } + }).as('getKBData'); + + cy.wait('@getKBData'); + cy.get('.euiPanel').eq(4).within(() => { + cy.get('h2').contains(/2\.00\s*KB/); + }); + + // Test MB display + cy.intercept('GET', '**/api/live_queries', { + statusCode: 200, + body: { + response: { + live_queries: [{ + measurements: { + memory: { number: 2097152 } // 2MB + } + }] + } + } + }).as('getMBData'); + + cy.wait('@getMBData'); + cy.get('.euiPanel').eq(4).within(() => { + cy.get('h2').contains(/2\.00\s*MB/); + }); + }); + + it('validates chart tooltips', () => { + cy.contains('h3', 'Queries by Node').closest('.euiPanel').within(() => { + // Test donut chart tooltips + cy.get('.vega-embed').trigger('mouseover'); + cy.get('.vega-tooltip').should('be.visible') + .and('contain', 'Node') + .and('contain', 'Count'); + + // Switch to bar chart and test tooltips + cy.get('.euiButtonGroup').contains('Bar').click(); + cy.wait(500); + cy.get('.vega-embed').trigger('mouseover'); + cy.get('.vega-tooltip').should('be.visible') + .and('contain', 'Node') + .and('contain', 'Count'); + }); + }); + + it('handles error states', () => { + cy.intercept('GET', '**/api/live_queries', { + statusCode: 500, + body: { error: 'Internal Server Error' } + }).as('getErrorResponse'); + + cy.navigateToLiveQueries(); + cy.wait('@getErrorResponse'); + + // Verify error handling in metrics panels + cy.get('.euiPanel').each(($panel) => { + cy.wrap($panel).within(() => { + cy.get('h2').contains('N/A'); + }); + }); + + // Verify error handling in charts + cy.contains('h3', 'Queries by Node') + .closest('.euiPanel') + .contains('No data available'); + + cy.contains('h3', 'Queries by Index') + .closest('.euiPanel') + .contains('No data available'); + }); + + it('validates longest query ID link', () => { + cy.fixture('stub_live_queries.json').then((data) => { + const longestQueryId = data.response.live_queries[0].id; + cy.get('.euiPanel').eq(2).within(() => { + cy.get('a').should('have.attr', 'href', `#/navigation/`); + cy.get('a').should('contain', longestQueryId); + }); + }); + }); + + it('verifies chart toggle button states', () => { + cy.contains('h3', 'Queries by Node').closest('.euiPanel').within(() => { + cy.get('.euiButtonGroup').contains('Donut').should('have.class', 'euiButtonGroupButton-isSelected'); + cy.get('.euiButtonGroup').contains('Bar').click(); + cy.get('.euiButtonGroup').contains('Bar').should('have.class', 'euiButtonGroupButton-isSelected'); + }); + + cy.contains('h3', 'Queries by Index').closest('.euiPanel').within(() => { + cy.get('.euiButtonGroup').contains('Donut').should('have.class', 'euiButtonGroupButton-isSelected'); + cy.get('.euiButtonGroup').contains('Bar').click(); + cy.get('.euiButtonGroup').contains('Bar').should('have.class', 'euiButtonGroupButton-isSelected'); + }); + }); +}); diff --git a/cypress/fixtures/stub.live_queries.json b/cypress/fixtures/stub_live_queries.json similarity index 100% rename from cypress/fixtures/stub.live_queries.json rename to cypress/fixtures/stub_live_queries.json diff --git a/cypress/support/commands.js b/cypress/support/commands.js index e05117c1..d840a8ba 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -const { ADMIN_AUTH, OVERVIEW_PATH, CONFIGURATION_PATH, BASE_PATH } = require('./constants'); +const { ADMIN_AUTH, OVERVIEW_PATH, CONFIGURATION_PATH, BASE_PATH, LIVEQUERIES_PATH} = require('./constants'); /** * Overwrites the default visit command to authenticate before visiting @@ -209,6 +209,11 @@ Cypress.Commands.add('navigateToConfiguration', () => { cy.waitForPageLoad(CONFIGURATION_PATH, { contains: 'Query insights - Configuration' }); }); +Cypress.Commands.add('navigateToLiveQueries', () => { + cy.visit(LIVEQUERIES_PATH); + cy.waitForPageLoad(LIVEQUERIES_PATH, { contains: 'Query insights - In-flight queries scoreboard' }); +}); + Cypress.Commands.add('waitForPluginToLoad', () => { // CI environments need much longer waits for plugin initialization const isCI = Cypress.env('CI') || !Cypress.config('isInteractive'); diff --git a/cypress/support/constants.js b/cypress/support/constants.js index 6b68b5b3..a5b0523c 100644 --- a/cypress/support/constants.js +++ b/cypress/support/constants.js @@ -9,6 +9,8 @@ export const PLUGIN_NAME = 'query-insights-dashboards'; export const OVERVIEW_PATH = `${BASE_PATH}/app/${PLUGIN_NAME}#/queryInsights`; export const CONFIGURATION_PATH = `${BASE_PATH}/app/${PLUGIN_NAME}#/configuration`; +export const LIVEQUERIES_PATH = `${BASE_PATH}/app/${PLUGIN_NAME}#/LiveQueries`; + export const METRICS = { LATENCY: 'latency', CPU: 'cpu', diff --git a/public/pages/InflightQueries/InflightQueries.test.tsx b/public/pages/InflightQueries/InflightQueries.test.tsx new file mode 100644 index 00000000..c3e4a9bc --- /dev/null +++ b/public/pages/InflightQueries/InflightQueries.test.tsx @@ -0,0 +1,277 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import { render, screen, act, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import InflightQueries from './InflightQueries'; +import { retrieveLiveQueries } from '../../../common/utils/QueryUtils'; +import * as vegaEmbed from 'vega-embed'; +import '@testing-library/jest-dom'; + +// Mock dependencies +jest.mock('../../../common/utils/QueryUtils'); +jest.mock('vega-embed', () => ({ + __esModule: true, + default: jest.fn(), +})); + +describe('InflightQueries', () => { + const mockCoreStart = { + chrome: { + setBreadcrumbs: jest.fn(), + navControls: jest.fn(), + getBreadcrumbs: jest.fn(), + getIsVisible: jest.fn(), + setIsVisible: jest.fn(), + recentlyAccessed: { + add: jest.fn(), + get: jest.fn(), + }, + docTitle: { + change: jest.fn(), + reset: jest.fn(), + }, + }, + uiSettings: { + get: jest.fn().mockReturnValue(false), + set: jest.fn(), + remove: jest.fn(), + overrideLocalDefault: jest.fn(), + isOverridden: jest.fn(), + }, + http: { + get: jest.fn(), + post: jest.fn(), + put: jest.fn(), + delete: jest.fn(), + fetch: jest.fn(), + addLoadingCountListener: jest.fn(), + getLoadingCount: jest.fn(), + }, + notifications: { + toasts: { + addSuccess: jest.fn(), + addError: jest.fn(), + add: jest.fn(), + remove: jest.fn(), + get: jest.fn(), + }, + }, + application: { + capabilities: {}, + navigateToApp: jest.fn(), + currentAppId$: jest.fn(), + getUrlForApp: jest.fn(), + registerMountContext: jest.fn(), + }, + docLinks: { + links: {}, + }, + i18n: { + translate: jest.fn((key) => key), + }, + savedObjects: { + client: { + create: jest.fn(), + bulkCreate: jest.fn(), + delete: jest.fn(), + find: jest.fn(), + get: jest.fn(), + update: jest.fn(), + bulk: jest.fn(), + }, + }, + overlays: { + openFlyout: jest.fn(), + openModal: jest.fn(), + banners: { + add: jest.fn(), + remove: jest.fn(), + get: jest.fn(), + }, + }, + }; + + const dataSourceMenuMock = jest.fn(() =>
Mock DataSourceMenu
); + + const dataSourceManagementMock = { + ui: { + getDataSourceMenu: jest.fn().mockReturnValue(dataSourceMenuMock), + }, + }; + + const mockParams = {}; + const mockDepsStart = {}; + + const mockLiveQueriesResponse = { + response: { + live_queries: [ + { + id: 'query1', + node_id: 'node1', + description: 'indices[index1]', + measurements: { + latency: { number: 1000000000 }, // 1 second + cpu: { number: 2000000000 }, // 2 seconds + memory: { number: 1024 * 1024 }, // 1 MB + }, + }, + { + id: 'query2', + node_id: 'node1', + description: 'indices[index2]', + measurements: { + latency: { number: 2000000000 }, + cpu: { number: 1000000000 }, + memory: { number: 2 * 1024 * 1024 }, + }, + }, + ], + }, + }; + + beforeEach(() => { + jest.clearAllMocks(); + (retrieveLiveQueries as jest.Mock).mockResolvedValue(mockLiveQueriesResponse); + (vegaEmbed.default as jest.Mock).mockResolvedValue({}); + }); + + const renderInflightQueries = () => { + return render( + + ); + }; + + it('renders the component with initial metrics', async () => { + renderInflightQueries(); + + await waitFor(() => { + expect(screen.getByText('Active queries')).toBeInTheDocument(); + expect(screen.getByText('2')).toBeInTheDocument(); // Active queries count + expect(screen.getByText('1.50 s')).toBeInTheDocument(); // Avg elapsed time + expect(screen.getByText('2.00 s')).toBeInTheDocument(); // Longest running query + expect(screen.getByText('3.00 s')).toBeInTheDocument(); // Total CPU usage + expect(screen.getByText('3.00 MB')).toBeInTheDocument(); // Total memory usage + }); + }); + + it('updates charts when switching between donut and bar views', async () => { + renderInflightQueries(); + + const barButtons = screen.getAllByLabelText('Bar'); + await act(async () => { + userEvent.click(barButtons[0]); // Click first bar button (Queries by Node) + }); + + expect(vegaEmbed.default).toHaveBeenCalledWith( + expect.any(HTMLDivElement), + expect.objectContaining({ + mark: { type: 'bar' }, + }), + expect.any(Object) + ); + }); + + it('shows "No data available" when there are no queries', async () => { + (retrieveLiveQueries as jest.Mock).mockResolvedValue({ + response: { live_queries: [] }, + }); + + renderInflightQueries(); + + await waitFor(() => { + expect(screen.getAllByText('No data available')).toHaveLength(2); + }); + }); + + it('handles error when fetching queries', async () => { + const error = new Error('Failed to fetch queries'); + (retrieveLiveQueries as jest.Mock).mockRejectedValue(error); + + renderInflightQueries(); + + await waitFor(() => { + expect(mockCoreStart.notifications.toasts.addError).toHaveBeenCalledWith( + error, + { title: 'Error fetching live queries' } + ); + }); + }); + + it('updates data periodically', async () => { + jest.useFakeTimers(); + + renderInflightQueries(); + + await waitFor(() => { + expect(retrieveLiveQueries).toHaveBeenCalledTimes(1); + }); + + act(() => { + jest.advanceTimersByTime(2000); // Advance time by polling interval + }); + + expect(retrieveLiveQueries).toHaveBeenCalledTimes(2); + + jest.useRealTimers(); + }); + + it('formats time values correctly', async () => { + const mockSmallLatency = { + response: { + live_queries: [ + { + id: 'query1', + measurements: { + latency: { number: 500 }, // 500 nanoseconds + cpu: { number: 1000000 }, // 1 millisecond + memory: { number: 500 }, + }, + }, + ], + }, + }; + + (retrieveLiveQueries as jest.Mock).mockResolvedValue(mockSmallLatency); + + renderInflightQueries(); + + await waitFor(() => { + expect(screen.getByText('0.50 µs')).toBeInTheDocument(); + expect(screen.getByText('1.00 ms')).toBeInTheDocument(); + }); + }); + + it('formats memory values correctly', async () => { + const mockMemoryValues = { + response: { + live_queries: [ + { + id: 'query1', + measurements: { + latency: { number: 1000000 }, + cpu: { number: 1000000 }, + memory: { number: 2 * 1024 * 1024 * 1024 }, // 2 GB + }, + }, + ], + }, + }; + + (retrieveLiveQueries as jest.Mock).mockResolvedValue(mockMemoryValues); + + renderInflightQueries(); + + await waitFor(() => { + expect(screen.getByText('2.00 GB')).toBeInTheDocument(); + }); + }); +}); diff --git a/public/pages/InflightQueries/InflightQueries.tsx b/public/pages/InflightQueries/InflightQueries.tsx index ee5e2b4e..0164731d 100644 --- a/public/pages/InflightQueries/InflightQueries.tsx +++ b/public/pages/InflightQueries/InflightQueries.tsx @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import React, {useEffect, useContext, useState, useRef} from 'react'; +import React, {useEffect, useState, useRef} from 'react'; import { EuiFlexGroup, EuiFlexItem, @@ -18,14 +18,8 @@ import { EuiSpacer, } from '@elastic/eui'; import embed, { VisualizationSpec } from 'vega-embed'; -import { useHistory, useLocation } from 'react-router-dom'; import { AppMountParameters, CoreStart } from 'opensearch-dashboards/public'; import { DataSourceManagementPluginSetup } from 'src/plugins/data_source_management/public'; -import { - QUERY_INSIGHTS, - DataSourceContext, -} from '../TopNQueries/TopNQueries'; -import { QueryInsightsDataSourceMenu } from '../../components/DataSourcePicker'; import { QueryInsightsDashboardsPluginStartDependencies } from '../../types'; import {LiveSearchQueryResponse} from "../../../types/types"; import {retrieveLiveQueries} from "../../../common/utils/QueryUtils"; @@ -40,54 +34,113 @@ const inflightQueries = ({ dataSourceManagement?: DataSourceManagementPluginSetup; depsStart: QueryInsightsDashboardsPluginStartDependencies; }) => { - const history = useHistory(); - const location = useLocation(); const [query, setQuery] = useState(null); - const { dataSource, setDataSource } = useContext(DataSourceContext)!; const [nodeCounts, setNodeCounts] = useState({}); const [indexCounts, setIndexCounts] = useState({}); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); + const chartRefByNode = useRef(null); + const chartRefByIndex = useRef(null); + useEffect(() => { + fetchliveQueries(); + const interval = setInterval(() => { + fetchliveQueries(); + }, 2000); + + return () => clearInterval(interval); + }, [core]); const fetchliveQueries = async () => { - const retrievedQueries = await retrieveLiveQueries(core); - setQuery(retrievedQueries); - const nodeCount = {}; - const indexCount = {}; - - query.response.live_queries.forEach(query => { - // Count nodes - nodeCount[query.node_id] = (nodeCount[query.node_id] || 0) + 1; - - // Extract and count indices - const indexMatch = query.description.match(/indices\[(.*?)\]/); - if (indexMatch) { - const index = indexMatch[1]; - indexCount[index] = (indexCount[index] || 0) + 1; + try { + setIsLoading(true); + const retrievedQueries = await retrieveLiveQueries(core); + setQuery(retrievedQueries); + + if (retrievedQueries?.response?.live_queries) { + const tempNodeCount = {}; + const indexCount = {}; + + // Count occurrences + retrievedQueries.response.live_queries.forEach(query => { + tempNodeCount[query.node_id] = (tempNodeCount[query.node_id] || 0) + 1; + + const indexMatch = query.description.match(/indices\[(.*?)\]/); + if (indexMatch) { + const index = indexMatch[1]; + indexCount[index] = (indexCount[index] || 0) + 1; + } + }); + + // Sort nodes by count and limit to top 9 + const sortedNodes = Object.entries(tempNodeCount) + .sort(([, a], [, b]) => b - a); + + const nodeCount = {}; + let othersCount = 0; + + sortedNodes.forEach(([nodeId, count], index) => { + if (index < 9) { + nodeCount[nodeId] = count; + } else { + othersCount += count; + } + }); + + if (othersCount > 0) { + nodeCount['others'] = othersCount; + } + + setNodeCounts(nodeCount); + setIndexCounts(indexCount); } - }); + } catch (err) { + setError(err); + } finally { + setIsLoading(false); + } + }; + - setNodeCounts(nodeCount); - setIndexCounts(indexCount); + const formatTime = (seconds: number): string => { + if (seconds < 0.001) { + return `${(seconds * 1000000).toFixed(2)} µs`; + } else if (seconds < 1) { + return `${(seconds * 1000).toFixed(2)} ms`; + } else if (seconds < 60) { + return `${seconds.toFixed(2)} s`; + } else if (seconds < 3600) { + const minutes = Math.floor(seconds / 60); + const remainingSeconds = seconds % 60; + return `${minutes}m ${remainingSeconds.toFixed(2)}s`; + } else if (seconds < 86400) { + const hours = Math.floor(seconds / 3600); + const minutes = Math.floor((seconds % 3600) / 60); + const remainingSeconds = seconds % 60; + return `${hours}h ${minutes}m ${remainingSeconds.toFixed(2)}s`; + } else { + const days = Math.floor(seconds / 86400); + const hours = Math.floor((seconds % 86400) / 3600); + const minutes = Math.floor((seconds % 3600) / 60); + const remainingSeconds = seconds % 60; + return `${days}d ${hours}h ${minutes}m ${remainingSeconds.toFixed(2)}s`; + } + }; + + + const formatMemory = (bytes: number): string => { + if (bytes < 1024) { + return `${bytes.toFixed(2)} B`; + } else if (bytes < 1024 * 1024) { + return `${(bytes / 1024).toFixed(2)} KB`; + } else if (bytes < 1024 * 1024 * 1024) { + return `${(bytes / 1024 / 1024).toFixed(2)} MB`; + } else { + return `${(bytes / 1024 / 1024 / 1024).toFixed(2)} GB`; + } }; - useEffect(() => { - fetchliveQueries(); - }, []); - useEffect(() => { - core.chrome.setBreadcrumbs([ - { - text: 'Query insights', - href: QUERY_INSIGHTS, - onClick: (e) => { - e.preventDefault(); - history.push(QUERY_INSIGHTS); - }, - }, - ]); - }, [core.chrome, history, location]); const chartOptions = [ { id: 'donut', label: 'Donut', iconType: 'visPie' }, @@ -105,9 +158,6 @@ const inflightQueries = ({ setSelectedChartIdByNode(optionId); }; - - - const metrics = React.useMemo(() => { console.log(query); if (!query || !query.response?.live_queries?.length) return null; @@ -146,81 +196,89 @@ const inflightQueries = ({ }; }, [query]); + const getChartData = (counts: Record, type: 'node' | 'index') => { + return Object.entries(counts).map(([key, value]) => ({ + label: type === 'node' ? `${key}` : key, // Use descriptive label instead of 'a' + value: value // Use 'value' instead of 'b' for clarity + })); + }; - - - - const getChartSpec = (type: string): VisualizationSpec => { - const isDonut = type.includes('donut'); // Donut is at index 0 + const getChartSpec = (type: string, chartType: 'node' | 'index'): VisualizationSpec => { + const isDonut = type.includes('donut'); return { width: 400, height: 300, mark: isDonut - ? { type: 'arc', innerRadius: 50 } // donut - : { type: 'bar' }, // bar + ? { type: 'arc', innerRadius: 50 } + : { type: 'bar' }, encoding: isDonut - ? { - theta: { field: 'b', type: 'quantitative' }, - color: { field: 'a', type: 'nominal' }, - } - : { - x: { field: 'a', type: 'ordinal', axis: { labelAngle: 0 } }, - y: { field: 'b', type: 'quantitative' }, - }, + ? { + theta: { field: 'value', type: 'quantitative' }, + color: { + field: 'label', + type: 'nominal', + title: chartType === 'node' ? 'Nodes' : 'Indices' + }, + tooltip: [ + { field: 'label', type: 'nominal', title: chartType === 'node' ? 'Node' : 'Index' }, + { field: 'value', type: 'quantitative', title: 'Count' } + ] + } + : { + y: { + field: 'value', + type: 'quantitative', + axis: { title: 'Count' } + }, + x: { + field: 'label', + type: 'nominal', + axis: { labelAngle: -45, title: chartType === 'node' ? 'Nodes' : 'Indices' } + }, + color: { + field: 'label', + type: 'nominal', + title: chartType === 'node' ? 'Nodes' : 'Indices' + }, + tooltip: [ + { field: 'label', type: 'nominal', title: chartType === 'node' ? 'Node' : 'Index' }, + { field: 'value', type: 'quantitative', title: 'Count' } + ] + }, }; }; - const data = { - table: [ - { a: 'A', b: 28 }, - { a: 'B', b: 55 }, - { a: 'C', b: 43 }, - { a: 'D', b: 91 }, - ], - }; - const chartRefByNode = useRef(null); - const chartRefByIndex = useRef(null); - +// Update the embed calls useEffect(() => { if (chartRefByNode.current) { embed( - chartRefByNode.current, - { - ...getChartSpec(selectedChartIdByNode), - data: { values: data.table }, - }, - { actions: false } + chartRefByNode.current, + { + ...getChartSpec(selectedChartIdByNode, 'node'), + data: { values: getChartData(nodeCounts, 'node') }, + }, + { actions: false } ).catch(console.error); } - }, [data, selectedChartIdByNode]); // 🧠 key fix: add selectedChartIdByNode - + }, [nodeCounts, selectedChartIdByNode]); useEffect(() => { - console.log([{ name: 'table', values: data.table }],); if (chartRefByIndex.current) { - embed(chartRefByIndex.current, { - ...getChartSpec(selectedChartIdByIndex), - data: { values: data.table }, - }, { actions: false }).catch(console.error); + embed( + chartRefByIndex.current, + { + ...getChartSpec(selectedChartIdByIndex, 'index'), + data: { values: getChartData(indexCounts, 'index') }, + }, + { actions: false } + ).catch(console.error); } - }, [data, selectedChartIdByIndex]); - + }, [indexCounts, selectedChartIdByIndex]); return (
- {}} - onSelectedDataSource= {fetchliveQueries} - dataSourcePickerReadOnly={true} - /> {/* Active Queries */} @@ -248,7 +306,7 @@ const inflightQueries = ({

Avg. elapsed time

-

{metrics ? metrics.avgElapsedSec.toFixed(2) + ' s' : 'N/A'}

+

{metrics ? formatTime(metrics.avgElapsedSec) : 'N/A'}

(Avg. across {metrics?.activeQueries ?? 'N/A'})

@@ -267,7 +325,7 @@ const inflightQueries = ({

Longest running query

-

{metrics ? metrics.longestElapsedSec.toFixed(2) + ' s' : 'N/A'}

+

{metrics ? formatTime(metrics.longestElapsedSec) : 'N/A'}

ID: {metrics?.longestQueryId ?? 'N/A'}

@@ -286,7 +344,7 @@ const inflightQueries = ({

Total CPU usage

-

{metrics ? metrics.totalCPUSec.toFixed(2) + ' s' : 'N/A'}

+

{metrics ? formatTime(metrics.totalCPUSec) : 'N/A'}

(Sum across {metrics?.activeQueries ?? 'N/A'})

@@ -306,9 +364,7 @@ const inflightQueries = ({

- {metrics - ? (metrics.totalMemoryBytes / 1024 / 1024 / 1024).toFixed(2) + ' GB' - : 'N/A'} + {metrics ? formatMemory(metrics.totalMemoryBytes) : 'N/A'}

@@ -320,6 +376,7 @@ const inflightQueries = ({
+ {/* Queries by Node */} @@ -328,16 +385,26 @@ const inflightQueries = ({

Queries by Node

-
+ {Object.keys(nodeCounts).length > 0 ? ( +
+ ) : ( + + + +

No data available

+
+ +
+ )} @@ -349,19 +416,26 @@ const inflightQueries = ({

Queries by Index

-
- - - + {Object.keys(indexCounts).length > 0 ? ( +
+ ) : ( + + + +

No data available

+
+ +
+ )} diff --git a/public/pages/TopNQueries/TopNQueries.tsx b/public/pages/TopNQueries/TopNQueries.tsx index 2557298b..813a979d 100644 --- a/public/pages/TopNQueries/TopNQueries.tsx +++ b/public/pages/TopNQueries/TopNQueries.tsx @@ -39,7 +39,7 @@ import { getDataSourceFromUrl } from '../../utils/datasource-utils'; export const QUERY_INSIGHTS = '/queryInsights'; export const CONFIGURATION = '/configuration'; -export const LIVE_QUERIES = '/InflightQueries' +export const LIVE_QUERIES = '/LiveQueries'; export interface MetricSettings { isEnabled: boolean; From 01d267a8012bf0d6642c097fdf432cf70e08eec7 Mon Sep 17 00:00:00 2001 From: Kishore Kumaar Natarajan Date: Mon, 19 May 2025 08:37:52 -0700 Subject: [PATCH 04/26] Live queries dashboard Signed-off-by: Kishore Kumaar Natarajan --- common/utils/QueryUtils.ts | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/common/utils/QueryUtils.ts b/common/utils/QueryUtils.ts index 0da42bf5..1ff4b28f 100644 --- a/common/utils/QueryUtils.ts +++ b/common/utils/QueryUtils.ts @@ -43,12 +43,19 @@ export const retrieveQueryById = async ( }; try { - const topQueriesResponse = await Promise.any([ - fetchMetric(`/api/top_queries/latency`), - fetchMetric(`/api/top_queries/cpu`), - fetchMetric(`/api/top_queries/memory`), - ]); - return topQueriesResponse.response.top_queries[0] || null; + const endpoints = [ + `/api/top_queries/latency`, + `/api/top_queries/cpu`, + `/api/top_queries/memory`, + ]; + + for (const endpoint of endpoints) { + const result = await fetchMetric(endpoint); + if (result.response.top_queries.length > 0) { + return result.response.top_queries[0]; + } + } + return null; } catch (error) { console.error('Error retrieving query details:', error); return null; From 2177a0f337dcb856ec5fc6655a62efce23f03efe Mon Sep 17 00:00:00 2001 From: Kishore Kumaar Natarajan Date: Mon, 19 May 2025 22:32:46 -0700 Subject: [PATCH 05/26] Added Tests Signed-off-by: Kishore Kumaar Natarajan --- cypress/e2e/5_live_queries.cy.js | 17 +- .../__snapshots__/app.test.tsx.snap | 12 + .../InflightQueries/InflightQueries.test.tsx | 246 +++++++++++++----- .../pages/InflightQueries/InflightQueries.tsx | 18 +- public/pages/TopNQueries/TopNQueries.test.tsx | 5 +- .../__snapshots__/TopNQueries.test.tsx.snap | 96 +++++++ test/jest.config.js | 1 + test/mocks/vega-embed.ts | 3 + 8 files changed, 310 insertions(+), 88 deletions(-) create mode 100644 test/mocks/vega-embed.ts diff --git a/cypress/e2e/5_live_queries.cy.js b/cypress/e2e/5_live_queries.cy.js index 604f1668..a6931700 100644 --- a/cypress/e2e/5_live_queries.cy.js +++ b/cypress/e2e/5_live_queries.cy.js @@ -132,30 +132,29 @@ describe('Inflight Queries Dashboard', () => { cy.navigateToLiveQueries(); cy.wait('@getEmptyQueries'); - // Check all metrics panels show N/A cy.get('.euiPanel').eq(0).within(() => { cy.contains('Active queries'); - cy.get('h2').contains('N/A'); + cy.get('h2').contains('0'); }); cy.get('.euiPanel').eq(1).within(() => { cy.contains('Avg. elapsed time'); - cy.get('h2').contains('N/A'); + cy.get('h2').contains('0'); }); cy.get('.euiPanel').eq(2).within(() => { cy.contains('Longest running query'); - cy.get('h2').contains('N/A'); + cy.get('h2').contains('0'); }); cy.get('.euiPanel').eq(3).within(() => { cy.contains('Total CPU usage'); - cy.get('h2').contains('N/A'); + cy.get('h2').contains('0'); }); cy.get('.euiPanel').eq(4).within(() => { cy.contains('Total memory usage'); - cy.get('h2').contains('N/A'); + cy.get('h2').contains('0'); }); cy.contains('h3', 'Queries by Node') @@ -281,14 +280,12 @@ describe('Inflight Queries Dashboard', () => { cy.navigateToLiveQueries(); cy.wait('@getErrorResponse'); - // Verify error handling in metrics panels cy.get('.euiPanel').each(($panel) => { cy.wrap($panel).within(() => { - cy.get('h2').contains('N/A'); + cy.get('h2').contains('0'); }); }); - - // Verify error handling in charts + cy.contains('h3', 'Queries by Node') .closest('.euiPanel') .contains('No data available'); diff --git a/public/components/__snapshots__/app.test.tsx.snap b/public/components/__snapshots__/app.test.tsx.snap index 60a61c8f..3ee67421 100644 --- a/public/components/__snapshots__/app.test.tsx.snap +++ b/public/components/__snapshots__/app.test.tsx.snap @@ -17,6 +17,18 @@ exports[` spec renders the component 1`] = ` class="euiTabs" role="tablist" > +
- -
-
-
-
+ + + + + + + + Auto-refresh +
+
+ +
+
+ +
-
-
-`; - -exports[`InflightQueries shows 0 when there are no queries 1`] = ` -
-
@@ -680,13 +340,11 @@ exports[`InflightQueries shows 0 when there are no queries 1`] = `
-

- - Queries by Node - -

+ Queries by Node +

@@ -788,11 +446,8 @@ exports[`InflightQueries shows 0 when there are no queries 1`] = `
-
-

- - Queries by Index - -

+ Queries by Index +

@@ -934,11 +587,8 @@ exports[`InflightQueries shows 0 when there are no queries 1`] = `
-
+
+
+
+
+
+
+
+ +
+ + + +
+
+
+
+
+
+
+
+ +
+
+
+
+ +
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+ +
+ +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+
+
+
+ + + Timestamp + + + + + + Task ID + + + + + + Index + + + + + + Node + + + + + + Time elapsed + + + + + + CPU usage + + + + + + Memory usage + + + + + + Search type + + + + + + Coordinator node + + + + + + Status + + + + + + Actions + + +
+
+ + No items found + +
+
+
+
+
+
`; diff --git a/public/pages/TopNQueries/TopNQueries.tsx b/public/pages/TopNQueries/TopNQueries.tsx index 9848576a..1779b475 100644 --- a/public/pages/TopNQueries/TopNQueries.tsx +++ b/public/pages/TopNQueries/TopNQueries.tsx @@ -12,7 +12,7 @@ import { DataSourceOption } from 'src/plugins/data_source_management/public/comp import QueryInsights from '../QueryInsights/QueryInsights'; import Configuration from '../Configuration/Configuration'; import QueryDetails from '../QueryDetails/QueryDetails'; -import LiveQueries from '../InflightQueries/InflightQueries'; +import { InflightQueries } from '../InflightQueries/InflightQueries'; import { SearchQueryRecord } from '../../../types/types'; import { QueryGroupDetails } from '../QueryGroupDetails/QueryGroupDetails'; import { QueryInsightsDashboardsPluginStartDependencies } from '../../types'; @@ -416,7 +416,7 @@ const TopNQueries = ({ /> {tabs.map(renderTab)} - /_cancel`, + req: { + taskId: { + type: 'string', + required: true, + }, + }, + }, + method: 'POST', + }); }; diff --git a/server/routes/index.ts b/server/routes/index.ts index f5b8421d..67d8c030 100644 --- a/server/routes/index.ts +++ b/server/routes/index.ts @@ -402,4 +402,30 @@ export function defineRoutes(router: IRouter, dataSourceEnabled: boolean) { } } ); + router.post( + { + path: '/api/tasks/{taskId}/cancel', + validate: { + params: schema.object({ + taskId: schema.string(), + }), + }, + }, + async (context, request, response) => { + try { + const esClient = context.core.opensearch.client.asCurrentUser; + const { taskId } = request.params; + + const result = await esClient.transport.request({ + method: 'POST', + path: `/_tasks/${taskId}/_cancel`, + }); + + return response.ok({ body: { ok: true, result } }); + } catch (e) { + console.error(e); + return response.customError({ statusCode: 500, body: e.message }); + } + } + ); } diff --git a/types/types.ts b/types/types.ts index cdb50590..75eb9dcb 100644 --- a/types/types.ts +++ b/types/types.ts @@ -77,6 +77,7 @@ export interface LiveSearchQueryRecord { memory?: Measurement; }; node_id: string; + is_cancelled: boolean; } export interface LiveSearchQueryResponse { From 1268212f2b8adfec66059cb168ce1c9002d3cd27 Mon Sep 17 00:00:00 2001 From: Kishore Kumaar Natarajan Date: Fri, 6 Jun 2025 11:36:23 -0700 Subject: [PATCH 17/26] updated the dependencies Signed-off-by: Kishore Kumaar Natarajan --- yarn.lock | 671 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 657 insertions(+), 14 deletions(-) diff --git a/yarn.lock b/yarn.lock index d2922d20..799611f4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -244,12 +244,10 @@ core-js-pure "^3.30.2" regenerator-runtime "^0.14.0" -"@babel/runtime@^7.12.5", "@babel/runtime@^7.22.9", "@babel/runtime@^7.9.2": - version "7.26.10" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.26.10.tgz#a07b4d8fa27af131a633d7b3524db803eb4764c2" - integrity sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw== - dependencies: - regenerator-runtime "^0.14.0" +"@babel/runtime@^7.12.5", "@babel/runtime@^7.26.10", "@babel/runtime@^7.9.2": + version "7.27.3" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.27.3.tgz#10491113799fb8d77e1d9273384d5d68deeea8f6" + integrity sha512-7EYtGezsdiDMyY80+65EzwiGmcJqpmcZCojSXaRgdrBaGtWTgDZKq69cPIVped6MkIM78cTQ2GOiEYjwOlG4xw== "@babel/template@^7.26.9", "@babel/template@^7.3.3": version "7.26.9" @@ -763,10 +761,12 @@ lodash "^4.17.15" redent "^3.0.0" -"@testing-library/user-event@^14.4.3": - version "14.6.1" - resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-14.6.1.tgz#13e09a32d7a8b7060fe38304788ebf4197cd2149" - integrity sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw== +"@testing-library/user-event@^13.5.0": + version "13.5.0" + resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-13.5.0.tgz#69d77007f1e124d55314a2b73fd204b333b13295" + integrity sha512-5Kwtbo3Y/NowpkbRuSepbyMFkZmHgD+vPzYB/RJ4oxt5Gj/avFFBYjhw27cqSVPVw/3a67NK1PbiIr9k4Gwmdg== + dependencies: + "@babel/runtime" "^7.12.5" "@tootallnate/once@1": version "1.1.2" @@ -818,6 +818,16 @@ dependencies: "@babel/types" "^7.20.7" +"@types/estree@^1.0.0": + version "1.0.7" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.7.tgz#4158d3105276773d5b7695cd4834b1722e4f37a8" + integrity sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ== + +"@types/geojson@7946.0.4": + version "7946.0.4" + resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.4.tgz#4e049756383c3f055dd8f3d24e63fb543e98eb07" + integrity sha512-MHmwBtCb7OCv1DSivz2UNJXPGU/1btAWRKlqJ2saEhVJkpkvqHMMaOpKg0v4sAbDWSQekHGvPVMM8nQ+Jen03Q== + "@types/graceful-fs@^4.1.2": version "4.1.9" resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.9.tgz#2a06bc0f68a20ab37b3e36aa238be6abdf49e8b4" @@ -857,6 +867,11 @@ expect "^29.0.0" pretty-format "^29.0.0" +"@types/luxon@^3.6.2": + version "3.6.2" + resolved "https://registry.yarnpkg.com/@types/luxon/-/luxon-3.6.2.tgz#be6536931801f437eafcb9c0f6d6781f72308041" + integrity sha512-R/BdP7OxEMc44l2Ex5lSXHoIXTB2JLNa3y2QISIbr58U/YcsffyQrYW//hZSdrfxrjRZj3GcUoxMPGdO8gSYuw== + "@types/node@*": version "22.13.10" resolved "https://registry.yarnpkg.com/@types/node/-/node-22.13.10.tgz#df9ea358c5ed991266becc3109dc2dc9125d77e4" @@ -1876,6 +1891,15 @@ cliui@^7.0.2: strip-ansi "^6.0.0" wrap-ansi "^7.0.0" +cliui@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" + integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.1" + wrap-ansi "^7.0.0" + co@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" @@ -1922,11 +1946,16 @@ combined-stream@^1.0.8, combined-stream@~1.0.6: dependencies: delayed-stream "~1.0.0" -commander@^2.20.0: +commander@2, 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: + 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@^6.2.0, commander@^6.2.1: version "6.2.1" resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c" @@ -2166,6 +2195,136 @@ cypress@^13.6.0: untildify "^4.0.0" yauzl "^2.10.0" +"d3-array@1 - 3", "d3-array@2 - 3", "d3-array@2.10.0 - 3", "d3-array@2.5.0 - 3", d3-array@3.2.4, d3-array@^3.2.2: + version "3.2.4" + resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-3.2.4.tgz#15fec33b237f97ac5d7c986dc77da273a8ed0bb5" + integrity sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg== + dependencies: + internmap "1 - 2" + +"d3-color@1 - 3", d3-color@^3.1.0: + 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-delaunay@^6.0.2: + version "6.0.4" + resolved "https://registry.yarnpkg.com/d3-delaunay/-/d3-delaunay-6.0.4.tgz#98169038733a0a5babbeda55054f795bb9e4a58b" + integrity sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A== + dependencies: + delaunator "5" + +"d3-dispatch@1 - 3": + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-dispatch/-/d3-dispatch-3.0.1.tgz#5fc75284e9c2375c36c839411a0cf550cbfc4d5e" + integrity sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg== + +d3-dsv@^3.0.1: + 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-force@^3.0.0: + 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.1.0: + 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-projection@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/d3-geo-projection/-/d3-geo-projection-4.0.0.tgz#dc229e5ead78d31869a4e87cf1f45bd2716c48ca" + integrity sha512-p0bK60CEzph1iqmnxut7d/1kyTmm3UWtPlwdkM31AU+LW+BXazd5zJdoCn7VFxNCHXRngPHRnsNn5uGjLRGndg== + dependencies: + commander "7" + d3-array "1 - 3" + d3-geo "1.12.0 - 3" + +"d3-geo@1.12.0 - 3", d3-geo@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/d3-geo/-/d3-geo-3.1.1.tgz#6027cf51246f9b2ebd64f99e01dc7c3364033a4d" + integrity sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q== + dependencies: + d3-array "2.5.0 - 3" + +d3-hierarchy@^3.1.2: + 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.0.1: + 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@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-3.1.0.tgz#22df939032fb5a71ae8b1800d61ddb7851c42526" + integrity sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ== + +"d3-quadtree@1 - 3": + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-quadtree/-/d3-quadtree-3.0.1.tgz#6dca3e8be2b393c9a9d514dabbd80a92deef1a4f" + integrity sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw== + +d3-scale-chromatic@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz#34c39da298b23c20e02f1a4b239bd0f22e7f1314" + integrity sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ== + dependencies: + d3-color "1 - 3" + d3-interpolate "1 - 3" + +d3-scale@^4.0.2: + 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-shape@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-3.2.0.tgz#a1a839cbd9ba45f28674c69d7f855bcf91dfc6a5" + integrity sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA== + dependencies: + d3-path "^3.1.0" + +"d3-time-format@2 - 4", d3-time-format@^4.1.0: + 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.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-3.1.0.tgz#9310db56e992e3c0175e1ef385e545e48a9bb5c7" + integrity sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q== + dependencies: + d3-array "2 - 3" + +"d3-timer@1 - 3", d3-timer@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-3.0.1.tgz#6284d2a2708285b1abb7e201eda4380af35e63b0" + integrity sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA== + dashdash@^1.12.0: version "1.14.1" resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" @@ -2297,6 +2456,13 @@ define-properties@^1.1.4, define-properties@^1.2.1: has-property-descriptors "^1.0.0" object-keys "^1.1.1" +delaunator@5: + version "5.0.1" + resolved "https://registry.yarnpkg.com/delaunator/-/delaunator-5.0.1.tgz#39032b08053923e924d6094fe2cde1a99cc51278" + integrity sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw== + dependencies: + robust-predicates "^3.0.2" + delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" @@ -2800,6 +2966,11 @@ fast-deep-equal@^3.1.1: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== +fast-json-patch@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/fast-json-patch/-/fast-json-patch-3.1.1.tgz#85064ea1b1ebf97a3f7ad01e23f9337e72c66947" + integrity sha512-vf6IHUX2SBcA+5/+4883dsIjpBTqmfBjmYiWK1savxQmFk4JfBMLa7ynTYOs1Rolp/T1betJxHiGD3g1Mn8lUQ== + fast-json-stable-stringify@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" @@ -2836,6 +3007,11 @@ file-uri-to-path@1.0.0: resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== +filesize@^10.1.6: + version "10.1.6" + resolved "https://registry.yarnpkg.com/filesize/-/filesize-10.1.6.tgz#31194da825ac58689c0bce3948f33ce83aabd361" + integrity sha512-sJslQKU2uM33qH5nqewAwVB2QgR6w1aMNsYUp3aN5rMRyXEwJGmZvaWzeJFNTOXWlHQyBFCWrdj3fV/fsTOX8w== + fill-range@^7.1.1: version "7.1.1" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" @@ -3280,6 +3456,13 @@ iconv-lite@0.4.24: dependencies: safer-buffer ">= 2.1.2 < 3" +iconv-lite@0.6: + version "0.6.3" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + ieee754@^1.1.13, ieee754@^1.1.4: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" @@ -3353,6 +3536,11 @@ internal-slot@^1.1.0: hasown "^2.0.2" side-channel "^1.1.0" +"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.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" @@ -4206,6 +4394,16 @@ json-schema@0.4.0: resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.4.0.tgz#f7de4cf6efab838ebaeb3236474cbba5a1930ab5" integrity sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA== +json-stringify-pretty-compact@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/json-stringify-pretty-compact/-/json-stringify-pretty-compact-3.0.0.tgz#f71ef9d82ef16483a407869556588e91b681d9ab" + integrity sha512-Rc2suX5meI0S3bfdZuA7JMFBGkJ875ApfVyq2WHELjBiiG22My/l7/8zPpH/CfFVQHuVLd8NLR0nv6vi0BYYKA== + +json-stringify-pretty-compact@~4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/json-stringify-pretty-compact/-/json-stringify-pretty-compact-4.0.0.tgz#cf4844770bddee3cb89a6170fe4b00eee5dbf1d4" + integrity sha512-3CNZ2DnrpByG9Nqj6Xo8vqbjT4F6N+tb4Gb28ESAZjYZ5yqvmc56J+/kuIwkaAMOyblTQhUW7PxMkUb8Q36N3Q== + json-stringify-safe@~5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" @@ -4371,6 +4569,11 @@ lru-cache@^5.1.1: dependencies: yallist "^3.0.2" +luxon@^3.6.1: + version "3.6.1" + resolved "https://registry.yarnpkg.com/luxon/-/luxon-3.6.1.tgz#d283ffc4c0076cb0db7885ec6da1c49ba97e47b0" + integrity sha512-tJLxrKJhO2ukZ5z0gyjY1zPh3Rh88Ej9P7jNrZiHMUXHae1yvI2imgOZtL1TO8TW6biMMKfTtAOoEJANgtWBMQ== + lz-string@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.5.0.tgz#c1ab50f77887b712621201ba9fd4e3a6ed099941" @@ -4558,6 +4761,13 @@ neo-async@^2.5.0, neo-async@^2.6.1: resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== +node-fetch@^2.6.7: + version "2.7.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" + integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== + dependencies: + whatwg-url "^5.0.0" + node-int64@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" @@ -5218,6 +5428,11 @@ ripemd160@^2.0.0, ripemd160@^2.0.1: hash-base "^3.0.0" inherits "^2.0.1" +robust-predicates@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/robust-predicates/-/robust-predicates-3.0.2.tgz#d5b28528c4824d20fc48df1928d41d9efa1ad771" + integrity sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg== + run-queue@^1.0.0, run-queue@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/run-queue/-/run-queue-1.0.3.tgz#e848396f057d223f24386924618e25694161ec47" @@ -5225,6 +5440,11 @@ run-queue@^1.0.0, run-queue@^1.0.3: dependencies: aproba "^1.1.1" +rw@1: + version "1.3.3" + resolved "https://registry.yarnpkg.com/rw/-/rw-1.3.3.tgz#3f862dfa91ab766b14885ef4d01124bfda074fb4" + integrity sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ== + rxjs@^7.5.1: version "7.8.2" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.2.tgz#955bc473ed8af11a002a2be52071bf475638607b" @@ -5270,7 +5490,7 @@ safe-regex-test@^1.1.0: es-errors "^1.3.0" is-regex "^1.2.1" -"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: +"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== @@ -5311,6 +5531,11 @@ semver@^7.3.2, semver@^7.5.3: resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.1.tgz#abd5098d82b18c6c81f6074ff2647fd3e7220c9f" integrity sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA== +semver@^7.3.7: + version "7.7.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.2.tgz#67d99fdcd35cec21e6f8b87a7fd515a33f982b58" + integrity sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA== + serialize-javascript@6.0.2, serialize-javascript@^4.0.0: version "6.0.2" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.2.tgz#defa1e055c83bf6d59ea805d8da862254eb6a6c2" @@ -5562,7 +5787,7 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" -string-width@^4.1.0, string-width@^4.2.0: +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: 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== @@ -5822,6 +6047,13 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" +topojson-client@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/topojson-client/-/topojson-client-3.1.0.tgz#22e8b1ed08a2b922feeb4af6f53b6ef09a467b99" + integrity sha512-605uxS6bcYxGXw9qi62XyrV6Q3xwbndjachmNxu8HWTtVPxZfEJN9fd/SZS1Q54Sn2y0TMyMxFj/cJINqGHrKw== + dependencies: + commander "2" + tough-cookie@^4.0.0: version "4.1.4" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.4.tgz#945f1461b45b5a8c76821c33ea49c3ac192c1b36" @@ -5846,6 +6078,11 @@ tr46@^2.1.0: dependencies: punycode "^2.1.1" +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== + tree-kill@1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" @@ -5862,7 +6099,7 @@ ts-loader@^6.2.1: micromatch "^4.0.0" semver "^6.0.0" -tslib@^2.1.0, tslib@^2.4.0: +tslib@^2.1.0, tslib@^2.4.0, tslib@~2.8.1: version "2.8.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== @@ -6069,6 +6306,381 @@ v8-to-istanbul@^8.1.0: convert-source-map "^1.6.0" source-map "^0.7.3" +vega-canvas@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/vega-canvas/-/vega-canvas-1.2.7.tgz#cf62169518f5dcd91d24ad352998c2248f8974fb" + integrity sha512-OkJ9CACVcN9R5Pi9uF6MZBF06pO6qFpDYHWSKBJsdHP5o724KrsgR6UvbnXFH82FdsiTOff/HqjuaG8C7FL+9Q== + +vega-crossfilter@~4.1.3: + version "4.1.3" + resolved "https://registry.yarnpkg.com/vega-crossfilter/-/vega-crossfilter-4.1.3.tgz#2c404ddcd420605c84fb088f3e9beb671dca498e" + integrity sha512-nyPJAXAUABc3EocUXvAL1J/IWotZVsApIcvOeZaUdEQEtZ7bt8VtP2nj3CLbHBA8FZZVV+K6SmdwvCOaAD4wFQ== + dependencies: + d3-array "^3.2.2" + vega-dataflow "^5.7.7" + vega-util "^1.17.3" + +vega-dataflow@^5.7.7, vega-dataflow@~5.7.7: + version "5.7.7" + resolved "https://registry.yarnpkg.com/vega-dataflow/-/vega-dataflow-5.7.7.tgz#d766f650aaaf27836894bdb6ee391fd43d7a22ef" + integrity sha512-R2NX2HvgXL+u4E6u+L5lKvvRiCtnE6N6l+umgojfi53suhhkFP+zB+2UAQo4syxuZ4763H1csfkKc4xpqLzKnw== + dependencies: + vega-format "^1.1.3" + vega-loader "^4.5.3" + vega-util "^1.17.3" + +vega-embed@6.21.0: + version "6.21.0" + resolved "https://registry.yarnpkg.com/vega-embed/-/vega-embed-6.21.0.tgz#a6f7d4965c653e40620bfd0a51fb419321cff02c" + integrity sha512-Tzo9VAfgNRb6XpxSFd7uphSeK2w5OxDY2wDtmpsQ+rQlPSEEI9TE6Jsb2nHRLD5J4FrmXKLrTcORqidsNQSXEg== + dependencies: + fast-json-patch "^3.1.1" + json-stringify-pretty-compact "^3.0.0" + semver "^7.3.7" + tslib "^2.4.0" + vega-interpreter "^1.0.4" + vega-schema-url-parser "^2.2.0" + vega-themes "^2.10.0" + vega-tooltip "^0.28.0" + +vega-encode@~4.10.2: + version "4.10.2" + resolved "https://registry.yarnpkg.com/vega-encode/-/vega-encode-4.10.2.tgz#dcef19d040905f5ef43e8a730f6ec501fe4b2a73" + integrity sha512-fsjEY1VaBAmqwt7Jlpz0dpPtfQFiBdP9igEefvumSpy7XUxOJmDQcRDnT3Qh9ctkv3itfPfI9g8FSnGcv2b4jQ== + dependencies: + d3-array "^3.2.2" + d3-interpolate "^3.0.1" + vega-dataflow "^5.7.7" + vega-scale "^7.4.2" + vega-util "^1.17.3" + +vega-event-selector@^3.0.1, vega-event-selector@~3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/vega-event-selector/-/vega-event-selector-3.0.1.tgz#b99e92147b338158f8079d81b28b2e7199c2e259" + integrity sha512-K5zd7s5tjr1LiOOkjGpcVls8GsH/f2CWCrWcpKy74gTCp+llCdwz0Enqo013ZlGaRNjfgD/o1caJRt3GSaec4A== + +vega-expression@^5.2.0, vega-expression@~5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/vega-expression/-/vega-expression-5.2.0.tgz#a5dfa8dd79066082add1846618f3d7f0a364305f" + integrity sha512-WRMa4ny3iZIVAzDlBh3ipY2QUuLk2hnJJbfbncPgvTF7BUgbIbKq947z+JicWksYbokl8n1JHXJoqi3XvpG0Zw== + dependencies: + "@types/estree" "^1.0.0" + vega-util "^1.17.3" + +vega-expression@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/vega-expression/-/vega-expression-5.1.2.tgz#1ed0677787b8e5f4047c9b847ecc20da0a2a8d05" + integrity sha512-fFeDTh4UtOxlZWL54jf1ZqJHinyerWq/ROiqrQxqLkNJRJ86RmxYTgXwt65UoZ/l4VUv9eAd2qoJeDEf610Umw== + dependencies: + "@types/estree" "^1.0.0" + vega-util "^1.17.3" + +vega-force@~4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/vega-force/-/vega-force-4.2.2.tgz#e70ac31cf73d3ffaed361202613809e8c516d6f0" + integrity sha512-cHZVaY2VNNIG2RyihhSiWniPd2W9R9kJq0znxzV602CgUVgxEfTKtx/lxnVCn8nNrdKAYrGiqIsBzIeKG1GWHw== + dependencies: + d3-force "^3.0.0" + vega-dataflow "^5.7.7" + vega-util "^1.17.3" + +vega-format@^1.1.3, vega-format@~1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/vega-format/-/vega-format-1.1.3.tgz#66e0fd8eb0ba8d9e638b3c62a023cdab9af57bc5" + integrity sha512-wQhw7KR46wKJAip28FF/CicW+oiJaPAwMKdrxlnTA0Nv8Bf7bloRlc+O3kON4b4H1iALLr9KgRcYTOeXNs2MOA== + dependencies: + d3-array "^3.2.2" + d3-format "^3.1.0" + d3-time-format "^4.1.0" + vega-time "^2.1.3" + vega-util "^1.17.3" + +vega-functions@^5.18.0, vega-functions@~5.18.0: + version "5.18.0" + resolved "https://registry.yarnpkg.com/vega-functions/-/vega-functions-5.18.0.tgz#297fbb982492622bf57ca8148447ad88cdee859c" + integrity sha512-+D+ey4bDAhZA2CChh7bRZrcqRUDevv05kd2z8xH+il7PbYQLrhi6g1zwvf8z3KpgGInFf5O13WuFK5DQGkz5lQ== + dependencies: + d3-array "^3.2.2" + d3-color "^3.1.0" + d3-geo "^3.1.0" + vega-dataflow "^5.7.7" + vega-expression "^5.2.0" + vega-scale "^7.4.2" + vega-scenegraph "^4.13.1" + vega-selections "^5.6.0" + vega-statistics "^1.9.0" + vega-time "^2.1.3" + vega-util "^1.17.3" + +vega-geo@~4.4.3: + version "4.4.3" + resolved "https://registry.yarnpkg.com/vega-geo/-/vega-geo-4.4.3.tgz#038dc85e1c030b2f2c8bda61fb31ff6bdfea123b" + integrity sha512-+WnnzEPKIU1/xTFUK3EMu2htN35gp9usNZcC0ZFg2up1/Vqu6JyZsX0PIO51oXSIeXn9bwk6VgzlOmJUcx92tA== + dependencies: + d3-array "^3.2.2" + d3-color "^3.1.0" + d3-geo "^3.1.0" + vega-canvas "^1.2.7" + vega-dataflow "^5.7.7" + vega-projection "^1.6.2" + vega-statistics "^1.9.0" + vega-util "^1.17.3" + +vega-hierarchy@~4.1.3: + version "4.1.3" + resolved "https://registry.yarnpkg.com/vega-hierarchy/-/vega-hierarchy-4.1.3.tgz#7721aec582cdf332da6a4ee8332a94e3ac064881" + integrity sha512-0Z+TYKRgOEo8XYXnJc2HWg1EGpcbNAhJ9Wpi9ubIbEyEHqIgjCIyFVN8d4nSfsJOcWDzsSmRqohBztxAhOCSaw== + dependencies: + d3-hierarchy "^3.1.2" + vega-dataflow "^5.7.7" + vega-util "^1.17.3" + +vega-interpreter@^1.0.4: + version "1.2.0" + resolved "https://registry.yarnpkg.com/vega-interpreter/-/vega-interpreter-1.2.0.tgz#a712a99843a02a0a021bd7ae4d79e43b8895ca17" + integrity sha512-p408/0IPevyR/bIKdXGNzOixkTYCkH83zNhGypRqDxd/qVrdJVrh9RcECOYx1MwEc6JTB1BeK2lArHiGGuG7Hw== + dependencies: + vega-util "^1.17.3" + +vega-label@~1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/vega-label/-/vega-label-1.3.1.tgz#2e370dac88e91615317ba5120cbfc6c43c6227dd" + integrity sha512-Emx4b5s7pvuRj3fBkAJ/E2snCoZACfKAwxVId7f/4kYVlAYLb5Swq6W8KZHrH4M9Qds1XJRUYW9/Y3cceqzEFA== + dependencies: + vega-canvas "^1.2.7" + vega-dataflow "^5.7.7" + vega-scenegraph "^4.13.1" + vega-util "^1.17.3" + +vega-lite@^5.6.0: + version "5.23.0" + resolved "https://registry.yarnpkg.com/vega-lite/-/vega-lite-5.23.0.tgz#342cbe8e5ccd3e3eeb4721818b1d5cb26b60ad8a" + integrity sha512-l4J6+AWE3DIjvovEoHl2LdtCUkfm4zs8Xxx7INwZEAv+XVb6kR6vIN1gt3t2gN2gs/y4DYTs/RPoTeYAuEg6mA== + dependencies: + json-stringify-pretty-compact "~4.0.0" + tslib "~2.8.1" + vega-event-selector "~3.0.1" + vega-expression "~5.1.1" + vega-util "~1.17.2" + yargs "~17.7.2" + +vega-loader@^4.5.3, vega-loader@~4.5.3: + version "4.5.3" + resolved "https://registry.yarnpkg.com/vega-loader/-/vega-loader-4.5.3.tgz#f89cf4def5b2c61f65f845ec695b6e14d15c36e9" + integrity sha512-dUfIpxTLF2magoMaur+jXGvwMxjtdlDZaIS8lFj6N7IhUST6nIvBzuUlRM+zLYepI5GHtCLOnqdKU4XV0NggCA== + dependencies: + d3-dsv "^3.0.1" + node-fetch "^2.6.7" + topojson-client "^3.1.0" + vega-format "^1.1.3" + vega-util "^1.17.3" + +vega-parser@~6.6.0: + version "6.6.0" + resolved "https://registry.yarnpkg.com/vega-parser/-/vega-parser-6.6.0.tgz#f6aa6fc07c89f83196a1951ed9cf832436a429ab" + integrity sha512-jltyrwCTtWeidi/6VotLCybhIl+ehwnzvFWYOdWNUP0z/EskdB64YmawNwjCjzTBMemeiQtY6sJPPbewYqe3Vg== + dependencies: + vega-dataflow "^5.7.7" + vega-event-selector "^3.0.1" + vega-functions "^5.18.0" + vega-scale "^7.4.2" + vega-util "^1.17.3" + +vega-projection@^1.6.2, vega-projection@~1.6.2: + version "1.6.2" + resolved "https://registry.yarnpkg.com/vega-projection/-/vega-projection-1.6.2.tgz#69ea9a2404bad12642d3ec5c4f5615b27cdcb630" + integrity sha512-3pcVaQL9R3Zfk6PzopLX6awzrQUeYOXJzlfLGP2Xd93mqUepBa6m/reVrTUoSFXA3v9lfK4W/PS2AcVzD/MIcQ== + dependencies: + d3-geo "^3.1.0" + d3-geo-projection "^4.0.0" + vega-scale "^7.4.2" + +vega-regression@~1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/vega-regression/-/vega-regression-1.3.1.tgz#e1de74062250da33e823897565155caefb041dbb" + integrity sha512-AmccF++Z9uw4HNZC/gmkQGe6JsRxTG/R4QpbcSepyMvQN1Rj5KtVqMcmVFP1r3ivM4dYGFuPlzMWvuqp0iKMkQ== + dependencies: + d3-array "^3.2.2" + vega-dataflow "^5.7.7" + vega-statistics "^1.9.0" + vega-util "^1.17.3" + +vega-runtime@^6.2.1, vega-runtime@~6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/vega-runtime/-/vega-runtime-6.2.1.tgz#4749ea1530d822a789ae8e431bad0965ff2925ee" + integrity sha512-b4eot3tWKCk++INWqot+6sLn3wDTj/HE+tRSbiaf8aecuniPMlwJEK7wWuhVGeW2Ae5n8fI/8TeTViaC94bNHA== + dependencies: + vega-dataflow "^5.7.7" + vega-util "^1.17.3" + +vega-scale@^7.4.2, vega-scale@~7.4.2: + version "7.4.2" + resolved "https://registry.yarnpkg.com/vega-scale/-/vega-scale-7.4.2.tgz#4e4d24aa478ba475b410b0ac9acda88e52acd5fd" + integrity sha512-o6Hl76aU1jlCK7Q8DPYZ8OGsp4PtzLdzI6nGpLt8rxoE78QuB3GBGEwGAQJitp4IF7Lb2rL5oAXEl3ZP6xf9jg== + dependencies: + d3-array "^3.2.2" + d3-interpolate "^3.0.1" + d3-scale "^4.0.2" + d3-scale-chromatic "^3.1.0" + vega-time "^2.1.3" + vega-util "^1.17.3" + +vega-scenegraph@^4.13.1, vega-scenegraph@~4.13.1: + version "4.13.1" + resolved "https://registry.yarnpkg.com/vega-scenegraph/-/vega-scenegraph-4.13.1.tgz#5a7ab99cc8c4ae48a2322823faab4edef2080df9" + integrity sha512-LFY9+sLIxRfdDI9ZTKjLoijMkIAzPLBWHpPkwv4NPYgdyx+0qFmv+puBpAUGUY9VZqAZ736Uj5NJY9zw+/M3yQ== + dependencies: + d3-path "^3.1.0" + d3-shape "^3.2.0" + vega-canvas "^1.2.7" + vega-loader "^4.5.3" + vega-scale "^7.4.2" + vega-util "^1.17.3" + +vega-schema-url-parser@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/vega-schema-url-parser/-/vega-schema-url-parser-2.2.0.tgz#a0d1e02915adfbfcb1fd517c8c2ebe2419985c1e" + integrity sha512-yAtdBnfYOhECv9YC70H2gEiqfIbVkq09aaE4y/9V/ovEFmH9gPKaEgzIZqgT7PSPQjKhsNkb6jk6XvSoboxOBw== + +vega-selections@^5.6.0: + version "5.6.0" + resolved "https://registry.yarnpkg.com/vega-selections/-/vega-selections-5.6.0.tgz#9ffa55039f2e7ad71d4926147b8d458375ce611f" + integrity sha512-UE2w78rUUbaV3Ph+vQbQDwh8eywIJYRxBiZdxEG/Tr/KtFMLdy2BDgNZuuDO1Nv8jImPJwONmqjNhNDYwM0VJQ== + dependencies: + d3-array "3.2.4" + vega-expression "^5.2.0" + vega-util "^1.17.3" + +vega-statistics@^1.9.0, vega-statistics@~1.9.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/vega-statistics/-/vega-statistics-1.9.0.tgz#7d6139cea496b22d60decfa6abd73346f70206f9" + integrity sha512-GAqS7mkatpXcMCQKWtFu1eMUKLUymjInU0O8kXshWaQrVWjPIO2lllZ1VNhdgE0qGj4oOIRRS11kzuijLshGXQ== + dependencies: + d3-array "^3.2.2" + +vega-themes@^2.10.0: + version "2.15.0" + resolved "https://registry.yarnpkg.com/vega-themes/-/vega-themes-2.15.0.tgz#cf7592efb45406957e9beb67d7033ee5f7b7a511" + integrity sha512-DicRAKG9z+23A+rH/3w3QjJvKnlGhSbbUXGjBvYGseZ1lvj9KQ0BXZ2NS/+MKns59LNpFNHGi9us/wMlci4TOA== + +vega-time@^2.1.3, vega-time@~2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/vega-time/-/vega-time-2.1.3.tgz#507e3b0af61ebcd6a9c56de89fe1213924c2c1f2" + integrity sha512-hFcWPdTV844IiY0m97+WUoMLADCp+8yUQR1NStWhzBzwDDA7QEGGwYGxALhdMOaDTwkyoNj3V/nox2rQAJD/vQ== + dependencies: + d3-array "^3.2.2" + d3-time "^3.1.0" + vega-util "^1.17.3" + +vega-tooltip@^0.28.0: + version "0.28.0" + resolved "https://registry.yarnpkg.com/vega-tooltip/-/vega-tooltip-0.28.0.tgz#8bae2601ffae5e67622de37108f53f284e9a978b" + integrity sha512-DbK0V5zzk+p9cphZZXV91ZGeKq0zr6JIS0VndUoGTisldzw4tRgmpGQcTfMjew53o7/voeTM2ELTnJAJRzX4tg== + dependencies: + vega-util "^1.17.0" + +vega-transforms@~4.12.1: + version "4.12.1" + resolved "https://registry.yarnpkg.com/vega-transforms/-/vega-transforms-4.12.1.tgz#81a5c5505a2844542f99ab966b094c7b29d7d9f8" + integrity sha512-Qxo+xeEEftY1jYyKgzOGc9NuW4/MqGm1YPZ5WrL9eXg2G0410Ne+xL/MFIjHF4hRX+3mgFF4Io2hPpfy/thjLg== + dependencies: + d3-array "^3.2.2" + vega-dataflow "^5.7.7" + vega-statistics "^1.9.0" + vega-time "^2.1.3" + vega-util "^1.17.3" + +vega-typings@~1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/vega-typings/-/vega-typings-1.5.0.tgz#f3304157b86b8bf45c983959d1530f8098cd5493" + integrity sha512-tcZ2HwmiQEOXIGyBMP8sdCnoFoVqHn4KQ4H0MQiHwzFU1hb1EXURhfc+Uamthewk4h/9BICtAM3AFQMjBGpjQA== + dependencies: + "@types/geojson" "7946.0.4" + vega-event-selector "^3.0.1" + vega-expression "^5.2.0" + vega-util "^1.17.3" + +vega-util@^1.17.0, vega-util@^1.17.3, vega-util@~1.17.2: + version "1.17.3" + resolved "https://registry.yarnpkg.com/vega-util/-/vega-util-1.17.3.tgz#8f24d867daae69580874dcf75de10c65ac9ede5d" + integrity sha512-nSNpZLUrRvFo46M5OK4O6x6f08WD1yOcEzHNlqivF+sDLSsVpstaF6fdJYwrbf/debFi2L9Tkp4gZQtssup9iQ== + +vega-view-transforms@~4.6.1: + version "4.6.1" + resolved "https://registry.yarnpkg.com/vega-view-transforms/-/vega-view-transforms-4.6.1.tgz#423a1e2dae14c1506876272281e4835778fa6914" + integrity sha512-RYlyMJu5kZV4XXjmyTQKADJWDB25SMHsiF+B1rbE1p+pmdQPlp5tGdPl9r5dUJOp3p8mSt/NGI8GPGucmPMxtw== + dependencies: + vega-dataflow "^5.7.7" + vega-scenegraph "^4.13.1" + vega-util "^1.17.3" + +vega-view@~5.16.0: + version "5.16.0" + resolved "https://registry.yarnpkg.com/vega-view/-/vega-view-5.16.0.tgz#e3882f9dcb9368322a255224e5e54db864b3c226" + integrity sha512-Nxp1MEAY+8bphIm+7BeGFzWPoJnX9+hgvze6wqCAPoM69YiyVR0o0VK8M2EESIL+22+Owr0Fdy94hWHnmon5tQ== + dependencies: + d3-array "^3.2.2" + d3-timer "^3.0.1" + vega-dataflow "^5.7.7" + vega-format "^1.1.3" + vega-functions "^5.18.0" + vega-runtime "^6.2.1" + vega-scenegraph "^4.13.1" + vega-util "^1.17.3" + +vega-voronoi@~4.2.4: + version "4.2.4" + resolved "https://registry.yarnpkg.com/vega-voronoi/-/vega-voronoi-4.2.4.tgz#f45addec69e7b40598106f221014300a58d061ef" + integrity sha512-lWNimgJAXGeRFu2Pz8axOUqVf1moYhD+5yhBzDSmckE9I5jLOyZc/XvgFTXwFnsVkMd1QW1vxJa+y9yfUblzYw== + dependencies: + d3-delaunay "^6.0.2" + vega-dataflow "^5.7.7" + vega-util "^1.17.3" + +vega-wordcloud@~4.1.6: + version "4.1.6" + resolved "https://registry.yarnpkg.com/vega-wordcloud/-/vega-wordcloud-4.1.6.tgz#a428e8e7b2f83eca454631057d6864c226b41a14" + integrity sha512-lFmF3u9/ozU0P+WqPjeThQfZm0PigdbXDwpIUCxczrCXKYJLYFmZuZLZR7cxtmpZ0/yuvRvAJ4g123LXbSZF8A== + dependencies: + vega-canvas "^1.2.7" + vega-dataflow "^5.7.7" + vega-scale "^7.4.2" + vega-statistics "^1.9.0" + vega-util "^1.17.3" + +vega@^5.32.0: + version "5.33.0" + resolved "https://registry.yarnpkg.com/vega/-/vega-5.33.0.tgz#15e5eb9a4a42aaac0564257795daea3acc0d1b21" + integrity sha512-jNAGa7TxLojOpMMMrKMXXBos4K6AaLJbCgGDOw1YEkLRjUkh12pcf65J2lMSdEHjcEK47XXjKiOUVZ8L+MniBA== + dependencies: + vega-crossfilter "~4.1.3" + vega-dataflow "~5.7.7" + vega-encode "~4.10.2" + vega-event-selector "~3.0.1" + vega-expression "~5.2.0" + vega-force "~4.2.2" + vega-format "~1.1.3" + vega-functions "~5.18.0" + vega-geo "~4.4.3" + vega-hierarchy "~4.1.3" + vega-label "~1.3.1" + vega-loader "~4.5.3" + vega-parser "~6.6.0" + vega-projection "~1.6.2" + vega-regression "~1.3.1" + vega-runtime "~6.2.1" + vega-scale "~7.4.2" + vega-scenegraph "~4.13.1" + vega-statistics "~1.9.0" + vega-time "~2.1.3" + vega-transforms "~4.12.1" + vega-typings "~1.5.0" + vega-util "~1.17.2" + vega-view "~5.16.0" + vega-view-transforms "~4.6.1" + vega-voronoi "~4.2.4" + vega-wordcloud "~4.1.6" + verror@1.10.0: version "1.10.0" resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" @@ -6122,6 +6734,11 @@ watchpack@^1.7.4: chokidar "^3.4.1" watchpack-chokidar2 "^2.0.1" +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== + webidl-conversions@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-5.0.0.tgz#ae59c8a00b121543a2acc65c0434f57b0fc11aff" @@ -6182,6 +6799,14 @@ whatwg-mimetype@^2.3.0: resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + whatwg-url@^8.0.0, whatwg-url@^8.5.0: version "8.7.0" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-8.7.0.tgz#656a78e510ff8f3937bc0bcbe9f5c0ac35941b77" @@ -6354,6 +6979,11 @@ yargs-parser@^20.2.2: resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== +yargs-parser@^21.1.1: + version "21.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== + yargs@^15.3.1: version "15.4.1" resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8" @@ -6384,6 +7014,19 @@ yargs@^16.2.0: y18n "^5.0.5" yargs-parser "^20.2.2" +yargs@~17.7.2: + version "17.7.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" + integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== + dependencies: + cliui "^8.0.1" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.1.1" + yauzl@^2.10.0: version "2.10.0" resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" From 809dac59bbeea562879dfe30ab8bc979b58a5931 Mon Sep 17 00:00:00 2001 From: Kishore Kumaar Natarajan Date: Fri, 6 Jun 2025 11:43:09 -0700 Subject: [PATCH 18/26] updated the dependencies Signed-off-by: Kishore Kumaar Natarajan --- package.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 24386abf..d782aa39 100644 --- a/package.json +++ b/package.json @@ -32,12 +32,12 @@ "@babel/runtime": "^7.26.10", "@babel/runtime-corejs3": "^7.22.9", "filesize": "^10.1.6", - "luxon": "^3.6.1", "object-hash": "^3.0.0", "plotly.js-dist": "^2.34.0", "vega": "^5.32.0", "vega-embed": "6.21.0", - "vega-lite": "^5.6.0" + "vega-lite": "^5.6.0", + "luxon": "^3.2.1" }, "resolutions": { "@types/react": "^16.9.8", @@ -57,7 +57,6 @@ "@babel/helpers": "^7.22.9", "@babel/runtime": "^7.26.10", "@babel/runtime-corejs3": "^7.22.9", - "luxon": "^3.2.1" }, "devDependencies": { "@cypress/webpack-preprocessor": "^6.0.1", From 1dcf26c753d6136398af53e5d309eeaec40efba5 Mon Sep 17 00:00:00 2001 From: Kishore Kumaar Natarajan Date: Fri, 6 Jun 2025 11:44:17 -0700 Subject: [PATCH 19/26] updated the dependencies Signed-off-by: Kishore Kumaar Natarajan --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d782aa39..95e5c5dc 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "glob-parent": "5.1.2", "@babel/helpers": "^7.22.9", "@babel/runtime": "^7.26.10", - "@babel/runtime-corejs3": "^7.22.9", + "@babel/runtime-corejs3": "^7.22.9" }, "devDependencies": { "@cypress/webpack-preprocessor": "^6.0.1", From 80c118946929f58f4bc5b6b7b51f0a2ff21730af Mon Sep 17 00:00:00 2001 From: Kishore Kumaar Natarajan Date: Fri, 6 Jun 2025 12:01:33 -0700 Subject: [PATCH 20/26] fixing lint Signed-off-by: Kishore Kumaar Natarajan --- public/pages/InflightQueries/InflightQueries.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/pages/InflightQueries/InflightQueries.test.tsx b/public/pages/InflightQueries/InflightQueries.test.tsx index 0ec80343..9e1c29ab 100644 --- a/public/pages/InflightQueries/InflightQueries.test.tsx +++ b/public/pages/InflightQueries/InflightQueries.test.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { CoreStart } from 'opensearch-dashboards/public'; import { render, screen, waitFor, within, act } from '@testing-library/react'; -import {InflightQueries} from './InflightQueries'; +import { InflightQueries } from './InflightQueries'; import { retrieveLiveQueries } from '../../../common/utils/QueryUtils'; jest.mock('vega-embed', () => ({ __esModule: true, From f3f5827c4cb51a9a2046a8f3ef362156f96f8dff Mon Sep 17 00:00:00 2001 From: Kishore Kumaar Natarajan Date: Mon, 9 Jun 2025 12:35:30 -0700 Subject: [PATCH 21/26] updated Package.json Signed-off-by: Kishore Kumaar Natarajan --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 95e5c5dc..caa3a050 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,7 @@ "@elastic/eslint-import-resolver-kibana": "link:../../packages/osd-eslint-import-resolver-opensearch-dashboards", "@testing-library/dom": "^8.11.3", "@testing-library/jest-dom": "^5.17.0", - "@testing-library/user-event": "^13.5.0", + "@testing-library/user-event": "^14.4.3", "@types/luxon": "^3.6.2", "@types/object-hash": "^3.0.0", "@types/react-dom": "^16.9.8", From 746dd4a0cc558fae9fe313f4b336c7b70a89da1f Mon Sep 17 00:00:00 2001 From: Kishore Kumaar Natarajan Date: Mon, 9 Jun 2025 15:00:51 -0700 Subject: [PATCH 22/26] Updated the fetchliveQueries logic Signed-off-by: Kishore Kumaar Natarajan --- cypress/e2e/5_live_queries.cy.js | 162 ++++++++++-------- .../pages/InflightQueries/InflightQueries.tsx | 62 +++++-- 2 files changed, 142 insertions(+), 82 deletions(-) diff --git a/cypress/e2e/5_live_queries.cy.js b/cypress/e2e/5_live_queries.cy.js index 58093b74..b0c33a94 100644 --- a/cypress/e2e/5_live_queries.cy.js +++ b/cypress/e2e/5_live_queries.cy.js @@ -22,43 +22,42 @@ describe('Inflight Queries Dashboard', () => { }); it('displays metrics panels correctly', () => { - cy.get('.euiPanel') - .eq(0) - .within(() => { - cy.contains('Active queries'); - cy.get('h2').contains(20); - }); + cy.get('[data-test-subj="panel-active-queries"]') + .within(() => { + cy.contains('Active queries'); + cy.get('h2 > b').should('contain.text', '20'); + }); - cy.get('.euiPanel') - .eq(1) - .within(() => { - cy.contains('Avg. elapsed time'); - cy.get('h2').contains('7.19 s'); - }); + cy.get('[data-test-subj="panel-avg-elapsed-time"]') + .within(() => { + cy.contains('Avg. elapsed time'); + cy.get('h2 > b').should('contain.text', '7.19 s'); + cy.contains('(Avg. across 20)'); + }); - cy.get('.euiPanel') - .eq(2) - .within(() => { - cy.contains('Longest running query'); - cy.get('h2').contains('9.69 s'); - cy.contains('ID: node-A1B2C4E5:3614'); - }); + cy.get('[data-test-subj="panel-longest-query"]') + .within(() => { + cy.contains('Longest running query'); + cy.get('h2 > b').should('contain.text', '9.69 s'); + cy.contains('ID: node-A1B2C4E5:3614'); + }); - cy.get('.euiPanel') - .eq(3) - .within(() => { - cy.contains('Total CPU usage'); - cy.get('h2').contains('1.68 ms'); - }); + cy.get('[data-test-subj="panel-total-cpu"]') + .within(() => { + cy.contains('Total CPU usage'); + cy.get('h2 > b').should('contain.text', '1.68 ms'); + cy.contains('(Sum across 20)'); + }); - cy.get('.euiPanel') - .eq(4) - .within(() => { - cy.contains('Total memory usage'); - cy.get('h2').contains('69.12 KB'); - }); + cy.get('[data-test-subj="panel-total-memory"]') + .within(() => { + cy.contains('Total memory usage'); + cy.get('h2 > b').should('contain.text', '69.12 KB'); + cy.contains('(Sum across 20)'); + }); }); + it('verifies table headers and row content in memory table', () => { const expectedHeaders = [ '', @@ -181,6 +180,39 @@ describe('Inflight Queries Dashboard', () => { cy.get('@getPeriodicQueries.all').should('have.length.at.least', 3); }); + it('displays correct chart data for node and index charts', () => { + // Wait for charts to render + cy.get('[data-test-subj="vega-chart-node"]') + .invoke('attr', 'data-chart-values') + .then((json) => { + const data = JSON.parse(json); + expect(data).to.deep.equal([ + { label: 'Node 1', value: 2 }, + { label: 'Node 2', value: 1 }, + { label: 'Node 3', value: 1 }, + { label: 'Node 4', value: 1 }, + { label: 'Node 5', value: 1 }, + { label: 'Node 6', value: 1 }, + { label: 'Node 7', value: 1 }, + { label: 'Node 8', value: 1 }, + { label: 'Node 9', value: 1 }, + { label: 'others', value: 10 }, + ]); + expect(data).to.have.length(10); + }); + + cy.get('[data-test-subj="vega-chart-index"]') + .invoke('attr', 'data-chart-values') + .then((json) => { + const data = JSON.parse(json); + expect(data).to.deep.include({ label: 'top_queries-2025.06.06-11009', value: 19 }); + expect(data).to.deep.include({ label: 'opensearch', value: 1 }); + expect(data).to.have.length(2); + }); + + }); + + it('handles empty response state', () => { cy.intercept('GET', '**/api/live_queries', (req) => { req.reply({ @@ -196,52 +228,44 @@ describe('Inflight Queries Dashboard', () => { cy.navigateToLiveQueries(); cy.wait('@getEmptyQueries'); - cy.get('.euiPanel') - .eq(0) - .within(() => { - cy.contains('Active queries'); - cy.get('h2').contains('0'); - }); + cy.get('[data-test-subj="panel-active-queries"]').within(() => { + cy.contains('Active queries'); + cy.get('h2 > b').should('contain.text', '0'); + }); - cy.get('.euiPanel') - .eq(1) - .within(() => { - cy.contains('Avg. elapsed time'); - cy.get('h2').contains('0'); - }); + cy.get('[data-test-subj="panel-avg-elapsed-time"]').within(() => { + cy.contains('Avg. elapsed time'); + cy.get('h2 > b').should('contain.text', '0'); + }); - cy.get('.euiPanel') - .eq(2) - .within(() => { - cy.contains('Longest running query'); - cy.get('h2').contains('0'); - }); + cy.get('[data-test-subj="panel-longest-query"]').within(() => { + cy.contains('Longest running query'); + cy.get('h2 > b').should('contain.text', '0'); + }); - cy.get('.euiPanel') - .eq(3) - .within(() => { - cy.contains('Total CPU usage'); - cy.get('h2').contains('0'); - }); + cy.get('[data-test-subj="panel-total-cpu"]').within(() => { + cy.contains('Total CPU usage'); + cy.get('h2 > b').should('contain.text', '0'); + }); - cy.get('.euiPanel') - .eq(4) - .within(() => { - cy.contains('Total memory usage'); - cy.get('h2').contains('0'); - }); + cy.get('[data-test-subj="panel-total-memory"]').within(() => { + cy.contains('Total memory usage'); + cy.get('h2 > b').should('contain.text', '0'); + }); + cy.get('[data-test-subj="vega-chart-node"]').should('not.exist'); cy.contains('p', 'Queries by Node') - .closest('.euiPanel') - .within(() => { - cy.contains('No data available').should('be.visible'); - }); + .closest('.euiPanel') + .within(() => { + cy.contains('No data available').should('be.visible'); + }); + cy.get('[data-test-subj="vega-chart-index"]').should('not.exist'); cy.contains('p', 'Queries by Index') - .closest('.euiPanel') - .within(() => { - cy.contains('No data available').should('be.visible'); - }); + .closest('.euiPanel') + .within(() => { + cy.contains('No data available').should('be.visible'); + }); }); it('validates time unit conversions', () => { cy.intercept('GET', '**/api/live_queries', { diff --git a/public/pages/InflightQueries/InflightQueries.tsx b/public/pages/InflightQueries/InflightQueries.tsx index 900ed5c2..c3dcf8a4 100644 --- a/public/pages/InflightQueries/InflightQueries.tsx +++ b/public/pages/InflightQueries/InflightQueries.tsx @@ -27,7 +27,8 @@ import { LiveSearchQueryResponse } from '../../../types/types'; import { retrieveLiveQueries } from '../../../common/utils/QueryUtils'; export const InflightQueries = ({ core }: { core: CoreStart }) => { const DEFAULT_REFRESH_INTERVAL = 5000; // default 5s - const TOP_N_NODE_R_INDEX = 9; + const TOP_N_DISPLAY_LIMIT = 9; + const isFetching = useRef(false); const [query, setQuery] = useState(null); const [nodeCounts, setNodeCounts] = useState({}); @@ -92,7 +93,7 @@ export const InflightQueries = ({ core }: { core: CoreStart }) => { const nodeCount: Record = {}; let othersCount = 0; sortedNodes.forEach(([nodeId, count], index) => { - if (index < TOP_N_NODE_R_INDEX) nodeCount[nodeId] = count; + if (index < TOP_N_DISPLAY_LIMIT ) nodeCount[nodeId] = count; else othersCount += count; }); if (othersCount > 0) nodeCount.others = othersCount; @@ -102,7 +103,7 @@ export const InflightQueries = ({ core }: { core: CoreStart }) => { const topIndexCount: Record = {}; let indexOthersCount = 0; sortedIndices.forEach(([indexName, count], i) => { - if (i < TOP_N_NODE_R_INDEX) topIndexCount[indexName] = count; + if (i < TOP_N_DISPLAY_LIMIT ) topIndexCount[indexName] = count; else indexOthersCount += count; }); if (indexOthersCount > 0) topIndexCount.others = indexOthersCount; @@ -110,16 +111,48 @@ export const InflightQueries = ({ core }: { core: CoreStart }) => { } }; + function withTimeout(promise: Promise, ms: number): Promise { + return new Promise((resolve, reject) => { + const timeoutId = setTimeout(() => reject(new Error('Timed out')), ms); + promise + .then((res) => { + clearTimeout(timeoutId); + resolve(res); + }) + .catch((err) => { + clearTimeout(timeoutId); + reject(err); + }); + }); + } + + const fetchliveQueriesSafe = async () => { + if (isFetching.current) { + return; + } + isFetching.current = true; + try { + await withTimeout(fetchliveQueries(), refreshInterval - 500); + } catch (e) { + console.warn('[LiveQueries] fetchliveQueries timed out or failed', e); + } finally { + isFetching.current = false; + } + }; + useEffect(() => { - fetchliveQueries(); + fetchliveQueriesSafe(); + if (!autoRefreshEnabled) return; + const interval = setInterval(() => { - fetchliveQueries(); + fetchliveQueriesSafe(); }, refreshInterval); return () => clearInterval(interval); }, [autoRefreshEnabled, refreshInterval, core]); + const [pagination, setPagination] = useState({ pageIndex: 0 }); const formatTime = (seconds: number): string => { if (seconds < 1e-3) return `${(seconds * 1e6).toFixed(2)} µs`; @@ -167,6 +200,9 @@ export const InflightQueries = ({ core }: { core: CoreStart }) => { onSelectionChange: (selected: any[]) => setSelectedItems(selected), }; + + + const metrics = React.useMemo(() => { if (!query || !query.response?.live_queries?.length) return null; @@ -325,8 +361,8 @@ export const InflightQueries = ({ core }: { core: CoreStart }) => { {/* Active Queries */} - - + +

Active queries

@@ -344,7 +380,7 @@ export const InflightQueries = ({ core }: { core: CoreStart }) => { {/* Avg. elapsed time */} - + @@ -365,7 +401,7 @@ export const InflightQueries = ({ core }: { core: CoreStart }) => { {/* Longest running query */} - + @@ -388,7 +424,7 @@ export const InflightQueries = ({ core }: { core: CoreStart }) => { {/* Total CPU usage */} - + @@ -409,7 +445,7 @@ export const InflightQueries = ({ core }: { core: CoreStart }) => { {/* Total memory usage */} - + @@ -446,7 +482,7 @@ export const InflightQueries = ({ core }: { core: CoreStart }) => {
{Object.keys(nodeCounts).length > 0 ? ( -
+
) : ( @@ -476,7 +512,7 @@ export const InflightQueries = ({ core }: { core: CoreStart }) => { {Object.keys(indexCounts).length > 0 ? ( -
+
) : ( From 231db5bad005d6122cab1444d0d5567a48e8a68e Mon Sep 17 00:00:00 2001 From: Kishore Kumaar Natarajan Date: Mon, 9 Jun 2025 16:05:28 -0700 Subject: [PATCH 23/26] Removed nodes labeled Signed-off-by: Kishore Kumaar Natarajan --- cypress/e2e/5_live_queries.cy.js | 175 +++++++----------- .../InflightQueries/InflightQueries.test.tsx | 3 +- .../pages/InflightQueries/InflightQueries.tsx | 100 ++++++---- .../InflightQueries.test.tsx.snap | 32 +--- 4 files changed, 140 insertions(+), 170 deletions(-) diff --git a/cypress/e2e/5_live_queries.cy.js b/cypress/e2e/5_live_queries.cy.js index b0c33a94..e6f1c759 100644 --- a/cypress/e2e/5_live_queries.cy.js +++ b/cypress/e2e/5_live_queries.cy.js @@ -22,54 +22,47 @@ describe('Inflight Queries Dashboard', () => { }); it('displays metrics panels correctly', () => { - cy.get('[data-test-subj="panel-active-queries"]') - .within(() => { - cy.contains('Active queries'); - cy.get('h2 > b').should('contain.text', '20'); - }); + cy.get('[data-test-subj="panel-active-queries"]').within(() => { + cy.contains('Active queries'); + cy.get('h2 > b').should('contain.text', '20'); + }); - cy.get('[data-test-subj="panel-avg-elapsed-time"]') - .within(() => { - cy.contains('Avg. elapsed time'); - cy.get('h2 > b').should('contain.text', '7.19 s'); - cy.contains('(Avg. across 20)'); - }); + cy.get('[data-test-subj="panel-avg-elapsed-time"]').within(() => { + cy.contains('Avg. elapsed time'); + cy.get('h2 > b').should('contain.text', '7.19 s'); + cy.contains('(Avg. across 20)'); + }); - cy.get('[data-test-subj="panel-longest-query"]') - .within(() => { - cy.contains('Longest running query'); - cy.get('h2 > b').should('contain.text', '9.69 s'); - cy.contains('ID: node-A1B2C4E5:3614'); - }); + cy.get('[data-test-subj="panel-longest-query"]').within(() => { + cy.contains('Longest running query'); + cy.get('h2 > b').should('contain.text', '9.69 s'); + cy.contains('ID: node-A1B2C4E5:3614'); + }); - cy.get('[data-test-subj="panel-total-cpu"]') - .within(() => { - cy.contains('Total CPU usage'); - cy.get('h2 > b').should('contain.text', '1.68 ms'); - cy.contains('(Sum across 20)'); - }); + cy.get('[data-test-subj="panel-total-cpu"]').within(() => { + cy.contains('Total CPU usage'); + cy.get('h2 > b').should('contain.text', '1.68 ms'); + cy.contains('(Sum across 20)'); + }); - cy.get('[data-test-subj="panel-total-memory"]') - .within(() => { - cy.contains('Total memory usage'); - cy.get('h2 > b').should('contain.text', '69.12 KB'); - cy.contains('(Sum across 20)'); - }); + cy.get('[data-test-subj="panel-total-memory"]').within(() => { + cy.contains('Total memory usage'); + cy.get('h2 > b').should('contain.text', '69.12 KB'); + cy.contains('(Sum across 20)'); + }); }); - it('verifies table headers and row content in memory table', () => { const expectedHeaders = [ '', 'Timestamp', 'Task ID', 'Index', - 'Node', + 'Coordinator node', 'Time elapsed', 'CPU usage', 'Memory usage', 'Search type', - 'Coordinator node', 'Status', 'Actions', ]; @@ -78,14 +71,13 @@ describe('Inflight Queries Dashboard', () => { 'Jun 05, 2025 @ 10:24:26 PM', 'node-A1B2C3D4E5:3600', 'top_queries-2025.06.06-11009', - 'Node 1', + 'node-A1B2C3D4E5', '7.99 s', '89.95 µs', '3.73 KB', 'QUERY_THEN_FETCH', - 'node-A1B2C3D4E5', 'Cancelled', - '', // Action column + '', ]; cy.get('.euiTable thead tr th').should(($headers) => { @@ -118,15 +110,11 @@ describe('Inflight Queries Dashboard', () => { }); it('selects all checkboxes and shows bulk cancel text', () => { - // Click the header checkbox to select all cy.get('.euiTable thead tr th input[type="checkbox"]').check({ force: true }); - - // Count the number of rows selected cy.get('.euiTable tbody tr input[type="checkbox"]:checked').then(($rows) => { const selectedCount = $rows.length; const expectedText = `Cancel ${selectedCount} queries`; - // Assert the bulk action banner shows the correct message cy.contains(expectedText).should('be.visible'); }); }); @@ -135,7 +123,7 @@ describe('Inflight Queries Dashboard', () => { cy.get('[data-test-subj="live-queries-autorefresh-toggle"]').as('toggle'); cy.get('[data-test-subj="live-queries-refresh-interval"]').as('dropdown'); - cy.get('@toggle').click(); // Turn it off + cy.get('@toggle').click(); cy.get('@toggle').should('have.attr', 'aria-checked', 'false'); cy.get('@dropdown').should('be.disabled'); }); @@ -181,38 +169,38 @@ describe('Inflight Queries Dashboard', () => { }); it('displays correct chart data for node and index charts', () => { - // Wait for charts to render - cy.get('[data-test-subj="vega-chart-node"]') - .invoke('attr', 'data-chart-values') - .then((json) => { - const data = JSON.parse(json); - expect(data).to.deep.equal([ - { label: 'Node 1', value: 2 }, - { label: 'Node 2', value: 1 }, - { label: 'Node 3', value: 1 }, - { label: 'Node 4', value: 1 }, - { label: 'Node 5', value: 1 }, - { label: 'Node 6', value: 1 }, - { label: 'Node 7', value: 1 }, - { label: 'Node 8', value: 1 }, - { label: 'Node 9', value: 1 }, - { label: 'others', value: 10 }, - ]); - expect(data).to.have.length(10); - }); - - cy.get('[data-test-subj="vega-chart-index"]') - .invoke('attr', 'data-chart-values') - .then((json) => { - const data = JSON.parse(json); - expect(data).to.deep.include({ label: 'top_queries-2025.06.06-11009', value: 19 }); - expect(data).to.deep.include({ label: 'opensearch', value: 1 }); - expect(data).to.have.length(2); - }); - + return cy + .get('[data-test-subj="vega-chart-node"]') + .invoke('attr', 'data-chart-values') + .then((json) => { + const data = JSON.parse(json); + expect(data).to.deep.equal([ + { label: 'node-A1B2C3D4E5', value: 2 }, + { label: '4W2VTHIgQY-oB7dSrYz4B', value: 1 }, + { label: 'node-X9Y8Z7W6', value: 1 }, + { label: 'node-P0Q9R8S7', value: 1 }, + { label: '4W2VTHIgQY-oB7dSrYz4', value: 1 }, + { label: 'node-M2O3P4Q5', value: 1 }, + { label: 'node-B2C3D4E5', value: 1 }, + { label: 'node-N2O3P4Q5', value: 1 }, + { label: '2VTHIgQY-oB7dSrYz4BQ', value: 1 }, + { label: 'others', value: 10 }, + ]); + expect(data).to.have.length(10); + }) + .then(() => { + return cy + .get('[data-test-subj="vega-chart-index"]') + .invoke('attr', 'data-chart-values') + .then((json) => { + const data = JSON.parse(json); + expect(data).to.deep.include({ label: 'top_queries-2025.06.06-11009', value: 19 }); + expect(data).to.deep.include({ label: 'opensearch', value: 1 }); + expect(data).to.have.length(2); + }); + }); }); - it('handles empty response state', () => { cy.intercept('GET', '**/api/live_queries', (req) => { req.reply({ @@ -255,17 +243,17 @@ describe('Inflight Queries Dashboard', () => { cy.get('[data-test-subj="vega-chart-node"]').should('not.exist'); cy.contains('p', 'Queries by Node') - .closest('.euiPanel') - .within(() => { - cy.contains('No data available').should('be.visible'); - }); + .closest('.euiPanel') + .within(() => { + cy.contains('No data available').should('be.visible'); + }); cy.get('[data-test-subj="vega-chart-index"]').should('not.exist'); cy.contains('p', 'Queries by Index') - .closest('.euiPanel') - .within(() => { - cy.contains('No data available').should('be.visible'); - }); + .closest('.euiPanel') + .within(() => { + cy.contains('No data available').should('be.visible'); + }); }); it('validates time unit conversions', () => { cy.intercept('GET', '**/api/live_queries', { @@ -275,7 +263,7 @@ describe('Inflight Queries Dashboard', () => { live_queries: [ { measurements: { - latency: { number: 500 }, // 500 nanoseconds = 0.5 microseconds + latency: { number: 500 }, cpu: { number: 100 }, memory: { number: 1000 }, }, @@ -292,7 +280,6 @@ describe('Inflight Queries Dashboard', () => { cy.get('h2').contains(/0\.50\s*µs/); }); - // Test milliseconds cy.intercept('GET', '**/api/live_queries', { statusCode: 200, body: { @@ -300,7 +287,7 @@ describe('Inflight Queries Dashboard', () => { live_queries: [ { measurements: { - latency: { number: 1000000 }, // 1 millisecond + latency: { number: 1000000 }, cpu: { number: 100000 }, memory: { number: 1000 }, }, @@ -319,7 +306,6 @@ describe('Inflight Queries Dashboard', () => { }); it('validates memory unit conversions', () => { - // Intercept and assert 2.00 KB cy.intercept('GET', '**/api/live_queries', { statusCode: 200, body: { @@ -333,7 +319,7 @@ describe('Inflight Queries Dashboard', () => { measurements: { latency: { number: 1 }, cpu: { number: 1 }, - memory: { number: 2048 }, // 2 KB + memory: { number: 2048 }, }, }, ], @@ -345,7 +331,6 @@ describe('Inflight Queries Dashboard', () => { cy.wait('@getKBData'); cy.contains('h2', /2\s*KB/).should('exist'); - // Intercept and assert 2.00 MB cy.intercept('GET', '**/api/live_queries', { statusCode: 200, body: { @@ -359,7 +344,7 @@ describe('Inflight Queries Dashboard', () => { measurements: { latency: { number: 1 }, cpu: { number: 1 }, - memory: { number: 2 * 1024 * 1024 }, // 2 MB + memory: { number: 2 * 1024 * 1024 }, }, }, ], @@ -367,7 +352,6 @@ describe('Inflight Queries Dashboard', () => { }, }).as('getMBData'); - // Click refresh button to trigger re-fetch cy.get('[data-test-subj="live-queries-refresh-button"]').click(); cy.wait('@getMBData'); cy.contains('h2', /2\s*MB/).should('exist'); @@ -398,25 +382,6 @@ describe('Inflight Queries Dashboard', () => { }); }); - it('handles error states', () => { - cy.intercept('GET', '**/api/live_queries', { - statusCode: 500, - body: { error: 'Internal Server Error' }, - }).as('getErrorResponse'); - - cy.navigateToLiveQueries(); - cy.wait('@getErrorResponse'); - - // Check that exactly 5 metric panels show '0' - cy.get('.euiPanel') - .filter((_, el) => el.querySelector('h2')?.textContent.trim() === '0') - .should('have.length', 5); - - // Check for "No data available" in charts - cy.contains('p', 'Queries by Node').closest('.euiPanel').contains('No data available'); - cy.contains('p', 'Queries by Index').closest('.euiPanel').contains('No data available'); - }); - it('filters table to show only "opensearch" index queries', () => { cy.contains('button', 'Index').click(); cy.contains('[role="option"]', 'opensearch').click(); diff --git a/public/pages/InflightQueries/InflightQueries.test.tsx b/public/pages/InflightQueries/InflightQueries.test.tsx index 9e1c29ab..19202112 100644 --- a/public/pages/InflightQueries/InflightQueries.test.tsx +++ b/public/pages/InflightQueries/InflightQueries.test.tsx @@ -145,12 +145,11 @@ describe('InflightQueries', () => { 'Jun 05, 2025 @ 10:24:26 PM', 'node-A1B2C3D4E5:3600', 'top_queries-2025.06.06-11009', - 'Node 1', + 'Coordinator nodenode-A1B2C3D4E5', 'Time elapsed7.99 s', '89.95 µs', '3.73 KB', 'QUERY_THEN_FETCH', - 'node-A1B2C3D4E5', 'Cancelled', ]; diff --git a/public/pages/InflightQueries/InflightQueries.tsx b/public/pages/InflightQueries/InflightQueries.tsx index c3dcf8a4..4e91e71f 100644 --- a/public/pages/InflightQueries/InflightQueries.tsx +++ b/public/pages/InflightQueries/InflightQueries.tsx @@ -30,6 +30,8 @@ export const InflightQueries = ({ core }: { core: CoreStart }) => { const TOP_N_DISPLAY_LIMIT = 9; const isFetching = useRef(false); const [query, setQuery] = useState(null); + const [nodeChartError, setNodeChartError] = useState(false); + const [indexChartError, setIndexChartError] = useState(false); const [nodeCounts, setNodeCounts] = useState({}); const [indexCounts, setIndexCounts] = useState({}); @@ -42,21 +44,12 @@ export const InflightQueries = ({ core }: { core: CoreStart }) => { const liveQueries = query?.response?.live_queries ?? []; - const nodeIdMap: Record = {}; - let nodeCounter = 1; - const convertTime = (unixTime: number) => { const date = new Date(unixTime); const loc = date.toDateString().split(' '); return `${loc[1]} ${loc[2]}, ${loc[3]} @ ${date.toLocaleTimeString('en-US')}`; }; - const getNodeLabel = (rawId: string): string => { - if (!nodeIdMap[rawId]) { - nodeIdMap[rawId] = `Node ${nodeCounter++}`; - } - return nodeIdMap[rawId]; - }; const fetchliveQueries = async () => { const retrievedQueries = await retrieveLiveQueries(core); @@ -71,7 +64,7 @@ export const InflightQueries = ({ core }: { core: CoreStart }) => { index: indexMatch ? indexMatch[1] : 'N/A', search_type: searchTypeMatch ? searchTypeMatch[1] : 'N/A', coordinator_node: q.node_id, - node_label: getNodeLabel(q.node_id), + node_label: q.node_id, }; }); @@ -79,10 +72,7 @@ export const InflightQueries = ({ core }: { core: CoreStart }) => { parsedQueries.forEach((liveQuery) => { const nodeId = liveQuery.node_id; - const nodeLabel = getNodeLabel(nodeId); // Get friendly label like "Node 1" - - tempNodeCount[nodeLabel] = (tempNodeCount[nodeLabel] || 0) + 1; - + tempNodeCount[nodeId] = (tempNodeCount[nodeId] || 0) + 1; const index = liveQuery.index; if (index && typeof index === 'string') { indexCount[index] = (indexCount[index] || 0) + 1; @@ -93,7 +83,7 @@ export const InflightQueries = ({ core }: { core: CoreStart }) => { const nodeCount: Record = {}; let othersCount = 0; sortedNodes.forEach(([nodeId, count], index) => { - if (index < TOP_N_DISPLAY_LIMIT ) nodeCount[nodeId] = count; + if (index < TOP_N_DISPLAY_LIMIT) nodeCount[nodeId] = count; else othersCount += count; }); if (othersCount > 0) nodeCount.others = othersCount; @@ -103,7 +93,7 @@ export const InflightQueries = ({ core }: { core: CoreStart }) => { const topIndexCount: Record = {}; let indexOthersCount = 0; sortedIndices.forEach(([indexName, count], i) => { - if (i < TOP_N_DISPLAY_LIMIT ) topIndexCount[indexName] = count; + if (i < TOP_N_DISPLAY_LIMIT) topIndexCount[indexName] = count; else indexOthersCount += count; }); if (indexOthersCount > 0) topIndexCount.others = indexOthersCount; @@ -115,14 +105,14 @@ export const InflightQueries = ({ core }: { core: CoreStart }) => { return new Promise((resolve, reject) => { const timeoutId = setTimeout(() => reject(new Error('Timed out')), ms); promise - .then((res) => { - clearTimeout(timeoutId); - resolve(res); - }) - .catch((err) => { - clearTimeout(timeoutId); - reject(err); - }); + .then((res) => { + clearTimeout(timeoutId); + resolve(res); + }) + .catch((err) => { + clearTimeout(timeoutId); + reject(err); + }); }); } @@ -152,7 +142,6 @@ export const InflightQueries = ({ core }: { core: CoreStart }) => { return () => clearInterval(interval); }, [autoRefreshEnabled, refreshInterval, core]); - const [pagination, setPagination] = useState({ pageIndex: 0 }); const formatTime = (seconds: number): string => { if (seconds < 1e-3) return `${(seconds * 1e6).toFixed(2)} µs`; @@ -200,9 +189,6 @@ export const InflightQueries = ({ core }: { core: CoreStart }) => { onSelectionChange: (selected: any[]) => setSelectedItems(selected), }; - - - const metrics = React.useMemo(() => { if (!query || !query.response?.live_queries?.length) return null; @@ -242,8 +228,8 @@ export const InflightQueries = ({ core }: { core: CoreStart }) => { const getChartData = (counts: Record, type: 'node' | 'index') => { return Object.entries(counts).map(([key, value]) => ({ - label: type === 'node' ? `${key}` : key, // Use descriptive label instead of 'a' - value, // Use 'value' instead of 'b' for clarity + label: type === 'node' ? `${key}` : key, + value, })); }; @@ -291,7 +277,6 @@ export const InflightQueries = ({ core }: { core: CoreStart }) => { }; }; - // Update the embed calls useEffect(() => { if (chartRefByNode.current) { embed( @@ -301,7 +286,12 @@ export const InflightQueries = ({ core }: { core: CoreStart }) => { data: { values: getChartData(nodeCounts, 'node') }, }, { actions: false, renderer: 'svg' } - ).catch(console.error); + ) + .then(() => setNodeChartError(false)) + .catch((error) => { + console.error('Node chart rendering failed:', error); + setNodeChartError(true); + }); } }, [nodeCounts, selectedChartIdByNode]); @@ -314,7 +304,12 @@ export const InflightQueries = ({ core }: { core: CoreStart }) => { data: { values: getChartData(indexCounts, 'index') }, }, { actions: false, renderer: 'svg' } - ).catch(console.error); + ) + .then(() => setIndexChartError(false)) + .catch((error) => { + console.error('Index chart rendering failed:', error); + setIndexChartError(true); + }); } }, [indexCounts, selectedChartIdByIndex]); @@ -334,7 +329,7 @@ export const InflightQueries = ({ core }: { core: CoreStart }) => {