diff --git a/src/datasources/alarms/AlarmsDataSourceCore.test.ts b/src/datasources/alarms/AlarmsDataSourceCore.test.ts index cc875925..a75ccdb2 100644 --- a/src/datasources/alarms/AlarmsDataSourceCore.test.ts +++ b/src/datasources/alarms/AlarmsDataSourceCore.test.ts @@ -261,5 +261,50 @@ describe('AlarmsDataSourceCore', () => { expect(workspaces).toEqual(new Map()); }); + + [ + { + error: new Error('Request failed with status code: 404'), + expectedErrorDescription: + 'The query builder lookups failed because the requested resource was not found. Please check the query parameters and try again.', + case: '404 error', + }, + { + error: new Error('Request failed with status code: 429'), + expectedErrorDescription: + 'The query builder lookups failed due to too many requests. Please try again later.', + case: '429 error', + }, + { + error: new Error('Request failed with status code: 504'), + expectedErrorDescription: + 'The query builder lookups experienced a timeout error. Some values might not be available. Narrow your query with a more specific filter and try again.', + case: '504 error', + }, + { + error: new Error('Request failed with status code: 500, Error message: {"message": "Internal Server Error"}'), + expectedErrorDescription: + 'Some values may not be available in the query builder lookups due to the following error: Internal Server Error.', + case: '500 error with message', + }, + { + error: new Error('API failed'), + expectedErrorDescription: + 'Some values may not be available in the query builder lookups due to an unknown error.', + case: 'Unknown error', + }, + ].forEach(({ error, expectedErrorDescription, case: testCase }) => { + it(`should handle ${testCase}`, async () => { + const expectedErrorTitle = 'Warning during alarms query'; + jest + .spyOn((datastore as any).workspaceUtils, 'getWorkspaces') + .mockRejectedValue(error); + + await datastore.loadWorkspaces(); + + expect(datastore.errorTitle).toBe(expectedErrorTitle); + expect(datastore.errorDescription).toBe(expectedErrorDescription); + }); + }); }); }); diff --git a/src/datasources/alarms/AlarmsDataSourceCore.ts b/src/datasources/alarms/AlarmsDataSourceCore.ts index 7da2eba5..4eaa72ad 100644 --- a/src/datasources/alarms/AlarmsDataSourceCore.ts +++ b/src/datasources/alarms/AlarmsDataSourceCore.ts @@ -12,6 +12,9 @@ import { QueryBuilderOperations } from "core/query-builder.constants"; import { BackendSrv, getBackendSrv, getTemplateSrv, TemplateSrv } from "@grafana/runtime"; export abstract class AlarmsDataSourceCore extends DataSourceBase { + public errorTitle?: string; + public errorDescription?: string; + private readonly queryAlarmsUrl = `${this.instanceSettings.url}${QUERY_ALARMS_RELATIVE_PATH}`; private readonly workspaceUtils: WorkspaceUtils; @@ -50,8 +53,10 @@ export abstract class AlarmsDataSourceCore extends DataSourceBase { public async loadWorkspaces(): Promise> { try { return await this.workspaceUtils.getWorkspaces(); - } catch (_error){ - // #AB3283306 - Error handling for workspace dependency + } catch (error){ + if (!this.errorTitle) { + this.handleDependenciesError(error); + } return new Map(); } } @@ -75,6 +80,26 @@ export abstract class AlarmsDataSourceCore extends DataSourceBase { }) ); + private handleDependenciesError(error: unknown): void { + const errorDetails = extractErrorInfo((error as Error).message); + this.errorTitle = 'Warning during alarms query'; + switch (errorDetails.statusCode) { + case '404': + this.errorDescription = 'The query builder lookups failed because the requested resource was not found. Please check the query parameters and try again.'; + break; + case '429': + this.errorDescription = 'The query builder lookups failed due to too many requests. Please try again later.'; + break; + case '504': + this.errorDescription = `The query builder lookups experienced a timeout error. Some values might not be available. Narrow your query with a more specific filter and try again.`; + break; + default: + this.errorDescription = errorDetails.message + ? `Some values may not be available in the query builder lookups due to the following error: ${errorDetails.message}.` + : 'Some values may not be available in the query builder lookups due to an unknown error.'; + } + } + private timeFieldsQuery(field: string): ExpressionTransformFunction { return (value: string, operation: string): string => { const formattedValue = value === '${__now:date}' ? new Date().toISOString() : value; diff --git a/src/datasources/alarms/components/editors/alarms-count/AlarmsCountQueryEditor.test.tsx b/src/datasources/alarms/components/editors/alarms-count/AlarmsCountQueryEditor.test.tsx index 748d2f36..39168406 100644 --- a/src/datasources/alarms/components/editors/alarms-count/AlarmsCountQueryEditor.test.tsx +++ b/src/datasources/alarms/components/editors/alarms-count/AlarmsCountQueryEditor.test.tsx @@ -1,10 +1,9 @@ import React from 'react'; -import { render, screen } from '@testing-library/react'; +import { act, render, screen } from '@testing-library/react'; import { AlarmsCountQueryEditor } from './AlarmsCountQueryEditor'; import { QueryType } from 'datasources/alarms/types/types'; import { AlarmsCountQuery } from 'datasources/alarms/types/AlarmsCount.types'; import { AlarmsCountDataSource } from 'datasources/alarms/query-type-handlers/alarms-count/AlarmsCountDataSource'; -import { act } from 'react-dom/test-utils'; const mockHandleQueryChange = jest.fn(); const mockGlobalVars = [{ label: '$var1', value: '$value1' }]; @@ -79,4 +78,17 @@ describe('AlarmsCountQueryEditor', () => { expect(mockDatasource.loadWorkspaces).toHaveBeenCalled(); expect(container.getByText('WorkspaceName')).toBeInTheDocument(); }); + + it('should display error title and description when error occurs', async () => { + mockDatasource.loadWorkspaces = jest.fn().mockImplementation(() => { + mockDatasource.errorTitle = 'Test Error Title'; + mockDatasource.errorDescription = 'Test Error Description'; + return Promise.resolve(new Map()); + }); + + await renderElement(); + + expect(screen.getByText('Test Error Title')).toBeInTheDocument(); + expect(screen.getByText('Test Error Description')).toBeInTheDocument(); + }); }); diff --git a/src/datasources/alarms/components/editors/alarms-count/AlarmsCountQueryEditor.tsx b/src/datasources/alarms/components/editors/alarms-count/AlarmsCountQueryEditor.tsx index d14c298c..37452c8f 100644 --- a/src/datasources/alarms/components/editors/alarms-count/AlarmsCountQueryEditor.tsx +++ b/src/datasources/alarms/components/editors/alarms-count/AlarmsCountQueryEditor.tsx @@ -2,9 +2,10 @@ import { AlarmsCountQuery } from "datasources/alarms/types/AlarmsCount.types"; import React, { useEffect, useState } from "react"; import { InlineField } from "core/components/InlineField"; import { AlarmsQueryBuilder } from "../../query-builder/AlarmsQueryBuilder"; -import { LABEL_WIDTH, labels, tooltips } from "datasources/alarms/constants/AlarmsQueryEditor.constants"; +import { ERROR_SEVERITY_WARNING, LABEL_WIDTH, labels, tooltips } from "datasources/alarms/constants/AlarmsQueryEditor.constants"; import { AlarmsCountDataSource } from "datasources/alarms/query-type-handlers/alarms-count/AlarmsCountDataSource"; import { Workspace } from "core/types"; +import { FloatingError } from "core/errors"; type Props = { query: AlarmsCountQuery; @@ -36,17 +37,24 @@ export function AlarmsCountQueryEditor({ query, handleQueryChange, datasource }: }; return ( - - + + + + - + ); } diff --git a/src/datasources/alarms/constants/AlarmsQueryEditor.constants.ts b/src/datasources/alarms/constants/AlarmsQueryEditor.constants.ts index 7b9f25d1..2a06d786 100644 --- a/src/datasources/alarms/constants/AlarmsQueryEditor.constants.ts +++ b/src/datasources/alarms/constants/AlarmsQueryEditor.constants.ts @@ -1,4 +1,7 @@ +import { AlertVariant } from "@grafana/ui"; + export const LABEL_WIDTH = 26; +export const ERROR_SEVERITY_WARNING: AlertVariant = 'warning'; export const labels = { queryBy: 'Query By',