From 8b82358dbdded04802096fb2753f1fc3388e84eb Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 28 May 2025 16:19:27 +0000 Subject: [PATCH] [Backport 2.x] [BUG FIX] Enable Correct Sorting for Metrics in Query Insights Dashboard + dependent PRs (#200) * Feat: dynamically display columns (#103) Signed-off-by: David Zane * [Improvement] Improved Cypress test that validates the dynamic column (#168) Signed-off-by: David Zane * [BUG FIX] Enable Correct Sorting for Metrics in Query Insights Dashboard (#173) Signed-off-by: David Zane --------- Signed-off-by: David Zane Co-authored-by: Kishore Kumaar Natarajan <30365405+KishoreKicha14@users.noreply.github.com> (cherry picked from commit 7a211315645cdfe4271546b795d1b98450865331) Signed-off-by: github-actions[bot] --- cypress/e2e/1_top_queries.cy.js | 164 +++++++-- cypress/fixtures/stub_top_queries.json | 140 ++++++++ cypress/support/commands.js | 14 + .../__snapshots__/app.test.tsx.snap | 8 +- .../QueryInsights/QueryInsights.test.tsx | 113 +++++- public/pages/QueryInsights/QueryInsights.tsx | 243 +++++++++---- .../__snapshots__/QueryInsights.test.tsx.snap | 329 +----------------- 7 files changed, 602 insertions(+), 409 deletions(-) create mode 100644 cypress/fixtures/stub_top_queries.json diff --git a/cypress/e2e/1_top_queries.cy.js b/cypress/e2e/1_top_queries.cy.js index 99bf6418..1a8cd1b0 100644 --- a/cypress/e2e/1_top_queries.cy.js +++ b/cypress/e2e/1_top_queries.cy.js @@ -10,15 +10,16 @@ import { METRICS } from '../support/constants'; const indexName = 'sample_index'; /** - Helper function to clean up the environment: - - Deletes the test index. - - Disables the top queries features. + Helper function to clean up the environment: + - Deletes the test index. + - Disables the top queries features. */ const clearAll = () => { cy.deleteIndexByName(indexName); cy.disableTopQueries(METRICS.LATENCY); cy.disableTopQueries(METRICS.CPU); cy.disableTopQueries(METRICS.MEMORY); + cy.disableGrouping(); }; describe('Query Insights Dashboard', () => { @@ -81,22 +82,6 @@ describe('Query Insights Dashboard', () => { }); }); - /** - * Validate pagination works as expected - */ - it('should paginate the query table', () => { - for (let i = 0; i < 20; i++) { - cy.searchOnIndex(indexName); - } - // waiting for the query insights queue to drain - cy.wait(10000); - cy.reload(); - cy.get('.euiPagination').should('be.visible'); - cy.get('.euiPagination__item').contains('2').click(); - // Verify rows on the second page - cy.get('.euiTableRow').should('have.length.greaterThan', 0); - }); - it('should switch between tabs', () => { // Click Configuration tab cy.getElementByText('.euiTab', 'Configuration').click({ force: true }); @@ -119,16 +104,151 @@ describe('Query Insights Dashboard', () => { cy.get('.euiFieldSearch').type('random_string'); cy.get('.euiTableRow').should('have.length.greaterThan', 0); cy.get('.euiFieldSearch').clear(); - cy.get('.euiTableRow').should('have.length.greaterThan', 0); // Validate reset + cy.get('.euiTableRow').should('have.length.greaterThan', 0); }); it('should display a message when no top queries are found', () => { - clearAll(); // disable top n queries - // waiting for the query insights queue to drain + clearAll(); cy.wait(10000); cy.reload(); cy.contains('No items found'); }); + it('should paginate the query table', () => { + for (let i = 0; i < 20; i++) { + cy.searchOnIndex(indexName); + } + cy.wait(10000); + cy.reload(); + cy.get('.euiPagination').should('be.visible'); + cy.get('.euiPagination__item').contains('2').click(); + // Verify rows on the second page + cy.get('.euiTableRow').should('have.length.greaterThan', 0); + }); after(() => clearAll()); }); + +describe('Query Insights Dashboard - Dynamic Columns with Stubbed Top Queries', () => { + beforeEach(() => { + cy.fixture('stub_top_queries.json').then((stubResponse) => { + cy.intercept('GET', '**/api/top_queries/*', { + statusCode: 200, + body: stubResponse, + }).as('getTopQueries'); + }); + + cy.navigateToOverview(); + cy.wait(1000); + cy.wait('@getTopQueries'); + }); + + const testMetricSorting = (columnLabel, columnIndex) => { + cy.get('.euiTableHeaderCell').contains(columnLabel).click(); + cy.wait(1000); + + cy.get('.euiTableRow').then(($rows) => { + const values = [...$rows].map(($row) => { + const rawText = Cypress.$($row).find('td').eq(columnIndex).text().trim(); + return parseFloat(rawText.replace(/[^\d.]/g, '')); // remove 'ms'/'B' + }); + const sortedAsc = [...values].sort((a, b) => a - b); + expect(values).to.deep.equal(sortedAsc); + }); + + cy.get('.euiTableHeaderCell').contains(columnLabel).click(); + cy.wait(1000); + + cy.get('.euiTableRow').then(($rows) => { + const values = [...$rows].map(($row) => { + const rawText = Cypress.$($row).find('td').eq(columnIndex).text().trim(); + return parseFloat(rawText.replace(/[^\d.]/g, '')); + }); + const sortedDesc = [...values].sort((a, b) => b - a); + expect(values).to.deep.equal(sortedDesc); + }); + }; + + it('should render only individual query-related headers when NONE filter is applied', () => { + cy.wait(1000); + cy.get('.euiFilterButton').contains('Type').click(); + cy.get('.euiFilterSelectItem').contains('query').click(); + cy.wait(1000); + + const expectedHeaders = [ + 'Id', + 'Type', + 'Timestamp', + 'Latency', + 'CPU Time', + 'Memory Usage', + 'Indices', + 'Search Type', + 'Coordinator Node ID', + 'Total Shards', + ]; + + cy.get('.euiTableHeaderCell').should('have.length', expectedHeaders.length); + + cy.get('.euiTableHeaderCell').should(($headers) => { + const actualHeaders = $headers.map((index, el) => Cypress.$(el).text().trim()).get(); + expect(actualHeaders).to.deep.equal(expectedHeaders); + }); + testMetricSorting('Timestamp', 2); + testMetricSorting('Latency', 3); + testMetricSorting('CPU Time', 4); + testMetricSorting('Memory Usage', 5); + }); + + it('should render only group-related headers in the correct order when SIMILARITY filter is applied', () => { + cy.get('.euiFilterButton').contains('Type').click(); + cy.get('.euiFilterSelectItem').contains('group').click(); + cy.wait(1000); + + const expectedHeaders = [ + 'Id', + 'Type', + 'Query Count', + 'Average Latency', + 'Average CPU Time', + 'Average Memory Usage', + ]; + + cy.get('.euiTableHeaderCell').should(($headers) => { + const actualHeaders = $headers.map((index, el) => Cypress.$(el).text().trim()).get(); + expect(actualHeaders).to.deep.equal(expectedHeaders); + }); + testMetricSorting('Query Count', 2); + testMetricSorting('Average Latency', 3); + testMetricSorting('Average CPU Time', 4); + testMetricSorting('Average Memory Usage', 5); + }); + it('should display both query and group data with proper headers when both are selected', () => { + cy.get('.euiFilterButton').contains('Type').click(); + cy.get('.euiFilterSelectItem').contains('query').click(); + cy.get('.euiFilterSelectItem').contains('group').click(); + cy.wait(1000); + + const expectedGroupHeaders = [ + 'Id', + 'Type', + 'Query Count', + 'Timestamp', + 'Avg Latency / Latency', + 'Avg CPU Time / CPU Time', + 'Avg Memory Usage / Memory Usage', + 'Indices', + 'Search Type', + 'Coordinator Node ID', + 'Total Shards', + ]; + cy.get('.euiTableHeaderCell').should(($headers) => { + const actualHeaders = $headers.map((index, el) => Cypress.$(el).text().trim()).get(); + expect(actualHeaders).to.deep.equal(expectedGroupHeaders); + }); + testMetricSorting('Query Count', 2); + testMetricSorting('Timestamp', 3); + testMetricSorting('Avg Latency / Latency', 4); + testMetricSorting('Avg CPU Time / CPU Time', 5); + testMetricSorting('Avg Memory Usage / Memory Usage', 6); + }); +}); diff --git a/cypress/fixtures/stub_top_queries.json b/cypress/fixtures/stub_top_queries.json new file mode 100644 index 00000000..e92c9074 --- /dev/null +++ b/cypress/fixtures/stub_top_queries.json @@ -0,0 +1,140 @@ +{ + "ok": true, + "response": { + "top_queries": [ + { + "timestamp": 1713934974000, + "id": "a2e1c822-3e3c-4d1b-adb2-9f73af094b43", + "search_type": "query_then_fetch", + "indices": ["my-index"], + "node_id": "UYKFun8PSAeJvkkt9cWf0w", + "group_by": "NONE", + "total_shards": 1, + "labels": { + "X-Opaque-Id": "90eb5c3b-8448-4af3-84ce-a941eee9ed5f" + }, + "measurements": { + "cpu": { "number": 1340000, "count": 1, "aggregationType": "NONE" }, + "latency": { "number": 22, "count": 1, "aggregationType": "NONE" }, + "memory": { "number": 204800, "count": 1, "aggregationType": "NONE" } + } + }, + { + "timestamp": 1713934989000, + "id": "130a5d36-615e-43e8-ad99-e1b90d527f44", + "search_type": "query_then_fetch", + "indices": [".kibana"], + "node_id": "UYKFun8PSAeJvkkt9cWf0w", + "group_by": "SIMILARITY", + "total_shards": 1, + "labels": {}, + "query_group_hashcode": "2219a4bb71bd0d262e6d0f5504b88537", + "measurements": { + "memory": { "number": 58000, "count": 1, "aggregationType": "AVERAGE" }, + "cpu": { "number": 1780000, "count": 1, "aggregationType": "AVERAGE" }, + "latency": { "number": 10, "count": 1, "aggregationType": "AVERAGE" } + } + }, + { + "timestamp": 1713935004000, + "id": "a2e1c822-3e3c-4d1b-adb2-9f73af094b43", + "search_type": "query_then_fetch", + "indices": ["my-index"], + "node_id": "UYKFun8PSAeJvkkt9cWf0w", + "group_by": "NONE", + "total_shards": 1, + "labels": { + "X-Opaque-Id": "90eb5c3b-8448-4af3-84ce-a941eee9ed5f" + }, + "measurements": { + "memory": { "number": 170320, "count": 1, "aggregationType": "NONE" }, + "cpu": { "number": 2100000, "count": 1, "aggregationType": "NONE" }, + "latency": { "number": 13, "count": 1, "aggregationType": "NONE" } + } + }, + { + "timestamp": 1713935019000, + "id": "7cd4c7f1-3803-4c5e-a41c-258e04f96f78", + "search_type": "query_then_fetch", + "indices": ["my-index"], + "node_id": "UYKFun8PSAeJvkkt9cWf0w", + "group_by": "NONE", + "total_shards": 1, + "labels": { + "X-Opaque-Id": "8a936346-8d19-409c-9fe6-8b890eca1f7c" + }, + "measurements": { + "cpu": { "number": 990000, "count": 1, "aggregationType": "NONE" }, + "latency": { "number": 18, "count": 1, "aggregationType": "NONE" }, + "memory": { "number": 81200, "count": 1, "aggregationType": "NONE" } + } + }, + { + "timestamp": 1713935033000, + "id": "76f5e51f-33f6-480c-8b20-8003abb93d19", + "search_type": "query_then_fetch", + "indices": [".kibana"], + "node_id": "UYKFun8PSAeJvkkt9cWf0w", + "group_by": "SIMILARITY", + "total_shards": 1, + "labels": { + "X-Opaque-Id": "660baeb1-077b-4884-8bae-890cfe30e776" + }, + "query_group_hashcode": "a336f9580d5d980f7403f6d179f454eb", + "measurements": { + "memory": { "number": 133600, "count": 1, "aggregationType": "AVERAGE" }, + "cpu": { "number": 990000, "count": 1, "aggregationType": "AVERAGE" }, + "latency": { "number": 9, "count": 1, "aggregationType": "AVERAGE" } + } + }, + { + "timestamp": 1713935048000, + "id": "37d633a7-20e6-41a1-96e9-cd4806511dbf", + "search_type": "query_then_fetch", + "indices": [".kibana"], + "node_id": "UYKFun8PSAeJvkkt9cWf0w", + "group_by": "SIMILARITY", + "total_shards": 1, + "labels": {}, + "query_group_hashcode": "7cef9a399c117a0278025a89e943eebc", + "measurements": { + "memory": { "number": 640320, "count": 3, "aggregationType": "AVERAGE" }, + "cpu": { "number": 3190000, "count": 3, "aggregationType": "AVERAGE" }, + "latency": { "number": 6, "count": 1, "aggregationType": "NONE" } + } + }, + { + "timestamp": 1713935062000, + "id": "9982b7fc-0339-47d8-b77f-8de1bda76b72", + "search_type": "query_then_fetch", + "indices": [".kibana"], + "node_id": "UYKFun8PSAeJvkkt9cWf0w", + "group_by": "SIMILARITY", + "total_shards": 1, + "labels": {}, + "query_group_hashcode": "7cef9a399c117a0278025a89e943eebc", + "measurements": { + "memory": { "number": 812400, "count": 4, "aggregationType": "AVERAGE" }, + "cpu": { "number": 2280000, "count": 4, "aggregationType": "AVERAGE" }, + "latency": { "number": 4, "count": 4, "aggregationType": "AVERAGE" } + } + }, + { + "timestamp": 1713935078000, + "id": "d8dccf54-8dcb-4411-9fd6-977844be8fb3", + "search_type": "query_then_fetch", + "indices": [".kibana"], + "node_id": "UYKFun8PSAeJvkkt9cWf0w", + "group_by": "SIMILARITY", + "total_shards": 1, + "labels": {}, + "query_group_hashcode": "2219a4bb71bd0d262e6d0f5504b88537", + "measurements": { + "memory": { "number": 59000, "count": 1, "aggregationType": "AVERAGE" }, + "cpu": { "number": 1720000, "count": 1, "aggregationType": "AVERAGE" }, + "latency": { "number": 11, "count": 1, "aggregationType": "NONE" } + } + } + ] + } +} diff --git a/cypress/support/commands.js b/cypress/support/commands.js index e67de0ed..0067fd0b 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -138,6 +138,20 @@ Cypress.Commands.add('disableGrouping', () => { failOnStatusCode: true, }); }); +Cypress.Commands.add('setWindowSize', (size = '1m') => { + cy.request({ + method: 'PUT', + url: `${Cypress.env('openSearchUrl')}/_cluster/settings`, + body: { + persistent: { + 'search.insights.top_queries.latency.window_size': size, + 'search.insights.top_queries.cpu.window_size': size, + 'search.insights.top_queries.memory.window_size': size, + }, + }, + failOnStatusCode: true, + }); +}); Cypress.Commands.add('createIndexByName', (indexName, body = {}) => { cy.request('POST', `${Cypress.env('openSearchUrl')}/${indexName}/_doc`, body); diff --git a/public/components/__snapshots__/app.test.tsx.snap b/public/components/__snapshots__/app.test.tsx.snap index 25915bf5..60a61c8f 100644 --- a/public/components/__snapshots__/app.test.tsx.snap +++ b/public/components/__snapshots__/app.test.tsx.snap @@ -520,7 +520,7 @@ exports[` spec renders the component 1`] = ` aria-live="polite" aria-sort="none" class="euiTableHeaderCell" - data-test-subj="tableHeaderCell_measurements_2" + data-test-subj="tableHeaderCell_Query Count_2" role="columnheader" scope="col" > @@ -570,7 +570,7 @@ exports[` spec renders the component 1`] = ` aria-live="polite" aria-sort="none" class="euiTableHeaderCell" - data-test-subj="tableHeaderCell_measurements.latency_4" + data-test-subj="tableHeaderCell_Latency_4" role="columnheader" scope="col" > @@ -595,7 +595,7 @@ exports[` spec renders the component 1`] = ` aria-live="polite" aria-sort="none" class="euiTableHeaderCell" - data-test-subj="tableHeaderCell_measurements.cpu_5" + data-test-subj="tableHeaderCell_CPU Time_5" role="columnheader" scope="col" > @@ -620,7 +620,7 @@ exports[` spec renders the component 1`] = ` aria-live="polite" aria-sort="none" class="euiTableHeaderCell" - data-test-subj="tableHeaderCell_measurements.memory_6" + data-test-subj="tableHeaderCell_Memory Usage_6" role="columnheader" scope="col" > diff --git a/public/pages/QueryInsights/QueryInsights.test.tsx b/public/pages/QueryInsights/QueryInsights.test.tsx index 8552a775..49d6a7d4 100644 --- a/public/pages/QueryInsights/QueryInsights.test.tsx +++ b/public/pages/QueryInsights/QueryInsights.test.tsx @@ -4,7 +4,7 @@ */ import React from 'react'; -import { render, screen, fireEvent } from '@testing-library/react'; +import { waitFor, render, screen, fireEvent } from '@testing-library/react'; import '@testing-library/jest-dom/extend-expect'; import QueryInsights from './QueryInsights'; import { MemoryRouter } from 'react-router-dom'; @@ -61,7 +61,7 @@ describe('QueryInsights Component', () => { }); afterAll(() => { - jest.resetAllMocks(); // Reset all mocks after all tests + jest.resetAllMocks(); }); beforeEach(() => { @@ -87,11 +87,116 @@ describe('QueryInsights Component', () => { it('triggers onTimeChange when the date picker changes', () => { renderQueryInsights(); - // Find the date picker update button const updateButton = screen.getByRole('button', { name: /Refresh/i }); fireEvent.click(updateButton); - // Verify the onTimeChange callback is triggered expect(mockOnTimeChange).toHaveBeenCalled(); }); + + it('renders the expected column headers in the correct sequence for default', async () => { + renderQueryInsights(); + + await waitFor(() => expect(screen.getByRole('table')).toBeInTheDocument()); + + const headers = await waitFor(() => screen.getAllByRole('columnheader', { hidden: false })); + + const renderedHeaders = headers.map((h) => h.textContent?.trim()); + + const expectedHeaders = [ + 'Id', + 'Type', + 'Query Count', + 'Timestamp', + 'Avg Latency / Latency', + 'Avg CPU Time / CPU Time', + 'Avg Memory Usage / Memory Usage', + 'Indices', + 'Search Type', + 'Coordinator Node ID', + 'Total Shards', + ]; + + expect(renderedHeaders).toEqual(expectedHeaders); + }); + + it('renders correct columns when SIMILARITY filter is applied', async () => { + renderQueryInsights(); + + const typeFilterButton = screen + .getAllByRole('button') + .find((btn) => btn.textContent?.trim() === 'Type'); + fireEvent.click(typeFilterButton!); + await waitFor(() => expect(screen.getByRole('dialog')).toBeInTheDocument()); + const groupOption = await screen.findByText(/group/i); // Use this if options are plain text + fireEvent.click(groupOption); + const headers = await screen.findAllByRole('columnheader', { hidden: true }); + const headerTexts = headers.map((h) => h.textContent?.trim()); + const expectedHeaders = [ + 'Id', + 'Type', + 'Query Count', + 'Average Latency', + 'Average CPU Time', + 'Average Memory Usage', + ]; + + expect(headerTexts).toEqual(expectedHeaders); + }); + + it('renders only individual query-related column headers when NONE filter is applied', async () => { + renderQueryInsights(); + + const typeFilterButton = screen + .getAllByRole('button') + .find((btn) => btn.textContent?.trim() === 'Type'); + fireEvent.click(typeFilterButton!); + await waitFor(() => expect(screen.getByRole('dialog')).toBeInTheDocument()); + fireEvent.click(screen.getByRole('option', { name: /query/i })); + const headers = await screen.findAllByRole('columnheader', { hidden: true }); + const headerTexts = headers.map((h) => h.textContent?.trim()); + const expectedHeaders = [ + 'Id', + 'Type', + 'Timestamp', + 'Latency', + 'CPU Time', + 'Memory Usage', + 'Indices', + 'Search Type', + 'Coordinator Node ID', + 'Total Shards', + ]; + + expect(headerTexts).toEqual(expectedHeaders); + }); + + it('renders column headers when both NONE and SIMILARITY filter is applied', async () => { + renderQueryInsights(); + + const typeFilterButton = screen + .getAllByRole('button') + .find((btn) => btn.textContent?.trim() === 'Type'); + fireEvent.click(typeFilterButton!); + await waitFor(() => expect(screen.getByRole('dialog')).toBeInTheDocument()); + fireEvent.click(screen.getByRole('option', { name: /query/i })); + await waitFor(() => expect(screen.getByRole('dialog')).toBeInTheDocument()); + fireEvent.click(screen.getByRole('option', { name: /group/i })); + const headers = await screen.findAllByRole('columnheader', { hidden: true }); + const headerTexts = headers.map((h) => h.textContent?.trim()); + const expectedHeaders = [ + 'Id', + 'Type', + 'Query Count', + 'Timestamp', + 'Avg Latency / Latency', + 'Avg CPU Time / CPU Time', + 'Avg Memory Usage / Memory Usage', + 'Indices', + 'Search Type', + 'Coordinator Node ID', + 'Total Shards', + ]; + + expect(headerTexts).toEqual(expectedHeaders); + }); }); diff --git a/public/pages/QueryInsights/QueryInsights.tsx b/public/pages/QueryInsights/QueryInsights.tsx index 9fb3a3e5..136b7583 100644 --- a/public/pages/QueryInsights/QueryInsights.tsx +++ b/public/pages/QueryInsights/QueryInsights.tsx @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import React, { useContext, useEffect, useState } from 'react'; +import React, { useMemo, useContext, useEffect, useState } from 'react'; import { EuiBasicTableColumn, EuiInMemoryTable, EuiLink, EuiSuperDatePicker } from '@elastic/eui'; import { useHistory, useLocation } from 'react-router-dom'; import { AppMountParameters, CoreStart } from 'opensearch-dashboards/public'; @@ -68,6 +68,7 @@ const QueryInsights = ({ const history = useHistory(); const location = useLocation(); const [pagination, setPagination] = useState({ pageIndex: 0 }); + const [selectedFilter, setSelectedFilter] = useState([]); const from = parseDateString(currStart); const to = parseDateString(currEnd); @@ -91,8 +92,22 @@ const QueryInsights = ({ const loc = date.toDateString().split(' '); return `${loc[1]} ${loc[2]}, ${loc[3]} @ ${date.toLocaleTimeString('en-US')}`; }; + useEffect(() => { + if (queries.length === 0) return; + + const allAreGroups = queries.every((query) => query.group_by === 'SIMILARITY'); + const allAreQueries = queries.every((query) => query.group_by === 'NONE'); + + if (allAreGroups) { + setSelectedFilter(['SIMILARITY']); + } else if (allAreQueries) { + setSelectedFilter(['NONE']); + } else { + setSelectedFilter(['SIMILARITY', 'NONE']); + } + }, [queries]); - const cols: Array> = [ + const baseColumns: Array> = [ { name: ID, render: (query: SearchQueryRecord) => { @@ -137,26 +152,98 @@ const QueryInsights = ({ sortable: (query) => query.group_by || 'query', truncateText: true, }, + ]; + const querycountColumn: Array> = [ { - field: MEASUREMENTS_FIELD, name: QUERY_COUNT, - render: (measurements: SearchQueryRecord['measurements']) => - `${ - measurements?.latency?.count || - measurements?.cpu?.count || - measurements?.memory?.count || + render: (query: SearchQueryRecord) => { + const count = + query.measurements?.latency?.count || + query.measurements?.cpu?.count || + query.measurements?.memory?.count || + 1; + return `${count}`; + }, + sortable: (query: SearchQueryRecord) => { + return ( + query.measurements?.latency?.count || + query.measurements?.cpu?.count || + query.measurements?.memory?.count || 1 - }`, - sortable: (measurements: SearchQueryRecord['measurements']) => { - return Number( - measurements?.latency?.count || - measurements?.cpu?.count || - measurements?.memory?.count || - 1 ); }, truncateText: true, }, + ]; + + // @ts-ignore + const metricColumns: Array> = [ + { + name: + selectedFilter.includes('SIMILARITY') && selectedFilter.includes('NONE') + ? `Avg ${LATENCY} / ${LATENCY}` + : selectedFilter.includes('SIMILARITY') + ? `Average ${LATENCY}` + : `${LATENCY}`, + render: (query: SearchQueryRecord) => + calculateMetric( + query.measurements?.latency?.number, + query.measurements?.latency?.count, + 'ms', + 1, + METRIC_DEFAULT_MSG + ), + sortable: (query) => + calculateMetricNumber( + query.measurements?.latency?.number, + query.measurements?.latency?.count + ), + truncateText: true, + }, + { + name: + selectedFilter.includes('SIMILARITY') && selectedFilter.includes('NONE') + ? `Avg ${CPU_TIME} / ${CPU_TIME}` + : selectedFilter.includes('SIMILARITY') + ? `Average ${CPU_TIME}` + : `${CPU_TIME}`, + render: (query: SearchQueryRecord) => + calculateMetric( + query.measurements?.cpu?.number, + query.measurements?.cpu?.count, + 'ms', + 1000000, + METRIC_DEFAULT_MSG + ), + sortable: (query) => + calculateMetricNumber(query.measurements?.cpu?.number, query.measurements?.cpu?.count), + truncateText: true, + }, + { + name: + selectedFilter.includes('SIMILARITY') && selectedFilter.includes('NONE') + ? `Avg ${MEMORY_USAGE} / ${MEMORY_USAGE}` + : selectedFilter.includes('SIMILARITY') + ? `Average ${MEMORY_USAGE}` + : MEMORY_USAGE, + render: (query: SearchQueryRecord) => + calculateMetric( + query.measurements?.memory?.number, + query.measurements?.memory?.count, + 'B', + 1, + METRIC_DEFAULT_MSG + ), + sortable: (query) => + calculateMetricNumber( + query.measurements?.memory?.number, + query.measurements?.memory?.count + ), + truncateText: true, + }, + ]; + + const timestampColumn: Array> = [ { // Make into flyout instead? name: TIMESTAMP, @@ -176,54 +263,8 @@ const QueryInsights = ({ sortable: (query) => query.timestamp, truncateText: true, }, - { - field: LATENCY_FIELD, - name: LATENCY, - render: (latency: SearchQueryRecord['measurements']['latency']) => { - return calculateMetric(latency?.number, latency?.count, 'ms', 1, METRIC_DEFAULT_MSG); - }, - sortable: (query: SearchQueryRecord) => { - return calculateMetricNumber( - query.measurements?.latency?.number, - query.measurements?.latency?.count - ); - }, - truncateText: true, - }, - { - field: CPU_FIELD, - name: CPU_TIME, - render: (cpu: SearchQueryRecord['measurements']['cpu']) => { - return calculateMetric( - cpu?.number, - cpu?.count, - 'ms', - 1000000, // Divide by 1,000,000 for CPU time - METRIC_DEFAULT_MSG - ); - }, - sortable: (query: SearchQueryRecord) => { - return calculateMetricNumber( - query.measurements?.cpu?.number, - query.measurements?.cpu?.count - ); - }, - truncateText: true, - }, - { - field: MEMORY_FIELD, - name: MEMORY_USAGE, - render: (memory: SearchQueryRecord['measurements']['memory']) => { - return calculateMetric(memory?.number, memory?.count, 'B', 1, METRIC_DEFAULT_MSG); - }, - sortable: (query: SearchQueryRecord) => { - return calculateMetricNumber( - query.measurements?.memory?.number, - query.measurements?.memory?.count - ); - }, - truncateText: true, - }, + ]; + const QueryTypeSpecificColumns: Array> = [ { field: INDICES_FIELD, name: INDICES, @@ -265,6 +306,76 @@ const QueryInsights = ({ truncateText: true, }, ]; + const filteredQueries = useMemo(() => { + return queries.filter( + (query) => selectedFilter.length === 0 || selectedFilter.includes(query.group_by) + ); + }, [queries, selectedFilter]); + + const defaultColumns = [ + ...baseColumns, + ...querycountColumn, + ...timestampColumn, + ...metricColumns, + ...QueryTypeSpecificColumns, + ]; + + const groupTypeColumns = [...baseColumns, ...querycountColumn, ...metricColumns]; + const queryTypeColumns = [ + ...baseColumns, + ...timestampColumn, + ...metricColumns, + ...QueryTypeSpecificColumns, + ]; + + const columnsToShow = useMemo(() => { + const hasQueryType = selectedFilter.includes('NONE'); + const hasGroupType = selectedFilter.includes('SIMILARITY'); + + if (hasQueryType && hasGroupType) { + if (queries.length === 0) return defaultColumns; + else { + const containsOnlyQueryType = queries.every((q) => q.group_by === 'NONE'); + const containsOnlyGroupType = queries.every((q) => q.group_by === 'SIMILARITY'); + + if (containsOnlyQueryType) { + return queryTypeColumns; + } + + if (containsOnlyGroupType) { + return groupTypeColumns; + } + return defaultColumns; + } + } + if (hasGroupType) return groupTypeColumns; + if (hasQueryType) return queryTypeColumns; + + return defaultColumns; + }, [selectedFilter, queries]); + + const onChangeFilter = ({ query: searchQuery }) => { + const text = searchQuery?.text || ''; + + const newFilters = new Set(); + + if (text.includes('group_by:(SIMILARITY)')) { + newFilters.add('SIMILARITY'); + } else if (text.includes('group_by:(NONE)')) { + newFilters.add('NONE'); + } else if ( + text.includes('group_by:(NONE or SIMILARITY)') || + text.includes('group_by:(SIMILARITY or NONE)') || + !text + ) { + newFilters.add('SIMILARITY'); + newFilters.add('NONE'); + } + + if (JSON.stringify([...newFilters]) !== JSON.stringify(selectedFilter)) { + setSelectedFilter([...newFilters]); + } + }; const onRefresh = async ({ start, end }: { start: string; end: string }) => { onTimeChange({ start, end }); @@ -291,8 +402,8 @@ const QueryInsights = ({ dataSourcePickerReadOnly={false} /> @@ -527,7 +527,7 @@ exports[`QueryInsights Component renders the table with the correct columns and aria-live="polite" aria-sort="none" class="euiTableHeaderCell" - data-test-subj="tableHeaderCell_measurements.latency_4" + data-test-subj="tableHeaderCell_Avg Latency / Latency_4" role="columnheader" scope="col" > @@ -541,9 +541,9 @@ exports[`QueryInsights Component renders the table with the correct columns and > - Latency + Avg Latency / Latency @@ -552,7 +552,7 @@ exports[`QueryInsights Component renders the table with the correct columns and aria-live="polite" aria-sort="none" class="euiTableHeaderCell" - data-test-subj="tableHeaderCell_measurements.cpu_5" + data-test-subj="tableHeaderCell_Avg CPU Time / CPU Time_5" role="columnheader" scope="col" > @@ -566,9 +566,9 @@ exports[`QueryInsights Component renders the table with the correct columns and > - CPU Time + Avg CPU Time / CPU Time @@ -577,7 +577,7 @@ exports[`QueryInsights Component renders the table with the correct columns and aria-live="polite" aria-sort="none" class="euiTableHeaderCell" - data-test-subj="tableHeaderCell_measurements.memory_6" + data-test-subj="tableHeaderCell_Avg Memory Usage / Memory Usage_6" role="columnheader" scope="col" > @@ -591,9 +591,9 @@ exports[`QueryInsights Component renders the table with the correct columns and > - Memory Usage + Avg Memory Usage / Memory Usage @@ -705,322 +705,23 @@ exports[`QueryInsights Component renders the table with the correct columns and class="euiTableRow" >
- Id -
-
- - - -
- - -
- Type -
-
- - - -
- - -
- Query Count -
-
- 1 -
- - -
- Timestamp -
-
- + No items found
- -
- Latency -
-
- 8.00 ms -
- - -
- CPU Time -
-
- 5.51 ms -
- - -
- Memory Usage -
-
- Not enabled -
- - -
- Indices -
-
- top_queries-2024.09.12 -
- - -
- Search Type -
-
- query then fetch -
- - -
- Coordinator Node ID -
-
- Q36D2z_NRGKim6EZZMgi6A -
- - -
- Total Shards -
-
- 1 -
- -
-
-
-
-
-
- -
-
-
-
- -
-
-