* Skip buildinfo fetching for Loki data sources
* Fix and add tests
* Fix linter
* Improve typings
* Improve Loki's buildinfo notice
* Fix rename, improve prom app display name
(cherry picked from commit 43ab0c1f95)
This commit is contained in:
@@ -16,7 +16,7 @@ import { GrafanaAlertStateDecision, PromApplication } from 'app/types/unified-al
|
|||||||
import { searchFolders } from '../../../../app/features/manage-dashboards/state/actions';
|
import { searchFolders } from '../../../../app/features/manage-dashboards/state/actions';
|
||||||
|
|
||||||
import RuleEditor from './RuleEditor';
|
import RuleEditor from './RuleEditor';
|
||||||
import { fetchBuildInfo } from './api/buildInfo';
|
import { discoverFeatures } from './api/buildInfo';
|
||||||
import { fetchRulerRules, fetchRulerRulesGroup, fetchRulerRulesNamespace, setRulerRuleGroup } from './api/ruler';
|
import { fetchRulerRules, fetchRulerRulesGroup, fetchRulerRulesNamespace, setRulerRuleGroup } from './api/ruler';
|
||||||
import { ExpressionEditorProps } from './components/rule-editor/ExpressionEditor';
|
import { ExpressionEditorProps } from './components/rule-editor/ExpressionEditor';
|
||||||
import { mockDataSource, MockDataSourceSrv } from './mocks';
|
import { mockDataSource, MockDataSourceSrv } from './mocks';
|
||||||
@@ -47,7 +47,7 @@ const mocks = {
|
|||||||
getAllDataSources: jest.mocked(getAllDataSources),
|
getAllDataSources: jest.mocked(getAllDataSources),
|
||||||
searchFolders: jest.mocked(searchFolders),
|
searchFolders: jest.mocked(searchFolders),
|
||||||
api: {
|
api: {
|
||||||
fetchBuildInfo: jest.mocked(fetchBuildInfo),
|
discoverFeatures: jest.mocked(discoverFeatures),
|
||||||
fetchRulerRulesGroup: jest.mocked(fetchRulerRulesGroup),
|
fetchRulerRulesGroup: jest.mocked(fetchRulerRulesGroup),
|
||||||
setRulerRuleGroup: jest.mocked(setRulerRuleGroup),
|
setRulerRuleGroup: jest.mocked(setRulerRuleGroup),
|
||||||
fetchRulerRulesNamespace: jest.mocked(fetchRulerRulesNamespace),
|
fetchRulerRulesNamespace: jest.mocked(fetchRulerRulesNamespace),
|
||||||
@@ -136,8 +136,8 @@ describe('RuleEditor', () => {
|
|||||||
});
|
});
|
||||||
mocks.searchFolders.mockResolvedValue([]);
|
mocks.searchFolders.mockResolvedValue([]);
|
||||||
|
|
||||||
mocks.api.fetchBuildInfo.mockResolvedValue({
|
mocks.api.discoverFeatures.mockResolvedValue({
|
||||||
application: PromApplication.Cortex,
|
application: PromApplication.Lotex,
|
||||||
features: {
|
features: {
|
||||||
rulerApiEnabled: true,
|
rulerApiEnabled: true,
|
||||||
},
|
},
|
||||||
@@ -146,9 +146,9 @@ describe('RuleEditor', () => {
|
|||||||
await renderRuleEditor();
|
await renderRuleEditor();
|
||||||
await waitFor(() => expect(mocks.searchFolders).toHaveBeenCalled());
|
await waitFor(() => expect(mocks.searchFolders).toHaveBeenCalled());
|
||||||
|
|
||||||
await waitFor(() => expect(mocks.api.fetchBuildInfo).toHaveBeenCalled());
|
await waitFor(() => expect(mocks.api.discoverFeatures).toHaveBeenCalled());
|
||||||
userEvent.type(await ui.inputs.name.find(), 'my great new rule');
|
await userEvent.type(await ui.inputs.name.find(), 'my great new rule');
|
||||||
userEvent.click(await ui.buttons.lotexAlert.get());
|
await userEvent.click(await ui.buttons.lotexAlert.get());
|
||||||
const dataSourceSelect = ui.inputs.dataSource.get();
|
const dataSourceSelect = ui.inputs.dataSource.get();
|
||||||
userEvent.click(byRole('combobox').get(dataSourceSelect));
|
userEvent.click(byRole('combobox').get(dataSourceSelect));
|
||||||
await clickSelectOption(dataSourceSelect, 'Prom (default)');
|
await clickSelectOption(dataSourceSelect, 'Prom (default)');
|
||||||
@@ -235,7 +235,7 @@ describe('RuleEditor', () => {
|
|||||||
},
|
},
|
||||||
] as DashboardSearchHit[]);
|
] as DashboardSearchHit[]);
|
||||||
|
|
||||||
mocks.api.fetchBuildInfo.mockResolvedValue({
|
mocks.api.discoverFeatures.mockResolvedValue({
|
||||||
application: PromApplication.Prometheus,
|
application: PromApplication.Prometheus,
|
||||||
features: {
|
features: {
|
||||||
rulerApiEnabled: false,
|
rulerApiEnabled: false,
|
||||||
@@ -245,7 +245,7 @@ describe('RuleEditor', () => {
|
|||||||
// fill out the form
|
// fill out the form
|
||||||
await renderRuleEditor();
|
await renderRuleEditor();
|
||||||
await waitFor(() => expect(mocks.searchFolders).toHaveBeenCalled());
|
await waitFor(() => expect(mocks.searchFolders).toHaveBeenCalled());
|
||||||
await waitFor(() => expect(mocks.api.fetchBuildInfo).toHaveBeenCalled());
|
await waitFor(() => expect(mocks.api.discoverFeatures).toHaveBeenCalled());
|
||||||
|
|
||||||
userEvent.type(await ui.inputs.name.find(), 'my great new rule');
|
userEvent.type(await ui.inputs.name.find(), 'my great new rule');
|
||||||
|
|
||||||
@@ -329,8 +329,8 @@ describe('RuleEditor', () => {
|
|||||||
});
|
});
|
||||||
mocks.searchFolders.mockResolvedValue([]);
|
mocks.searchFolders.mockResolvedValue([]);
|
||||||
|
|
||||||
mocks.api.fetchBuildInfo.mockResolvedValue({
|
mocks.api.discoverFeatures.mockResolvedValue({
|
||||||
application: PromApplication.Cortex,
|
application: PromApplication.Lotex,
|
||||||
features: {
|
features: {
|
||||||
rulerApiEnabled: true,
|
rulerApiEnabled: true,
|
||||||
},
|
},
|
||||||
@@ -338,9 +338,9 @@ describe('RuleEditor', () => {
|
|||||||
|
|
||||||
await renderRuleEditor();
|
await renderRuleEditor();
|
||||||
await waitFor(() => expect(mocks.searchFolders).toHaveBeenCalled());
|
await waitFor(() => expect(mocks.searchFolders).toHaveBeenCalled());
|
||||||
await waitFor(() => expect(mocks.api.fetchBuildInfo).toHaveBeenCalled());
|
await waitFor(() => expect(mocks.api.discoverFeatures).toHaveBeenCalled());
|
||||||
userEvent.type(await ui.inputs.name.find(), 'my great new recording rule');
|
await userEvent.type(await ui.inputs.name.find(), 'my great new recording rule');
|
||||||
userEvent.click(await ui.buttons.lotexRecordingRule.get());
|
await userEvent.click(await ui.buttons.lotexRecordingRule.get());
|
||||||
|
|
||||||
const dataSourceSelect = ui.inputs.dataSource.get();
|
const dataSourceSelect = ui.inputs.dataSource.get();
|
||||||
userEvent.click(byRole('combobox').get(dataSourceSelect));
|
userEvent.click(byRole('combobox').get(dataSourceSelect));
|
||||||
@@ -449,7 +449,7 @@ describe('RuleEditor', () => {
|
|||||||
|
|
||||||
await renderRuleEditor(uid);
|
await renderRuleEditor(uid);
|
||||||
await waitFor(() => expect(mocks.searchFolders).toHaveBeenCalled());
|
await waitFor(() => expect(mocks.searchFolders).toHaveBeenCalled());
|
||||||
await waitFor(() => expect(mocks.api.fetchBuildInfo).toHaveBeenCalled());
|
await waitFor(() => expect(mocks.api.discoverFeatures).toHaveBeenCalled());
|
||||||
await waitFor(() => expect(mocks.searchFolders).toHaveBeenCalled());
|
await waitFor(() => expect(mocks.searchFolders).toHaveBeenCalled());
|
||||||
|
|
||||||
// check that it's filled in
|
// check that it's filled in
|
||||||
@@ -551,10 +551,10 @@ describe('RuleEditor', () => {
|
|||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
mocks.api.fetchBuildInfo.mockImplementation(async (dataSourceName) => {
|
mocks.api.discoverFeatures.mockImplementation(async (dataSourceName) => {
|
||||||
if (dataSourceName === 'loki with ruler' || dataSourceName === 'cortex with ruler') {
|
if (dataSourceName === 'loki with ruler' || dataSourceName === 'cortex with ruler') {
|
||||||
return {
|
return {
|
||||||
application: PromApplication.Cortex,
|
application: PromApplication.Lotex,
|
||||||
features: {
|
features: {
|
||||||
rulerApiEnabled: true,
|
rulerApiEnabled: true,
|
||||||
alertManagerConfigApi: false,
|
alertManagerConfigApi: false,
|
||||||
@@ -565,7 +565,7 @@ describe('RuleEditor', () => {
|
|||||||
}
|
}
|
||||||
if (dataSourceName === 'loki with local rule store') {
|
if (dataSourceName === 'loki with local rule store') {
|
||||||
return {
|
return {
|
||||||
application: PromApplication.Cortex,
|
application: PromApplication.Lotex,
|
||||||
features: {
|
features: {
|
||||||
rulerApiEnabled: false,
|
rulerApiEnabled: false,
|
||||||
alertManagerConfigApi: false,
|
alertManagerConfigApi: false,
|
||||||
@@ -576,7 +576,7 @@ describe('RuleEditor', () => {
|
|||||||
}
|
}
|
||||||
if (dataSourceName === 'cortex without ruler api') {
|
if (dataSourceName === 'cortex without ruler api') {
|
||||||
return {
|
return {
|
||||||
application: PromApplication.Cortex,
|
application: PromApplication.Lotex,
|
||||||
features: {
|
features: {
|
||||||
rulerApiEnabled: false,
|
rulerApiEnabled: false,
|
||||||
alertManagerConfigApi: false,
|
alertManagerConfigApi: false,
|
||||||
@@ -613,7 +613,7 @@ describe('RuleEditor', () => {
|
|||||||
|
|
||||||
// render rule editor, select mimir/loki managed alerts
|
// render rule editor, select mimir/loki managed alerts
|
||||||
await renderRuleEditor();
|
await renderRuleEditor();
|
||||||
await waitFor(() => expect(mocks.api.fetchBuildInfo).toHaveBeenCalled());
|
await waitFor(() => expect(mocks.api.discoverFeatures).toHaveBeenCalled());
|
||||||
await waitFor(() => expect(mocks.searchFolders).toHaveBeenCalled());
|
await waitFor(() => expect(mocks.searchFolders).toHaveBeenCalled());
|
||||||
|
|
||||||
await ui.inputs.name.find();
|
await ui.inputs.name.find();
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { AccessControlAction } from 'app/types';
|
|||||||
import { PromAlertingRuleState, PromApplication } from 'app/types/unified-alerting-dto';
|
import { PromAlertingRuleState, PromApplication } from 'app/types/unified-alerting-dto';
|
||||||
|
|
||||||
import RuleList from './RuleList';
|
import RuleList from './RuleList';
|
||||||
import { fetchBuildInfo } from './api/buildInfo';
|
import { discoverFeatures } from './api/buildInfo';
|
||||||
import { fetchRules } from './api/prometheus';
|
import { fetchRules } from './api/prometheus';
|
||||||
import { deleteNamespace, deleteRulerRulesGroup, fetchRulerRules, setRulerRuleGroup } from './api/ruler';
|
import { deleteNamespace, deleteRulerRulesGroup, fetchRulerRules, setRulerRuleGroup } from './api/ruler';
|
||||||
import {
|
import {
|
||||||
@@ -48,7 +48,7 @@ const mocks = {
|
|||||||
getAllDataSourcesMock: jest.mocked(getAllDataSources),
|
getAllDataSourcesMock: jest.mocked(getAllDataSources),
|
||||||
|
|
||||||
api: {
|
api: {
|
||||||
fetchBuildInfo: jest.mocked(fetchBuildInfo),
|
discoverFeatures: jest.mocked(discoverFeatures),
|
||||||
fetchRules: jest.mocked(fetchRules),
|
fetchRules: jest.mocked(fetchRules),
|
||||||
fetchRulerRules: jest.mocked(fetchRulerRules),
|
fetchRulerRules: jest.mocked(fetchRulerRules),
|
||||||
deleteGroup: jest.mocked(deleteRulerRulesGroup),
|
deleteGroup: jest.mocked(deleteRulerRulesGroup),
|
||||||
@@ -125,7 +125,7 @@ describe('RuleList', () => {
|
|||||||
|
|
||||||
setDataSourceSrv(new MockDataSourceSrv(dataSources));
|
setDataSourceSrv(new MockDataSourceSrv(dataSources));
|
||||||
|
|
||||||
mocks.api.fetchBuildInfo.mockResolvedValue({
|
mocks.api.discoverFeatures.mockResolvedValue({
|
||||||
application: PromApplication.Prometheus,
|
application: PromApplication.Prometheus,
|
||||||
features: {
|
features: {
|
||||||
rulerApiEnabled: true,
|
rulerApiEnabled: true,
|
||||||
@@ -217,8 +217,8 @@ describe('RuleList', () => {
|
|||||||
mocks.getAllDataSourcesMock.mockReturnValue([dataSources.prom]);
|
mocks.getAllDataSourcesMock.mockReturnValue([dataSources.prom]);
|
||||||
|
|
||||||
setDataSourceSrv(new MockDataSourceSrv({ prom: dataSources.prom }));
|
setDataSourceSrv(new MockDataSourceSrv({ prom: dataSources.prom }));
|
||||||
mocks.api.fetchBuildInfo.mockResolvedValue({
|
mocks.api.discoverFeatures.mockResolvedValue({
|
||||||
application: PromApplication.Cortex,
|
application: PromApplication.Lotex,
|
||||||
features: {
|
features: {
|
||||||
rulerApiEnabled: true,
|
rulerApiEnabled: true,
|
||||||
},
|
},
|
||||||
@@ -359,8 +359,8 @@ describe('RuleList', () => {
|
|||||||
mocks.getAllDataSourcesMock.mockReturnValue([dataSources.prom]);
|
mocks.getAllDataSourcesMock.mockReturnValue([dataSources.prom]);
|
||||||
setDataSourceSrv(new MockDataSourceSrv({ prom: dataSources.prom }));
|
setDataSourceSrv(new MockDataSourceSrv({ prom: dataSources.prom }));
|
||||||
|
|
||||||
mocks.api.fetchBuildInfo.mockResolvedValue({
|
mocks.api.discoverFeatures.mockResolvedValue({
|
||||||
application: PromApplication.Cortex,
|
application: PromApplication.Lotex,
|
||||||
features: {
|
features: {
|
||||||
rulerApiEnabled: true,
|
rulerApiEnabled: true,
|
||||||
},
|
},
|
||||||
@@ -504,8 +504,8 @@ describe('RuleList', () => {
|
|||||||
mocks.getAllDataSourcesMock.mockReturnValue(Object.values(testDatasources));
|
mocks.getAllDataSourcesMock.mockReturnValue(Object.values(testDatasources));
|
||||||
setDataSourceSrv(new MockDataSourceSrv(testDatasources));
|
setDataSourceSrv(new MockDataSourceSrv(testDatasources));
|
||||||
|
|
||||||
mocks.api.fetchBuildInfo.mockResolvedValue({
|
mocks.api.discoverFeatures.mockResolvedValue({
|
||||||
application: PromApplication.Cortex,
|
application: PromApplication.Lotex,
|
||||||
features: {
|
features: {
|
||||||
rulerApiEnabled: true,
|
rulerApiEnabled: true,
|
||||||
},
|
},
|
||||||
@@ -690,8 +690,8 @@ describe('RuleList', () => {
|
|||||||
|
|
||||||
mocks.getAllDataSourcesMock.mockReturnValue([dataSources.prom]);
|
mocks.getAllDataSourcesMock.mockReturnValue([dataSources.prom]);
|
||||||
setDataSourceSrv(new MockDataSourceSrv({ prom: dataSources.prom }));
|
setDataSourceSrv(new MockDataSourceSrv({ prom: dataSources.prom }));
|
||||||
mocks.api.fetchBuildInfo.mockResolvedValue({
|
mocks.api.discoverFeatures.mockResolvedValue({
|
||||||
application: PromApplication.Cortex,
|
application: PromApplication.Lotex,
|
||||||
features: {
|
features: {
|
||||||
rulerApiEnabled: true,
|
rulerApiEnabled: true,
|
||||||
},
|
},
|
||||||
@@ -717,8 +717,8 @@ describe('RuleList', () => {
|
|||||||
|
|
||||||
mocks.getAllDataSourcesMock.mockReturnValue([dataSources.prom]);
|
mocks.getAllDataSourcesMock.mockReturnValue([dataSources.prom]);
|
||||||
setDataSourceSrv(new MockDataSourceSrv({ prom: dataSources.prom }));
|
setDataSourceSrv(new MockDataSourceSrv({ prom: dataSources.prom }));
|
||||||
mocks.api.fetchBuildInfo.mockResolvedValue({
|
mocks.api.discoverFeatures.mockResolvedValue({
|
||||||
application: PromApplication.Cortex,
|
application: PromApplication.Lotex,
|
||||||
features: {
|
features: {
|
||||||
rulerApiEnabled: true,
|
rulerApiEnabled: true,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { of, throwError } from 'rxjs';
|
|||||||
|
|
||||||
import { PromApplication } from 'app/types/unified-alerting-dto';
|
import { PromApplication } from 'app/types/unified-alerting-dto';
|
||||||
|
|
||||||
import { fetchDataSourceBuildInfo } from './buildInfo';
|
import { discoverDataSourceFeatures } from './buildInfo';
|
||||||
import { fetchRules } from './prometheus';
|
import { fetchRules } from './prometheus';
|
||||||
import { fetchTestRulerRulesGroup } from './ruler';
|
import { fetchTestRulerRulesGroup } from './ruler';
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@ const mocks = {
|
|||||||
|
|
||||||
beforeEach(() => jest.clearAllMocks());
|
beforeEach(() => jest.clearAllMocks());
|
||||||
|
|
||||||
describe('buildInfo', () => {
|
describe('discoverDataSourceFeatures', () => {
|
||||||
describe('When buildinfo returns 200 response', () => {
|
describe('When buildinfo returns 200 response', () => {
|
||||||
it('Should return Prometheus with disabled ruler API when application and features fields are missing', async () => {
|
it('Should return Prometheus with disabled ruler API when application and features fields are missing', async () => {
|
||||||
fetch.mockReturnValue(
|
fetch.mockReturnValue(
|
||||||
@@ -36,7 +36,11 @@ describe('buildInfo', () => {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const response = await fetchDataSourceBuildInfo({ url: '/datasource/proxy', name: 'Prometheus' });
|
const response = await discoverDataSourceFeatures({
|
||||||
|
url: '/datasource/proxy',
|
||||||
|
name: 'Prometheus',
|
||||||
|
type: 'prometheus',
|
||||||
|
});
|
||||||
|
|
||||||
expect(response.application).toBe(PromApplication.Prometheus);
|
expect(response.application).toBe(PromApplication.Prometheus);
|
||||||
expect(response.features.rulerApiEnabled).toBe(false);
|
expect(response.features.rulerApiEnabled).toBe(false);
|
||||||
@@ -63,7 +67,11 @@ describe('buildInfo', () => {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const response = await fetchDataSourceBuildInfo({ url: '/datasource/proxy', name: 'Prometheus' });
|
const response = await discoverDataSourceFeatures({
|
||||||
|
url: '/datasource/proxy',
|
||||||
|
name: 'Prometheus',
|
||||||
|
type: 'prometheus',
|
||||||
|
});
|
||||||
|
|
||||||
expect(response.application).toBe(PromApplication.Mimir);
|
expect(response.application).toBe(PromApplication.Mimir);
|
||||||
expect(response.features.rulerApiEnabled).toBe(rulerApiEnabled);
|
expect(response.features.rulerApiEnabled).toBe(rulerApiEnabled);
|
||||||
@@ -71,6 +79,28 @@ describe('buildInfo', () => {
|
|||||||
expect(mocks.fetchTestRulerRulesGroup).not.toHaveBeenCalled();
|
expect(mocks.fetchTestRulerRulesGroup).not.toHaveBeenCalled();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
it('When the data source is Loki should not call the buildinfo endpoint', async () => {
|
||||||
|
await discoverDataSourceFeatures({ url: '/datasource/proxy', name: 'Loki', type: 'loki' });
|
||||||
|
|
||||||
|
expect(fetch).not.toBeCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('When the data source is Loki should test Prom and Ruler API endpoints to discover available features', async () => {
|
||||||
|
mocks.fetchTestRulerRulesGroup.mockResolvedValue(null);
|
||||||
|
mocks.fetchRules.mockResolvedValue([]);
|
||||||
|
|
||||||
|
const response = await discoverDataSourceFeatures({ url: '/datasource/proxy', name: 'Loki', type: 'loki' });
|
||||||
|
|
||||||
|
expect(response.application).toBe(PromApplication.Lotex);
|
||||||
|
expect(response.features.rulerApiEnabled).toBe(true);
|
||||||
|
|
||||||
|
expect(mocks.fetchTestRulerRulesGroup).toHaveBeenCalledTimes(1);
|
||||||
|
expect(mocks.fetchTestRulerRulesGroup).toHaveBeenCalledWith('Loki');
|
||||||
|
|
||||||
|
expect(mocks.fetchRules).toHaveBeenCalledTimes(1);
|
||||||
|
expect(mocks.fetchRules).toHaveBeenCalledWith('Loki');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('When buildinfo returns 404 error', () => {
|
describe('When buildinfo returns 404 error', () => {
|
||||||
@@ -89,9 +119,13 @@ describe('buildInfo', () => {
|
|||||||
});
|
});
|
||||||
mocks.fetchRules.mockResolvedValue([]);
|
mocks.fetchRules.mockResolvedValue([]);
|
||||||
|
|
||||||
const response = await fetchDataSourceBuildInfo({ url: '/datasource/proxy', name: 'Cortex' });
|
const response = await discoverDataSourceFeatures({
|
||||||
|
url: '/datasource/proxy',
|
||||||
|
name: 'Cortex',
|
||||||
|
type: 'prometheus',
|
||||||
|
});
|
||||||
|
|
||||||
expect(response.application).toBe(PromApplication.Cortex);
|
expect(response.application).toBe(PromApplication.Lotex);
|
||||||
expect(response.features.rulerApiEnabled).toBe(false);
|
expect(response.features.rulerApiEnabled).toBe(false);
|
||||||
|
|
||||||
expect(mocks.fetchTestRulerRulesGroup).toHaveBeenCalledTimes(1);
|
expect(mocks.fetchTestRulerRulesGroup).toHaveBeenCalledTimes(1);
|
||||||
@@ -111,9 +145,13 @@ describe('buildInfo', () => {
|
|||||||
mocks.fetchTestRulerRulesGroup.mockResolvedValue(null);
|
mocks.fetchTestRulerRulesGroup.mockResolvedValue(null);
|
||||||
mocks.fetchRules.mockResolvedValue([]);
|
mocks.fetchRules.mockResolvedValue([]);
|
||||||
|
|
||||||
const response = await fetchDataSourceBuildInfo({ url: '/datasource/proxy', name: 'Cortex' });
|
const response = await discoverDataSourceFeatures({
|
||||||
|
url: '/datasource/proxy',
|
||||||
|
name: 'Cortex',
|
||||||
|
type: 'prometheus',
|
||||||
|
});
|
||||||
|
|
||||||
expect(response.application).toBe(PromApplication.Cortex);
|
expect(response.application).toBe(PromApplication.Lotex);
|
||||||
expect(response.features.rulerApiEnabled).toBe(true);
|
expect(response.features.rulerApiEnabled).toBe(true);
|
||||||
|
|
||||||
expect(mocks.fetchTestRulerRulesGroup).toHaveBeenCalledTimes(1);
|
expect(mocks.fetchTestRulerRulesGroup).toHaveBeenCalledTimes(1);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { lastValueFrom } from 'rxjs';
|
import { lastValueFrom } from 'rxjs';
|
||||||
|
|
||||||
import { getBackendSrv } from '@grafana/runtime';
|
import { getBackendSrv } from '@grafana/runtime';
|
||||||
import { PromApplication, PromBuildInfo, PromBuildInfoResponse } from 'app/types/unified-alerting-dto';
|
import { PromApplication, PromApiFeatures, PromBuildInfoResponse } from 'app/types/unified-alerting-dto';
|
||||||
|
|
||||||
import { isFetchError } from '../utils/alertmanager';
|
import { isFetchError } from '../utils/alertmanager';
|
||||||
import { RULER_NOT_SUPPORTED_MSG } from '../utils/constants';
|
import { RULER_NOT_SUPPORTED_MSG } from '../utils/constants';
|
||||||
@@ -18,27 +18,22 @@ import { fetchTestRulerRulesGroup } from './ruler';
|
|||||||
* Prometheus and Mimir expose a `buildinfo` endpoint, Cortex does not.
|
* Prometheus and Mimir expose a `buildinfo` endpoint, Cortex does not.
|
||||||
* Mimir reports which "features" are enabled or available via the buildinfo endpoint, Prometheus does not.
|
* Mimir reports which "features" are enabled or available via the buildinfo endpoint, Prometheus does not.
|
||||||
*/
|
*/
|
||||||
export async function fetchDataSourceBuildInfo(dsSettings: { url: string; name: string }): Promise<PromBuildInfo> {
|
export async function discoverDataSourceFeatures(dsSettings: {
|
||||||
const { url, name } = dsSettings;
|
url: string;
|
||||||
|
name: string;
|
||||||
|
type: 'prometheus' | 'loki';
|
||||||
|
}): Promise<PromApiFeatures> {
|
||||||
|
const { url, name, type } = dsSettings;
|
||||||
|
|
||||||
const response = await lastValueFrom(
|
// The current implementation of Loki's build info endpoint is useless
|
||||||
getBackendSrv().fetch<PromBuildInfoResponse>({
|
// because it doesn't provide information about Loki's available features (e.g. Ruler API)
|
||||||
url: `${url}/api/v1/status/buildinfo`,
|
// It's better to skip fetching it for Loki and go the Cortex path (manual discovery)
|
||||||
showErrorAlert: false,
|
const buildInfoResponse = type === 'prometheus' ? await fetchPromBuildInfo(url) : undefined;
|
||||||
showSuccessAlert: false,
|
|
||||||
})
|
|
||||||
).catch((e) => {
|
|
||||||
if ('status' in e && e.status === 404) {
|
|
||||||
return null; // Cortex does not support buildinfo endpoint, we return an empty response
|
|
||||||
}
|
|
||||||
|
|
||||||
throw e;
|
|
||||||
});
|
|
||||||
|
|
||||||
// check if the component returns buildinfo
|
// check if the component returns buildinfo
|
||||||
const hasBuildInfo = response !== null;
|
const hasBuildInfo = buildInfoResponse !== undefined;
|
||||||
|
|
||||||
// we are dealing with a Cortex datasource since the response for buildinfo came up empty
|
// we are dealing with a Cortex or Loki datasource since the response for buildinfo came up empty
|
||||||
if (!hasBuildInfo) {
|
if (!hasBuildInfo) {
|
||||||
// check if we can fetch rules via the prometheus compatible api
|
// check if we can fetch rules via the prometheus compatible api
|
||||||
const promRulesSupported = await hasPromRulesSupport(name);
|
const promRulesSupported = await hasPromRulesSupport(name);
|
||||||
@@ -50,7 +45,7 @@ export async function fetchDataSourceBuildInfo(dsSettings: { url: string; name:
|
|||||||
const rulerSupported = await hasRulerSupport(name);
|
const rulerSupported = await hasRulerSupport(name);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
application: PromApplication.Cortex,
|
application: PromApplication.Lotex,
|
||||||
features: {
|
features: {
|
||||||
rulerApiEnabled: rulerSupported,
|
rulerApiEnabled: rulerSupported,
|
||||||
},
|
},
|
||||||
@@ -58,7 +53,7 @@ export async function fetchDataSourceBuildInfo(dsSettings: { url: string; name:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// if no features are reported but buildinfo was return we're talking to Prometheus
|
// if no features are reported but buildinfo was return we're talking to Prometheus
|
||||||
const { features } = response.data.data;
|
const { features } = buildInfoResponse.data;
|
||||||
if (!features) {
|
if (!features) {
|
||||||
return {
|
return {
|
||||||
application: PromApplication.Prometheus,
|
application: PromApplication.Prometheus,
|
||||||
@@ -80,17 +75,39 @@ export async function fetchDataSourceBuildInfo(dsSettings: { url: string; name:
|
|||||||
/**
|
/**
|
||||||
* Attempt to fetch buildinfo from our component
|
* Attempt to fetch buildinfo from our component
|
||||||
*/
|
*/
|
||||||
export async function fetchBuildInfo(dataSourceName: string): Promise<PromBuildInfo> {
|
export async function discoverFeatures(dataSourceName: string): Promise<PromApiFeatures> {
|
||||||
const dsConfig = getDataSourceByName(dataSourceName);
|
const dsConfig = getDataSourceByName(dataSourceName);
|
||||||
if (!dsConfig) {
|
if (!dsConfig) {
|
||||||
throw new Error(`Cannot find data source configuration for ${dataSourceName}`);
|
throw new Error(`Cannot find data source configuration for ${dataSourceName}`);
|
||||||
}
|
}
|
||||||
const { url, name } = dsConfig;
|
const { url, name, type } = dsConfig;
|
||||||
if (!url) {
|
if (!url) {
|
||||||
throw new Error(`The data souce url cannot be empty.`);
|
throw new Error(`The data souce url cannot be empty.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return fetchDataSourceBuildInfo({ name, url });
|
if (type !== 'prometheus' && type !== 'loki') {
|
||||||
|
throw new Error(`The build info request is not available for ${type}. Only 'prometheus' and 'loki' are supported`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return discoverDataSourceFeatures({ name, url, type });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchPromBuildInfo(url: string): Promise<PromBuildInfoResponse | undefined> {
|
||||||
|
const response = await lastValueFrom(
|
||||||
|
getBackendSrv().fetch<PromBuildInfoResponse>({
|
||||||
|
url: `${url}/api/v1/status/buildinfo`,
|
||||||
|
showErrorAlert: false,
|
||||||
|
showSuccessAlert: false,
|
||||||
|
})
|
||||||
|
).catch((e) => {
|
||||||
|
if ('status' in e && e.status === 404) {
|
||||||
|
return undefined; // Cortex does not support buildinfo endpoint, we return an empty response
|
||||||
|
}
|
||||||
|
|
||||||
|
throw e;
|
||||||
|
});
|
||||||
|
|
||||||
|
return response?.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ import {
|
|||||||
updateAlertManagerConfig,
|
updateAlertManagerConfig,
|
||||||
} from '../api/alertmanager';
|
} from '../api/alertmanager';
|
||||||
import { fetchAnnotations } from '../api/annotations';
|
import { fetchAnnotations } from '../api/annotations';
|
||||||
import { fetchBuildInfo } from '../api/buildInfo';
|
import { discoverFeatures } from '../api/buildInfo';
|
||||||
import { fetchNotifiers } from '../api/grafana';
|
import { fetchNotifiers } from '../api/grafana';
|
||||||
import { FetchPromRulesFilter, fetchRules } from '../api/prometheus';
|
import { FetchPromRulesFilter, fetchRules } from '../api/prometheus';
|
||||||
import {
|
import {
|
||||||
@@ -231,12 +231,12 @@ export const fetchRulesSourceBuildInfoAction = createAsyncThunk(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { id, name } = ds;
|
const { id, name } = ds;
|
||||||
const buildInfo = await fetchBuildInfo(name);
|
const buildInfo = await discoverFeatures(name);
|
||||||
|
|
||||||
const rulerConfig: RulerDataSourceConfig | undefined = buildInfo.features.rulerApiEnabled
|
const rulerConfig: RulerDataSourceConfig | undefined = buildInfo.features.rulerApiEnabled
|
||||||
? {
|
? {
|
||||||
dataSourceName: name,
|
dataSourceName: name,
|
||||||
apiVersion: buildInfo.application === PromApplication.Cortex ? 'legacy' : 'config',
|
apiVersion: buildInfo.application === PromApplication.Lotex ? 'legacy' : 'config',
|
||||||
}
|
}
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
|
|||||||
@@ -34,10 +34,10 @@ import {
|
|||||||
} from '@grafana/runtime';
|
} from '@grafana/runtime';
|
||||||
import { Badge, BadgeColor, Tooltip } from '@grafana/ui';
|
import { Badge, BadgeColor, Tooltip } from '@grafana/ui';
|
||||||
import { safeStringifyValue } from 'app/core/utils/explore';
|
import { safeStringifyValue } from 'app/core/utils/explore';
|
||||||
import { fetchDataSourceBuildInfo } from 'app/features/alerting/unified/api/buildInfo';
|
import { discoverDataSourceFeatures } from 'app/features/alerting/unified/api/buildInfo';
|
||||||
import { getTimeSrv, TimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
import { getTimeSrv, TimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
||||||
import { getTemplateSrv, TemplateSrv } from 'app/features/templating/template_srv';
|
import { getTemplateSrv, TemplateSrv } from 'app/features/templating/template_srv';
|
||||||
import { PromApplication, PromBuildInfo } from 'app/types/unified-alerting-dto';
|
import { PromApplication, PromApiFeatures } from 'app/types/unified-alerting-dto';
|
||||||
|
|
||||||
import { addLabelToQuery } from './add_label_to_query';
|
import { addLabelToQuery } from './add_label_to_query';
|
||||||
import PrometheusLanguageProvider from './language_provider';
|
import PrometheusLanguageProvider from './language_provider';
|
||||||
@@ -827,7 +827,7 @@ export class PrometheusDatasource
|
|||||||
|
|
||||||
async getBuildInfo() {
|
async getBuildInfo() {
|
||||||
try {
|
try {
|
||||||
const buildInfo = await fetchDataSourceBuildInfo(this);
|
const buildInfo = await discoverDataSourceFeatures({ url: this.url, name: this.name, type: 'prometheus' });
|
||||||
return buildInfo;
|
return buildInfo;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// We don't want to break the rest of functionality if build info does not work correctly
|
// We don't want to break the rest of functionality if build info does not work correctly
|
||||||
@@ -835,7 +835,7 @@ export class PrometheusDatasource
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getBuildInfoMessage(buildInfo: PromBuildInfo) {
|
getBuildInfoMessage(buildInfo: PromApiFeatures) {
|
||||||
const enabled = <Badge color="green" icon="check" text="Ruler API enabled" />;
|
const enabled = <Badge color="green" icon="check" text="Ruler API enabled" />;
|
||||||
const disabled = <Badge color="orange" icon="exclamation-triangle" text="Ruler API not enabled" />;
|
const disabled = <Badge color="orange" icon="exclamation-triangle" text="Ruler API not enabled" />;
|
||||||
const unsupported = (
|
const unsupported = (
|
||||||
@@ -850,17 +850,23 @@ export class PrometheusDatasource
|
|||||||
);
|
);
|
||||||
|
|
||||||
const LOGOS = {
|
const LOGOS = {
|
||||||
[PromApplication.Cortex]: '/public/app/plugins/datasource/prometheus/img/cortex_logo.svg',
|
[PromApplication.Lotex]: '/public/app/plugins/datasource/prometheus/img/cortex_logo.svg',
|
||||||
[PromApplication.Mimir]: '/public/app/plugins/datasource/prometheus/img/mimir_logo.svg',
|
[PromApplication.Mimir]: '/public/app/plugins/datasource/prometheus/img/mimir_logo.svg',
|
||||||
[PromApplication.Prometheus]: '/public/app/plugins/datasource/prometheus/img/prometheus_logo.svg',
|
[PromApplication.Prometheus]: '/public/app/plugins/datasource/prometheus/img/prometheus_logo.svg',
|
||||||
};
|
};
|
||||||
|
|
||||||
const COLORS: Record<PromApplication, BadgeColor> = {
|
const COLORS: Record<PromApplication, BadgeColor> = {
|
||||||
[PromApplication.Cortex]: 'blue',
|
[PromApplication.Lotex]: 'blue',
|
||||||
[PromApplication.Mimir]: 'orange',
|
[PromApplication.Mimir]: 'orange',
|
||||||
[PromApplication.Prometheus]: 'red',
|
[PromApplication.Prometheus]: 'red',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const AppDisplayNames: Record<PromApplication, string> = {
|
||||||
|
[PromApplication.Lotex]: 'Cortex',
|
||||||
|
[PromApplication.Mimir]: 'Mimir',
|
||||||
|
[PromApplication.Prometheus]: 'Prometheus',
|
||||||
|
};
|
||||||
|
|
||||||
// this will inform the user about what "subtype" the datasource is; Mimir, Cortex or vanilla Prometheus
|
// this will inform the user about what "subtype" the datasource is; Mimir, Cortex or vanilla Prometheus
|
||||||
const applicationSubType = (
|
const applicationSubType = (
|
||||||
<Badge
|
<Badge
|
||||||
@@ -870,7 +876,7 @@ export class PrometheusDatasource
|
|||||||
style={{ width: 14, height: 14, verticalAlign: 'text-bottom' }}
|
style={{ width: 14, height: 14, verticalAlign: 'text-bottom' }}
|
||||||
src={LOGOS[buildInfo.application ?? PromApplication.Prometheus]}
|
src={LOGOS[buildInfo.application ?? PromApplication.Prometheus]}
|
||||||
/>{' '}
|
/>{' '}
|
||||||
{buildInfo.application}
|
{buildInfo.application ? AppDisplayNames[buildInfo.application] : 'Unknown'}
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
color={COLORS[buildInfo.application ?? PromApplication.Prometheus]}
|
color={COLORS[buildInfo.application ?? PromApplication.Prometheus]}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export enum PromRuleType {
|
|||||||
Recording = 'recording',
|
Recording = 'recording',
|
||||||
}
|
}
|
||||||
export enum PromApplication {
|
export enum PromApplication {
|
||||||
Cortex = 'Cortex',
|
Lotex = 'Lotex',
|
||||||
Mimir = 'Mimir',
|
Mimir = 'Mimir',
|
||||||
Prometheus = 'Prometheus',
|
Prometheus = 'Prometheus',
|
||||||
}
|
}
|
||||||
@@ -44,7 +44,7 @@ export interface PromBuildInfoResponse {
|
|||||||
status: 'success';
|
status: 'success';
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PromBuildInfo {
|
export interface PromApiFeatures {
|
||||||
application?: PromApplication;
|
application?: PromApplication;
|
||||||
features: {
|
features: {
|
||||||
rulerApiEnabled: boolean;
|
rulerApiEnabled: boolean;
|
||||||
|
|||||||
Reference in New Issue
Block a user