diff --git a/package.json b/package.json index 430ab9e1ba77d..2881d309c2459 100644 --- a/package.json +++ b/package.json @@ -92,7 +92,8 @@ "**/react": "^16.12.0", "**/react-test-renderer": "^16.12.0", "**/deepmerge": "^4.2.2", - "**/serialize-javascript": "^2.1.1" + "**/serialize-javascript": "^2.1.1", + "**/fast-deep-equal": "^3.1.1" }, "workspaces": { "packages": [ diff --git a/x-pack/legacy/plugins/siem/public/components/link_to/redirect_to_detection_engine.tsx b/x-pack/legacy/plugins/siem/public/components/link_to/redirect_to_detection_engine.tsx index 0a91f38061734..2b7a2f14dfea7 100644 --- a/x-pack/legacy/plugins/siem/public/components/link_to/redirect_to_detection_engine.tsx +++ b/x-pack/legacy/plugins/siem/public/components/link_to/redirect_to_detection_engine.tsx @@ -52,11 +52,15 @@ export const RedirectToEditRulePage = ({ location: { search } }: DetectionEngine ); }; -export const getDetectionEngineUrl = () => `#/link-to/${DETECTION_ENGINE_PAGE_NAME}`; +const baseDetectionEngineUrl = `#/link-to/${DETECTION_ENGINE_PAGE_NAME}`; + +export const getDetectionEngineUrl = () => `${baseDetectionEngineUrl}`; export const getDetectionEngineAlertUrl = () => - `#/link-to/${DETECTION_ENGINE_PAGE_NAME}/${DetectionEngineTab.alerts}`; -export const getRulesUrl = () => `#/link-to/${DETECTION_ENGINE_PAGE_NAME}/rules`; -export const getCreateRuleUrl = () => `#/link-to/${DETECTION_ENGINE_PAGE_NAME}/rules/create-rule`; -export const getRuleDetailsUrl = () => `#/link-to/${DETECTION_ENGINE_PAGE_NAME}/rules/rule-details`; -export const getEditRuleUrl = () => - `#/link-to/${DETECTION_ENGINE_PAGE_NAME}/rules/rule-details/edit-rule`; + `${baseDetectionEngineUrl}/${DetectionEngineTab.alerts}`; +export const getDetectionEngineTabUrl = (tabPath: string) => `${baseDetectionEngineUrl}/${tabPath}`; +export const getRulesUrl = () => `${baseDetectionEngineUrl}/rules`; +export const getCreateRuleUrl = () => `${baseDetectionEngineUrl}/rules/create`; +export const getRuleDetailsUrl = (detailName: string) => + `${baseDetectionEngineUrl}/rules/id/${detailName}`; +export const getEditRuleUrl = (detailName: string) => + `${baseDetectionEngineUrl}/rules/id/${detailName}/edit`; diff --git a/x-pack/legacy/plugins/siem/public/components/navigation/breadcrumbs/index.ts b/x-pack/legacy/plugins/siem/public/components/navigation/breadcrumbs/index.ts index 9eee5b21e83f3..91055bca066c4 100644 --- a/x-pack/legacy/plugins/siem/public/components/navigation/breadcrumbs/index.ts +++ b/x-pack/legacy/plugins/siem/public/components/navigation/breadcrumbs/index.ts @@ -10,6 +10,7 @@ import { getOr, omit } from 'lodash/fp'; import { APP_NAME } from '../../../../common/constants'; import { getBreadcrumbs as getHostDetailsBreadcrumbs } from '../../../pages/hosts/details/utils'; import { getBreadcrumbs as getIPDetailsBreadcrumbs } from '../../../pages/network/ip_details'; +import { getBreadcrumbs as getDetectionRulesBreadcrumbs } from '../../../pages/detection_engine/rules/utils'; import { SiemPageName } from '../../../pages/home/types'; import { RouteSpyState, HostRouteSpyState, NetworkRouteSpyState } from '../../../utils/route/types'; import { getOverviewUrl } from '../../link_to'; @@ -38,6 +39,9 @@ const isNetworkRoutes = (spyState: RouteSpyState): spyState is NetworkRouteSpySt const isHostsRoutes = (spyState: RouteSpyState): spyState is HostRouteSpyState => spyState != null && spyState.pageName === SiemPageName.hosts; +const isDetectionsRoutes = (spyState: RouteSpyState) => + spyState != null && spyState.pageName === SiemPageName.detections; + export const getBreadcrumbsForRoute = ( object: RouteSpyState & TabNavigationProps ): Breadcrumb[] | null => { @@ -76,6 +80,24 @@ export const getBreadcrumbsForRoute = ( ), ]; } + if (isDetectionsRoutes(spyState) && object.navTabs) { + const tempNav: SearchNavTab = { urlKey: 'detections', isDetailPage: false }; + let urlStateKeys = [getOr(tempNav, spyState.pageName, object.navTabs)]; + if (spyState.tabName != null) { + urlStateKeys = [...urlStateKeys, getOr(tempNav, spyState.tabName, object.navTabs)]; + } + + return [ + ...siemRootBreadcrumb, + ...getDetectionRulesBreadcrumbs( + spyState, + urlStateKeys.reduce( + (acc: string[], item: SearchNavTab) => [...acc, getSearch(item, object)], + [] + ) + ), + ]; + } if ( spyState != null && object.navTabs && diff --git a/x-pack/legacy/plugins/siem/public/components/navigation/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/navigation/index.test.tsx index b6efc07ad8fe3..56be39f67b1bd 100644 --- a/x-pack/legacy/plugins/siem/public/components/navigation/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/navigation/index.test.tsx @@ -187,6 +187,7 @@ describe('SIEM Navigation', () => { query: { language: 'kuery', query: '' }, savedQuery: undefined, search: '', + state: undefined, tabName: 'authentications', timeline: { id: '', isOpen: false }, timerange: { diff --git a/x-pack/legacy/plugins/siem/public/components/navigation/index.tsx b/x-pack/legacy/plugins/siem/public/components/navigation/index.tsx index 61ac84667d80f..040a6e7847b77 100644 --- a/x-pack/legacy/plugins/siem/public/components/navigation/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/navigation/index.tsx @@ -4,7 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { isEqual } from 'lodash/fp'; +import isEqual from 'lodash/fp/isEqual'; +import deepEqual from 'fast-deep-equal'; import React, { useEffect } from 'react'; import { connect } from 'react-redux'; import { compose } from 'redux'; @@ -16,67 +17,78 @@ import { setBreadcrumbs } from './breadcrumbs'; import { TabNavigation } from './tab_navigation'; import { SiemNavigationProps, SiemNavigationComponentProps } from './types'; -export const SiemNavigationComponent = React.memo< - SiemNavigationComponentProps & SiemNavigationProps & RouteSpyState ->( - ({ detailName, display, navTabs, pageName, pathName, search, tabName, urlState, flowTarget }) => { - useEffect(() => { - if (pathName) { - setBreadcrumbs({ - query: urlState.query, - detailName, - filters: urlState.filters, - navTabs, - pageName, - pathName, - savedQuery: urlState.savedQuery, - search, - tabName, - flowTarget, - timerange: urlState.timerange, - timeline: urlState.timeline, - }); - } - }, [pathName, search, navTabs, urlState]); +export const SiemNavigationComponent: React.FC = ({ + detailName, + display, + navTabs, + pageName, + pathName, + search, + tabName, + urlState, + flowTarget, + state, +}) => { + useEffect(() => { + if (pathName) { + setBreadcrumbs({ + query: urlState.query, + detailName, + filters: urlState.filters, + navTabs, + pageName, + pathName, + savedQuery: urlState.savedQuery, + search, + tabName, + flowTarget, + timerange: urlState.timerange, + timeline: urlState.timeline, + state, + }); + } + }, [pathName, search, navTabs, urlState, state]); - return ( - - ); - }, - (prevProps, nextProps) => { - return ( + return ( + + ); +}; + +export const SiemNavigationRedux = compose< + React.ComponentClass +>(connect(makeMapStateToProps))( + React.memo( + SiemNavigationComponent, + (prevProps, nextProps) => prevProps.pathName === nextProps.pathName && prevProps.search === nextProps.search && isEqual(prevProps.navTabs, nextProps.navTabs) && - isEqual(prevProps.urlState, nextProps.urlState) - ); - } + isEqual(prevProps.urlState, nextProps.urlState) && + deepEqual(prevProps.state, nextProps.state) + ) ); -SiemNavigationComponent.displayName = 'SiemNavigationComponent'; - -export const SiemNavigationRedux = compose< - React.ComponentClass ->(connect(makeMapStateToProps))(SiemNavigationComponent); - -export const SiemNavigation = React.memo(props => { +const SiemNavigationContainer: React.FC = props => { const [routeProps] = useRouteSpy(); const stateNavReduxProps: RouteSpyState & SiemNavigationProps = { ...routeProps, ...props, }; + return ; -}); +}; -SiemNavigation.displayName = 'SiemNavigation'; +export const SiemNavigation = SiemNavigationContainer; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.tsx index d9e0377b34060..5586749ce38d8 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.tsx @@ -230,8 +230,13 @@ const makeMapStateToProps = () => { }; }; -export const DetectionEngine = connect(makeMapStateToProps, { +const mapDispatchToProps = { setAbsoluteRangeDatePicker: dispatchSetAbsoluteRangeDatePicker, -})(DetectionEngineComponent); +}; + +export const DetectionEngine = connect( + makeMapStateToProps, + mapDispatchToProps +)(DetectionEngineComponent); DetectionEngine.displayName = 'DetectionEngine'; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/index.tsx index 33186d2787d8a..6db8d93e46ac9 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/index.tsx @@ -19,7 +19,7 @@ const detectionEnginePath = `/:pageName(detections)`; type Props = Partial> & { url: string }; -export const DetectionEngineContainer = React.memo(() => ( +const DetectionEngineContainerComponent: React.FC = () => ( (() => ( - + - + (() => ( /> -)); -DetectionEngineContainer.displayName = 'DetectionEngineContainer'; +); + +export const DetectionEngineContainer = React.memo(DetectionEngineContainerComponent); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx index a23c681a5aab2..40c694160f73b 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx @@ -109,7 +109,7 @@ const RuleDetailsComponent = memo( hasIndexWrite, signalIndexName, } = useUserInfo(); - const { ruleId } = useParams(); + const { detailName: ruleId } = useParams(); const [isLoading, rule] = useRule(ruleId); // This is used to re-trigger api rule status when user de/activate rule const [ruleEnabled, setRuleEnabled] = useState(null); @@ -381,7 +381,7 @@ const RuleDetailsComponent = memo( }} - + ); } @@ -402,8 +402,10 @@ const makeMapStateToProps = () => { }; }; -export const RuleDetails = connect(makeMapStateToProps, { +const mapDispatchToProps = { setAbsoluteRangeDatePicker: dispatchSetAbsoluteRangeDatePicker, -})(RuleDetailsComponent); +}; + +export const RuleDetails = connect(makeMapStateToProps, mapDispatchToProps)(RuleDetailsComponent); RuleDetails.displayName = 'RuleDetails'; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/index.tsx index 9b7833afd7f4d..be56e916ae6c9 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/index.tsx @@ -57,7 +57,7 @@ export const EditRuleComponent = memo(() => { canUserCRUD, hasManageApiKey, } = useUserInfo(); - const { ruleId } = useParams(); + const { detailName: ruleId } = useParams(); const [loading, rule] = useRule(ruleId); const userHasNoPermissions = @@ -347,7 +347,7 @@ export const EditRuleComponent = memo(() => { - + ); }); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/translations.ts index e1257007d44a3..d144a6d56a168 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/translations.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/translations.ts @@ -25,6 +25,14 @@ export const PAGE_TITLE = i18n.translate('xpack.siem.detectionEngine.rules.pageT defaultMessage: 'Signal detection rules', }); +export const ADD_PAGE_TITLE = i18n.translate('xpack.siem.detectionEngine.rules.addPageTitle', { + defaultMessage: 'Create', +}); + +export const EDIT_PAGE_TITLE = i18n.translate('xpack.siem.detectionEngine.rules.editPageTitle', { + defaultMessage: 'Edit', +}); + export const REFRESH = i18n.translate('xpack.siem.detectionEngine.rules.allRules.refreshTitle', { defaultMessage: 'Refresh', }); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/utils.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/utils.ts new file mode 100644 index 0000000000000..55772aa73ecf3 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/utils.ts @@ -0,0 +1,98 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Breadcrumb } from 'ui/chrome'; +import { isEmpty } from 'lodash/fp'; + +import { + getDetectionEngineUrl, + getDetectionEngineTabUrl, + getRulesUrl, + getRuleDetailsUrl, + getCreateRuleUrl, + getEditRuleUrl, +} from '../../../components/link_to/redirect_to_detection_engine'; +import * as i18nDetections from '../translations'; +import * as i18nRules from './translations'; +import { RouteSpyState } from '../../../utils/route/types'; + +const getTabBreadcrumb = (pathname: string, search: string[]) => { + const tabPath = pathname.split('/')[2]; + + if (tabPath === 'alerts') { + return { + text: i18nDetections.ALERT, + href: `${getDetectionEngineTabUrl(tabPath)}${!isEmpty(search[0]) ? search[0] : ''}`, + }; + } + + if (tabPath === 'signals') { + return { + text: i18nDetections.SIGNAL, + href: `${getDetectionEngineTabUrl(tabPath)}${!isEmpty(search[0]) ? search[0] : ''}`, + }; + } + + if (tabPath === 'rules') { + return { + text: i18nRules.PAGE_TITLE, + href: `${getRulesUrl()}${!isEmpty(search[0]) ? search[0] : ''}`, + }; + } +}; + +const isRuleCreatePage = (pathname: string) => + pathname.includes('/rules') && pathname.includes('/create'); + +const isRuleEditPage = (pathname: string) => + pathname.includes('/rules') && pathname.includes('/edit'); + +export const getBreadcrumbs = (params: RouteSpyState, search: string[]): Breadcrumb[] => { + let breadcrumb = [ + { + text: i18nDetections.PAGE_TITLE, + href: `${getDetectionEngineUrl()}${!isEmpty(search[0]) ? search[0] : ''}`, + }, + ]; + + const tabBreadcrumb = getTabBreadcrumb(params.pathName, search); + + if (tabBreadcrumb) { + breadcrumb = [...breadcrumb, tabBreadcrumb]; + } + + if (params.detailName && params.state?.ruleName) { + breadcrumb = [ + ...breadcrumb, + { + text: params.state.ruleName, + href: `${getRuleDetailsUrl(params.detailName)}${!isEmpty(search[1]) ? search[1] : ''}`, + }, + ]; + } + + if (isRuleCreatePage(params.pathName)) { + breadcrumb = [ + ...breadcrumb, + { + text: i18nRules.ADD_PAGE_TITLE, + href: `${getCreateRuleUrl()}${!isEmpty(search[1]) ? search[1] : ''}`, + }, + ]; + } + + if (isRuleEditPage(params.pathName) && params.detailName && params.state?.ruleName) { + breadcrumb = [ + ...breadcrumb, + { + text: i18nRules.EDIT_PAGE_TITLE, + href: `${getEditRuleUrl(params.detailName)}${!isEmpty(search[1]) ? search[1] : ''}`, + }, + ]; + } + + return breadcrumb; +}; diff --git a/x-pack/legacy/plugins/siem/public/pages/hosts/details/utils.ts b/x-pack/legacy/plugins/siem/public/pages/hosts/details/utils.ts index 52e016502940b..c321478f10174 100644 --- a/x-pack/legacy/plugins/siem/public/pages/hosts/details/utils.ts +++ b/x-pack/legacy/plugins/siem/public/pages/hosts/details/utils.ts @@ -5,8 +5,8 @@ */ import { Breadcrumb } from 'ui/chrome'; +import { get, isEmpty } from 'lodash/fp'; -import { get } from 'lodash/fp'; import { hostsModel } from '../../../store'; import { HostsTableType } from '../../../store/hosts/model'; import { getHostsUrl, getHostDetailsUrl } from '../../../components/link_to/redirect_to_hosts'; @@ -29,7 +29,7 @@ export const getBreadcrumbs = (params: HostRouteSpyState, search: string[]): Bre let breadcrumb = [ { text: i18n.PAGE_TITLE, - href: `${getHostsUrl()}${search && search[0] ? search[0] : ''}`, + href: `${getHostsUrl()}${!isEmpty(search[0]) ? search[0] : ''}`, }, ]; @@ -38,7 +38,7 @@ export const getBreadcrumbs = (params: HostRouteSpyState, search: string[]): Bre ...breadcrumb, { text: params.detailName, - href: `${getHostDetailsUrl(params.detailName)}${search && search[1] ? search[1] : ''}`, + href: `${getHostDetailsUrl(params.detailName)}${!isEmpty(search[1]) ? search[1] : ''}`, }, ]; } diff --git a/x-pack/legacy/plugins/siem/public/pages/network/ip_details/utils.ts b/x-pack/legacy/plugins/siem/public/pages/network/ip_details/utils.ts index fed832167a60e..a468812e2718d 100644 --- a/x-pack/legacy/plugins/siem/public/pages/network/ip_details/utils.ts +++ b/x-pack/legacy/plugins/siem/public/pages/network/ip_details/utils.ts @@ -5,8 +5,8 @@ */ import { Breadcrumb } from 'ui/chrome'; +import { get, isEmpty } from 'lodash/fp'; -import { get } from 'lodash/fp'; import { decodeIpv6 } from '../../../lib/helpers'; import { getNetworkUrl, getIPDetailsUrl } from '../../../components/link_to/redirect_to_network'; import { networkModel } from '../../../store/network'; @@ -28,7 +28,7 @@ export const getBreadcrumbs = (params: NetworkRouteSpyState, search: string[]): let breadcrumb = [ { text: i18n.PAGE_TITLE, - href: `${getNetworkUrl()}${search && search[0] ? search[0] : ''}`, + href: `${getNetworkUrl()}${!isEmpty(search[0]) ? search[0] : ''}`, }, ]; if (params.detailName != null) { @@ -37,7 +37,7 @@ export const getBreadcrumbs = (params: NetworkRouteSpyState, search: string[]): { text: decodeIpv6(params.detailName), href: `${getIPDetailsUrl(params.detailName, params.flowTarget)}${ - search && search[1] ? search[1] : '' + !isEmpty(search[1]) ? search[1] : '' }`, }, ]; diff --git a/x-pack/legacy/plugins/siem/public/utils/route/helpers.ts b/x-pack/legacy/plugins/siem/public/utils/route/helpers.ts index 188ae9c6c1866..39efccc9f45b8 100644 --- a/x-pack/legacy/plugins/siem/public/utils/route/helpers.ts +++ b/x-pack/legacy/plugins/siem/public/utils/route/helpers.ts @@ -15,6 +15,7 @@ export const initRouteSpy: RouteSpyState = { tabName: undefined, search: '', pathName: '/', + state: undefined, }; export const RouterSpyStateContext = createContext<[RouteSpyState, Dispatch]>([ diff --git a/x-pack/legacy/plugins/siem/public/utils/route/spy_routes.tsx b/x-pack/legacy/plugins/siem/public/utils/route/spy_routes.tsx index 5c24b2f48488d..c88562abef6ae 100644 --- a/x-pack/legacy/plugins/siem/public/utils/route/spy_routes.tsx +++ b/x-pack/legacy/plugins/siem/public/utils/route/spy_routes.tsx @@ -8,6 +8,7 @@ import * as H from 'history'; import { isEqual } from 'lodash/fp'; import { memo, useEffect, useState } from 'react'; import { withRouter } from 'react-router-dom'; +import deepEqual from 'fast-deep-equal'; import { SpyRouteProps } from './types'; import { useRouteSpy } from './use_route_spy'; @@ -19,6 +20,7 @@ export const SpyRouteComponent = memo( match: { params: { pageName, detailName, tabName, flowTarget }, }, + state, }) => { const [isInitializing, setIsInitializing] = useState(true); const [route, dispatch] = useRouteSpy(); @@ -61,8 +63,24 @@ export const SpyRouteComponent = memo( }, }); } + } else { + if (pageName && !deepEqual(state, route.state)) { + dispatch({ + type: 'updateRoute', + route: { + pageName, + detailName, + tabName, + search, + pathName: pathname, + history, + flowTarget, + state, + }, + }); + } } - }, [pathname, search, pageName, detailName, tabName, flowTarget]); + }, [pathname, search, pageName, detailName, tabName, flowTarget, state]); return null; } ); diff --git a/x-pack/legacy/plugins/siem/public/utils/route/types.ts b/x-pack/legacy/plugins/siem/public/utils/route/types.ts index 79d2677eff06f..d3eca36bd0d96 100644 --- a/x-pack/legacy/plugins/siem/public/utils/route/types.ts +++ b/x-pack/legacy/plugins/siem/public/utils/route/types.ts @@ -21,6 +21,7 @@ export interface RouteSpyState { pathName: string; history?: H.History; flowTarget?: FlowTarget; + state?: Record; } export interface HostRouteSpyState extends RouteSpyState { @@ -38,7 +39,10 @@ export type RouteSpyAction = } | { type: 'updateRouteWithOutSearch'; - route: Pick; + route: Pick< + RouteSpyState, + 'pageName' & 'detailName' & 'tabName' & 'pathName' & 'history' & 'state' + >; } | { type: 'updateRoute'; @@ -55,4 +59,6 @@ export type SpyRouteProps = RouteComponentProps<{ tabName: HostsTableType | undefined; search: string; flowTarget: FlowTarget | undefined; -}>; +}> & { + state?: Record; +}; diff --git a/yarn.lock b/yarn.lock index b534a7a4fcb79..65419d66f7675 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13303,17 +13303,7 @@ fancy-log@^1.3.2: color-support "^1.1.3" time-stamp "^1.0.0" -fast-deep-equal@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz#96256a3bc975595eb36d82e9929d060d893439ff" - integrity sha1-liVqO8l1WV6zbYLpkp0GDYk0Of8= - -fast-deep-equal@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" - integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk= - -fast-deep-equal@^3.1.1: +fast-deep-equal@^1.0.0, fast-deep-equal@^2.0.1, fast-deep-equal@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz#545145077c501491e33b15ec408c294376e94ae4" integrity sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==