diff --git a/package.json b/package.json index 6a807bd..8b50281 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,10 @@ "test:jest:update-snapshots": "yarn run test:jest -u", "lint": "node ../../scripts/eslint ." }, + "dependencies": { + "object-hash": "^3.0.0", + "plotly.js-dist": "^2.34.0" + }, "resolutions": { "@types/react": "^16.9.8", "**/@types/jest": "^29.3.1", diff --git a/public/pages/QueryDetails/Components/QuerySummary.tsx b/public/pages/QueryDetails/Components/QuerySummary.tsx new file mode 100644 index 0000000..7d6d5fb --- /dev/null +++ b/public/pages/QueryDetails/Components/QuerySummary.tsx @@ -0,0 +1,71 @@ +import React from 'react'; +import { EuiFlexGrid, EuiFlexItem, EuiHorizontalRule, EuiPanel, EuiText } from '@elastic/eui'; + +const QuerySummary = ({ query }: { query: any }) => { + 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')}`; + }; + return ( + + +

Summary

+
+ + + + +

Timestamp

+
+ {convertTime(query.timestamp)} +
+ + +

Latency

+
+ {`${query.latency} ms`} +
+ + +

CPU Usage

+
+ {`${query.cpu} ns`} +
+ + +

Memory

+
+ {`${query.memory} B`} +
+ + +

Indexes

+
+ {query.indices.toString()} +
+ + +

Search type

+
+ {query.search_type.replaceAll('_', ' ')} +
+ + +

Coordinator node ID

+
+ {query.node_id} +
+ + +

Total shards

+
+ {query.total_shards} +
+
+
+ ); +}; + +// eslint-disable-next-line import/no-default-export +export default QuerySummary; diff --git a/public/pages/QueryDetails/QueryDetails.tsx b/public/pages/QueryDetails/QueryDetails.tsx new file mode 100644 index 0000000..ce9e9d7 --- /dev/null +++ b/public/pages/QueryDetails/QueryDetails.tsx @@ -0,0 +1,148 @@ +import React, { useEffect } from 'react'; +import Plotly from 'plotly.js-dist'; +import { + EuiButton, + EuiCodeBlock, + EuiFlexGrid, + EuiFlexGroup, + EuiFlexItem, + EuiHorizontalRule, + EuiPanel, + EuiSpacer, + EuiText, + EuiTitle, +} from '@elastic/eui'; +import hash from 'object-hash'; +import { useParams, useHistory, useLocation } from 'react-router-dom'; +import { CoreStart } from '../../../../../src/core/public'; +import QuerySummary from './Components/QuerySummary'; +import { QUERY_INSIGHTS } from '../TopNQueries/TopNQueries'; + +const QueryDetails = ({ queries, core }: { queries: any; core: CoreStart }) => { + const { hashedQuery } = useParams<{ hashedQuery: string }>(); + const query = queries.find((q: any) => hash(q) === hashedQuery); + + 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 history = useHistory(); + const location = useLocation(); + + useEffect(() => { + core.chrome.setBreadcrumbs([ + { + text: 'Query insights', + href: QUERY_INSIGHTS, + onClick: (e) => { + e.preventDefault(); + history.push(QUERY_INSIGHTS); + }, + }, + { text: `Query details: ${convertTime(query.timestamp)}` }, + ]); + }, [core.chrome, history, location, query.timestamp]); + + useEffect(() => { + let x: number[] = Object.values(query.phase_latency_map); + if (x.length < 3) { + x = [0, 0, 0]; + } + const data = [ + { + x: x.reverse(), + y: ['Fetch ', 'Query ', 'Expand '], + type: 'bar', + orientation: 'h', + width: 0.5, + marker: { color: ['#F990C0', '#1BA9F5', '#7DE2D1'] }, + base: [x[2] + x[1], x[2], 0], + text: x.map((value) => `${value}ms`), + textposition: 'outside', + cliponaxis: false, + }, + ]; + const layout = { + autosize: true, + margin: { l: 80, r: 80, t: 25, b: 15, pad: 0 }, + autorange: true, + height: 120, + xaxis: { + side: 'top', + zeroline: false, + ticksuffix: 'ms', + autorangeoptions: { clipmin: 0 }, + tickfont: { color: '#535966' }, + linecolor: '#D4DAE5', + gridcolor: '#D4DAE5', + }, + yaxis: { linecolor: '#D4DAE5' }, + }; + const config = { responsive: true }; + Plotly.newPlot('latency', data, layout, config); + }, [query]); + + const queryString = JSON.stringify(JSON.parse(JSON.stringify(query.source)), null, 2); + const queryDisplay = `{\n "query": ${queryString ? queryString.replace(/\n/g, '\n ') : ''}\n}`; + + return ( +
+ +

