diff --git a/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataQueriesTab.test.tsx b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataQueriesTab.test.tsx index e3cb5a4f4b1..aad14f4fa8b 100644 --- a/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataQueriesTab.test.tsx +++ b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataQueriesTab.test.tsx @@ -175,6 +175,9 @@ const MixedDs = { id: 'grafana', mixed: true, }, + getRef: () => { + return { type: 'datasource', uid: '-- Mixed --' }; + }, }; const MixedDsSettingsMock = { @@ -760,6 +763,67 @@ describe('PanelDataQueriesTab', () => { }); }); }); + + describe('updateDatasourceIfNeeded', () => { + it('should update datasource when different datasource reference is provided', async () => { + const { queriesTab } = await setupScene('panel-1'); + + // Initially should have testdata datasource + expect(queriesTab.state.datasource?.uid).toBe('gdev-testdata'); + + // Call updateDatasourceIfNeeded with prometheus datasource + await queriesTab.updateDatasourceIfNeeded({ uid: 'gdev-prometheus' }); + + // Should update to prometheus datasource + expect(queriesTab.state.datasource?.uid).toBe('gdev-prometheus'); + expect(queriesTab.state.dsSettings?.uid).toBe('gdev-prometheus'); + }); + + it('should not update datasource when same datasource reference is provided', async () => { + const { queriesTab } = await setupScene('panel-1'); + + // Initially should have testdata datasource + expect(queriesTab.state.datasource?.uid).toBe('gdev-testdata'); + + const originalDatasource = queriesTab.state.datasource; + const originalDsSettings = queriesTab.state.dsSettings; + + // Call updateDatasourceIfNeeded with same datasource + await queriesTab.updateDatasourceIfNeeded({ uid: 'gdev-testdata' }); + + // Should not change the datasource + expect(queriesTab.state.datasource).toBe(originalDatasource); + expect(queriesTab.state.dsSettings).toBe(originalDsSettings); + }); + + it('should update datasource to mixed when mixed datasource reference is provided', async () => { + const { queriesTab } = await setupScene('panel-1'); + + // Initially should have testdata datasource + expect(queriesTab.state.datasource?.uid).toBe('gdev-testdata'); + + // Call updateDatasourceIfNeeded with mixed datasource + await queriesTab.updateDatasourceIfNeeded({ uid: '-- Mixed --' }); + + // Should update to mixed datasource + expect(queriesTab.state.datasource?.uid).toBe('-- Mixed --'); + expect(queriesTab.state.dsSettings?.uid).toBe('-- Mixed --'); + }); + + it('should handle case when datasource instance settings are not found', async () => { + const { queriesTab } = await setupScene('panel-1'); + + // Initially should have testdata datasource + expect(queriesTab.state.datasource?.uid).toBe('gdev-testdata'); + + // Call updateDatasourceIfNeeded with non-existent datasource + await queriesTab.updateDatasourceIfNeeded({ uid: 'non-existent-ds' }); + + // Should fall back to default datasource (since mock returns default when not found) + expect(queriesTab.state.datasource?.uid).toBe('gdev-testdata'); + expect(queriesTab.state.dsSettings?.uid).toBe('gdev-testdata'); + }); + }); }); }); diff --git a/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataQueriesTab.tsx b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataQueriesTab.tsx index d46b7270cb5..9af2af9d11f 100644 --- a/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataQueriesTab.tsx +++ b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataQueriesTab.tsx @@ -12,7 +12,7 @@ import { SceneObjectState, SceneDataQuery, } from '@grafana/scenes'; -import { DataQuery } from '@grafana/schema'; +import { DataQuery, DataSourceRef } from '@grafana/schema'; import { Button, Stack, Tab } from '@grafana/ui'; import { addQuery } from 'app/core/utils/query'; import { getLastUsedDatasourceFromStorage } from 'app/features/dashboard/utils/dashboard'; @@ -315,6 +315,21 @@ export class PanelDataQueriesTab extends SceneObjectBase => { + const { datasource } = this.state; + const shouldChangeDatasource = datasource?.uid !== newDatasourceRef.uid; + if (shouldChangeDatasource) { + const newDatasource = getDatasourceSrv().getInstanceSettings(newDatasourceRef); + if (newDatasource) { + await this.onChangeDataSource(newDatasource); + } + } + }; } export function PanelDataQueriesTabRendered({ model }: SceneComponentProps) { @@ -339,6 +354,7 @@ export function PanelDataQueriesTabRendered({ model }: SceneComponentProps q.datasource?.uid).filter((uid) => uid !== ExpressionDatasourceUID) @@ -347,13 +363,7 @@ export function PanelDataQueriesTabRendered({ model }: SceneComponentProps diff --git a/public/app/features/query/components/QueryEditorRows.test.tsx b/public/app/features/query/components/QueryEditorRows.test.tsx index 220a1a75cf1..c61ec4b15c1 100644 --- a/public/app/features/query/components/QueryEditorRows.test.tsx +++ b/public/app/features/query/components/QueryEditorRows.test.tsx @@ -87,6 +87,117 @@ describe('QueryEditorRows', () => { ); }); + it('Should call onUpdateDatasources when replacing query with different datasource creates mixed scenario', () => { + const onQueriesChangeMock = jest.fn(); + const onUpdateDatasourcesMock = jest.fn(); + + const testProps = { + ...props, + onQueriesChange: onQueriesChangeMock, + onUpdateDatasources: onUpdateDatasourcesMock, + dsSettings: { ...props.dsSettings, uid: 'current-datasource' }, + queries: [ + { datasource: { uid: 'current-datasource', type: 'alertmanager' }, refId: 'A' }, + { datasource: { uid: 'current-datasource', type: 'alertmanager' }, refId: 'B' }, + ], + }; + + const component = new QueryEditorRows(testProps); + const replacementQuery = { + refId: 'A', + datasource: { uid: 'different-datasource', type: 'prometheus' }, + expr: 'new query content', + }; + + component.onReplaceQuery(replacementQuery, 0); + + expect(onUpdateDatasourcesMock).toHaveBeenCalledWith({ + uid: MIXED_DATASOURCE_NAME, + }); + }); + + it('Should call onUpdateDatasources when replacing query results in single different datasource', () => { + const onQueriesChangeMock = jest.fn(); + const onUpdateDatasourcesMock = jest.fn(); + + const testProps = { + ...props, + onQueriesChange: onQueriesChangeMock, + onUpdateDatasources: onUpdateDatasourcesMock, + dsSettings: { ...props.dsSettings, uid: 'current-datasource' }, + queries: [{ datasource: { uid: 'current-datasource', type: 'alertmanager' }, refId: 'A' }], + }; + + const component = new QueryEditorRows(testProps); + const replacementQuery = { + refId: 'A', + datasource: { uid: 'different-datasource', type: 'prometheus' }, + expr: 'new query content', + }; + + component.onReplaceQuery(replacementQuery, 0); + + expect(onUpdateDatasourcesMock).toHaveBeenCalledWith({ + uid: 'different-datasource', + }); + }); + + it('Should not call onUpdateDatasources when replacing query with same datasource', () => { + const onQueriesChangeMock = jest.fn(); + const onUpdateDatasourcesMock = jest.fn(); + + const testProps = { + ...props, + onQueriesChange: onQueriesChangeMock, + onUpdateDatasources: onUpdateDatasourcesMock, + dsSettings: { ...props.dsSettings, uid: 'same-datasource' }, + queries: [ + { datasource: { uid: 'same-datasource', type: 'prometheus' }, refId: 'A' }, + { datasource: { uid: 'same-datasource', type: 'prometheus' }, refId: 'B' }, + ], + }; + + const component = new QueryEditorRows(testProps); + const replacementQuery = { + refId: 'A', + datasource: { uid: 'same-datasource', type: 'prometheus' }, + expr: 'new query content', + }; + + component.onReplaceQuery(replacementQuery, 0); + + expect(onUpdateDatasourcesMock).not.toHaveBeenCalled(); + }); + + it('Should call onUpdateDatasources with mixed datasource when replacing creates mixed scenario', () => { + const onQueriesChangeMock = jest.fn(); + const onUpdateDatasourcesMock = jest.fn(); + + const testProps = { + ...props, + onQueriesChange: onQueriesChangeMock, + onUpdateDatasources: onUpdateDatasourcesMock, + dsSettings: { ...props.dsSettings, uid: 'current-datasource' }, + queries: [ + { datasource: { uid: 'datasource-1', type: 'loki' }, refId: 'A' }, + { datasource: { uid: 'datasource-2', type: 'test-data' }, refId: 'B' }, + ], + }; + + const component = new QueryEditorRows(testProps); + const replacementQuery = { + refId: 'A', + datasource: { uid: 'datasource-3', type: 'prometheus' }, + expr: 'new query content', + }; + + component.onReplaceQuery(replacementQuery, 0); + + expect(onUpdateDatasourcesMock).toHaveBeenCalledWith({ + uid: MIXED_DATASOURCE_NAME, + }); + }); + it('Should render queries', async () => { const { renderResult: { rerender },