mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Chore: Enable useUnknownInCatchVariables
for stricter type checking in catch blocks (#50591)
* wrap a bunch of errors * wrap more things! * fix up some unit tests * wrap more errors * tiny bit of tidy up
This commit is contained in:
parent
2fbe99c1be
commit
803473f479
@ -12,6 +12,7 @@ export { featureEnabled } from './utils/licensing';
|
||||
export { logInfo, logDebug, logWarning, logError } from './utils/logging';
|
||||
export {
|
||||
DataSourceWithBackend,
|
||||
HealthCheckError,
|
||||
HealthCheckResult,
|
||||
HealthCheckResultDetails,
|
||||
HealthStatus,
|
||||
|
@ -123,6 +123,10 @@ export interface FetchError<T = any> {
|
||||
config: BackendSrvRequest;
|
||||
}
|
||||
|
||||
export function isFetchError(e: unknown): e is FetchError {
|
||||
return typeof e === 'object' && e !== null && 'status' in e && 'data' in e;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to communicate via http(s) to a remote backend such as the Grafana backend,
|
||||
* a datasource etc. The BackendSrv is using the {@link https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API | Fetch API}
|
||||
|
@ -48,7 +48,7 @@ export function isExpressionReference(ref?: DataSourceRef | string | null): bool
|
||||
return v === ExpressionDatasourceRef.type || v === '-100'; // -100 was a legacy accident that should be removed
|
||||
}
|
||||
|
||||
class HealthCheckError extends Error {
|
||||
export class HealthCheckError extends Error {
|
||||
details: HealthCheckResultDetails;
|
||||
|
||||
constructor(message: string, details: HealthCheckResultDetails) {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
import { getBackendSrv, isFetchError } from '@grafana/runtime';
|
||||
import { Role } from 'app/types';
|
||||
|
||||
export const fetchRoleOptions = async (orgId?: number, query?: string): Promise<Role[]> => {
|
||||
@ -33,7 +33,9 @@ export const fetchUserRoles = async (userId: number, orgId?: number): Promise<Ro
|
||||
}
|
||||
return roles;
|
||||
} catch (error) {
|
||||
error.isHandled = true;
|
||||
if (isFetchError(error)) {
|
||||
error.isHandled = true;
|
||||
}
|
||||
return [];
|
||||
}
|
||||
};
|
||||
@ -62,7 +64,9 @@ export const fetchTeamRoles = async (teamId: number, orgId?: number): Promise<Ro
|
||||
}
|
||||
return roles;
|
||||
} catch (error) {
|
||||
error.isHandled = true;
|
||||
if (isFetchError(error)) {
|
||||
error.isHandled = true;
|
||||
}
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
@ -78,7 +78,7 @@ export default class RichHistoryLocalStorage implements RichHistoryStorage {
|
||||
try {
|
||||
store.setObject(RICH_HISTORY_KEY, updatedHistory);
|
||||
} catch (error) {
|
||||
if (error.name === 'QuotaExceededError') {
|
||||
if (error instanceof Error && error.name === 'QuotaExceededError') {
|
||||
throwError(RichHistoryServiceError.StorageFull, `Saving rich history failed: ${error.message}`);
|
||||
} else {
|
||||
throw error;
|
||||
|
@ -44,7 +44,9 @@ export class Store {
|
||||
} catch (error) {
|
||||
// Likely hitting storage quota
|
||||
const errorToThrow = new Error(`Could not save item in localStorage: ${key}. [${error}]`);
|
||||
errorToThrow.name = error.name;
|
||||
if (error instanceof Error) {
|
||||
errorToThrow.name = error.name;
|
||||
}
|
||||
throw errorToThrow;
|
||||
}
|
||||
return true;
|
||||
|
@ -5,12 +5,21 @@ export interface CancelablePromise<T> {
|
||||
cancel: () => void;
|
||||
}
|
||||
|
||||
export interface CancelablePromiseRejection {
|
||||
isCanceled: boolean;
|
||||
}
|
||||
|
||||
export function isCancelablePromiseRejection(promise: unknown): promise is CancelablePromiseRejection {
|
||||
return typeof promise === 'object' && promise !== null && 'isCanceled' in promise;
|
||||
}
|
||||
|
||||
export const makePromiseCancelable = <T>(promise: Promise<T>): CancelablePromise<T> => {
|
||||
let hasCanceled_ = false;
|
||||
|
||||
const wrappedPromise = new Promise<T>((resolve, reject) => {
|
||||
promise.then((val) => (hasCanceled_ ? reject({ isCanceled: true }) : resolve(val)));
|
||||
promise.catch((error) => (hasCanceled_ ? reject({ isCanceled: true }) : reject(error)));
|
||||
const canceledPromiseRejection: CancelablePromiseRejection = { isCanceled: true };
|
||||
promise.then((val) => (hasCanceled_ ? reject(canceledPromiseRejection) : resolve(val)));
|
||||
promise.catch((error) => (hasCanceled_ ? reject(canceledPromiseRejection) : reject(error)));
|
||||
});
|
||||
|
||||
return {
|
||||
|
@ -58,11 +58,13 @@ export async function addToRichHistory(
|
||||
});
|
||||
warning = result.warning;
|
||||
} catch (error) {
|
||||
if (error.name === RichHistoryServiceError.StorageFull) {
|
||||
richHistoryStorageFull = true;
|
||||
showQuotaExceededError && dispatch(notifyApp(createErrorNotification(error.message)));
|
||||
} else if (error.name !== RichHistoryServiceError.DuplicatedEntry) {
|
||||
dispatch(notifyApp(createErrorNotification('Rich History update failed', error.message)));
|
||||
if (error instanceof Error) {
|
||||
if (error.name === RichHistoryServiceError.StorageFull) {
|
||||
richHistoryStorageFull = true;
|
||||
showQuotaExceededError && dispatch(notifyApp(createErrorNotification(error.message)));
|
||||
} else if (error.name !== RichHistoryServiceError.DuplicatedEntry) {
|
||||
dispatch(notifyApp(createErrorNotification('Rich History update failed', error.message)));
|
||||
}
|
||||
}
|
||||
// Saving failed. Do not add new entry.
|
||||
return { richHistoryStorageFull, limitExceeded };
|
||||
@ -101,7 +103,9 @@ export async function updateStarredInRichHistory(id: string, starred: boolean) {
|
||||
try {
|
||||
return await getRichHistoryStorage().updateStarred(id, starred);
|
||||
} catch (error) {
|
||||
dispatch(notifyApp(createErrorNotification('Saving rich history failed', error.message)));
|
||||
if (error instanceof Error) {
|
||||
dispatch(notifyApp(createErrorNotification('Saving rich history failed', error.message)));
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
@ -110,7 +114,9 @@ export async function updateCommentInRichHistory(id: string, newComment: string
|
||||
try {
|
||||
return await getRichHistoryStorage().updateComment(id, newComment);
|
||||
} catch (error) {
|
||||
dispatch(notifyApp(createErrorNotification('Saving rich history failed', error.message)));
|
||||
if (error instanceof Error) {
|
||||
dispatch(notifyApp(createErrorNotification('Saving rich history failed', error.message)));
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
@ -120,7 +126,9 @@ export async function deleteQueryInRichHistory(id: string) {
|
||||
await getRichHistoryStorage().deleteRichHistory(id);
|
||||
return id;
|
||||
} catch (error) {
|
||||
dispatch(notifyApp(createErrorNotification('Saving rich history failed', error.message)));
|
||||
if (error instanceof Error) {
|
||||
dispatch(notifyApp(createErrorNotification('Saving rich history failed', error.message)));
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
@ -156,8 +164,9 @@ export async function migrateQueryHistoryFromLocalStorage(): Promise<LocalStorag
|
||||
dispatch(notifyApp(createSuccessNotification('Query history successfully migrated from local storage')));
|
||||
return { status: LocalStorageMigrationStatus.Successful };
|
||||
} catch (error) {
|
||||
dispatch(notifyApp(createWarningNotification(`Query history migration failed. ${error.message}`)));
|
||||
return { status: LocalStorageMigrationStatus.Failed, error };
|
||||
const errorToThrow = error instanceof Error ? error : new Error('Uknown error occurred.');
|
||||
dispatch(notifyApp(createWarningNotification(`Query history migration failed. ${errorToThrow.message}`)));
|
||||
return { status: LocalStorageMigrationStatus.Failed, error: errorToThrow };
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { debounce } from 'lodash';
|
||||
|
||||
import { dateTimeFormatTimeAgo } from '@grafana/data';
|
||||
import { featureEnabled, getBackendSrv, locationService } from '@grafana/runtime';
|
||||
import { featureEnabled, getBackendSrv, isFetchError, locationService } from '@grafana/runtime';
|
||||
import config from 'app/core/config';
|
||||
import { contextSrv } from 'app/core/core';
|
||||
import { accessControlQueryParam } from 'app/core/utils/accessControl';
|
||||
@ -43,12 +43,14 @@ export function loadAdminUserPage(userId: number): ThunkResult<void> {
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
|
||||
const userError = {
|
||||
title: error.data.message,
|
||||
body: error.data.error,
|
||||
};
|
||||
if (isFetchError(error)) {
|
||||
const userError = {
|
||||
title: error.data.message,
|
||||
body: error.data.error,
|
||||
};
|
||||
|
||||
dispatch(userAdminPageFailedAction(userError));
|
||||
dispatch(userAdminPageFailedAction(userError));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -212,12 +214,14 @@ export function loadLdapState(): ThunkResult<void> {
|
||||
const connectionInfo = await getBackendSrv().get(`/api/admin/ldap/status`);
|
||||
dispatch(ldapConnectionInfoLoadedAction(connectionInfo));
|
||||
} catch (error) {
|
||||
error.isHandled = true;
|
||||
const ldapError = {
|
||||
title: error.data.message,
|
||||
body: error.data.error,
|
||||
};
|
||||
dispatch(ldapFailedAction(ldapError));
|
||||
if (isFetchError(error)) {
|
||||
error.isHandled = true;
|
||||
const ldapError = {
|
||||
title: error.data.message,
|
||||
body: error.data.error,
|
||||
};
|
||||
dispatch(ldapFailedAction(ldapError));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -235,13 +239,15 @@ export function loadUserMapping(username: string): ThunkResult<void> {
|
||||
};
|
||||
dispatch(userMappingInfoLoadedAction(userInfo));
|
||||
} catch (error) {
|
||||
error.isHandled = true;
|
||||
const userError = {
|
||||
title: error.data.message,
|
||||
body: error.data.error,
|
||||
};
|
||||
dispatch(clearUserMappingInfoAction());
|
||||
dispatch(userMappingInfoFailedAction(userError));
|
||||
if (isFetchError(error)) {
|
||||
error.isHandled = true;
|
||||
const userError = {
|
||||
title: error.data.message,
|
||||
body: error.data.error,
|
||||
};
|
||||
dispatch(clearUserMappingInfoAction());
|
||||
dispatch(userMappingInfoFailedAction(userError));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { getBackendSrv, locationService } from '@grafana/runtime';
|
||||
import { getBackendSrv, isFetchError, locationService } from '@grafana/runtime';
|
||||
import { notifyApp } from 'app/core/actions';
|
||||
import { createErrorNotification, createSuccessNotification } from 'app/core/copy/appNotification';
|
||||
import { AlertRuleDTO, NotifierDTO, ThunkResult } from 'app/types';
|
||||
@ -28,7 +28,9 @@ export function createNotificationChannel(data: any): ThunkResult<Promise<void>>
|
||||
dispatch(notifyApp(createSuccessNotification('Notification created')));
|
||||
locationService.push('/alerting/notifications');
|
||||
} catch (error) {
|
||||
dispatch(notifyApp(createErrorNotification(error.data.error)));
|
||||
if (isFetchError(error)) {
|
||||
dispatch(notifyApp(createErrorNotification(error.data.error)));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -39,7 +41,9 @@ export function updateNotificationChannel(data: any): ThunkResult<void> {
|
||||
await getBackendSrv().put(`/api/alert-notifications/${data.id}`, data);
|
||||
dispatch(notifyApp(createSuccessNotification('Notification updated')));
|
||||
} catch (error) {
|
||||
dispatch(notifyApp(createErrorNotification(error.data.error)));
|
||||
if (isFetchError(error)) {
|
||||
dispatch(notifyApp(createErrorNotification(error.data.error)));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { lastValueFrom } from 'rxjs';
|
||||
|
||||
import { urlUtil } from '@grafana/data';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
import { getBackendSrv, isFetchError } from '@grafana/runtime';
|
||||
import {
|
||||
AlertmanagerAlert,
|
||||
AlertManagerCortexConfig,
|
||||
@ -18,7 +18,6 @@ import {
|
||||
ExternalAlertmanagerConfig,
|
||||
} from 'app/plugins/datasource/alertmanager/types';
|
||||
|
||||
import { isFetchError } from '../utils/alertmanager';
|
||||
import { getDatasourceAPIUid, GRAFANA_RULES_SOURCE_NAME } from '../utils/datasource';
|
||||
|
||||
// "grafana" for grafana-managed, otherwise a datasource name
|
||||
@ -39,6 +38,7 @@ export async function fetchAlertManagerConfig(alertManagerSourceName: string): P
|
||||
// if no config has been uploaded to grafana, it returns error instead of latest config
|
||||
if (
|
||||
alertManagerSourceName === GRAFANA_RULES_SOURCE_NAME &&
|
||||
isFetchError(e) &&
|
||||
e.data?.message?.includes('could not find an Alertmanager configuration')
|
||||
) {
|
||||
return {
|
||||
|
@ -12,6 +12,7 @@ jest.mock('./prometheus');
|
||||
jest.mock('./ruler');
|
||||
jest.mock('app/core/services/context_srv', () => {});
|
||||
jest.mock('@grafana/runtime', () => ({
|
||||
...jest.requireActual('@grafana/runtime'),
|
||||
getBackendSrv: () => ({ fetch }),
|
||||
}));
|
||||
|
||||
|
@ -1,9 +1,8 @@
|
||||
import { lastValueFrom } from 'rxjs';
|
||||
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
import { getBackendSrv, isFetchError } from '@grafana/runtime';
|
||||
import { PromApplication, PromApiFeatures, PromBuildInfoResponse } from 'app/types/unified-alerting-dto';
|
||||
|
||||
import { isFetchError } from '../utils/alertmanager';
|
||||
import { RULER_NOT_SUPPORTED_MSG } from '../utils/constants';
|
||||
import { getDataSourceByName } from '../utils/datasource';
|
||||
|
||||
|
@ -112,7 +112,7 @@ export default function AlertmanagerConfig(): JSX.Element {
|
||||
JSON.parse(v);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return e.message;
|
||||
return e instanceof Error ? e.message : 'Invalid JSON.';
|
||||
}
|
||||
},
|
||||
})}
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { FetchError } from '@grafana/runtime';
|
||||
import {
|
||||
AlertManagerCortexConfig,
|
||||
MatcherOperator,
|
||||
@ -260,7 +259,3 @@ export function getMonthsString(months?: string[]): string {
|
||||
export function getYearsString(years?: string[]): string {
|
||||
return 'Years: ' + (years?.join(', ') ?? 'All');
|
||||
}
|
||||
|
||||
export function isFetchError(e: unknown): e is FetchError {
|
||||
return typeof e === 'object' && e !== null && 'status' in e && 'data' in e;
|
||||
}
|
||||
|
@ -1,11 +1,9 @@
|
||||
import { AsyncThunk, createSlice, Draft, isAsyncThunkAction, PayloadAction, SerializedError } from '@reduxjs/toolkit';
|
||||
|
||||
import { AppEvents } from '@grafana/data';
|
||||
import { FetchError } from '@grafana/runtime';
|
||||
import { FetchError, isFetchError } from '@grafana/runtime';
|
||||
import { appEvents } from 'app/core/core';
|
||||
|
||||
import { isFetchError } from './alertmanager';
|
||||
|
||||
export interface AsyncRequestState<T> {
|
||||
result?: T;
|
||||
loading: boolean;
|
||||
|
@ -82,7 +82,7 @@ export const validateIntervals = (
|
||||
getValidIntervals(intervals, dependencies);
|
||||
return null;
|
||||
} catch (err) {
|
||||
return err.message;
|
||||
return err instanceof Error ? err.message : 'Invalid intervals';
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -6,7 +6,7 @@ import { Subscription } from 'rxjs';
|
||||
|
||||
import { FieldConfigSource, GrafanaTheme2 } from '@grafana/data';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { locationService } from '@grafana/runtime';
|
||||
import { isFetchError, locationService } from '@grafana/runtime';
|
||||
import {
|
||||
HorizontalGroup,
|
||||
InlineSwitch,
|
||||
@ -163,7 +163,9 @@ export class PanelEditorUnconnected extends PureComponent<Props> {
|
||||
await saveAndRefreshLibraryPanel(this.props.panel, this.props.dashboard.meta.folderId!);
|
||||
this.props.notifyApp(createPanelLibrarySuccessNotification('Library panel saved'));
|
||||
} catch (err) {
|
||||
this.props.notifyApp(createPanelLibraryErrorNotification(`Error saving library panel: "${err.statusText}"`));
|
||||
if (isFetchError(err)) {
|
||||
this.props.notifyApp(createPanelLibraryErrorNotification(`Error saving library panel: "${err.statusText}"`));
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -64,7 +64,7 @@ export const SaveDashboardAsForm: React.FC<SaveDashboardAsFormProps> = ({
|
||||
await validationSrv.validateNewDashboardName(getFormValues().$folder.id, dashboardName);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return e.message;
|
||||
return e instanceof Error ? e.message : 'Dashboard name is invalid';
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -2,7 +2,7 @@ import configureMockStore from 'redux-mock-store';
|
||||
import thunk from 'redux-thunk';
|
||||
import { Subject } from 'rxjs';
|
||||
|
||||
import { locationService, setEchoSrv } from '@grafana/runtime';
|
||||
import { FetchError, locationService, setEchoSrv } from '@grafana/runtime';
|
||||
import { getBackendSrv } from 'app/core/services/backend_srv';
|
||||
import { keybindingSrv } from 'app/core/services/keybindingSrv';
|
||||
import { variableAdapters } from 'app/features/variables/adapters';
|
||||
@ -208,7 +208,15 @@ describeInitScenario('Initializing home dashboard', (ctx) => {
|
||||
describeInitScenario('Initializing home dashboard cancelled', (ctx) => {
|
||||
ctx.setup(() => {
|
||||
ctx.args.routeName = DashboardRoutes.Home;
|
||||
ctx.backendSrv.get.mockRejectedValue({ cancelled: true });
|
||||
const fetchError: FetchError = {
|
||||
cancelled: true,
|
||||
config: {
|
||||
url: '/api/dashboards/home',
|
||||
},
|
||||
data: 'foo',
|
||||
status: 500,
|
||||
};
|
||||
ctx.backendSrv.get.mockRejectedValue(fetchError);
|
||||
});
|
||||
|
||||
it('Should abort init process', () => {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { locationUtil, setWeekStart } from '@grafana/data';
|
||||
import { config, locationService } from '@grafana/runtime';
|
||||
import { config, isFetchError, locationService } from '@grafana/runtime';
|
||||
import { notifyApp } from 'app/core/actions';
|
||||
import { createErrorNotification } from 'app/core/copy/appNotification';
|
||||
import { backendSrv } from 'app/core/services/backend_srv';
|
||||
@ -90,7 +90,7 @@ async function fetchDashboard(
|
||||
}
|
||||
} catch (err) {
|
||||
// Ignore cancelled errors
|
||||
if (err.cancelled) {
|
||||
if (isFetchError(err) && err.cancelled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -184,7 +184,9 @@ export function initDashboard(args: InitDashboardArgs): ThunkResult<void> {
|
||||
|
||||
keybindingSrv.setupDashboardBindings(dashboard);
|
||||
} catch (err) {
|
||||
dispatch(notifyApp(createErrorNotification('Dashboard init failed', err)));
|
||||
if (err instanceof Error) {
|
||||
dispatch(notifyApp(createErrorNotification('Dashboard init failed', err)));
|
||||
}
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { of } from 'rxjs';
|
||||
import { thunkTester } from 'test/core/thunk/thunkTester';
|
||||
|
||||
import { BackendSrvRequest, FetchResponse } from '@grafana/runtime';
|
||||
import { BackendSrvRequest, FetchError, FetchResponse } from '@grafana/runtime';
|
||||
import { getBackendSrv } from 'app/core/services/backend_srv';
|
||||
import { ThunkResult, ThunkDispatch } from 'app/types';
|
||||
|
||||
@ -316,13 +316,17 @@ describe('testDataSource', () => {
|
||||
|
||||
it('then testDataSourceFailed should be dispatched with response error message', async () => {
|
||||
const result = {
|
||||
message: 'Error testing datasource',
|
||||
message: 'Response error message',
|
||||
};
|
||||
const dispatchedActions = await failDataSourceTest({
|
||||
message: 'Error testing datasource',
|
||||
const error: FetchError = {
|
||||
config: {
|
||||
url: '',
|
||||
},
|
||||
data: { message: 'Response error message' },
|
||||
status: 400,
|
||||
statusText: 'Bad Request',
|
||||
});
|
||||
};
|
||||
const dispatchedActions = await failDataSourceTest(error);
|
||||
expect(dispatchedActions).toEqual([testDataSourceStarting(), testDataSourceFailed(result)]);
|
||||
});
|
||||
|
||||
@ -330,10 +334,15 @@ describe('testDataSource', () => {
|
||||
const result = {
|
||||
message: 'Response error message',
|
||||
};
|
||||
const dispatchedActions = await failDataSourceTest({
|
||||
const error: FetchError = {
|
||||
config: {
|
||||
url: '',
|
||||
},
|
||||
data: { message: 'Response error message' },
|
||||
status: 400,
|
||||
statusText: 'Bad Request',
|
||||
});
|
||||
};
|
||||
const dispatchedActions = await failDataSourceTest(error);
|
||||
expect(dispatchedActions).toEqual([testDataSourceStarting(), testDataSourceFailed(result)]);
|
||||
});
|
||||
|
||||
@ -341,7 +350,15 @@ describe('testDataSource', () => {
|
||||
const result = {
|
||||
message: 'HTTP error Bad Request',
|
||||
};
|
||||
const dispatchedActions = await failDataSourceTest({ data: {}, statusText: 'Bad Request' });
|
||||
const error: FetchError = {
|
||||
config: {
|
||||
url: '',
|
||||
},
|
||||
data: {},
|
||||
statusText: 'Bad Request',
|
||||
status: 400,
|
||||
};
|
||||
const dispatchedActions = await failDataSourceTest(error);
|
||||
expect(dispatchedActions).toEqual([testDataSourceStarting(), testDataSourceFailed(result)]);
|
||||
});
|
||||
});
|
||||
|
@ -1,7 +1,14 @@
|
||||
import { lastValueFrom } from 'rxjs';
|
||||
|
||||
import { DataSourcePluginMeta, DataSourceSettings, locationUtil } from '@grafana/data';
|
||||
import { DataSourceWithBackend, getDataSourceSrv, locationService } from '@grafana/runtime';
|
||||
import {
|
||||
DataSourceWithBackend,
|
||||
getDataSourceSrv,
|
||||
HealthCheckError,
|
||||
HealthCheckResultDetails,
|
||||
isFetchError,
|
||||
locationService,
|
||||
} from '@grafana/runtime';
|
||||
import { updateNavIndex } from 'app/core/actions';
|
||||
import { getBackendSrv } from 'app/core/services/backend_srv';
|
||||
import { accessControlQueryParam } from 'app/core/utils/accessControl';
|
||||
@ -77,7 +84,9 @@ export const initDataSourceSettings = (
|
||||
|
||||
dispatch(initDataSourceSettingsSucceeded(importedPlugin));
|
||||
} catch (err) {
|
||||
dispatch(initDataSourceSettingsFailed(err));
|
||||
if (err instanceof Error) {
|
||||
dispatch(initDataSourceSettingsFailed(err));
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
@ -104,9 +113,17 @@ export const testDataSource = (
|
||||
|
||||
dispatch(testDataSourceSucceeded(result));
|
||||
} catch (err) {
|
||||
const { statusText, message: errMessage, details, data } = err;
|
||||
let message: string | undefined;
|
||||
let details: HealthCheckResultDetails;
|
||||
|
||||
const message = errMessage || data?.message || 'HTTP error ' + statusText;
|
||||
if (err instanceof HealthCheckError) {
|
||||
message = err.message;
|
||||
details = err.details;
|
||||
} else if (isFetchError(err)) {
|
||||
message = err.data.message ?? `HTTP error ${err.statusText}`;
|
||||
} else if (err instanceof Error) {
|
||||
message = err.message;
|
||||
}
|
||||
|
||||
dispatch(testDataSourceFailed({ message, details }));
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { lastValueFrom } from 'rxjs';
|
||||
|
||||
import { locationUtil } from '@grafana/data';
|
||||
import { getBackendSrv, locationService } from '@grafana/runtime';
|
||||
import { getBackendSrv, isFetchError, locationService } from '@grafana/runtime';
|
||||
import { notifyApp, updateNavIndex } from 'app/core/actions';
|
||||
import { createSuccessNotification, createWarningNotification } from 'app/core/copy/appNotification';
|
||||
import { contextSrv } from 'app/core/core';
|
||||
@ -59,7 +59,7 @@ export function checkFolderPermissions(uid: string): ThunkResult<void> {
|
||||
);
|
||||
dispatch(setCanViewFolderPermissions(true));
|
||||
} catch (err) {
|
||||
if (err.status !== 403) {
|
||||
if (isFetchError(err) && err.status !== 403) {
|
||||
dispatch(notifyApp(createWarningNotification('Error checking folder permissions', err.data?.message)));
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { useAsync, useDebounce } from 'react-use';
|
||||
|
||||
import { isFetchError } from '@grafana/runtime';
|
||||
import { Button, Field, Input, Modal } from '@grafana/ui';
|
||||
import { FolderPicker } from 'app/core/components/Select/FolderPicker';
|
||||
|
||||
@ -36,7 +37,9 @@ export const AddLibraryPanelContents = ({ panel, initialFolderId, onDismiss }: A
|
||||
try {
|
||||
return !(await getLibraryPanelByName(panelName)).some((lp) => lp.folderId === folderId);
|
||||
} catch (err) {
|
||||
err.isHandled = true;
|
||||
if (isFetchError(err)) {
|
||||
err.isHandled = true;
|
||||
}
|
||||
return true;
|
||||
} finally {
|
||||
setWaiting(false);
|
||||
|
@ -2,6 +2,7 @@ import { useEffect } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import useAsyncFn from 'react-use/lib/useAsyncFn';
|
||||
|
||||
import { isFetchError } from '@grafana/runtime';
|
||||
import { notifyApp } from 'app/core/actions';
|
||||
import { PanelModel } from 'app/features/dashboard/state';
|
||||
|
||||
@ -17,8 +18,11 @@ export const usePanelSave = () => {
|
||||
try {
|
||||
return await saveAndRefreshLibraryPanel(panel, folderId);
|
||||
} catch (err) {
|
||||
err.isHandled = true;
|
||||
throw new Error(err.data.message);
|
||||
if (isFetchError(err)) {
|
||||
err.isHandled = true;
|
||||
throw new Error(err.data.message);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}, []);
|
||||
|
||||
|
@ -84,10 +84,12 @@ class UnthemedDashboardImport extends PureComponent<Props> {
|
||||
try {
|
||||
dashboard = JSON.parse(e.target.result);
|
||||
} catch (error) {
|
||||
appEvents.emit(AppEvents.alertError, [
|
||||
'Import failed',
|
||||
'JSON -> JS Serialization failed: ' + error.message,
|
||||
]);
|
||||
if (error instanceof Error) {
|
||||
appEvents.emit(AppEvents.alertError, [
|
||||
'Import failed',
|
||||
'JSON -> JS Serialization failed: ' + error.message,
|
||||
]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
importDashboardJson(dashboard);
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { DataSourceInstanceSettings, locationUtil } from '@grafana/data';
|
||||
import { getDataSourceSrv, locationService, getBackendSrv } from '@grafana/runtime';
|
||||
import { getDataSourceSrv, locationService, getBackendSrv, isFetchError } from '@grafana/runtime';
|
||||
import { notifyApp } from 'app/core/actions';
|
||||
import { createErrorNotification } from 'app/core/copy/appNotification';
|
||||
import { dashboardWatcher } from 'app/features/live/dashboard/dashboardWatcher';
|
||||
@ -34,7 +34,9 @@ export function fetchGcomDashboard(id: string): ThunkResult<void> {
|
||||
dispatch(processElements(dashboard.json));
|
||||
} catch (error) {
|
||||
dispatch(fetchFailed());
|
||||
dispatch(notifyApp(createErrorNotification(error.data.message || error)));
|
||||
if (isFetchError(error)) {
|
||||
dispatch(notifyApp(createErrorNotification(error.data.message || error)));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -213,11 +215,13 @@ async function moveDashboard(uid: string, toFolder: FolderInfo) {
|
||||
await saveDashboard(options);
|
||||
return { succeeded: true };
|
||||
} catch (err) {
|
||||
if (err.data?.status !== 'plugin-dashboard') {
|
||||
return { succeeded: false };
|
||||
}
|
||||
if (isFetchError(err)) {
|
||||
if (err.data?.status !== 'plugin-dashboard') {
|
||||
return { succeeded: false };
|
||||
}
|
||||
|
||||
err.isHandled = true;
|
||||
err.isHandled = true;
|
||||
}
|
||||
options.overwrite = true;
|
||||
|
||||
try {
|
||||
|
@ -33,6 +33,8 @@ async function withErrorHandling(apiCall: () => Promise<void>, message = 'Playli
|
||||
await apiCall();
|
||||
dispatch(notifyApp(createSuccessNotification(message)));
|
||||
} catch (e) {
|
||||
dispatch(notifyApp(createErrorNotification('Unable to save playlist', e)));
|
||||
if (e instanceof Error) {
|
||||
dispatch(notifyApp(createErrorNotification('Unable to save playlist', e)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { PluginError, PluginMeta, renderMarkdown } from '@grafana/data';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
import { getBackendSrv, isFetchError } from '@grafana/runtime';
|
||||
|
||||
import { API_ROOT, GCOM_API_ROOT } from './constants';
|
||||
import { isLocalPluginVisible, isRemotePluginVisible } from './helpers';
|
||||
@ -45,8 +45,10 @@ async function getRemotePlugin(id: string): Promise<RemotePlugin | undefined> {
|
||||
try {
|
||||
return await getBackendSrv().get(`${GCOM_API_ROOT}/plugins/${id}`, {});
|
||||
} catch (error) {
|
||||
// It can happen that GCOM is not available, in that case we show a limited set of information to the user.
|
||||
error.isHandled = true;
|
||||
if (isFetchError(error)) {
|
||||
// It can happen that GCOM is not available, in that case we show a limited set of information to the user.
|
||||
error.isHandled = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -66,8 +68,10 @@ async function getPluginVersions(id: string, isPublished: boolean): Promise<Vers
|
||||
grafanaDependency: v.grafanaDependency,
|
||||
}));
|
||||
} catch (error) {
|
||||
// It can happen that GCOM is not available, in that case we show a limited set of information to the user.
|
||||
error.isHandled = true;
|
||||
if (isFetchError(error)) {
|
||||
// It can happen that GCOM is not available, in that case we show a limited set of information to the user.
|
||||
error.isHandled = true;
|
||||
}
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@ -79,7 +83,9 @@ async function getLocalPluginReadme(id: string): Promise<string> {
|
||||
|
||||
return markdownAsHtml;
|
||||
} catch (error) {
|
||||
error.isHandled = true;
|
||||
if (isFetchError(error)) {
|
||||
error.isHandled = true;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { createAction, createAsyncThunk, Update } from '@reduxjs/toolkit';
|
||||
|
||||
import { PanelPlugin } from '@grafana/data';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
import { getBackendSrv, isFetchError } from '@grafana/runtime';
|
||||
import { importPanelPlugin } from 'app/features/plugins/importPanelPlugin';
|
||||
import { StoreState, ThunkResult } from 'app/types';
|
||||
|
||||
@ -39,7 +39,9 @@ export const fetchRemotePlugins = createAsyncThunk<RemotePlugin[], void, { rejec
|
||||
try {
|
||||
return await getRemotePlugins();
|
||||
} catch (error) {
|
||||
error.isHandled = true;
|
||||
if (isFetchError(error)) {
|
||||
error.isHandled = true;
|
||||
}
|
||||
return thunkApi.rejectWithValue([]);
|
||||
}
|
||||
}
|
||||
|
@ -191,7 +191,9 @@ export class DatasourceSrv implements DataSourceService {
|
||||
this.datasources[instance.uid] = instance;
|
||||
return instance;
|
||||
} catch (err) {
|
||||
appEvents.emit(AppEvents.alertError, [instanceSettings.name + ' plugin failed', err.toString()]);
|
||||
if (err instanceof Error) {
|
||||
appEvents.emit(AppEvents.alertError, [instanceSettings.name + ' plugin failed', err.toString()]);
|
||||
}
|
||||
return Promise.reject({ message: `Datasource: ${key} was not found` });
|
||||
}
|
||||
}
|
||||
|
@ -211,7 +211,7 @@ describe('query actions', () => {
|
||||
silenceConsoleOutput();
|
||||
it('then correct actions are dispatched', async () => {
|
||||
const variable = createVariable({ includeAll: true });
|
||||
const error = { message: 'failed to fetch metrics' };
|
||||
const error = new Error('failed to fetch metrics');
|
||||
|
||||
mocks[variable.datasource!.uid!].metricFindQuery = jest.fn(() => Promise.reject(error));
|
||||
|
||||
@ -233,10 +233,7 @@ describe('query actions', () => {
|
||||
toKeyedAction('key', addVariableEditorError({ errorProp: 'update', errorText: error.message }))
|
||||
);
|
||||
expect(dispatchedActions[3]).toEqual(
|
||||
toKeyedAction(
|
||||
'key',
|
||||
variableStateFailed(toVariablePayload(variable, { error: { message: 'failed to fetch metrics' } }))
|
||||
)
|
||||
toKeyedAction('key', variableStateFailed(toVariablePayload(variable, { error })))
|
||||
);
|
||||
expect(dispatchedActions[4].type).toEqual(notifyApp.type);
|
||||
expect(dispatchedActions[4].payload.title).toEqual('Templating [0]');
|
||||
|
@ -47,15 +47,17 @@ export const updateQueryVariableOptions = (
|
||||
getVariableQueryRunner().queueRequest({ identifier, datasource, searchFilter });
|
||||
});
|
||||
} catch (err) {
|
||||
const error = toDataQueryError(err);
|
||||
const { rootStateKey } = identifier;
|
||||
if (getVariablesState(rootStateKey, getState()).editor.id === identifier.id) {
|
||||
dispatch(
|
||||
toKeyedAction(rootStateKey, addVariableEditorError({ errorProp: 'update', errorText: error.message }))
|
||||
);
|
||||
}
|
||||
if (err instanceof Error) {
|
||||
const error = toDataQueryError(err);
|
||||
const { rootStateKey } = identifier;
|
||||
if (getVariablesState(rootStateKey, getState()).editor.id === identifier.id) {
|
||||
dispatch(
|
||||
toKeyedAction(rootStateKey, addVariableEditorError({ errorProp: 'update', errorText: error.message }))
|
||||
);
|
||||
}
|
||||
|
||||
throw error;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
@ -116,7 +116,9 @@ function useTimoutValidation(value: string | undefined) {
|
||||
rangeUtil.describeInterval(value);
|
||||
setErr(undefined);
|
||||
} catch (e) {
|
||||
setErr(e.toString());
|
||||
if (e instanceof Error) {
|
||||
setErr(e.toString());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
setErr(undefined);
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { filter, find, startsWith } from 'lodash';
|
||||
|
||||
import { DataSourceInstanceSettings, ScopedVars } from '@grafana/data';
|
||||
import { DataSourceWithBackend, getTemplateSrv } from '@grafana/runtime';
|
||||
import { DataSourceWithBackend, getTemplateSrv, isFetchError } from '@grafana/runtime';
|
||||
import { getTimeSrv, TimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
||||
|
||||
import { resourceTypeDisplayNames } from '../azureMetadata';
|
||||
@ -314,14 +314,18 @@ export default class AzureMonitorDatasource extends DataSourceWithBackend<AzureM
|
||||
});
|
||||
} catch (e) {
|
||||
let message = 'Azure Monitor: ';
|
||||
message += e.statusText ? e.statusText + ': ' : '';
|
||||
if (isFetchError(e)) {
|
||||
message += e.statusText ? e.statusText + ': ' : '';
|
||||
|
||||
if (e.data && e.data.error && e.data.error.code) {
|
||||
message += e.data.error.code + '. ' + e.data.error.message;
|
||||
} else if (e.data && e.data.error) {
|
||||
message += e.data.error;
|
||||
} else if (e.data) {
|
||||
message += e.data;
|
||||
if (e.data && e.data.error && e.data.error.code) {
|
||||
message += e.data.error.code + '. ' + e.data.error.message;
|
||||
} else if (e.data && e.data.error) {
|
||||
message += e.data.error;
|
||||
} else if (e.data) {
|
||||
message += e.data;
|
||||
} else {
|
||||
message += 'Cannot connect to Azure Monitor REST API.';
|
||||
}
|
||||
} else {
|
||||
message += 'Cannot connect to Azure Monitor REST API.';
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
import { DataSourcePluginOptionsEditorProps, SelectableValue, updateDatasourcePluginOption } from '@grafana/data';
|
||||
import { getBackendSrv, getTemplateSrv, TemplateSrv } from '@grafana/runtime';
|
||||
import { getBackendSrv, getTemplateSrv, isFetchError, TemplateSrv } from '@grafana/runtime';
|
||||
import { Alert } from '@grafana/ui';
|
||||
|
||||
import ResponseParser from '../azure_monitor/response_parser';
|
||||
@ -70,13 +70,15 @@ export class ConfigEditor extends PureComponent<Props, State> {
|
||||
this.setState({ error: undefined });
|
||||
return ResponseParser.parseSubscriptionsForSelect(result);
|
||||
} catch (err) {
|
||||
this.setState({
|
||||
error: {
|
||||
title: 'Error requesting subscriptions',
|
||||
description: 'Could not request subscriptions from Azure. Check your credentials and try again.',
|
||||
details: err?.data?.message,
|
||||
},
|
||||
});
|
||||
if (isFetchError(err)) {
|
||||
this.setState({
|
||||
error: {
|
||||
title: 'Error requesting subscriptions',
|
||||
description: 'Could not request subscriptions from Azure. Check your credentials and try again.',
|
||||
details: err?.data?.message,
|
||||
},
|
||||
});
|
||||
}
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
};
|
||||
|
@ -93,7 +93,11 @@ describe('AzureMonitor resourcePickerData', () => {
|
||||
await resourcePickerData.getSubscriptions();
|
||||
throw Error('expected getSubscriptions to fail but it succeeded');
|
||||
} catch (err) {
|
||||
expect(err.message).toEqual('No subscriptions were found');
|
||||
if (err instanceof Error) {
|
||||
expect(err.message).toEqual('No subscriptions were found');
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
@ -178,7 +182,11 @@ describe('AzureMonitor resourcePickerData', () => {
|
||||
await resourcePickerData.getResourceGroupsBySubscriptionId('123');
|
||||
throw Error('expected getResourceGroupsBySubscriptionId to fail but it succeeded');
|
||||
} catch (err) {
|
||||
expect(err.message).toEqual('unable to fetch resource groups');
|
||||
if (err instanceof Error) {
|
||||
expect(err.message).toEqual('unable to fetch resource groups');
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
@ -232,7 +240,11 @@ describe('AzureMonitor resourcePickerData', () => {
|
||||
await resourcePickerData.getResourcesForResourceGroup('dev');
|
||||
throw Error('expected getResourcesForResourceGroup to fail but it succeeded');
|
||||
} catch (err) {
|
||||
expect(err.message).toEqual('unable to fetch resource details');
|
||||
if (err instanceof Error) {
|
||||
expect(err.message).toEqual('unable to fetch resource details');
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
@ -317,7 +329,11 @@ describe('AzureMonitor resourcePickerData', () => {
|
||||
await resourcePickerData.search('dev', 'logs');
|
||||
throw Error('expected search test to fail but it succeeded');
|
||||
} catch (err) {
|
||||
expect(err.message).toEqual('unable to fetch resource details');
|
||||
if (err instanceof Error) {
|
||||
expect(err.message).toEqual('unable to fetch resource details');
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -79,8 +79,10 @@ export default class GraphiteQuery {
|
||||
try {
|
||||
this.parseTargetRecursive(astNode, null);
|
||||
} catch (err) {
|
||||
console.error('error parsing target:', err.message);
|
||||
this.error = err.message;
|
||||
if (err instanceof Error) {
|
||||
console.error('error parsing target:', err.message);
|
||||
this.error = err.message;
|
||||
}
|
||||
this.target.textEditor = true;
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,6 @@
|
||||
import { Lexer } from './lexer';
|
||||
import { GraphiteParserError } from './types';
|
||||
import { isGraphiteParserError } from './utils';
|
||||
|
||||
export class Parser {
|
||||
expression: any;
|
||||
@ -21,11 +23,13 @@ export class Parser {
|
||||
try {
|
||||
return this.functionCall() || this.metricExpression();
|
||||
} catch (e) {
|
||||
return {
|
||||
type: 'error',
|
||||
message: e.message,
|
||||
pos: e.pos,
|
||||
};
|
||||
if (isGraphiteParserError(e)) {
|
||||
return {
|
||||
type: 'error',
|
||||
message: e.message,
|
||||
pos: e.pos,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -222,7 +226,11 @@ export class Parser {
|
||||
|
||||
const token = this.consumeToken();
|
||||
if (token.isUnclosed) {
|
||||
throw { message: 'Unclosed string parameter', pos: token.pos };
|
||||
const error: GraphiteParserError = {
|
||||
message: 'Unclosed string parameter',
|
||||
pos: token.pos,
|
||||
};
|
||||
throw error;
|
||||
}
|
||||
|
||||
return {
|
||||
@ -234,10 +242,11 @@ export class Parser {
|
||||
errorMark(text: string) {
|
||||
const currentToken = this.tokens[this.index];
|
||||
const type = currentToken ? currentToken.type : 'end of string';
|
||||
throw {
|
||||
const error: GraphiteParserError = {
|
||||
message: text + ' instead found ' + type,
|
||||
pos: currentToken ? currentToken.pos : this.lexer.char,
|
||||
};
|
||||
throw error;
|
||||
}
|
||||
|
||||
// returns token value and incre
|
||||
|
@ -90,7 +90,9 @@ export async function checkOtherSegments(
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
handleMetricsAutoCompleteError(state, err);
|
||||
if (err instanceof Error) {
|
||||
handleMetricsAutoCompleteError(state, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -96,7 +96,9 @@ async function getAltSegments(
|
||||
return altSegments;
|
||||
}
|
||||
} catch (err) {
|
||||
handleMetricsAutoCompleteError(state, err);
|
||||
if (err instanceof Error) {
|
||||
handleMetricsAutoCompleteError(state, err);
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
@ -133,7 +135,9 @@ async function getTags(state: GraphiteQueryEditorState, index: number, tagPrefix
|
||||
altTags.splice(0, 0, state.removeTagValue);
|
||||
return altTags;
|
||||
} catch (err) {
|
||||
handleTagsAutoCompleteError(state, err);
|
||||
if (err instanceof Error) {
|
||||
handleTagsAutoCompleteError(state, err);
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
@ -168,7 +172,9 @@ async function getTagsAsSegments(state: GraphiteQueryEditorState, tagPrefix: str
|
||||
});
|
||||
} catch (err) {
|
||||
tagsAsSegments = [];
|
||||
handleTagsAutoCompleteError(state, err);
|
||||
if (err instanceof Error) {
|
||||
handleTagsAutoCompleteError(state, err);
|
||||
}
|
||||
}
|
||||
|
||||
return tagsAsSegments;
|
||||
|
@ -41,6 +41,11 @@ export interface MetricTankMeta {
|
||||
info: MetricTankSeriesMeta[];
|
||||
}
|
||||
|
||||
export interface GraphiteParserError {
|
||||
message: string;
|
||||
pos: number;
|
||||
}
|
||||
|
||||
export type GraphiteQueryImportConfiguration = {
|
||||
loki: GraphiteToLokiQueryImportConfiguration;
|
||||
};
|
||||
|
@ -1,5 +1,7 @@
|
||||
import { last } from 'lodash';
|
||||
|
||||
import { GraphiteParserError } from './types';
|
||||
|
||||
/**
|
||||
* Graphite-web before v1.6 returns HTTP 500 with full stack traces in an HTML page
|
||||
* when a query fails. It results in massive error alerts with HTML tags in the UI.
|
||||
@ -19,3 +21,7 @@ export function reduceError(error: any): any {
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
export function isGraphiteParserError(e: unknown): e is GraphiteParserError {
|
||||
return typeof e === 'object' && e !== null && 'message' in e && 'pos' in e;
|
||||
}
|
||||
|
@ -116,7 +116,9 @@ describe('InfluxDataSource', () => {
|
||||
try {
|
||||
await lastValueFrom(ctx.ds.query(queryOptions));
|
||||
} catch (err) {
|
||||
expect(err.message).toBe('InfluxDB Error: Query timeout');
|
||||
if (err instanceof Error) {
|
||||
expect(err.message).toBe('InfluxDB Error: Query timeout');
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -54,7 +54,9 @@ export function SearchForm({ datasource, query, onChange }: Props) {
|
||||
const filteredOptions = options.filter((item) => (item.value ? fuzzyMatch(item.value, query).found : false));
|
||||
return filteredOptions;
|
||||
} catch (error) {
|
||||
dispatch(notifyApp(createErrorNotification('Error', error)));
|
||||
if (error instanceof Error) {
|
||||
dispatch(notifyApp(createErrorNotification('Error', error)));
|
||||
}
|
||||
return [];
|
||||
} finally {
|
||||
setIsLoading((prevValue) => ({ ...prevValue, [loaderOfType]: false }));
|
||||
|
@ -49,9 +49,11 @@ export function buildVisualQueryFromString(expr: string): Context {
|
||||
} catch (err) {
|
||||
// Not ideal to log it here, but otherwise we would lose the stack trace.
|
||||
console.error(err);
|
||||
context.errors.push({
|
||||
text: err.message,
|
||||
});
|
||||
if (err instanceof Error) {
|
||||
context.errors.push({
|
||||
text: err.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// If we have empty query, we want to reset errors
|
||||
|
@ -210,7 +210,9 @@ export class OpenTsQueryCtrl extends QueryCtrl {
|
||||
errs.downsampleInterval = "You must supply a downsample interval (e.g. '1m' or '1h').";
|
||||
}
|
||||
} catch (err) {
|
||||
errs.downsampleInterval = err.message;
|
||||
if (err instanceof Error) {
|
||||
errs.downsampleInterval = err.message;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -13,7 +13,11 @@ import {
|
||||
Icon,
|
||||
} from '@grafana/ui';
|
||||
import { LocalStorageValueProvider } from 'app/core/components/LocalStorageValueProvider';
|
||||
import { CancelablePromise, makePromiseCancelable } from 'app/core/utils/CancelablePromise';
|
||||
import {
|
||||
CancelablePromise,
|
||||
isCancelablePromiseRejection,
|
||||
makePromiseCancelable,
|
||||
} from 'app/core/utils/CancelablePromise';
|
||||
|
||||
import { PrometheusDatasource } from '../datasource';
|
||||
import { roundMsToMin } from '../language_utils';
|
||||
@ -174,7 +178,9 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF
|
||||
await Promise.all(remainingTasks);
|
||||
this.onUpdateLanguage();
|
||||
} catch (err) {
|
||||
if (!err.isCanceled) {
|
||||
if (isCancelablePromiseRejection(err) && err.isCanceled) {
|
||||
// do nothing, promise was canceled
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ import {
|
||||
DataSourceWithBackend,
|
||||
BackendDataSourceResponse,
|
||||
toDataQueryResponse,
|
||||
isFetchError,
|
||||
} from '@grafana/runtime';
|
||||
import { Badge, BadgeColor, Tooltip } from '@grafana/ui';
|
||||
import { safeStringifyValue } from 'app/core/utils/explore';
|
||||
@ -224,7 +225,7 @@ export class PrometheusDatasource
|
||||
);
|
||||
} catch (err) {
|
||||
// If status code of error is Method Not Allowed (405) and HTTP method is POST, retry with GET
|
||||
if (this.httpMethod === 'POST' && (err.status === 405 || err.status === 400)) {
|
||||
if (this.httpMethod === 'POST' && isFetchError(err) && (err.status === 405 || err.status === 400)) {
|
||||
console.warn(`Couldn't use configured POST HTTP method for this request. Trying to use GET method instead.`);
|
||||
} else {
|
||||
throw err;
|
||||
|
@ -43,9 +43,11 @@ export function buildVisualQueryFromString(expr: string): Context {
|
||||
} catch (err) {
|
||||
// Not ideal to log it here, but otherwise we would lose the stack trace.
|
||||
console.error(err);
|
||||
context.errors.push({
|
||||
text: err.message,
|
||||
});
|
||||
if (err instanceof Error) {
|
||||
context.errors.push({
|
||||
text: err.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// If we have empty query, we want to reset errors
|
||||
|
@ -4,7 +4,7 @@ import React, { useCallback, useState, useEffect, useMemo } from 'react';
|
||||
import { Node } from 'slate';
|
||||
|
||||
import { GrafanaTheme2, isValidGoDuration, SelectableValue } from '@grafana/data';
|
||||
import { getTemplateSrv, TemplateSrv } from '@grafana/runtime';
|
||||
import { FetchError, getTemplateSrv, isFetchError, TemplateSrv } from '@grafana/runtime';
|
||||
import {
|
||||
InlineFieldRow,
|
||||
InlineField,
|
||||
@ -53,7 +53,7 @@ const NativeSearch = ({ datasource, query, onChange, onBlur, onRunQuery }: Props
|
||||
const [hasSyntaxLoaded, setHasSyntaxLoaded] = useState(false);
|
||||
const [serviceOptions, setServiceOptions] = useState<Array<SelectableValue<string>>>();
|
||||
const [spanOptions, setSpanOptions] = useState<Array<SelectableValue<string>>>();
|
||||
const [error, setError] = useState(null);
|
||||
const [error, setError] = useState<Error | FetchError | null>(null);
|
||||
const [inputErrors, setInputErrors] = useState<{ [key: string]: boolean }>({});
|
||||
const [isLoading, setIsLoading] = useState<{
|
||||
serviceName: boolean;
|
||||
@ -73,9 +73,9 @@ const NativeSearch = ({ datasource, query, onChange, onBlur, onRunQuery }: Props
|
||||
const filteredOptions = options.filter((item) => (item.value ? fuzzyMatch(item.value, query).found : false));
|
||||
return filteredOptions;
|
||||
} catch (error) {
|
||||
if (error?.status === 404) {
|
||||
if (isFetchError(error) && error?.status === 404) {
|
||||
setError(error);
|
||||
} else {
|
||||
} else if (error instanceof Error) {
|
||||
dispatch(notifyApp(createErrorNotification('Error', error)));
|
||||
}
|
||||
return [];
|
||||
@ -94,9 +94,9 @@ const NativeSearch = ({ datasource, query, onChange, onBlur, onRunQuery }: Props
|
||||
setSpanOptions(spans);
|
||||
} catch (error) {
|
||||
// Display message if Tempo is connected but search 404's
|
||||
if (error?.status === 404) {
|
||||
if (isFetchError(error) && error?.status === 404) {
|
||||
setError(error);
|
||||
} else {
|
||||
} else if (error instanceof Error) {
|
||||
dispatch(notifyApp(createErrorNotification('Error', error)));
|
||||
}
|
||||
}
|
||||
@ -110,7 +110,9 @@ const NativeSearch = ({ datasource, query, onChange, onBlur, onRunQuery }: Props
|
||||
await languageProvider.start();
|
||||
setHasSyntaxLoaded(true);
|
||||
} catch (error) {
|
||||
dispatch(notifyApp(createErrorNotification('Error', error)));
|
||||
if (error instanceof Error) {
|
||||
dispatch(notifyApp(createErrorNotification('Error', error)));
|
||||
}
|
||||
}
|
||||
};
|
||||
fetchTags();
|
||||
|
@ -190,7 +190,7 @@ export class TempoDatasource extends DataSourceWithBackend<TempoQuery, TempoJson
|
||||
)
|
||||
);
|
||||
} catch (error) {
|
||||
return of({ error: { message: error.message }, data: [] });
|
||||
return of({ error: { message: error instanceof Error ? error.message : 'Unknown error occurred' }, data: [] });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -222,7 +222,10 @@ export class TestDataDataSource extends DataSourceWithBackend<TestDataQuery> {
|
||||
});
|
||||
return of({ data, state: LoadingState.Done }).pipe(delay(100));
|
||||
} catch (ex) {
|
||||
return of({ data: [], error: ex }).pipe(delay(100));
|
||||
return of({
|
||||
data: [],
|
||||
error: ex instanceof Error ? ex : new Error('Unkown error'),
|
||||
}).pipe(delay(100));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -131,7 +131,8 @@ export function useServices(datasource: ZipkinDatasource): AsyncState<CascaderOp
|
||||
}
|
||||
return [];
|
||||
} catch (error) {
|
||||
dispatch(notifyApp(createErrorNotification('Failed to load services from Zipkin', error)));
|
||||
const errorToShow = error instanceof Error ? error : 'An unknown error occurred';
|
||||
dispatch(notifyApp(createErrorNotification('Failed to load services from Zipkin', errorToShow)));
|
||||
throw error;
|
||||
}
|
||||
}, [datasource]);
|
||||
@ -175,7 +176,8 @@ export function useLoadOptions(datasource: ZipkinDatasource) {
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
dispatch(notifyApp(createErrorNotification('Failed to load spans from Zipkin', error)));
|
||||
const errorToShow = error instanceof Error ? error : 'An unknown error occurred';
|
||||
dispatch(notifyApp(createErrorNotification('Failed to load spans from Zipkin', errorToShow)));
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
@ -216,7 +218,8 @@ export function useLoadOptions(datasource: ZipkinDatasource) {
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
dispatch(notifyApp(createErrorNotification('Failed to load spans from Zipkin', error)));
|
||||
const errorToShow = error instanceof Error ? error : 'An unknown error occurred';
|
||||
dispatch(notifyApp(createErrorNotification('Failed to load spans from Zipkin', errorToShow)));
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
@ -68,7 +68,11 @@ export const APIEditor: FC<StandardEditorProps<APIEditorConfig, any, any>> = (pr
|
||||
const json = JSON.parse(data);
|
||||
return <JSONFormatter json={json} />;
|
||||
} catch (error) {
|
||||
return `Invalid JSON provided: ${error.message}`;
|
||||
if (error instanceof Error) {
|
||||
return `Invalid JSON provided: ${error.message}`;
|
||||
} else {
|
||||
return 'Invalid JSON provided';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -571,7 +571,7 @@ class GraphElement {
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('flotcharts error', e);
|
||||
this.ctrl.error = e.message || 'Render Error';
|
||||
this.ctrl.error = e instanceof Error ? e.message : 'Render Error';
|
||||
this.ctrl.renderError = true;
|
||||
}
|
||||
|
||||
|
@ -61,7 +61,7 @@ function sortSeriesByLabel(s1: { label: string }, s2: { label: string }) {
|
||||
label1 = parseHistogramLabel(s1.label);
|
||||
label2 = parseHistogramLabel(s2.label);
|
||||
} catch (err) {
|
||||
console.error(err.message || err);
|
||||
console.error(err instanceof Error ? err.message : err);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -449,7 +449,7 @@ export class HeatmapRenderer {
|
||||
return formattedValueToString(v);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err.message || err);
|
||||
console.error(err instanceof Error ? err.message : err);
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
@ -51,8 +51,9 @@ export function prepScatter(
|
||||
builder = prepConfig(getData, series, theme, ttip);
|
||||
} catch (e) {
|
||||
console.log('prepScatter ERROR', e);
|
||||
const errorMessage = e instanceof Error ? e.message : 'Unknown error in prepScatter';
|
||||
return {
|
||||
error: e.message,
|
||||
error: errorMessage,
|
||||
series: [],
|
||||
};
|
||||
}
|
||||
|
@ -50,9 +50,10 @@ function tryExpectations(received: any[], expected: any[]): jest.CustomMatcherRe
|
||||
message: () => passMessage(received, expected),
|
||||
};
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : 'An unknown error occurred';
|
||||
return {
|
||||
pass: false,
|
||||
message: () => err,
|
||||
message: () => message,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,7 @@
|
||||
"allowJs": true,
|
||||
"strict": true,
|
||||
"resolveJsonModule": true,
|
||||
"useUnknownInCatchVariables": false,
|
||||
"useUnknownInCatchVariables": true,
|
||||
"incremental": true,
|
||||
"tsBuildInfoFile": "./tsconfig.tsbuildinfo"
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user