Query details

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

Query

+
+
+ + + Open in search comparision + + +
+ + + + {queryDisplay} + +
+
+ + + +

Latency

+
+ +
+ + + + +
+ ); +}; + +// eslint-disable-next-line import/no-default-export +export default QueryDetails; diff --git a/public/pages/QueryInsights/QueryInsights.tsx b/public/pages/QueryInsights/QueryInsights.tsx index 4cdde26..1d476dd 100644 --- a/public/pages/QueryInsights/QueryInsights.tsx +++ b/public/pages/QueryInsights/QueryInsights.tsx @@ -1,6 +1,7 @@ import React, { useEffect, useState } from 'react'; -import { EuiBasicTableColumn, EuiInMemoryTable, EuiSuperDatePicker } from '@elastic/eui'; +import { EuiBasicTableColumn, EuiInMemoryTable, EuiLink, EuiSuperDatePicker } from '@elastic/eui'; import { useHistory, useLocation } from 'react-router-dom'; +import hash from 'object-hash'; import { CoreStart } from '../../../../../src/core/public'; import { QUERY_INSIGHTS } from '../TopNQueries/TopNQueries'; @@ -59,7 +60,13 @@ const QueryInsights = ({ // Make into flyout instead? name: 'Timestamp', render: (query: any) => { - return {convertTime(query.timestamp)}; + return ( + + history.push(`/query-details/${hash(query)}`)}> + {convertTime(query.timestamp)} + + + ); }, sortable: (query) => query.timestamp, truncateText: true, diff --git a/public/pages/TopNQueries/TopNQueries.tsx b/public/pages/TopNQueries/TopNQueries.tsx index 1a09ca3..b6a6af3 100644 --- a/public/pages/TopNQueries/TopNQueries.tsx +++ b/public/pages/TopNQueries/TopNQueries.tsx @@ -4,6 +4,7 @@ import { EuiTab, EuiTabs, EuiTitle, EuiSpacer } from '@elastic/eui'; import dateMath from '@elastic/datemath'; import QueryInsights from '../QueryInsights/QueryInsights'; import Configuration from '../Configuration/Configuration'; +import QueryDetails from '../QueryDetails/QueryDetails'; import { CoreStart } from '../../../../../src/core/public'; export const QUERY_INSIGHTS = '/queryInsights'; @@ -222,30 +223,6 @@ const TopNQueries = ({ core }: { core: CoreStart }) => { [core] ); - const retrieveConfigInfo = useCallback( - async ( - get: boolean, - enabled: boolean = false, - metric: string = '', - newTopN: string = '', - newWindowSize: string = '', - newTimeUnit: string = '' - ) => { - try { - setMetricSettings(metric, { - isEnabled: enabled, - currTopN: newTopN, - currWindowSize: newWindowSize, - currTimeUnit: newTimeUnit, - }); - } catch (error) { - // eslint-disable-next-line no-console - console.error('Failed to set settings:', error); - } - }, - [] - ); - const onTimeChange = ({ start, end }: { start: string; end: string }) => { const usedRange = recentlyUsedRanges.filter( (range) => !(range.start === start && range.end === end) @@ -270,6 +247,9 @@ const TopNQueries = ({ core }: { core: CoreStart }) => { return (
+ + +

Query insights - Top N queries