diff --git a/common/constants.ts b/common/constants.ts
index 2186516..c685599 100644
--- a/common/constants.ts
+++ b/common/constants.ts
@@ -8,6 +8,6 @@ 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';
+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 1682cc8..25fd4b2 100644
--- a/package.json
+++ b/package.json
@@ -42,6 +42,7 @@
"@elastic/elastic-eslint-config-kibana": "link:../../packages/opensearch-eslint-config-opensearch-dashboards",
"@elastic/eslint-import-resolver-kibana": "link:../../packages/osd-eslint-import-resolver-opensearch-dashboards",
"@testing-library/dom": "^8.11.3",
+ "@testing-library/jest-dom": "^5.16.2",
"@testing-library/user-event": "^14.4.3",
"@types/react-dom": "^16.9.8",
"@types/object-hash": "^3.0.0",
diff --git a/public/components/__snapshots__/app.test.tsx.snap b/public/components/__snapshots__/app.test.tsx.snap
index 3571697..36c6673 100644
--- a/public/components/__snapshots__/app.test.tsx.snap
+++ b/public/components/__snapshots__/app.test.tsx.snap
@@ -172,10 +172,10 @@ exports[` spec renders the component 1`] = `
>
- Search type
+ Search Type
@@ -215,10 +215,10 @@ exports[` spec renders the component 1`] = `
>
- Coordinator node ID
+ Coordinator Node ID
@@ -566,9 +566,9 @@ exports[` spec renders the component 1`] = `
>
- Search type
+ Search Type
@@ -591,9 +591,9 @@ exports[` spec renders the component 1`] = `
>
- Coordinator node ID
+ Coordinator Node ID
@@ -616,9 +616,9 @@ exports[` spec renders the component 1`] = `
>
- Total shards
+ Total Shards
diff --git a/public/pages/Configuration/Configuration.test.tsx b/public/pages/Configuration/Configuration.test.tsx
new file mode 100644
index 0000000..0f9360d
--- /dev/null
+++ b/public/pages/Configuration/Configuration.test.tsx
@@ -0,0 +1,109 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import React from 'react';
+import { render, screen, fireEvent, waitFor } from '@testing-library/react';
+import '@testing-library/jest-dom/extend-expect';
+import { MemoryRouter } from 'react-router-dom';
+import Configuration from './Configuration';
+
+const mockConfigInfo = jest.fn();
+const mockCoreStart = {
+ chrome: {
+ setBreadcrumbs: jest.fn(),
+ },
+};
+
+const defaultLatencySettings = {
+ isEnabled: true,
+ currTopN: '5',
+ currWindowSize: '10',
+ currTimeUnit: 'MINUTES',
+};
+const defaultCpuSettings = {
+ isEnabled: false,
+ currTopN: '10',
+ currWindowSize: '1',
+ currTimeUnit: 'HOURS',
+};
+const defaultMemorySettings = {
+ isEnabled: false,
+ currTopN: '15',
+ currWindowSize: '2',
+ currTimeUnit: 'HOURS',
+};
+
+const renderConfiguration = (overrides = {}) =>
+ render(
+
+
+
+ );
+
+const getWindowSizeConfigurations = () => screen.getAllByRole('combobox');
+const getTopNSizeConfiguration = () => screen.getByRole('spinbutton');
+const getEnableToggle = () => screen.getByRole('switch');
+
+describe('Configuration Component', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('renders with default settings', () => {
+ const { container } = renderConfiguration();
+ expect(container).toMatchSnapshot('should match default settings snapshot');
+ });
+
+ // The following tests test the interactions on the frontend with Mocks.
+ it('updates state when toggling metrics and enables Save button when changes are made', () => {
+ const { container } = renderConfiguration();
+ // before toggling the metric
+ expect(getWindowSizeConfigurations()[0]).toHaveValue('latency');
+ expect(getEnableToggle()).toBeChecked();
+ // toggle the metric
+ fireEvent.change(getWindowSizeConfigurations()[0], { target: { value: 'cpu' } });
+ // after toggling the metric
+ expect(getWindowSizeConfigurations()[0]).toHaveValue('cpu');
+ // the enabled box should be disabled by default based on our configuration
+ const cpuEnableBox = getEnableToggle();
+ expect(cpuEnableBox).toBeInTheDocument();
+ expect(cpuEnableBox).not.toBeChecked();
+
+ fireEvent.click(getEnableToggle());
+ expect(getEnableToggle()).toBeChecked();
+ expect(screen.getByText('Save')).toBeEnabled();
+ expect(container).toMatchSnapshot('should match settings snapshot after toggling');
+ });
+
+ it('validates topNSize and windowSize inputs and disables Save button for invalid input', () => {
+ renderConfiguration();
+ fireEvent.change(getTopNSizeConfiguration(), { target: { value: '101' } });
+ expect(screen.queryByText('Save')).not.toBeInTheDocument();
+ fireEvent.change(getWindowSizeConfigurations()[1], { target: { value: '999' } });
+ expect(screen.queryByText('Save')).not.toBeInTheDocument();
+ });
+
+ it('calls configInfo and navigates on Save button click', async () => {
+ renderConfiguration();
+ fireEvent.change(getTopNSizeConfiguration(), { target: { value: '7' } });
+ fireEvent.click(screen.getByText('Save'));
+ await waitFor(() => {
+ expect(mockConfigInfo).toHaveBeenCalledWith(false, true, 'latency', '7', '10', 'MINUTES');
+ });
+ });
+
+ it('resets state on Cancel button click', async () => {
+ renderConfiguration();
+ fireEvent.change(getTopNSizeConfiguration(), { target: { value: '7' } });
+ fireEvent.click(screen.getByText('Cancel'));
+ expect(getTopNSizeConfiguration()).toHaveValue(5); // Resets to initial value
+ });
+});
diff --git a/public/pages/Configuration/__snapshots__/Configuration.test.tsx.snap b/public/pages/Configuration/__snapshots__/Configuration.test.tsx.snap
new file mode 100644
index 0000000..4034199
--- /dev/null
+++ b/public/pages/Configuration/__snapshots__/Configuration.test.tsx.snap
@@ -0,0 +1,1220 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Configuration Component renders with default settings: should match default settings snapshot 1`] = `
+
+
+
+
+
+
+
+
+
+ Statuses for configuration metrics
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`Configuration Component updates state when toggling metrics and enables Save button when changes are made: should match settings snapshot after toggling 1`] = `
+
+
+
+
+
+
+
+
+
+ Statuses for configuration metrics
+
+
+
+
+
+
+
+
+
+`;
diff --git a/public/pages/QueryDetails/QueryDetails.test.tsx b/public/pages/QueryDetails/QueryDetails.test.tsx
new file mode 100644
index 0000000..196f267
--- /dev/null
+++ b/public/pages/QueryDetails/QueryDetails.test.tsx
@@ -0,0 +1,54 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import React from 'react';
+import { render, screen } from '@testing-library/react';
+import QueryDetails from './QueryDetails';
+import Plotly from 'plotly.js-dist';
+import { MockQueries } from '../../../test/testUtils';
+import '@testing-library/jest-dom';
+import { MemoryRouter, Route } from 'react-router-dom';
+import hash from 'object-hash';
+// Mock the external dependencies
+jest.mock('plotly.js-dist', () => ({
+ newPlot: jest.fn(),
+}));
+
+const mockCoreStart = {
+ chrome: {
+ setBreadcrumbs: jest.fn(),
+ },
+};
+const mockQuery = MockQueries()[0];
+const mockParams = { hashedQuery: hash(mockQuery) };
+
+describe('QueryDetails component', () => {
+ beforeEach(() => {
+ jest.clearAllMocks(); // Clear all mock calls and instances before each test
+ });
+
+ const renderComponent = () =>
+ render(
+
+
+
+
+
+ );
+
+ it('renders the QueryDetails page', () => {
+ const { container } = renderComponent();
+ // Check if the query details are displayed correctly
+ expect(screen.getByText('Query details')).toBeInTheDocument();
+ expect(screen.getByText('Query')).toBeInTheDocument();
+
+ // Verify that the Plotly chart is rendered
+ expect(Plotly.newPlot).toHaveBeenCalledTimes(1);
+ // Verify the breadcrumbs were set correctly
+ expect(mockCoreStart.chrome.setBreadcrumbs).toHaveBeenCalled();
+
+ expect(container).toMatchSnapshot();
+ });
+});
diff --git a/public/pages/QueryDetails/QueryDetails.tsx b/public/pages/QueryDetails/QueryDetails.tsx
index c7cf406..5335376 100644
--- a/public/pages/QueryDetails/QueryDetails.tsx
+++ b/public/pages/QueryDetails/QueryDetails.tsx
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
-import React, { useEffect } from 'react';
+import React, { useCallback, useEffect } from 'react';
import Plotly from 'plotly.js-dist';
import {
EuiButton,
@@ -18,53 +18,37 @@ import {
EuiTitle,
} from '@elastic/eui';
import hash from 'object-hash';
-import { useParams, useHistory, useLocation } from 'react-router-dom';
+import { useParams, useHistory } from 'react-router-dom';
import { CoreStart } from 'opensearch-dashboards/public';
import QuerySummary from './Components/QuerySummary';
import { QUERY_INSIGHTS } from '../TopNQueries/TopNQueries';
+import { SearchQueryRecord } from '../../../types/types';
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 query = queries.find((q: SearchQueryRecord) => hash(q) === hashedQuery);
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]);
+ // Convert UNIX time to a readable format
+ const convertTime = useCallback((unixTime: number) => {
+ const date = new Date(unixTime);
+ const [_weekDay, month, day, year] = date.toDateString().split(' ');
+ return `${month} ${day}, ${year} @ ${date.toLocaleTimeString('en-US')}`;
+ }, []);
- useEffect(() => {
- let x: number[] = Object.values(query.phase_latency_map);
- if (x.length < 3) {
- x = [0, 0, 0];
- }
+ // Initialize the Plotly chart
+ const initPlotlyChart = useCallback(() => {
+ const latencies: number[] = Object.values(query?.phase_latency_map || [0, 0, 0]);
const data = [
{
- x: x.reverse(),
+ x: latencies.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`),
+ base: [latencies[2] + latencies[1], latencies[2], 0],
+ text: latencies.map((value) => `${value}ms`),
textposition: 'outside',
cliponaxis: false,
},
@@ -72,7 +56,6 @@ const QueryDetails = ({ queries, core }: { queries: any; core: CoreStart }) => {
const layout = {
autosize: true,
margin: { l: 80, r: 80, t: 25, b: 15, pad: 0 },
- autorange: true,
height: 120,
xaxis: {
side: 'top',
@@ -89,7 +72,22 @@ const QueryDetails = ({ queries, core }: { queries: any; core: CoreStart }) => {
Plotly.newPlot('latency', data, layout, config);
}, [query]);
- const queryString = JSON.stringify(JSON.parse(JSON.stringify(query.source)), null, 2);
+ useEffect(() => {
+ core.chrome.setBreadcrumbs([
+ {
+ text: 'Query insights',
+ href: QUERY_INSIGHTS,
+ onClick: (e) => {
+ e.preventDefault();
+ history.push(QUERY_INSIGHTS);
+ },
+ },
+ { text: `Query details: ${convertTime(query.timestamp)}` },
+ ]);
+ initPlotlyChart();
+ }, [query, history, core.chrome, convertTime, initPlotlyChart]);
+
+ const queryString = JSON.stringify(query.source, null, 2);
const queryDisplay = `{\n "query": ${queryString ? queryString.replace(/\n/g, '\n ') : ''}\n}`;
return (
@@ -117,7 +115,7 @@ const QueryDetails = ({ queries, core }: { queries: any; core: CoreStart }) => {
target="_blank"
href="https://playground.opensearch.org/app/searchRelevance#/"
>
- Open in search comparision
+ Open in search comparison
diff --git a/public/pages/QueryDetails/__snapshots__/QueryDetails.test.tsx.snap b/public/pages/QueryDetails/__snapshots__/QueryDetails.test.tsx.snap
new file mode 100644
index 0000000..912f011
--- /dev/null
+++ b/public/pages/QueryDetails/__snapshots__/QueryDetails.test.tsx.snap
@@ -0,0 +1,1155 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`QueryDetails component renders the QueryDetails page 1`] = `
+
+
+
+ Query details
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {
+
+
+
+
+
+
+
+ "query"
+
+
+ :
+
+
+
+ {
+
+
+
+
+
+
+
+ "query"
+
+
+ :
+
+
+
+ {
+
+
+
+
+
+
+
+ "bool"
+
+
+ :
+
+
+
+ {
+
+
+
+
+
+
+
+ "must"
+
+
+ :
+
+
+
+ [
+
+
+
+
+
+
+
+ {
+
+
+
+
+
+
+
+ "range"
+
+
+ :
+
+
+
+ {
+
+
+
+
+
+
+
+ "timestamp"
+
+
+ :
+
+
+
+ {
+
+
+
+
+
+
+
+ "from"
+
+
+ :
+
+
+
+ 1726092595177
+
+
+ ,
+
+
+
+
+
+
+
+ "to"
+
+
+ :
+
+
+
+ 1726178995177
+
+
+ ,
+
+
+
+
+
+
+
+ "include_lower"
+
+
+ :
+
+
+
+ true
+
+
+ ,
+
+
+
+
+
+
+
+ "include_upper"
+
+
+ :
+
+
+
+ true
+
+
+ ,
+
+
+
+
+
+
+
+ "boost"
+
+
+ :
+
+
+
+ 1
+
+
+
+
+
+
+
+ }
+
+
+
+
+
+
+
+ }
+
+
+
+
+
+
+
+ }
+
+
+
+
+
+
+
+ ]
+
+
+ ,
+
+
+
+
+
+
+
+ "must_not"
+
+
+ :
+
+
+
+ [
+
+
+
+
+
+
+
+ {
+
+
+
+
+
+
+
+ "match"
+
+
+ :
+
+
+
+ {
+
+
+
+
+
+
+
+ "indices"
+
+
+ :
+
+
+
+ {
+
+
+
+
+
+
+
+ "query"
+
+
+ :
+
+
+
+ "top_queries*"
+
+
+ ,
+
+
+
+
+
+
+
+ "operator"
+
+
+ :
+
+
+
+ "OR"
+
+
+ ,
+
+
+
+
+
+
+
+ "prefix_length"
+
+
+ :
+
+
+
+ 0
+
+
+ ,
+
+
+
+
+
+
+
+ "max_expansions"
+
+
+ :
+
+
+
+ 50
+
+
+ ,
+
+
+
+
+
+
+
+ "fuzzy_transpositions"
+
+
+ :
+
+
+
+ true
+
+
+ ,
+
+
+
+
+
+
+
+ "lenient"
+
+
+ :
+
+
+
+ false
+
+
+ ,
+
+
+
+
+
+
+
+ "zero_terms_query"
+
+
+ :
+
+
+
+ "NONE"
+
+
+ ,
+
+
+
+
+
+
+
+ "auto_generate_synonyms_phrase_query"
+
+
+ :
+
+
+
+ true
+
+
+ ,
+
+
+
+
+
+
+
+ "boost"
+
+
+ :
+
+
+
+ 1
+
+
+
+
+
+
+
+ }
+
+
+
+
+
+
+
+ }
+
+
+
+
+
+
+
+ }
+
+
+
+
+
+
+
+ ]
+
+
+ ,
+
+
+
+
+
+
+
+ "adjust_pure_negative"
+
+
+ :
+
+
+
+ true
+
+
+ ,
+
+
+
+
+
+
+
+ "boost"
+
+
+ :
+
+
+
+ 1
+
+
+
+
+
+
+
+ }
+
+
+
+
+
+
+
+ }
+
+
+
+
+
+
+
+ }
+
+
+
+
+
+
+
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
diff --git a/public/pages/QueryInsights/QueryInsights.test.tsx b/public/pages/QueryInsights/QueryInsights.test.tsx
new file mode 100644
index 0000000..9740d7b
--- /dev/null
+++ b/public/pages/QueryInsights/QueryInsights.test.tsx
@@ -0,0 +1,70 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import React from 'react';
+import { render, screen, fireEvent } from '@testing-library/react';
+import '@testing-library/jest-dom/extend-expect';
+import QueryInsights from './QueryInsights';
+import { MemoryRouter } from 'react-router-dom';
+import { MockQueries } from '../../../test/testUtils';
+
+// Mock functions and data
+const mockOnTimeChange = jest.fn();
+const mockCore = {
+ chrome: {
+ setBreadcrumbs: jest.fn(),
+ },
+};
+
+const sampleQueries = MockQueries();
+
+const renderQueryInsights = () =>
+ render(
+
+
+
+ );
+
+describe('QueryInsights Component', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('renders the table with the correct columns and data', () => {
+ const { container } = renderQueryInsights();
+ expect(container).toMatchSnapshot();
+ });
+
+ it('calls setBreadcrumbs on mount', () => {
+ renderQueryInsights();
+ expect(mockCore.chrome.setBreadcrumbs).toHaveBeenCalledWith([
+ {
+ text: 'Query insights',
+ href: '/queryInsights',
+ onClick: expect.any(Function),
+ },
+ ]);
+ });
+
+ 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();
+ });
+});
diff --git a/public/pages/QueryInsights/__snapshots__/QueryInsights.test.tsx.snap b/public/pages/QueryInsights/__snapshots__/QueryInsights.test.tsx.snap
new file mode 100644
index 0000000..d511a12
--- /dev/null
+++ b/public/pages/QueryInsights/__snapshots__/QueryInsights.test.tsx.snap
@@ -0,0 +1,856 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`QueryInsights Component renders the table with the correct columns and data 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Indices
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Search Type
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Coordinator Node ID
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Refresh
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Sep 12, 2024 @ 3:09:55 PM
+
+
+
+
+
+
+
+ 8 ms
+
+
+
+
+
+ 5.51 ms
+
+
+
+
+
+ Not enabled
+
+
+
+
+
+ top_queries-2024.09.12
+
+
+
+
+
+ query then fetch
+
+
+
+
+
+
+ Q36D2z_NRGKim6EZZMgi6A
+
+
+
+
+
+
+
+ 1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Rows per page
+ :
+ 10
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
diff --git a/public/pages/TopNQueries/TopNQueries.test.tsx b/public/pages/TopNQueries/TopNQueries.test.tsx
index f642c39..a8fabf1 100644
--- a/public/pages/TopNQueries/TopNQueries.test.tsx
+++ b/public/pages/TopNQueries/TopNQueries.test.tsx
@@ -51,22 +51,26 @@ const setUpDefaultEnabledSettings = () => {
(mockCore.http.get as jest.Mock).mockResolvedValueOnce(mockSettingsResponse);
};
+const renderTopNQueries = (type: string) =>
+ render(
+
+
+
+ );
+
describe('TopNQueries Component', () => {
beforeEach(() => {
jest.clearAllMocks();
});
it('renders and switches tabs correctly', () => {
- render(
-
-
-
- );
+ const container = renderTopNQueries(QUERY_INSIGHTS);
// Check for Query Insights tab content
expect(screen.getByText('Mocked QueryInsights')).toBeInTheDocument();
expect(screen.getByText('Top N queries')).toBeInTheDocument();
expect(screen.getByText('Configuration')).toBeInTheDocument();
+ expect(container).toMatchSnapshot();
// Switch to Configuration tab
fireEvent.click(screen.getByText('Configuration'));
@@ -90,24 +94,17 @@ describe('TopNQueries Component', () => {
},
};
(mockCore.http.get as jest.Mock).mockResolvedValueOnce(mockSettingsResponse);
- render(
-
-
-
- );
+ const container = renderTopNQueries(CONFIGURATION);
await waitFor(() => {
expect(mockCore.http.get).toHaveBeenCalledWith('/api/settings');
expect(screen.getByText('Mocked Configuration')).toBeInTheDocument();
+ expect(container).toMatchSnapshot();
});
});
it('fetches queries for all metrics in retrieveQueries', async () => {
setUpDefaultEnabledSettings();
- render(
-
-
-
- );
+ const container = renderTopNQueries(QUERY_INSIGHTS);
await waitFor(() => {
// Verify each endpoint is called
expect(mockCore.http.get).toHaveBeenCalledWith('/api/settings');
@@ -119,6 +116,8 @@ describe('TopNQueries Component', () => {
expect(mockCore.http.get).toHaveBeenCalledWith('/api/top_queries/memory', expect.any(Object));
// Check that deduplicated queries would be displayed in QueryInsights
expect(screen.getByText('Mocked QueryInsights')).toBeInTheDocument();
+
+ expect(container).toMatchSnapshot();
});
});
@@ -148,11 +147,7 @@ describe('TopNQueries Component', () => {
},
};
(mockCore.http.get as jest.Mock).mockResolvedValueOnce(mockSettingsResponse);
- render(
-
-
-
- );
+ const container = renderTopNQueries(QUERY_INSIGHTS);
await waitFor(() => {
// Verify each endpoint is called
expect(mockCore.http.get).toHaveBeenCalledWith('/api/settings');
@@ -170,6 +165,8 @@ describe('TopNQueries Component', () => {
);
// Check that deduplicated queries would be displayed in QueryInsights
expect(screen.getByText('Mocked QueryInsights')).toBeInTheDocument();
+
+ expect(container).toMatchSnapshot();
});
});
@@ -186,6 +183,7 @@ describe('TopNQueries Component', () => {
(mockCore.http.get as jest.Mock).mockResolvedValueOnce({
response: { top_queries: [{ id: 'newQuery' }] },
});
+
// Re-render with updated time range to simulate a change
rerender(
diff --git a/public/pages/TopNQueries/__snapshots__/TopNQueries.test.tsx.snap b/public/pages/TopNQueries/__snapshots__/TopNQueries.test.tsx.snap
new file mode 100644
index 0000000..804c1e1
--- /dev/null
+++ b/public/pages/TopNQueries/__snapshots__/TopNQueries.test.tsx.snap
@@ -0,0 +1,629 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`TopNQueries Component fetches queries for all metrics in retrieveQueries 1`] = `
+Object {
+ "asFragment": [Function],
+ "baseElement":
+
+
+
+ Query insights - Top N queries
+
+
+
+
+
+ Top N queries
+
+
+
+
+ Configuration
+
+
+
+
+
+ Mocked QueryInsights
+
+
+
+ ,
+ "container":
+
+
+ Query insights - Top N queries
+
+
+
+
+
+ Top N queries
+
+
+
+
+ Configuration
+
+
+
+
+
+ Mocked QueryInsights
+
+
+
,
+ "debug": [Function],
+ "findAllByAltText": [Function],
+ "findAllByDisplayValue": [Function],
+ "findAllByLabelText": [Function],
+ "findAllByPlaceholderText": [Function],
+ "findAllByRole": [Function],
+ "findAllByTestId": [Function],
+ "findAllByText": [Function],
+ "findAllByTitle": [Function],
+ "findByAltText": [Function],
+ "findByDisplayValue": [Function],
+ "findByLabelText": [Function],
+ "findByPlaceholderText": [Function],
+ "findByRole": [Function],
+ "findByTestId": [Function],
+ "findByText": [Function],
+ "findByTitle": [Function],
+ "getAllByAltText": [Function],
+ "getAllByDisplayValue": [Function],
+ "getAllByLabelText": [Function],
+ "getAllByPlaceholderText": [Function],
+ "getAllByRole": [Function],
+ "getAllByTestId": [Function],
+ "getAllByText": [Function],
+ "getAllByTitle": [Function],
+ "getByAltText": [Function],
+ "getByDisplayValue": [Function],
+ "getByLabelText": [Function],
+ "getByPlaceholderText": [Function],
+ "getByRole": [Function],
+ "getByTestId": [Function],
+ "getByText": [Function],
+ "getByTitle": [Function],
+ "queryAllByAltText": [Function],
+ "queryAllByDisplayValue": [Function],
+ "queryAllByLabelText": [Function],
+ "queryAllByPlaceholderText": [Function],
+ "queryAllByRole": [Function],
+ "queryAllByTestId": [Function],
+ "queryAllByText": [Function],
+ "queryAllByTitle": [Function],
+ "queryByAltText": [Function],
+ "queryByDisplayValue": [Function],
+ "queryByLabelText": [Function],
+ "queryByPlaceholderText": [Function],
+ "queryByRole": [Function],
+ "queryByTestId": [Function],
+ "queryByText": [Function],
+ "queryByTitle": [Function],
+ "rerender": [Function],
+ "unmount": [Function],
+}
+`;
+
+exports[`TopNQueries Component fetches queries for only enabled metrics in retrieveQueries 1`] = `
+Object {
+ "asFragment": [Function],
+ "baseElement":
+
+
+
+ Query insights - Top N queries
+
+
+
+
+
+ Top N queries
+
+
+
+
+ Configuration
+
+
+
+
+
+ Mocked QueryInsights
+
+
+
+ ,
+ "container":
+
+
+ Query insights - Top N queries
+
+
+
+
+
+ Top N queries
+
+
+
+
+ Configuration
+
+
+
+
+
+ Mocked QueryInsights
+
+
+
,
+ "debug": [Function],
+ "findAllByAltText": [Function],
+ "findAllByDisplayValue": [Function],
+ "findAllByLabelText": [Function],
+ "findAllByPlaceholderText": [Function],
+ "findAllByRole": [Function],
+ "findAllByTestId": [Function],
+ "findAllByText": [Function],
+ "findAllByTitle": [Function],
+ "findByAltText": [Function],
+ "findByDisplayValue": [Function],
+ "findByLabelText": [Function],
+ "findByPlaceholderText": [Function],
+ "findByRole": [Function],
+ "findByTestId": [Function],
+ "findByText": [Function],
+ "findByTitle": [Function],
+ "getAllByAltText": [Function],
+ "getAllByDisplayValue": [Function],
+ "getAllByLabelText": [Function],
+ "getAllByPlaceholderText": [Function],
+ "getAllByRole": [Function],
+ "getAllByTestId": [Function],
+ "getAllByText": [Function],
+ "getAllByTitle": [Function],
+ "getByAltText": [Function],
+ "getByDisplayValue": [Function],
+ "getByLabelText": [Function],
+ "getByPlaceholderText": [Function],
+ "getByRole": [Function],
+ "getByTestId": [Function],
+ "getByText": [Function],
+ "getByTitle": [Function],
+ "queryAllByAltText": [Function],
+ "queryAllByDisplayValue": [Function],
+ "queryAllByLabelText": [Function],
+ "queryAllByPlaceholderText": [Function],
+ "queryAllByRole": [Function],
+ "queryAllByTestId": [Function],
+ "queryAllByText": [Function],
+ "queryAllByTitle": [Function],
+ "queryByAltText": [Function],
+ "queryByDisplayValue": [Function],
+ "queryByLabelText": [Function],
+ "queryByPlaceholderText": [Function],
+ "queryByRole": [Function],
+ "queryByTestId": [Function],
+ "queryByText": [Function],
+ "queryByTitle": [Function],
+ "rerender": [Function],
+ "unmount": [Function],
+}
+`;
+
+exports[`TopNQueries Component renders and switches tabs correctly 1`] = `
+Object {
+ "asFragment": [Function],
+ "baseElement":
+
+
+
+ Query insights - Top N queries
+
+
+
+
+
+ Top N queries
+
+
+
+
+ Configuration
+
+
+
+
+
+ Mocked QueryInsights
+
+
+
+ ,
+ "container":
+
+
+ Query insights - Top N queries
+
+
+
+
+
+ Top N queries
+
+
+
+
+ Configuration
+
+
+
+
+
+ Mocked QueryInsights
+
+
+
,
+ "debug": [Function],
+ "findAllByAltText": [Function],
+ "findAllByDisplayValue": [Function],
+ "findAllByLabelText": [Function],
+ "findAllByPlaceholderText": [Function],
+ "findAllByRole": [Function],
+ "findAllByTestId": [Function],
+ "findAllByText": [Function],
+ "findAllByTitle": [Function],
+ "findByAltText": [Function],
+ "findByDisplayValue": [Function],
+ "findByLabelText": [Function],
+ "findByPlaceholderText": [Function],
+ "findByRole": [Function],
+ "findByTestId": [Function],
+ "findByText": [Function],
+ "findByTitle": [Function],
+ "getAllByAltText": [Function],
+ "getAllByDisplayValue": [Function],
+ "getAllByLabelText": [Function],
+ "getAllByPlaceholderText": [Function],
+ "getAllByRole": [Function],
+ "getAllByTestId": [Function],
+ "getAllByText": [Function],
+ "getAllByTitle": [Function],
+ "getByAltText": [Function],
+ "getByDisplayValue": [Function],
+ "getByLabelText": [Function],
+ "getByPlaceholderText": [Function],
+ "getByRole": [Function],
+ "getByTestId": [Function],
+ "getByText": [Function],
+ "getByTitle": [Function],
+ "queryAllByAltText": [Function],
+ "queryAllByDisplayValue": [Function],
+ "queryAllByLabelText": [Function],
+ "queryAllByPlaceholderText": [Function],
+ "queryAllByRole": [Function],
+ "queryAllByTestId": [Function],
+ "queryAllByText": [Function],
+ "queryAllByTitle": [Function],
+ "queryByAltText": [Function],
+ "queryByDisplayValue": [Function],
+ "queryByLabelText": [Function],
+ "queryByPlaceholderText": [Function],
+ "queryByRole": [Function],
+ "queryByTestId": [Function],
+ "queryByText": [Function],
+ "queryByTitle": [Function],
+ "rerender": [Function],
+ "unmount": [Function],
+}
+`;
+
+exports[`TopNQueries Component updates settings in retrieveConfigInfo based on API response 1`] = `
+Object {
+ "asFragment": [Function],
+ "baseElement":
+
+
+
+ Query insights - Configuration
+
+
+
+
+
+ Top N queries
+
+
+
+
+ Configuration
+
+
+
+
+
+ Mocked Configuration
+
+
+
+ ,
+ "container":
+
+
+ Query insights - Configuration
+
+
+
+
+
+ Top N queries
+
+
+
+
+ Configuration
+
+
+
+
+
+ Mocked Configuration
+
+
+
,
+ "debug": [Function],
+ "findAllByAltText": [Function],
+ "findAllByDisplayValue": [Function],
+ "findAllByLabelText": [Function],
+ "findAllByPlaceholderText": [Function],
+ "findAllByRole": [Function],
+ "findAllByTestId": [Function],
+ "findAllByText": [Function],
+ "findAllByTitle": [Function],
+ "findByAltText": [Function],
+ "findByDisplayValue": [Function],
+ "findByLabelText": [Function],
+ "findByPlaceholderText": [Function],
+ "findByRole": [Function],
+ "findByTestId": [Function],
+ "findByText": [Function],
+ "findByTitle": [Function],
+ "getAllByAltText": [Function],
+ "getAllByDisplayValue": [Function],
+ "getAllByLabelText": [Function],
+ "getAllByPlaceholderText": [Function],
+ "getAllByRole": [Function],
+ "getAllByTestId": [Function],
+ "getAllByText": [Function],
+ "getAllByTitle": [Function],
+ "getByAltText": [Function],
+ "getByDisplayValue": [Function],
+ "getByLabelText": [Function],
+ "getByPlaceholderText": [Function],
+ "getByRole": [Function],
+ "getByTestId": [Function],
+ "getByText": [Function],
+ "getByTitle": [Function],
+ "queryAllByAltText": [Function],
+ "queryAllByDisplayValue": [Function],
+ "queryAllByLabelText": [Function],
+ "queryAllByPlaceholderText": [Function],
+ "queryAllByRole": [Function],
+ "queryAllByTestId": [Function],
+ "queryAllByText": [Function],
+ "queryAllByTitle": [Function],
+ "queryByAltText": [Function],
+ "queryByDisplayValue": [Function],
+ "queryByLabelText": [Function],
+ "queryByPlaceholderText": [Function],
+ "queryByRole": [Function],
+ "queryByTestId": [Function],
+ "queryByText": [Function],
+ "queryByTitle": [Function],
+ "rerender": [Function],
+ "unmount": [Function],
+}
+`;
diff --git a/test/testUtils.ts b/test/testUtils.ts
new file mode 100644
index 0000000..55ad71d
--- /dev/null
+++ b/test/testUtils.ts
@@ -0,0 +1,99 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { SearchQueryRecord } from '../types/types';
+
+export const MockQueries = (): SearchQueryRecord[] => {
+ return [
+ {
+ timestamp: 1726178995210,
+ task_resource_usages: [
+ {
+ action: 'indices:data/read/search[phase/query]',
+ taskId: 18809,
+ parentTaskId: 18808,
+ nodeId: 'Q36D2z_NRGKim6EZZMgi6A',
+ taskResourceUsage: {
+ cpu_time_in_nanos: 3612000,
+ memory_in_bytes: 123944,
+ },
+ },
+ {
+ action: 'indices:data/read/search',
+ taskId: 18808,
+ parentTaskId: -1,
+ nodeId: 'Q36D2z_NRGKim6EZZMgi6A',
+ taskResourceUsage: {
+ cpu_time_in_nanos: 1898000,
+ memory_in_bytes: 24176,
+ },
+ },
+ ],
+ source: {
+ query: {
+ bool: {
+ must: [
+ {
+ range: {
+ timestamp: {
+ from: 1726092595177,
+ to: 1726178995177,
+ include_lower: true,
+ include_upper: true,
+ boost: 1.0,
+ },
+ },
+ },
+ ],
+ must_not: [
+ {
+ match: {
+ indices: {
+ query: 'top_queries*',
+ operator: 'OR',
+ prefix_length: 0,
+ max_expansions: 50,
+ fuzzy_transpositions: true,
+ lenient: false,
+ zero_terms_query: 'NONE',
+ auto_generate_synonyms_phrase_query: true,
+ boost: 1.0,
+ },
+ },
+ },
+ ],
+ adjust_pure_negative: true,
+ boost: 1.0,
+ },
+ },
+ },
+ query_hashcode: '80a17984b847133b8bf5e7d5dfbfa96c',
+ phase_latency_map: {
+ expand: 0,
+ query: 5,
+ fetch: 0,
+ },
+ labels: {
+ 'X-Opaque-Id': 'ae6c1170-5f98-47f4-b7fc-09ebcf574b81',
+ },
+ total_shards: 1,
+ search_type: 'query_then_fetch',
+ node_id: 'Q36D2z_NRGKim6EZZMgi6A',
+ indices: ['top_queries-2024.09.12'],
+ measurements: {
+ latency: {
+ number: 8,
+ count: 1,
+ aggregationType: 'NONE',
+ },
+ cpu: {
+ number: 5510000,
+ count: 1,
+ aggregationType: 'NONE',
+ },
+ },
+ },
+ ];
+};
diff --git a/types/types.ts b/types/types.ts
index 01f2f59..ca4ec71 100644
--- a/types/types.ts
+++ b/types/types.ts
@@ -3,8 +3,6 @@
* SPDX-License-Identifier: Apache-2.0
*/
-import { ISearchSource } from 'src/plugins/data/public';
-
export interface SearchQueryRecord {
timestamp: number;
measurements: {
@@ -14,12 +12,13 @@ export interface SearchQueryRecord {
};
total_shards: number;
node_id: string;
- source: ISearchSource;
+ source: Record;
labels: Record;
search_type: string;
indices: string[];
phase_latency_map: PhaseLatencyMap;
task_resource_usages: Task[];
+ query_hashcode: string;
}
export interface Measurement {
diff --git a/yarn.lock b/yarn.lock
index 721ae91..a3d79cd 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2,6 +2,11 @@
# yarn lockfile v1
+"@adobe/css-tools@^4.0.1":
+ version "4.4.1"
+ resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.4.1.tgz#2447a230bfe072c1659e6815129c03cf170710e3"
+ integrity sha512-12WGKBQzjUAI4ayyF4IAtfw2QR/IDoqk6jTddXDhtYTJF9ASmoE1zst7cVtP0aL/F1jUJL5r+JxKXKEgHNbEUQ==
+
"@ampproject/remapping@^2.2.0":
version "2.3.0"
resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4"
@@ -255,6 +260,13 @@
dependencies:
regenerator-runtime "^0.14.0"
+"@babel/runtime@^7.9.2":
+ version "7.26.0"
+ resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.26.0.tgz#8600c2f595f277c60815256418b85356a65173c1"
+ integrity sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==
+ dependencies:
+ regenerator-runtime "^0.14.0"
+
"@babel/template@^7.25.0", "@babel/template@^7.3.3":
version "7.25.0"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.25.0.tgz#e733dc3134b4fede528c15bc95e89cb98c52592a"
@@ -744,6 +756,21 @@
lz-string "^1.5.0"
pretty-format "^27.0.2"
+"@testing-library/jest-dom@^5.16.2":
+ version "5.17.0"
+ resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.17.0.tgz#5e97c8f9a15ccf4656da00fecab505728de81e0c"
+ integrity sha512-ynmNeT7asXyH3aSVv4vvX4Rb+0qjOhdNHnO/3vuZNqPmhDpV/+rCSGwQ7bLcmU2cJ4dvoheIO85LQj0IbJHEtg==
+ dependencies:
+ "@adobe/css-tools" "^4.0.1"
+ "@babel/runtime" "^7.9.2"
+ "@types/testing-library__jest-dom" "^5.9.1"
+ aria-query "^5.0.0"
+ chalk "^3.0.0"
+ css.escape "^1.5.1"
+ dom-accessibility-api "^0.5.6"
+ lodash "^4.17.15"
+ redent "^3.0.0"
+
"@testing-library/user-event@^14.4.3":
version "14.5.2"
resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-14.5.2.tgz#db7257d727c891905947bd1c1a99da20e03c2ebd"
@@ -830,7 +857,7 @@
dependencies:
"@types/istanbul-lib-report" "*"
-"@types/jest@^29.3.1":
+"@types/jest@*", "@types/jest@^29.3.1":
version "29.5.12"
resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.12.tgz#7f7dc6eb4cf246d2474ed78744b05d06ce025544"
integrity sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==
@@ -923,6 +950,13 @@
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8"
integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==
+"@types/testing-library__jest-dom@^5.9.1":
+ version "5.14.9"
+ resolved "https://registry.yarnpkg.com/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.9.tgz#0fb1e6a0278d87b6737db55af5967570b67cb466"
+ integrity sha512-FSYhIjFlfOpGSRyVoMBMuS3ws5ehFQODymf3vlI7U1K8c7PHwWwFY7VREfmsuzHSOnoKs/9/Y983ayOs7eRzqw==
+ dependencies:
+ "@types/jest" "*"
+
"@types/yargs-parser@*":
version "21.0.3"
resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15"
@@ -1248,6 +1282,11 @@ aria-query@5.1.3:
dependencies:
deep-equal "^2.0.5"
+aria-query@^5.0.0:
+ version "5.3.2"
+ resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.3.2.tgz#93f81a43480e33a338f19163a3d10a50c01dcd59"
+ integrity sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==
+
arr-diff@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520"
@@ -1814,6 +1853,14 @@ chalk@^2.3.0, chalk@^2.4.2:
escape-string-regexp "^1.0.5"
supports-color "^5.3.0"
+chalk@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4"
+ integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==
+ dependencies:
+ ansi-styles "^4.1.0"
+ supports-color "^7.1.0"
+
chalk@^4.0.0, chalk@^4.1.0:
version "4.1.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
@@ -2161,6 +2208,11 @@ crypto-browserify@^3.11.0:
randombytes "^2.1.0"
randomfill "^1.0.4"
+css.escape@^1.5.1:
+ version "1.5.1"
+ resolved "https://registry.yarnpkg.com/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb"
+ integrity sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==
+
cssom@^0.4.4:
version "0.4.4"
resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.4.4.tgz#5a66cf93d2d0b661d80bf6a44fb65f5c2e4e0a10"
@@ -2478,7 +2530,7 @@ diffie-hellman@^5.0.3:
miller-rabin "^4.0.0"
randombytes "^2.0.0"
-dom-accessibility-api@^0.5.9:
+dom-accessibility-api@^0.5.6, dom-accessibility-api@^0.5.9:
version "0.5.16"
resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz#5a7429e6066eb3664d911e33fb0e45de8eb08453"
integrity sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==
@@ -4925,7 +4977,7 @@ lodash.once@^4.1.1:
resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"
integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==
-lodash@^4.17.20, lodash@^4.17.21, lodash@^4.7.0:
+lodash@^4.17.15, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.7.0:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@@ -5094,6 +5146,11 @@ mimic-fn@^2.1.0:
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
+min-indent@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869"
+ integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==
+
minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7"
@@ -5783,6 +5840,14 @@ readdirp@~3.6.0:
dependencies:
picomatch "^2.2.1"
+redent@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f"
+ integrity sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==
+ dependencies:
+ indent-string "^4.0.0"
+ strip-indent "^3.0.0"
+
reflect.getprototypeof@^1.0.6, reflect.getprototypeof@^1.0.9:
version "1.0.10"
resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz#c629219e78a3316d8b604c765ef68996964e7bf9"
@@ -6530,6 +6595,13 @@ strip-final-newline@^2.0.0:
resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad"
integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==
+strip-indent@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001"
+ integrity sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==
+ dependencies:
+ min-indent "^1.0.0"
+
strip-json-comments@^3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"