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
pull/43838/head
Ashley Harrison 3 years ago committed by GitHub
parent 2fbe99c1be
commit 803473f479
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      packages/grafana-runtime/src/index.ts
  2. 4
      packages/grafana-runtime/src/services/backendSrv.ts
  3. 2
      packages/grafana-runtime/src/utils/DataSourceWithBackend.ts
  4. 10
      public/app/core/components/RolePicker/api.ts
  5. 2
      public/app/core/history/RichHistoryLocalStorage.ts
  6. 4
      public/app/core/store.ts
  7. 13
      public/app/core/utils/CancelablePromise.ts
  8. 29
      public/app/core/utils/richHistory.ts
  9. 44
      public/app/features/admin/state/actions.ts
  10. 10
      public/app/features/alerting/state/actions.ts
  11. 4
      public/app/features/alerting/unified/api/alertmanager.ts
  12. 1
      public/app/features/alerting/unified/api/buildInfo.test.ts
  13. 3
      public/app/features/alerting/unified/api/buildInfo.ts
  14. 2
      public/app/features/alerting/unified/components/admin/AlertmanagerConfig.tsx
  15. 5
      public/app/features/alerting/unified/utils/alertmanager.ts
  16. 4
      public/app/features/alerting/unified/utils/redux.ts
  17. 2
      public/app/features/dashboard/components/DashboardSettings/AutoRefreshIntervals.tsx
  18. 6
      public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx
  19. 2
      public/app/features/dashboard/components/SaveDashboard/forms/SaveDashboardAsForm.tsx
  20. 12
      public/app/features/dashboard/state/initDashboard.test.ts
  21. 8
      public/app/features/dashboard/state/initDashboard.ts
  22. 33
      public/app/features/datasources/state/actions.test.ts
  23. 27
      public/app/features/datasources/state/actions.ts
  24. 4
      public/app/features/folders/state/actions.ts
  25. 5
      public/app/features/library-panels/components/AddLibraryPanelModal/AddLibraryPanelModal.tsx
  26. 8
      public/app/features/library-panels/utils/usePanelSave.ts
  27. 10
      public/app/features/manage-dashboards/DashboardImportPage.tsx
  28. 16
      public/app/features/manage-dashboards/state/actions.ts
  29. 4
      public/app/features/playlist/api.ts
  30. 18
      public/app/features/plugins/admin/api.ts
  31. 6
      public/app/features/plugins/admin/state/actions.ts
  32. 4
      public/app/features/plugins/datasource_srv.ts
  33. 7
      public/app/features/variables/query/actions.test.tsx
  34. 18
      public/app/features/variables/query/actions.ts
  35. 4
      public/app/plugins/datasource/cloudwatch/components/ConfigEditor.tsx
  36. 22
      public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_monitor/azure_monitor_datasource.ts
  37. 18
      public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ConfigEditor.tsx
  38. 24
      public/app/plugins/datasource/grafana-azure-monitor-datasource/resourcePicker/resourcePickerData.test.ts
  39. 6
      public/app/plugins/datasource/graphite/graphite_query.ts
  40. 23
      public/app/plugins/datasource/graphite/parser.ts
  41. 4
      public/app/plugins/datasource/graphite/state/helpers.ts
  42. 12
      public/app/plugins/datasource/graphite/state/providers.ts
  43. 5
      public/app/plugins/datasource/graphite/types.ts
  44. 6
      public/app/plugins/datasource/graphite/utils.ts
  45. 4
      public/app/plugins/datasource/influxdb/specs/datasource.test.ts
  46. 4
      public/app/plugins/datasource/jaeger/components/SearchForm.tsx
  47. 8
      public/app/plugins/datasource/loki/querybuilder/parsing.ts
  48. 4
      public/app/plugins/datasource/opentsdb/query_ctrl.ts
  49. 10
      public/app/plugins/datasource/prometheus/components/PromQueryField.tsx
  50. 3
      public/app/plugins/datasource/prometheus/datasource.tsx
  51. 8
      public/app/plugins/datasource/prometheus/querybuilder/parsing.ts
  52. 16
      public/app/plugins/datasource/tempo/QueryEditor/NativeSearch.tsx
  53. 2
      public/app/plugins/datasource/tempo/datasource.ts
  54. 5
      public/app/plugins/datasource/testdata/datasource.ts
  55. 9
      public/app/plugins/datasource/zipkin/QueryField.tsx
  56. 6
      public/app/plugins/panel/canvas/editor/APIEditor.tsx
  57. 2
      public/app/plugins/panel/graph/graph.ts
  58. 2
      public/app/plugins/panel/heatmap/heatmap_data_converter.ts
  59. 2
      public/app/plugins/panel/heatmap/rendering.ts
  60. 3
      public/app/plugins/panel/xychart/scatter.ts
  61. 3
      public/test/matchers/toEmitValues.ts
  62. 2
      tsconfig.json

@ -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;
const message = errMessage || data?.message || 'HTTP error ' + statusText;
let message: string | undefined;
let details: HealthCheckResultDetails;
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 (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 (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;
} 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…
Cancel
Save