"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.useAppToasts = exports.unknownToErrorStack = exports.isEmptyObjectWhenStringified = exports.getStringifiedStack = exports.esErrorToErrorStack = exports.errorToErrorStackAdapter = exports.errorToErrorStack = exports.convertErrorToEnumerable = exports.appErrorToErrorStack = void 0; var _react = require("react"); var _fp = require("lodash/fp"); var _securitysolutionTGrid = require("@kbn/securitysolution-t-grid"); var _public = require("@kbn/kibana-react-plugin/public"); var _public2 = require("@kbn/data-plugin/public"); /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ /** * This gives a better presentation of error data sent from the API (both general platform errors and app-specific errors). * This uses platform's new Toasts service to prevent modal/toast z-index collision issues. * This fixes some issues you can see with re-rendering since using a class such as notifications.toasts. * This also has an adapter and transform for detecting if a bsearch's EsError is present and then adapts that to the * Kibana error toaster model so that the network error message will be shown rather than a stack trace. */ const useAppToasts = () => { const { toasts } = (0, _public.useKibana)().services.notifications; const addError = (0, _react.useRef)(toasts === null || toasts === void 0 ? void 0 : toasts.addError.bind(toasts)).current; const addSuccess = (0, _react.useRef)(toasts === null || toasts === void 0 ? void 0 : toasts.addSuccess.bind(toasts)).current; const addWarning = (0, _react.useRef)(toasts === null || toasts === void 0 ? void 0 : toasts.addWarning.bind(toasts)).current; const _addError = (0, _react.useCallback)((error, options) => { const adaptedError = errorToErrorStackAdapter(error); return addError(adaptedError, options); }, [addError]); return { api: toasts, addError: _addError, addSuccess, addWarning }; }; /** * Given an error of one type vs. another type this tries to adapt * the best it can to the existing error toaster which parses the .stack * as its error when you click the button to show the full error message. * @param error The error to adapt to. * @returns The adapted toaster error message. */ exports.useAppToasts = useAppToasts; const errorToErrorStackAdapter = error => { if (error != null && (0, _public2.isEsError)(error)) { return esErrorToErrorStack(error); } else if ((0, _securitysolutionTGrid.isAppError)(error)) { return appErrorToErrorStack(error); } else if (error instanceof Error) { return errorToErrorStack(error); } else { return unknownToErrorStack(error); } }; /** * See this file, we are not allowed to import files such as es_error. * So instead we say maybe err is on there so that we can unwrap it and get * our status code from it if possible within the error in our function. * src/plugins/data/public/search/errors/es_error.tsx */ exports.errorToErrorStackAdapter = errorToErrorStackAdapter; /** * This attempts its best to map between an IEsError which comes from bsearch to a error_toaster * See the file: src/core/public/notifications/toasts/error_toast.tsx * * NOTE: This is brittle at the moment from bsearch and the hope is that better support between * the error message and formatting of bsearch and the error_toast.tsx from Kibana core will be * supported in the future. However, for now, this is _hopefully_ temporary. * * Also see the file: * x-pack/plugins/security_solution/public/app/home/setup.tsx * * Where this same technique of overriding and changing the stack is occurring. */ const esErrorToErrorStack = error => { var _error$err, _error$attributes$rea, _error$attributes, _error$attributes$rea2, _error$attributes2; const maybeUnWrapped = error.err != null ? error.err : error; const statusCode = ((_error$err = error.err) === null || _error$err === void 0 ? void 0 : _error$err.statusCode) != null ? `(${error.err.statusCode})` : error.statusCode != null ? `(${error.statusCode})` : ''; const stringifiedError = getStringifiedStack(maybeUnWrapped); const adaptedError = new Error(`${(_error$attributes$rea = (_error$attributes = error.attributes) === null || _error$attributes === void 0 ? void 0 : _error$attributes.reason) !== null && _error$attributes$rea !== void 0 ? _error$attributes$rea : error.message} ${statusCode}`); adaptedError.name = (_error$attributes$rea2 = (_error$attributes2 = error.attributes) === null || _error$attributes2 === void 0 ? void 0 : _error$attributes2.reason) !== null && _error$attributes$rea2 !== void 0 ? _error$attributes$rea2 : error.message; if (stringifiedError != null) { adaptedError.stack = stringifiedError; } return adaptedError; }; /** * This attempts its best to map between a Kibana application error which can come from backend * REST API's that are typically of a particular format and form. * * The existing error_toaster code tries to consolidate network and software stack traces but really * here and our toasters we are using them for network response errors so we can troubleshoot things * as quick as possible. * * We override and use error.stack to be able to give _full_ network responses regardless of if they * are from Kibana or if they are from elasticSearch since sometimes Kibana errors might wrap the errors. * * Sometimes the errors are wrapped from io-ts, Kibana Schema or something else and we want to show * as full error messages as we can. */ exports.esErrorToErrorStack = esErrorToErrorStack; const appErrorToErrorStack = error => { const statusCode = (0, _securitysolutionTGrid.isKibanaError)(error) ? `(${error.body.statusCode})` : (0, _securitysolutionTGrid.isSecurityAppError)(error) ? `(${error.body.status_code})` : ''; const stringifiedError = getStringifiedStack(error); const adaptedError = new Error(`${String(error.body.message).trim() !== '' ? error.body.message : error.message} ${statusCode}`); // Note although all the Typescript typings say that error.name is a string and exists, we still can encounter an undefined so we // do an extra guard here and default to empty string if it is undefined adaptedError.name = error.name != null ? error.name : ''; if (stringifiedError != null) { adaptedError.stack = stringifiedError; } return adaptedError; }; /** * Takes an error and tries to stringify it and use that as the stack for the error toaster * @param error The error to convert into a message * @returns The exception error to return back */ exports.appErrorToErrorStack = appErrorToErrorStack; const errorToErrorStack = error => { const stringifiedError = getStringifiedStack(error); const adaptedError = new Error(error.message); adaptedError.name = error.name; if (stringifiedError != null) { adaptedError.stack = stringifiedError; } return adaptedError; }; /** * Last ditch effort to take something unknown which could be a string, number, * anything. This usually should not be called but just in case we do try our * best to stringify it and give a message, name, and replace the stack of it. * @param error The unknown error to convert into a message * @returns The exception error to return back */ exports.errorToErrorStack = errorToErrorStack; const unknownToErrorStack = error => { const stringifiedError = getStringifiedStack(error); const message = (0, _fp.isString)(error) ? error : error instanceof Object && stringifiedError != null ? stringifiedError : String(error); const adaptedError = new Error(message); adaptedError.name = message; if (stringifiedError != null) { adaptedError.stack = stringifiedError; } return adaptedError; }; /** * Stringifies the error. However, since Errors can JSON.stringify into empty objects this will * use a replacer to push those as enumerable properties so we can stringify them. * @param error The error to get a string representation of * @returns The string representation of the error */ exports.unknownToErrorStack = unknownToErrorStack; const getStringifiedStack = error => { try { return JSON.stringify(error, (_, value) => { const enumerable = convertErrorToEnumerable(value); if (isEmptyObjectWhenStringified(enumerable)) { return undefined; } else { return enumerable; } }, 2); } catch (err) { return undefined; } }; /** * Converts an error if this is an error to have enumerable so it can stringified * @param error The error which might not have enumerable properties. * @returns Enumerable error */ exports.getStringifiedStack = getStringifiedStack; const convertErrorToEnumerable = error => { if (error instanceof Error) { return { ...error, name: error.name, message: error.message, stack: error.stack }; } else { return error; } }; /** * If the object strings into an empty object we shouldn't show it as it doesn't * add value and sometimes different people/frameworks attach req,res,request,response * objects which don't stringify into anything or can have circular references. * @param item The item to see if we are empty or have a circular reference error with. * @returns True if this is a good object to stringify, otherwise false */ exports.convertErrorToEnumerable = convertErrorToEnumerable; const isEmptyObjectWhenStringified = item => { if (item instanceof Object) { try { return JSON.stringify(item) === '{}'; } catch (_) { // Do nothing, return false if we have a circular reference or other oddness. return false; } } else { return false; } }; exports.isEmptyObjectWhenStringified = isEmptyObjectWhenStringified;