From f95ecd43200707fd01f42c28d96832ad5934f24c Mon Sep 17 00:00:00 2001 From: Chenyang Ji Date: Tue, 10 Sep 2024 16:12:04 -0700 Subject: [PATCH] Fix bugs in UI and refactor code Signed-off-by: Chenyang Ji --- common/constants.ts | 13 +++ package.json | 1 + .../__snapshots__/app.test.tsx.snap | 14 +-- public/pages/Configuration/Configuration.tsx | 2 +- .../QueryDetails/Components/QuerySummary.tsx | 88 ++++++++----------- public/pages/QueryDetails/QueryDetails.tsx | 2 +- public/pages/QueryInsights/QueryInsights.tsx | 68 ++++++++------ public/pages/TopNQueries/TopNQueries.tsx | 11 ++- types/types.ts | 47 ++++++++++ yarn.lock | 5 ++ 10 files changed, 163 insertions(+), 88 deletions(-) create mode 100644 common/constants.ts create mode 100644 types/types.ts diff --git a/common/constants.ts b/common/constants.ts new file mode 100644 index 0000000..2186516 --- /dev/null +++ b/common/constants.ts @@ -0,0 +1,13 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export const TIMESTAMP = 'Timestamp'; +export const LATENCY = 'Latency'; +export const CPU_TIME = 'CPU Time'; +export const MEMORY_USAGE = 'Memory Usage'; +export const INDICES = 'Indices'; +export const SEARCH_TYPE = 'Search type'; +export const NODE_ID = 'Coordinator node ID'; +export const TOTAL_SHARDS = 'Total shards'; diff --git a/package.json b/package.json index 43a0b8e..12b846f 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "@testing-library/dom": "^8.11.3", "@testing-library/user-event": "^14.4.3", "@types/react-dom": "^16.9.8", + "@types/object-hash": "^3.0.0", "@types/react-router-dom": "^5.3.2", "cypress": "9.5.4", "cypress-real-events": "1.7.6", diff --git a/public/components/__snapshots__/app.test.tsx.snap b/public/components/__snapshots__/app.test.tsx.snap index cbb837a..3571697 100644 --- a/public/components/__snapshots__/app.test.tsx.snap +++ b/public/components/__snapshots__/app.test.tsx.snap @@ -452,7 +452,7 @@ exports[` spec renders the component 1`] = ` aria-live="polite" aria-sort="none" class="euiTableHeaderCell" - data-test-subj="tableHeaderCell_latency_1" + data-test-subj="tableHeaderCell_measurements_1" role="columnheader" scope="col" > @@ -477,7 +477,7 @@ exports[` spec renders the component 1`] = ` aria-live="polite" aria-sort="none" class="euiTableHeaderCell" - data-test-subj="tableHeaderCell_cpu_2" + data-test-subj="tableHeaderCell_measurements_2" role="columnheader" scope="col" > @@ -491,9 +491,9 @@ exports[` spec renders the component 1`] = ` > - CPU usage + CPU Time @@ -502,7 +502,7 @@ exports[` spec renders the component 1`] = ` aria-live="polite" aria-sort="none" class="euiTableHeaderCell" - data-test-subj="tableHeaderCell_memory_3" + data-test-subj="tableHeaderCell_measurements_3" role="columnheader" scope="col" > @@ -516,9 +516,9 @@ exports[` spec renders the component 1`] = ` > - Memory + Memory Usage diff --git a/public/pages/Configuration/Configuration.tsx b/public/pages/Configuration/Configuration.tsx index 8978cf1..735fb6a 100644 --- a/public/pages/Configuration/Configuration.tsx +++ b/public/pages/Configuration/Configuration.tsx @@ -23,7 +23,7 @@ import { EuiTitle, } from '@elastic/eui'; import { useHistory, useLocation } from 'react-router-dom'; -import { CoreStart } from '../../../../../src/core/public'; +import { CoreStart } from 'opensearch-dashboards/public'; import { QUERY_INSIGHTS, MetricSettings } from '../TopNQueries/TopNQueries'; const Configuration = ({ diff --git a/public/pages/QueryDetails/Components/QuerySummary.tsx b/public/pages/QueryDetails/Components/QuerySummary.tsx index 7110599..cd10517 100644 --- a/public/pages/QueryDetails/Components/QuerySummary.tsx +++ b/public/pages/QueryDetails/Components/QuerySummary.tsx @@ -5,13 +5,36 @@ import React from 'react'; import { EuiFlexGrid, EuiFlexItem, EuiHorizontalRule, EuiPanel, EuiText } from '@elastic/eui'; +import { SearchQueryRecord } from '../../../../types/types'; +import { + CPU_TIME, + INDICES, + LATENCY, + MEMORY_USAGE, + NODE_ID, + SEARCH_TYPE, + TIMESTAMP, + TOTAL_SHARDS, +} from '../../../../common/constants'; -const QuerySummary = ({ query }: { query: any }) => { +// Panel component for displaying query detail values +const PanelItem = ({ label, value }: { label: string; value: string | number }) => ( + + +

{label}

+
+ {value} +
+); + +const QuerySummary = ({ query }: { query: SearchQueryRecord }) => { 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')}`; }; + // eslint-disable-next-line @typescript-eslint/naming-convention + const { timestamp, measurements, indices, search_type, node_id, total_shards } = query; return ( @@ -19,54 +42,21 @@ const QuerySummary = ({ query }: { query: any }) => { - - -

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} -
+ + + + + + + +
); diff --git a/public/pages/QueryDetails/QueryDetails.tsx b/public/pages/QueryDetails/QueryDetails.tsx index 6bc091f..2da53a2 100644 --- a/public/pages/QueryDetails/QueryDetails.tsx +++ b/public/pages/QueryDetails/QueryDetails.tsx @@ -19,7 +19,7 @@ import { } from '@elastic/eui'; import hash from 'object-hash'; import { useParams, useHistory, useLocation } from 'react-router-dom'; -import { CoreStart } from '../../../../../src/core/public'; +import { CoreStart } from 'opensearch-dashboards/public'; import QuerySummary from './Components/QuerySummary'; import { QUERY_INSIGHTS } from '../TopNQueries/TopNQueries'; diff --git a/public/pages/QueryInsights/QueryInsights.tsx b/public/pages/QueryInsights/QueryInsights.tsx index 67912de..e5b7e93 100644 --- a/public/pages/QueryInsights/QueryInsights.tsx +++ b/public/pages/QueryInsights/QueryInsights.tsx @@ -7,13 +7,22 @@ import React, { useEffect, useState } from 'react'; 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 { CoreStart } from 'opensearch-dashboards/public'; import { QUERY_INSIGHTS } from '../TopNQueries/TopNQueries'; +import { SearchQueryRecord } from '../../../types/types'; +import { + CPU_TIME, + INDICES, + LATENCY, + MEMORY_USAGE, + NODE_ID, + SEARCH_TYPE, + TIMESTAMP, + TOTAL_SHARDS, +} from '../../../common/constants'; const TIMESTAMP_FIELD = 'timestamp'; -const LATENCY_FIELD = 'latency'; -const CPU_FIELD = 'cpu'; -const MEMORY_FIELD = 'memory'; +const MEASUREMENTS_FIELD = 'measurements'; const INDICES_FIELD = 'indices'; const SEARCH_TYPE_FIELD = 'search_type'; const NODE_ID_FIELD = 'node_id'; @@ -29,7 +38,7 @@ const QueryInsights = ({ currEnd, core, }: { - queries: any[]; + queries: SearchQueryRecord[]; loading: boolean; onTimeChange: any; recentlyUsedRanges: any[]; @@ -63,7 +72,7 @@ const QueryInsights = ({ const cols: Array> = [ { // Make into flyout instead? - name: 'Timestamp', + name: TIMESTAMP, render: (query: any) => { return ( @@ -77,51 +86,58 @@ const QueryInsights = ({ truncateText: true, }, { - field: LATENCY_FIELD, - name: 'Latency', - render: (latency: number) => - typeof latency !== 'undefined' ? `${latency} ms` : `${METRIC_DEFAULT_MSG}`, + field: MEASUREMENTS_FIELD, + name: LATENCY, + render: (measurements: any) => { + const latencyValue = measurements?.latency?.number; + return latencyValue !== undefined ? `${latencyValue} ms` : METRIC_DEFAULT_MSG; + }, sortable: true, truncateText: true, }, { - field: CPU_FIELD, - name: 'CPU usage', - render: (cpu: number) => (typeof cpu !== 'undefined' ? `${cpu} ns` : `${METRIC_DEFAULT_MSG}`), + field: MEASUREMENTS_FIELD, + name: CPU_TIME, + render: (measurements: { cpu?: { number?: number } }) => { + const cpuValue = measurements?.cpu?.number; + return cpuValue !== undefined ? `${cpuValue / 1000000} ms` : METRIC_DEFAULT_MSG; + }, sortable: true, truncateText: true, }, { - field: MEMORY_FIELD, - name: 'Memory', - render: (memory: number) => - typeof memory !== 'undefined' ? `${memory} B` : `${METRIC_DEFAULT_MSG}`, + field: MEASUREMENTS_FIELD, + name: MEMORY_USAGE, + render: (measurements: { memory?: { number?: number } }) => { + const memoryValue = measurements?.memory?.number; + return memoryValue !== undefined ? `${memoryValue} B` : METRIC_DEFAULT_MSG; + }, sortable: true, truncateText: true, }, { field: INDICES_FIELD, - name: 'Indices', + name: INDICES, render: (indices: string[]) => Array.from(new Set(indices.flat())).join(', '), sortable: true, truncateText: true, }, { field: SEARCH_TYPE_FIELD, - name: 'Search type', + name: SEARCH_TYPE, render: (searchType: string) => searchType.replaceAll('_', ' '), sortable: true, truncateText: true, }, { field: NODE_ID_FIELD, - name: 'Coordinator node ID', + name: NODE_ID, sortable: true, truncateText: true, }, { field: TOTAL_SHARDS_FIELD, - name: 'Total shards', + name: TOTAL_SHARDS, sortable: true, truncateText: true, }, @@ -158,7 +174,7 @@ const QueryInsights = ({ { type: 'field_value_selection', field: INDICES_FIELD, - name: 'Indices', + name: INDICES, multiSelect: true, options: filterDuplicates( queries.map((query) => { @@ -174,7 +190,7 @@ const QueryInsights = ({ { type: 'field_value_selection', field: SEARCH_TYPE_FIELD, - name: 'Search type', + name: SEARCH_TYPE, multiSelect: false, options: filterDuplicates( queries.map((query) => ({ @@ -187,7 +203,7 @@ const QueryInsights = ({ { type: 'field_value_selection', field: NODE_ID_FIELD, - name: 'Coordinator node ID', + name: NODE_ID, multiSelect: true, options: filterDuplicates( queries.map((query) => ({ @@ -212,9 +228,7 @@ const QueryInsights = ({ executeQueryOptions={{ defaultFields: [ TIMESTAMP_FIELD, - LATENCY_FIELD, - CPU_FIELD, - MEMORY_FIELD, + MEASUREMENTS_FIELD, INDICES_FIELD, SEARCH_TYPE_FIELD, NODE_ID_FIELD, diff --git a/public/pages/TopNQueries/TopNQueries.tsx b/public/pages/TopNQueries/TopNQueries.tsx index cacec6e..d3e8d18 100644 --- a/public/pages/TopNQueries/TopNQueries.tsx +++ b/public/pages/TopNQueries/TopNQueries.tsx @@ -11,6 +11,7 @@ import { CoreStart } from 'opensearch-dashboards/public'; import QueryInsights from '../QueryInsights/QueryInsights'; import Configuration from '../Configuration/Configuration'; import QueryDetails from '../QueryDetails/QueryDetails'; +import { SearchQueryRecord } from "../../../types/types"; export const QUERY_INSIGHTS = '/queryInsights'; export const CONFIGURATION = '/configuration'; @@ -66,7 +67,7 @@ const TopNQueries = ({ core }: { core: CoreStart }) => { } }; - const [queries, setQueries] = useState([]); + const [queries, setQueries] = useState([]); const tabs: Array<{ id: string; name: string; route: string }> = [ { @@ -114,7 +115,11 @@ const TopNQueries = ({ core }: { core: CoreStart }) => { }; const fetchMetric = async (endpoint: string) => { try { - const response = await core.http.get(endpoint, params); + // TODO: #13 refactor the interface definitions for requests and responses + const response: { response: { top_queries: SearchQueryRecord[] } } = await core.http.get( + endpoint, + params + ); return { response: { top_queries: Array.isArray(response?.response?.top_queries) @@ -142,7 +147,7 @@ const TopNQueries = ({ core }: { core: CoreStart }) => { ...respCpu.response.top_queries, ...respMemory.response.top_queries, ]; - const noDuplicates = Array.from( + const noDuplicates: SearchQueryRecord[] = Array.from( new Set(newQueries.map((item) => JSON.stringify(item))) ).map((item) => JSON.parse(item)); setQueries(noDuplicates); diff --git a/types/types.ts b/types/types.ts new file mode 100644 index 0000000..01f2f59 --- /dev/null +++ b/types/types.ts @@ -0,0 +1,47 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { ISearchSource } from 'src/plugins/data/public'; + +export interface SearchQueryRecord { + timestamp: number; + measurements: { + latency?: Measurement; + cpu?: Measurement; + memory?: Measurement; + }; + total_shards: number; + node_id: string; + source: ISearchSource; + labels: Record; + search_type: string; + indices: string[]; + phase_latency_map: PhaseLatencyMap; + task_resource_usages: Task[]; +} + +export interface Measurement { + number: number; + count: number; + aggregationType: string; +} + +export interface PhaseLatencyMap { + expand?: number; + query?: number; + fetch?: number; +} + +export interface TaskResourceUsage { + cpu_time_in_nanos: number; + memory_in_bytes: number; +} +export interface Task { + action: string; + taskId: number; + parentTaskId: number; + nodeId: string; + taskResourceUsage: TaskResourceUsage; +} diff --git a/yarn.lock b/yarn.lock index 6955c37..c5c5f59 100644 --- a/yarn.lock +++ b/yarn.lock @@ -841,6 +841,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.63.tgz#1788fa8da838dbb5f9ea994b834278205db6ca2b" integrity sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ== +"@types/object-hash@^3.0.0": + version "3.0.6" + resolved "https://registry.yarnpkg.com/@types/object-hash/-/object-hash-3.0.6.tgz#25c052428199d374ef723b7b0ed44b5bfe1b3029" + integrity sha512-fOBV8C1FIu2ELinoILQ+ApxcUKz4ngq+IWUYrxSGjXzzjUALijilampwkMgEtJ+h2njAW3pi853QpzNVCHB73w== + "@types/parse-json@^4.0.0": version "4.0.2" resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.2.tgz#5950e50960793055845e956c427fc2b0d70c5239"