Skip to content

Commit

Permalink
MDS support for query insights dashboards (#71) (#72)
Browse files Browse the repository at this point in the history
* Add support for MDS



* MDS for all paths, pages with dummy dataSourceId



* Fix all pages and apis



* Push data source into the url



* Persist data source selection across pages



* fix when local cluster id is empty



* fix manage link based on OpenSearch-Dashboards/pull/9059



* refresh page data when data source is changed



* fix lint



* fix unit tests and cypress tests



* make data source picker read only on detail pages



---------






(cherry picked from commit e4ccdb8)

Signed-off-by: Derek Ho <[email protected]>
Signed-off-by: David Zane <[email protected]>
Signed-off-by: Chenyang Ji <[email protected]>
Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Derek Ho <[email protected]>
Co-authored-by: David Zane <[email protected]>
4 people authored Jan 27, 2025
1 parent c3b2c47 commit c58cc2b
Showing 22 changed files with 852 additions and 338 deletions.
37 changes: 26 additions & 11 deletions cypress/e2e/3_configurations.cy.js
Original file line number Diff line number Diff line change
@@ -11,6 +11,13 @@ const clearAll = () => {
cy.disableTopQueries(METRICS.MEMORY);
};

const toggleMetricEnabled = async () => {
cy.get('button[data-test-subj="top-n-metric-toggle"]').trigger('mouseover');
cy.wait(1000);
cy.get('button[data-test-subj="top-n-metric-toggle"]').click({ force: true });
cy.wait(1000);
};

describe('Query Insights Configurations Page', () => {
beforeEach(() => {
clearAll();
@@ -66,22 +73,30 @@ describe('Query Insights Configurations Page', () => {
*/
it('should allow enabling and disabling metrics', () => {
// Validate the switch for enabling/disabling metrics
cy.get('button[role="switch"]').should('exist');
// Toggle the switch
cy.get('button[role="switch"]')
.first()
.should('have.attr', 'aria-checked', 'false') // Initially disabled
.click()
.should('have.attr', 'aria-checked', 'true'); // After toggling, it should be enabled
cy.get('button[data-test-subj="top-n-metric-toggle"]')
.should('exist')
.and('have.attr', 'aria-checked', 'false') // Initially disabled)
.trigger('mouseover')
.click();
cy.wait(1000);

cy.get('button[data-test-subj="top-n-metric-toggle"]').should(
'have.attr',
'aria-checked',
'true'
); // After toggling, it should be enabled
// Re-enable the switch
cy.get('button[role="switch"]').first().click().should('have.attr', 'aria-checked', 'false');
cy.get('button[data-test-subj="top-n-metric-toggle"]')
.trigger('mouseover')
.click()
.should('have.attr', 'aria-checked', 'false');
});

/**
* Validate the value of N (count) input
*/
it('should allow updating the value of N (count)', () => {
cy.get('button[role="switch"]').first().click();
toggleMetricEnabled();
// Locate the input for N
cy.get('input[type="number"]').should('have.attr', 'value', '3'); // Default 3
// Change the value to 50
@@ -95,7 +110,7 @@ describe('Query Insights Configurations Page', () => {
* Validate the window size dropdowns
*/
it('should allow selecting a window size and unit', () => {
cy.get('button[role="switch"]').first().click();
toggleMetricEnabled();
// Validate default values
cy.get('select#timeUnit').should('have.value', 'MINUTES'); // Default unit is "Minute(s)"
// Test valid time unit selection
@@ -192,7 +207,7 @@ describe('Query Insights Configurations Page', () => {
* After saving the status panel should show the correct status
*/
it('should allow saving the configuration', () => {
cy.get('button[role="switch"]').first().click();
toggleMetricEnabled();
cy.get('select#timeUnit').select('MINUTES');
cy.get('select#minutes').select('5');
cy.get('button[data-test-subj="save-config-button"]').click();
3 changes: 2 additions & 1 deletion opensearch_dashboards.json
Original file line number Diff line number Diff line change
@@ -5,5 +5,6 @@
"server": true,
"ui": true,
"requiredPlugins": ["navigation"],
"optionalPlugins": []
"optionalPlugins": ["dataSource",
"dataSourceManagement"]
}
15 changes: 11 additions & 4 deletions public/application.tsx
Original file line number Diff line number Diff line change
@@ -9,18 +9,25 @@ import { HashRouter as Router } from 'react-router-dom';
import { AppMountParameters, CoreStart } from '../../../src/core/public';
import { QueryInsightsDashboardsApp } from './components/app';
import { QueryInsightsDashboardsPluginStartDependencies } from './types';
import { DataSourceManagementPluginSetup } from '../../../src/plugins/data_source_management/public';

export const renderApp = (
core: CoreStart,
depsStart: QueryInsightsDashboardsPluginStartDependencies,
{ element }: AppMountParameters
params: AppMountParameters,
dataSourceManagement?: DataSourceManagementPluginSetup
) => {
ReactDOM.render(
<Router>
<QueryInsightsDashboardsApp core={core} depsStart={depsStart} />
<QueryInsightsDashboardsApp
core={core}
depsStart={depsStart}
params={params}
dataSourceManagement={dataSourceManagement}
/>
</Router>,
element
params.element
);

return () => ReactDOM.unmountComponentAtNode(element);
return () => ReactDOM.unmountComponentAtNode(params.element);
};
88 changes: 88 additions & 0 deletions public/components/DataSourcePicker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import React from 'react';
import {
DataSourceManagementPluginSetup,
DataSourceOption,
DataSourceSelectableConfig,
} from 'src/plugins/data_source_management/public';
import { AppMountParameters, CoreStart } from '../../../../src/core/public';
import { QueryInsightsDashboardsPluginStartDependencies } from '../types';

export interface DataSourceMenuProps {
dataSourceManagement: DataSourceManagementPluginSetup;
depsStart: QueryInsightsDashboardsPluginStartDependencies;
coreStart: CoreStart;
params: AppMountParameters;
setDataSource: React.Dispatch<React.SetStateAction<DataSourceOption>>;
selectedDataSource: DataSourceOption;
onManageDataSource: () => void;
onSelectedDataSource: () => void;
dataSourcePickerReadOnly: boolean;
}

export function getDataSourceEnabledUrl(dataSource: DataSourceOption) {
const url = new URL(window.location.href);
url.searchParams.set('dataSource', JSON.stringify(dataSource));
return url;
}

export function getDataSourceFromUrl(): DataSourceOption {
const urlParams = new URLSearchParams(window.location.search);
const dataSourceParam = (urlParams && urlParams.get('dataSource')) || '{}';
// following block is needed if the dataSource param is set to non-JSON value, say 'undefined'
try {
return JSON.parse(dataSourceParam);
} catch (e) {
return JSON.parse('{}'); // Return an empty object or some default value if parsing fails
}
}

export const QueryInsightsDataSourceMenu = React.memo(
(props: DataSourceMenuProps) => {
const {
coreStart,
depsStart,
dataSourceManagement,
params,
setDataSource,
selectedDataSource,
onManageDataSource,
onSelectedDataSource,
dataSourcePickerReadOnly,
} = props;
const { setHeaderActionMenu } = params;
const DataSourceMenu = dataSourceManagement?.ui.getDataSourceMenu<DataSourceSelectableConfig>();

const dataSourceEnabled = !!depsStart.dataSource?.dataSourceEnabled;

const wrapSetDataSourceWithUpdateUrl = (dataSources: DataSourceOption[]) => {
window.history.replaceState({}, '', getDataSourceEnabledUrl(dataSources[0]).toString());
setDataSource(dataSources[0]);
onSelectedDataSource();
};

return dataSourceEnabled ? (
<DataSourceMenu
onManageDataSource={onManageDataSource}
setMenuMountPoint={setHeaderActionMenu}
componentType={dataSourcePickerReadOnly ? 'DataSourceView' : 'DataSourceSelectable'}
componentConfig={{
onManageDataSource,
savedObjects: coreStart.savedObjects.client,
notifications: coreStart.notifications,
activeOption:
selectedDataSource.id || selectedDataSource.label ? [selectedDataSource] : undefined,
onSelectedDataSources: wrapSetDataSourceWithUpdateUrl,
fullWidth: true,
}}
/>
) : null;
},
(prevProps, newProps) =>
prevProps.selectedDataSource.id === newProps.selectedDataSource.id &&
prevProps.dataSourcePickerReadOnly === newProps.dataSourcePickerReadOnly
);
39 changes: 26 additions & 13 deletions public/components/app.test.tsx
Original file line number Diff line number Diff line change
@@ -8,28 +8,41 @@ import { render } from '@testing-library/react';
import { coreMock } from '../../../../src/core/public/mocks';
import { MemoryRouter as Router } from 'react-router-dom';
import { QueryInsightsDashboardsApp } from './app';
import { createMemoryHistory } from 'history';

describe('<QueryInsightsDashboardsApp /> spec', () => {
it('renders the component', () => {
const mockHttpStart = {
basePath: {
serverBasePath: '/app/opensearch-dashboards',
const coreStart = coreMock.createStart();
// Mock AppMountParameters
const params = {
appBasePath: '/',
history: createMemoryHistory(),
setHeaderActionMenu: jest.fn(),
element: document.createElement('div'),
};
// Mock plugin dependencies
const depsStart = {
navigation: {
ui: { TopNavMenu: () => null },
},
data: {
dataSources: {
dataSourceService: jest.fn(),
},
},
};
const coreStart = coreMock.createStart();

const { container } = render(
<Router>
<QueryInsightsDashboardsApp
basename="/"
core={coreStart}
http={mockHttpStart as any}
navigation={
{
ui: { TopNavMenu: () => null },
} as any
}
notifications={{} as any}
depsStart={depsStart}
params={params}
dataSourceManagement={{
ui: {
getDataSourceMenu: jest.fn(),
getDataSourceSelector: jest.fn(),
},
}}
/>
</Router>
);
20 changes: 18 additions & 2 deletions public/components/app.tsx
Original file line number Diff line number Diff line change
@@ -5,16 +5,32 @@

import React from 'react';
import { Route } from 'react-router-dom';
import { DataSourceManagementPluginSetup } from 'src/plugins/data_source_management/public';
import TopNQueries from '../pages/TopNQueries/TopNQueries';
import { CoreStart } from '../../../../src/core/public';
import { AppMountParameters, CoreStart } from '../../../../src/core/public';
import { QueryInsightsDashboardsPluginStartDependencies } from '../types';

export const QueryInsightsDashboardsApp = ({
core,
depsStart,
params,
dataSourceManagement,
}: {
core: CoreStart;
depsStart: QueryInsightsDashboardsPluginStartDependencies;
params: AppMountParameters;
dataSourceManagement?: DataSourceManagementPluginSetup;
}) => {
return <Route render={() => <TopNQueries core={core} depsStart={depsStart} />} />;
return (
<Route
render={() => (
<TopNQueries
core={core}
depsStart={depsStart}
params={params}
dataSourceManagement={dataSourceManagement}
/>
)}
/>
);
};
34 changes: 26 additions & 8 deletions public/pages/Configuration/Configuration.test.tsx
Original file line number Diff line number Diff line change
@@ -8,6 +8,7 @@ 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';
import { DataSourceContext } from '../TopNQueries/TopNQueries';

const mockConfigInfo = jest.fn();
const mockCoreStart = {
@@ -39,17 +40,34 @@ const groupBySettings = {
groupBy: 'SIMILARITY',
};

const dataSourceMenuMock = jest.fn(() => <div>Mock DataSourceMenu</div>);

const dataSourceManagementMock = {
ui: {
getDataSourceMenu: jest.fn().mockReturnValue(dataSourceMenuMock),
},
};
const mockDataSourceContext = {
dataSource: { id: 'test', label: 'Test' },
setDataSource: jest.fn(),
};

const renderConfiguration = (overrides = {}) =>
render(
<MemoryRouter>
<Configuration
latencySettings={{ ...defaultLatencySettings, ...overrides }}
cpuSettings={defaultCpuSettings}
memorySettings={defaultMemorySettings}
groupBySettings={groupBySettings}
configInfo={mockConfigInfo}
core={mockCoreStart}
/>
<DataSourceContext.Provider value={mockDataSourceContext}>
<Configuration
latencySettings={{ ...defaultLatencySettings, ...overrides }}
cpuSettings={defaultCpuSettings}
memorySettings={defaultMemorySettings}
groupBySettings={groupBySettings}
configInfo={mockConfigInfo}
core={mockCoreStart}
depsStart={{ navigation: {} }}
params={{} as any}
dataSourceManagement={dataSourceManagementMock}
/>
</DataSourceContext.Provider>
</MemoryRouter>
);

Loading

0 comments on commit c58cc2b

Please sign in to comment.