From d919a442705cb9f1cbf00031cac57091101790b4 Mon Sep 17 00:00:00 2001 From: Dennis Oelkers Date: Mon, 26 Apr 2021 16:06:01 +0200 Subject: [PATCH] Wrap system configuration elements in error boundary. (#10472) * Wrap configlets in individual error boundaries. * Typing `ConfigurationsPage`. * Turning class into functional component. * Replacing `connect` with `useStore`. * Adding tests. * Extending console suppression. * Adding license header. * Use `Promise.allSettled` to make sure loaded flag is set in case of rejections. * Extracting helper components. --- graylog2-web-interface/package.json | 1 + .../src/pages/ConfigurationsPage.jsx | 254 ------------------ .../src/pages/ConfigurationsPage.test.tsx | 66 +++++ .../src/pages/ConfigurationsPage.tsx | 176 ++++++++++++ .../configurations/ConfigletContainer.tsx | 56 ++++ .../pages/configurations/PluginConfigRows.tsx | 62 +++++ graylog2-web-interface/src/views/types.d.ts | 9 + graylog2-web-interface/yarn.lock | 18 +- 8 files changed, 384 insertions(+), 258 deletions(-) delete mode 100644 graylog2-web-interface/src/pages/ConfigurationsPage.jsx create mode 100644 graylog2-web-interface/src/pages/ConfigurationsPage.test.tsx create mode 100644 graylog2-web-interface/src/pages/ConfigurationsPage.tsx create mode 100644 graylog2-web-interface/src/pages/configurations/ConfigletContainer.tsx create mode 100644 graylog2-web-interface/src/pages/configurations/PluginConfigRows.tsx diff --git a/graylog2-web-interface/package.json b/graylog2-web-interface/package.json index 601bb5591725..ced4e7609fd7 100644 --- a/graylog2-web-interface/package.json +++ b/graylog2-web-interface/package.json @@ -85,6 +85,7 @@ "react-beautiful-dnd": "^13.1.0", "react-color": "^2.14.0", "react-day-picker": "^7.4.8", + "react-error-boundary": "^3.1.1", "react-grid-layout": "^0.18.3", "react-immutable-proptypes": "^2.1.0", "react-isolated-scroll": "^0.1.1", diff --git a/graylog2-web-interface/src/pages/ConfigurationsPage.jsx b/graylog2-web-interface/src/pages/ConfigurationsPage.jsx deleted file mode 100644 index 390a91a3f1c0..000000000000 --- a/graylog2-web-interface/src/pages/ConfigurationsPage.jsx +++ /dev/null @@ -1,254 +0,0 @@ -/* - * Copyright (C) 2020 Graylog, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the Server Side Public License, version 1, - * as published by MongoDB, Inc. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * Server Side Public License for more details. - * - * You should have received a copy of the Server Side Public License - * along with this program. If not, see - * . - */ -import React from 'react'; -import PropTypes from 'prop-types'; -import { PluginStore } from 'graylog-web-plugin/plugin'; - -import { Col, Row } from 'components/graylog'; -import { DocumentTitle, PageHeader, Spinner } from 'components/common'; -import connect from 'stores/connect'; -import CombinedProvider from 'injection/CombinedProvider'; -import { isPermitted } from 'util/PermissionsMixin'; -import SearchesConfig from 'components/configurations/SearchesConfig'; -import MessageProcessorsConfig from 'components/configurations/MessageProcessorsConfig'; -import SidecarConfig from 'components/configurations/SidecarConfig'; -import EventsConfig from 'components/configurations/EventsConfig'; -import UrlWhiteListConfig from 'components/configurations/UrlWhiteListConfig'; -import {} from 'components/maps/configurations'; -import style from 'components/configurations/ConfigurationStyles.lazy.css'; - -import DecoratorsConfig from '../components/configurations/DecoratorsConfig'; - -const { CurrentUserStore } = CombinedProvider.get('CurrentUser'); -const { ConfigurationsActions, ConfigurationsStore } = CombinedProvider.get('Configurations'); - -const SEARCHES_CLUSTER_CONFIG = 'org.graylog2.indexer.searches.SearchesClusterConfig'; -const MESSAGE_PROCESSORS_CONFIG = 'org.graylog2.messageprocessors.MessageProcessorsConfig'; -const SIDECAR_CONFIG = 'org.graylog.plugins.sidecar.system.SidecarConfiguration'; -const EVENTS_CONFIG = 'org.graylog.events.configuration.EventsConfiguration'; -const URL_WHITELIST_CONFIG = 'org.graylog2.system.urlwhitelist.UrlWhitelist'; - -class ConfigurationsPage extends React.Component { - checkLoadedTimer = undefined - - constructor(props) { - super(props); - - this.state = { loaded: false }; - } - - componentDidMount() { - style.use(); - const { currentUser: { permissions } } = this.props; - - this._checkConfig(); - - ConfigurationsActions.list(SEARCHES_CLUSTER_CONFIG); - ConfigurationsActions.listMessageProcessorsConfig(MESSAGE_PROCESSORS_CONFIG); - ConfigurationsActions.list(SIDECAR_CONFIG); - ConfigurationsActions.list(EVENTS_CONFIG); - - if (isPermitted(permissions, ['urlwhitelist:read'])) { - ConfigurationsActions.listWhiteListConfig(URL_WHITELIST_CONFIG); - } - - PluginStore.exports('systemConfigurations').forEach((systemConfig) => { - ConfigurationsActions.list(systemConfig.configType); - }); - } - - componentWillUnmount() { - style.unuse(); - this._clearTimeout(); - } - - _getConfig = (configType) => { - const { configuration } = this.props; - - if (configuration && configuration[configType]) { - return configuration[configType]; - } - - return null; - }; - - _onUpdate = (configType) => { - return (config) => { - switch (configType) { - case MESSAGE_PROCESSORS_CONFIG: - return ConfigurationsActions.updateMessageProcessorsConfig(configType, config); - case URL_WHITELIST_CONFIG: - return ConfigurationsActions.updateWhitelist(configType, config); - default: - return ConfigurationsActions.update(configType, config); - } - }; - }; - - _pluginConfigs = () => { - return PluginStore.exports('systemConfigurations').map((systemConfig, idx) => { - return React.createElement(systemConfig.component, { - // eslint-disable-next-line react/no-array-index-key - key: `system-configuration-${idx}`, - config: this._getConfig(systemConfig.configType) || undefined, - updateConfig: this._onUpdate(systemConfig.configType), - }); - }); - }; - - _pluginConfigRows = () => { - const pluginConfigs = this._pluginConfigs(); - const rows = []; - let idx = 0; - - // Put two plugin config components per row. - while (pluginConfigs.length > 0) { - idx += 1; - - rows.push( - - - {pluginConfigs.shift()} - - - {pluginConfigs.shift() || ( )} - - , - ); - } - - return rows; - }; - - _checkConfig = () => { - const { configuration } = this.props; - - this.checkLoadedTimer = setTimeout(() => { - if (Object.keys(configuration).length > 0) { - this.setState({ loaded: true }, this._clearTimeout); - - return; - } - - this._checkConfig(); - }, 100); - }; - - _clearTimeout = () => { - if (this.checkLoadedTimer) { - clearTimeout(this.checkLoadedTimer); - } - } - - render() { - const { loaded } = this.state; - const { currentUser: { permissions } } = this.props; - let Output = ( - - - - ); - - if (loaded) { - const searchesConfig = this._getConfig(SEARCHES_CLUSTER_CONFIG); - const messageProcessorsConfig = this._getConfig(MESSAGE_PROCESSORS_CONFIG); - const sidecarConfig = this._getConfig(SIDECAR_CONFIG); - const eventsConfig = this._getConfig(EVENTS_CONFIG); - const urlWhiteListConfig = this._getConfig(URL_WHITELIST_CONFIG); - - Output = ( - <> - {searchesConfig && ( - - - - )} - {messageProcessorsConfig && ( - - - - )} - {sidecarConfig && ( - - - - )} - {eventsConfig && ( - - - - )} - {isPermitted(permissions, ['urlwhitelist:read']) && urlWhiteListConfig && ( - - - - )} - - - - - ); - } - - const pluginConfigRows = this._pluginConfigRows(); - - return ( - - - - - You can configure system settings for different sub systems on this page. - - - - - {Output} - - - {pluginConfigRows.length > 0 && ( - - -

Plugins

-

Configuration for installed plugins.

-
-
- {pluginConfigRows} -
- -
- )} -
-
- ); - } -} - -ConfigurationsPage.propTypes = { - configuration: PropTypes.object.isRequired, - currentUser: PropTypes.object.isRequired, -}; - -export default connect(ConfigurationsPage, { configurations: ConfigurationsStore, currentUser: CurrentUserStore }, ({ configurations, currentUser, ...otherProps }) => ({ - ...configurations, - ...currentUser, - ...otherProps, -})); diff --git a/graylog2-web-interface/src/pages/ConfigurationsPage.test.tsx b/graylog2-web-interface/src/pages/ConfigurationsPage.test.tsx new file mode 100644 index 000000000000..f7ab8311689a --- /dev/null +++ b/graylog2-web-interface/src/pages/ConfigurationsPage.test.tsx @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2020 Graylog, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * . + */ +import * as React from 'react'; +import { render, screen } from 'wrappedTestingLibrary'; +import asMock from 'helpers/mocking/AsMock'; +import suppressConsole from 'helpers/suppressConsole'; + +import ConfigurationsPage from 'pages/ConfigurationsPage'; +import usePluginEntities from 'views/logic/usePluginEntities'; +import SidecarConfig from 'components/configurations/SidecarConfig'; + +jest.mock('views/logic/usePluginEntities'); +jest.mock('components/configurations/SearchesConfig', () => () => Search Configuration); +jest.mock('components/configurations/MessageProcessorsConfig', () => () => Message Processors Configuration); +jest.mock('components/configurations/SidecarConfig'); + +const ComponentThrowingError = () => { + throw Error('Boom!'); +}; + +const ComponentWorkingFine = () => ( + It is all good! +); + +describe('ConfigurationsPage', () => { + it('wraps core configuration elements with error boundary', async () => { + asMock(usePluginEntities).mockReturnValue([]); + + asMock(SidecarConfig).mockImplementation(ComponentThrowingError); + + await suppressConsole(async () => { + render(); + + await screen.findByText('Message Processors Configuration'); + await screen.findByText('Boom!'); + }); + }); + + it('wraps plugin configuration elements with error boundary', async () => { + asMock(usePluginEntities).mockReturnValue([ + { configType: 'foo', component: ComponentThrowingError }, + { configType: 'bar', component: ComponentWorkingFine }, + ]); + + suppressConsole(() => { + render(); + }); + + await screen.findByText('It is all good!'); + await screen.findByText('Boom!'); + }); +}); diff --git a/graylog2-web-interface/src/pages/ConfigurationsPage.tsx b/graylog2-web-interface/src/pages/ConfigurationsPage.tsx new file mode 100644 index 000000000000..b68e1fc140cb --- /dev/null +++ b/graylog2-web-interface/src/pages/ConfigurationsPage.tsx @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2020 Graylog, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * . + */ +import * as React from 'react'; +import { useEffect, useState } from 'react'; + +import { Col, Row } from 'components/graylog'; +import { DocumentTitle, PageHeader, Spinner } from 'components/common'; +import { useStore } from 'stores/connect'; +import CombinedProvider from 'injection/CombinedProvider'; +import { isPermitted } from 'util/PermissionsMixin'; +import SearchesConfig from 'components/configurations/SearchesConfig'; +import MessageProcessorsConfig from 'components/configurations/MessageProcessorsConfig'; +import SidecarConfig from 'components/configurations/SidecarConfig'; +import EventsConfig from 'components/configurations/EventsConfig'; +import UrlWhiteListConfig from 'components/configurations/UrlWhiteListConfig'; +import {} from 'components/maps/configurations'; +import style from 'components/configurations/ConfigurationStyles.lazy.css'; +import { Store } from 'stores/StoreTypes'; +import usePluginEntities from 'views/logic/usePluginEntities'; + +import ConfigletContainer from './configurations/ConfigletContainer'; +import PluginConfigRows from './configurations/PluginConfigRows'; + +import DecoratorsConfig from '../components/configurations/DecoratorsConfig'; + +const { CurrentUserStore } = CombinedProvider.get('CurrentUser'); +const { ConfigurationsActions, ConfigurationsStore } = CombinedProvider.get('Configurations'); + +const SEARCHES_CLUSTER_CONFIG = 'org.graylog2.indexer.searches.SearchesClusterConfig'; +const MESSAGE_PROCESSORS_CONFIG = 'org.graylog2.messageprocessors.MessageProcessorsConfig'; +const SIDECAR_CONFIG = 'org.graylog.plugins.sidecar.system.SidecarConfiguration'; +const EVENTS_CONFIG = 'org.graylog.events.configuration.EventsConfiguration'; +const URL_WHITELIST_CONFIG = 'org.graylog2.system.urlwhitelist.UrlWhitelist'; + +const _getConfig = (configType, configuration) => configuration?.[configType] ?? null; + +const _onUpdate = (configType: string) => { + return (config) => { + switch (configType) { + case MESSAGE_PROCESSORS_CONFIG: + return ConfigurationsActions.updateMessageProcessorsConfig(configType, config); + case URL_WHITELIST_CONFIG: + return ConfigurationsActions.updateWhitelist(configType, config); + default: + return ConfigurationsActions.update(configType, config); + } + }; +}; + +const ConfigurationsPage = () => { + const [loaded, setLoaded] = useState(false); + const pluginSystemConfigs = usePluginEntities('systemConfigurations'); + const configuration = useStore(ConfigurationsStore as Store>, (state) => state?.configuration); + const permissions = useStore(CurrentUserStore as Store<{ currentUser: { permissions: Array } }>, (state) => state?.currentUser?.permissions); + + useEffect(() => { + style.use(); + + return () => { style.unuse(); }; + }, []); + + useEffect(() => { + const promises = [ + ConfigurationsActions.list(SEARCHES_CLUSTER_CONFIG), + ConfigurationsActions.listMessageProcessorsConfig(MESSAGE_PROCESSORS_CONFIG), + ConfigurationsActions.list(SIDECAR_CONFIG), + ConfigurationsActions.list(EVENTS_CONFIG), + ]; + + if (isPermitted(permissions, ['urlwhitelist:read'])) { + promises.push(ConfigurationsActions.listWhiteListConfig(URL_WHITELIST_CONFIG)); + } + + const pluginPromises = pluginSystemConfigs + .map((systemConfig) => ConfigurationsActions.list(systemConfig.configType)); + + Promise.allSettled([...promises, ...pluginPromises]).then(() => setLoaded(true)); + }, [permissions, pluginSystemConfigs]); + + let Output = ( + + + + ); + + if (loaded) { + const searchesConfig = _getConfig(SEARCHES_CLUSTER_CONFIG, configuration); + const messageProcessorsConfig = _getConfig(MESSAGE_PROCESSORS_CONFIG, configuration); + const sidecarConfig = _getConfig(SIDECAR_CONFIG, configuration); + const eventsConfig = _getConfig(EVENTS_CONFIG, configuration); + const urlWhiteListConfig = _getConfig(URL_WHITELIST_CONFIG, configuration); + + Output = ( + <> + {searchesConfig && ( + + + + )} + {messageProcessorsConfig && ( + + + + )} + {sidecarConfig && ( + + + + )} + {eventsConfig && ( + + + + )} + {isPermitted(permissions, ['urlwhitelist:read']) && urlWhiteListConfig && ( + + + + )} + + + + + ); + } + + return ( + + + + + You can configure system settings for different sub systems on this page. + + + + + {Output} + + + {pluginSystemConfigs.length > 0 && ( + + +

Plugins

+

Configuration for installed plugins.

+
+
+ +
+ +
+ )} +
+
+ ); +}; + +export default ConfigurationsPage; diff --git a/graylog2-web-interface/src/pages/configurations/ConfigletContainer.tsx b/graylog2-web-interface/src/pages/configurations/ConfigletContainer.tsx new file mode 100644 index 000000000000..6182558e7e66 --- /dev/null +++ b/graylog2-web-interface/src/pages/configurations/ConfigletContainer.tsx @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2020 Graylog, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * . + */ +import * as React from 'react'; +import { ErrorBoundary } from 'react-error-boundary'; + +import { Col } from 'components/graylog'; + +type ErrorFallbackProps = { + error: { + message: string, + }, + title: string, +}; + +const ErrorFallback = ({ error, title }: ErrorFallbackProps) => ( + <> +

{title}

+

Something went wrong:

+
{error.message}
+ +); + +type BoundaryProps = { + children: React.ReactNode, + title: string, +} + +const Boundary = ({ children, title }: BoundaryProps) => ( + }> + {children} + +); + +const ConfigletContainer = ({ children, title }: BoundaryProps) => ( + + + {children} + + +); + +export default ConfigletContainer; diff --git a/graylog2-web-interface/src/pages/configurations/PluginConfigRows.tsx b/graylog2-web-interface/src/pages/configurations/PluginConfigRows.tsx new file mode 100644 index 000000000000..c35362be893b --- /dev/null +++ b/graylog2-web-interface/src/pages/configurations/PluginConfigRows.tsx @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2020 Graylog, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * . + */ +import * as React from 'react'; +import { useMemo } from 'react'; +import { chunk } from 'lodash'; +import { SystemConfiguration } from 'views/types'; + +import { Row } from 'components/graylog'; +import ConfigletContainer from 'pages/configurations/ConfigletContainer'; +import CombinedProvider from 'injection/CombinedProvider'; + +const { ConfigurationsActions } = CombinedProvider.get('Configurations'); + +const _onUpdate = (configType: string) => (config) => ConfigurationsActions.update(configType, config); + +const _getConfig = (configType, configuration) => configuration?.[configType] ?? null; + +const _pluginConfigs = (systemConfigs, configuration) => systemConfigs + .map(({ component: SystemConfigComponent, configType }) => ( + + + + )); + +type PluginConfigRowsProps = { + configuration: Record, + systemConfigs: Array, +}; + +const PluginConfigRows = ({ configuration, systemConfigs }: PluginConfigRowsProps) => { + const pluginConfigs = useMemo(() => _pluginConfigs(systemConfigs, configuration), [configuration, systemConfigs]); + + // Put two plugin config components per row. + const configRows = chunk(pluginConfigs, 2) + .map((configChunk, idx) => ( + // eslint-disable-next-line react/no-array-index-key + + {configChunk[0]} + {configChunk[1]} + + )); + + return <>{configRows}; +}; + +export default PluginConfigRows; diff --git a/graylog2-web-interface/src/views/types.d.ts b/graylog2-web-interface/src/views/types.d.ts index 6a72ed3a4b1f..fd9d7a46c90b 100644 --- a/graylog2-web-interface/src/views/types.d.ts +++ b/graylog2-web-interface/src/views/types.d.ts @@ -150,12 +150,21 @@ interface ExportFormat { fileExtension: string; } +interface SystemConfiguration { + configType: string; + component: React.ComponentType<{ + config: any, + updateConfig: (newConfig: any) => any, + }>; +} + declare module 'graylog-web-plugin/plugin' { export interface PluginExports { creators?: Array; enterpriseWidgets?: Array; fieldActions?: Array; searchTypes?: Array; + systemConfigurations?: Array; valueActions?: Array; 'views.completers'?: Array; 'views.elements.header'?: Array; diff --git a/graylog2-web-interface/yarn.lock b/graylog2-web-interface/yarn.lock index 8e96e35ebedf..e202ca4aa6b7 100644 --- a/graylog2-web-interface/yarn.lock +++ b/graylog2-web-interface/yarn.lock @@ -8221,11 +8221,11 @@ graceful-fs@^4.2.4: "@babel/preset-env" "7.13.10" "@babel/preset-typescript" "7.13.0" create-react-class "15.7.0" - eslint-config-graylog "file:../../../../.cache/yarn/v6/npm-graylog-web-plugin-4.1.0-SNAPSHOT-32d33910-c1e4-449a-bfe2-51a8e04f763f-1619004879966/node_modules/eslint-config-graylog" + eslint-config-graylog "file:../../../../.cache/yarn/v6/npm-graylog-web-plugin-4.1.0-SNAPSHOT-7e32b4b2-192b-4901-82f9-e85f872dc009-1618845194235/node_modules/eslint-config-graylog" formik "2.2.6" html-webpack-plugin "^4.2.0" javascript-natural-sort "0.7.1" - jest-preset-graylog "file:../../../../.cache/yarn/v6/npm-graylog-web-plugin-4.1.0-SNAPSHOT-32d33910-c1e4-449a-bfe2-51a8e04f763f-1619004879966/node_modules/jest-preset-graylog" + jest-preset-graylog "file:../../../../.cache/yarn/v6/npm-graylog-web-plugin-4.1.0-SNAPSHOT-7e32b4b2-192b-4901-82f9-e85f872dc009-1618845194235/node_modules/jest-preset-graylog" jquery "3.5.1" moment "2.29.1" moment-timezone "0.5.31" @@ -8238,7 +8238,7 @@ graceful-fs@^4.2.4: react-router-dom "5.2.0" reflux "0.2.13" styled-components "5.2.1" - stylelint-config-graylog "file:../../../../.cache/yarn/v6/npm-graylog-web-plugin-4.1.0-SNAPSHOT-32d33910-c1e4-449a-bfe2-51a8e04f763f-1619004879966/node_modules/stylelint-config-graylog" + stylelint-config-graylog "file:../../../../.cache/yarn/v6/npm-graylog-web-plugin-4.1.0-SNAPSHOT-7e32b4b2-192b-4901-82f9-e85f872dc009-1618845194235/node_modules/stylelint-config-graylog" typescript "4.2.3" webpack "4.44.2" webpack-cleanup-plugin "0.5.1" @@ -11885,6 +11885,16 @@ object.fromentries@^2.0.4: es-abstract "^1.18.0-next.2" has "^1.0.3" +object.fromentries@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.4.tgz#26e1ba5c4571c5c6f0890cef4473066456a120b8" + integrity sha512-EsFBshs5RUUpQEY1D4q/m59kMfz4YJvxuNCJcv/jWwOJr34EaVnG11ZrZa0UHB3wnzV1wx8m58T4hQL8IuNXlQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.18.0-next.2" + has "^1.0.3" + object.getownpropertydescriptors@^2.0.3, object.getownpropertydescriptors@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz#369bf1f9592d8ab89d712dced5cb81c7c5352649" @@ -13295,7 +13305,7 @@ react-draggable@^4.0.0, react-draggable@^4.0.3: classnames "^2.2.5" prop-types "^15.6.0" -react-error-boundary@^3.1.0: +react-error-boundary@^3.1.0, react-error-boundary@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/react-error-boundary/-/react-error-boundary-3.1.1.tgz#932c5ca5cbab8ec4fe37fd7b415aa5c3a47597e7" integrity sha512-W3xCd9zXnanqrTUeViceufD3mIW8Ut29BUD+S2f0eO2XCOU8b6UrJfY46RDGe5lxCJzfe4j0yvIfh0RbTZhKJw==