Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions src/datasources/alarms/AlarmsDataSourceCore.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,5 +261,50 @@ describe('AlarmsDataSourceCore', () => {

expect(workspaces).toEqual(new Map<string, Workspace>());
});

[
{
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);
});
});
});
});
29 changes: 27 additions & 2 deletions src/datasources/alarms/AlarmsDataSourceCore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<AlarmsQuery> {
public errorTitle?: string;
public errorDescription?: string;

private readonly queryAlarmsUrl = `${this.instanceSettings.url}${QUERY_ALARMS_RELATIVE_PATH}`;
private readonly workspaceUtils: WorkspaceUtils;

Expand Down Expand Up @@ -50,8 +53,10 @@ export abstract class AlarmsDataSourceCore extends DataSourceBase<AlarmsQuery> {
public async loadWorkspaces(): Promise<Map<string, Workspace>> {
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<string, Workspace>();
}
}
Expand All @@ -75,6 +80,26 @@ export abstract class AlarmsDataSourceCore extends DataSourceBase<AlarmsQuery> {
})
);

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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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' }];
Expand Down Expand Up @@ -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();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -36,17 +37,24 @@ export function AlarmsCountQueryEditor({ query, handleQueryChange, datasource }:
};

return (
<InlineField
label={labels.queryBy}
labelWidth={LABEL_WIDTH}
tooltip={tooltips.queryBy}
>
<AlarmsQueryBuilder
filter={query.filter}
globalVariableOptions={datasource.globalVariableOptions()}
workspaces={workspaces}
onChange={onFilterChange}
<>
<InlineField
label={labels.queryBy}
labelWidth={LABEL_WIDTH}
tooltip={tooltips.queryBy}
>
<AlarmsQueryBuilder
filter={query.filter}
globalVariableOptions={datasource.globalVariableOptions()}
workspaces={workspaces}
onChange={onFilterChange}
/>
</InlineField>
<FloatingError
message={datasource.errorTitle}
innerMessage={datasource.errorDescription}
severity={ERROR_SEVERITY_WARNING}
/>
</InlineField>
</>
);
}
Original file line number Diff line number Diff line change
@@ -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',
Expand Down