Alerting: Fix Loki buildinfo request error (#49073) (#49144)

* 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:
Konrad Lalik
2022-05-18 11:32:38 +02:00
committed by GitHub
parent e6357db4b0
commit f1e390ebf9
7 changed files with 137 additions and 76 deletions
@@ -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]}
+2 -2
View File
@@ -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;