diff --git a/package.json b/package.json index e603190c72698..5aabfc66e4637 100644 --- a/package.json +++ b/package.json @@ -348,7 +348,7 @@ "react-moment-proptypes": "^1.7.0", "react-monaco-editor": "^0.41.2", "react-popper-tooltip": "^2.10.1", - "react-query": "^3.21.0", + "react-query": "^3.21.1", "react-redux": "^7.2.0", "react-resizable": "^1.7.5", "react-resize-detector": "^4.2.0", diff --git a/x-pack/plugins/osquery/public/action_results/use_action_privileges.tsx b/x-pack/plugins/osquery/public/action_results/use_action_privileges.tsx index 2c80c874e89fa..6d0477b22edee 100644 --- a/x-pack/plugins/osquery/public/action_results/use_action_privileges.tsx +++ b/x-pack/plugins/osquery/public/action_results/use_action_privileges.tsx @@ -6,28 +6,16 @@ */ import { useQuery } from 'react-query'; - -import { i18n } from '@kbn/i18n'; import { useKibana } from '../common/lib/kibana'; -import { useErrorToast } from '../common/hooks/use_error_toast'; export const useActionResultsPrivileges = () => { const { http } = useKibana().services; - const setErrorToast = useErrorToast(); return useQuery( ['actionResultsPrivileges'], () => http.get('/internal/osquery/privileges_check'), { keepPreviousData: true, - select: (response) => response?.has_all_requested ?? false, - onSuccess: () => setErrorToast(), - onError: (error: Error) => - setErrorToast(error, { - title: i18n.translate('xpack.osquery.action_results_privileges.fetchError', { - defaultMessage: 'Error while fetching action results privileges', - }), - }), } ); }; diff --git a/x-pack/plugins/osquery/public/common/page_paths.ts b/x-pack/plugins/osquery/public/common/page_paths.ts index 0e0d8310ae8be..8df1006da181a 100644 --- a/x-pack/plugins/osquery/public/common/page_paths.ts +++ b/x-pack/plugins/osquery/public/common/page_paths.ts @@ -27,8 +27,6 @@ export interface DynamicPagePathValues { [key: string]: string; } -export const BASE_PATH = '/app/fleet'; - // If routing paths are changed here, please also check to see if // `pagePathGetters()`, below, needs any modifications export const PAGE_ROUTING_PATHS = { diff --git a/x-pack/plugins/osquery/public/components/app.tsx b/x-pack/plugins/osquery/public/components/app.tsx index 44407139ab492..33fb6ac6a2adf 100644 --- a/x-pack/plugins/osquery/public/components/app.tsx +++ b/x-pack/plugins/osquery/public/components/app.tsx @@ -5,6 +5,8 @@ * 2.0. */ +/* eslint-disable react-hooks/rules-of-hooks */ + import React, { useMemo } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiTabs, EuiTab } from '@elastic/eui'; @@ -14,10 +16,17 @@ import { Container, Nav, Wrapper } from './layouts'; import { OsqueryAppRoutes } from '../routes'; import { useRouterNavigate } from '../common/lib/kibana'; import { ManageIntegrationLink } from './manage_integration_link'; +import { useOsqueryIntegrationStatus } from '../common/hooks'; +import { OsqueryAppEmptyState } from './empty_state'; const OsqueryAppComponent = () => { const location = useLocation(); const section = useMemo(() => location.pathname.split('/')[1] ?? 'overview', [location.pathname]); + const { data: osqueryIntegration, isFetched } = useOsqueryIntegrationStatus(); + + if (isFetched && osqueryIntegration.install_status !== 'installed') { + return ; + } return ( diff --git a/x-pack/plugins/osquery/public/components/empty_state.tsx b/x-pack/plugins/osquery/public/components/empty_state.tsx new file mode 100644 index 0000000000000..1ee0d496c0ddc --- /dev/null +++ b/x-pack/plugins/osquery/public/components/empty_state.tsx @@ -0,0 +1,86 @@ +/* + * 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. + */ + +import React, { useCallback, useMemo } from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiButton } from '@elastic/eui'; + +import { KibanaPageTemplate } from '../../../../../src/plugins/kibana_react/public'; +import { INTEGRATIONS_PLUGIN_ID } from '../../../fleet/common'; +import { pagePathGetters } from '../../../fleet/public'; +import { isModifiedEvent, isLeftClickEvent, useKibana } from '../common/lib/kibana'; +import { OsqueryIcon } from './osquery_icon'; +import { useBreadcrumbs } from '../common/hooks/use_breadcrumbs'; +import { OSQUERY_INTEGRATION_NAME } from '../../common'; + +const OsqueryAppEmptyStateComponent = () => { + useBreadcrumbs('base'); + + const { + application: { getUrlForApp, navigateToApp }, + } = useKibana().services; + + const integrationHref = useMemo(() => { + return getUrlForApp(INTEGRATIONS_PLUGIN_ID, { + path: pagePathGetters.integration_details_overview({ + pkgkey: OSQUERY_INTEGRATION_NAME, + })[1], + }); + }, [getUrlForApp]); + + const integrationClick = useCallback( + (event) => { + if (!isModifiedEvent(event) && isLeftClickEvent(event)) { + event.preventDefault(); + return navigateToApp(INTEGRATIONS_PLUGIN_ID, { + path: pagePathGetters.integration_details_overview({ + pkgkey: OSQUERY_INTEGRATION_NAME, + })[1], + }); + } + }, + [navigateToApp] + ); + + const pageHeader = useMemo( + () => ({ + iconType: OsqueryIcon, + pageTitle: ( + + ), + description: ( + + ), + rightSideItems: [ + // eslint-disable-next-line @elastic/eui/href-or-on-click + + + , + ], + }), + [integrationClick, integrationHref] + ); + + return ; +}; + +export const OsqueryAppEmptyState = React.memo(OsqueryAppEmptyStateComponent); diff --git a/x-pack/plugins/osquery/public/components/manage_integration_link.tsx b/x-pack/plugins/osquery/public/components/manage_integration_link.tsx index 44b923860e1a8..32779ded46c50 100644 --- a/x-pack/plugins/osquery/public/components/manage_integration_link.tsx +++ b/x-pack/plugins/osquery/public/components/manage_integration_link.tsx @@ -24,11 +24,9 @@ const ManageIntegrationLinkComponent = () => { const integrationHref = useMemo(() => { if (osqueryIntegration) { return getUrlForApp(INTEGRATIONS_PLUGIN_ID, { - path: - '#' + - pagePathGetters.integration_details_policies({ - pkgkey: `${osqueryIntegration.name}-${osqueryIntegration.version}`, - })[1], + path: pagePathGetters.integration_details_policies({ + pkgkey: `${osqueryIntegration.name}-${osqueryIntegration.version}`, + })[1], }); } }, [getUrlForApp, osqueryIntegration]); @@ -39,11 +37,9 @@ const ManageIntegrationLinkComponent = () => { event.preventDefault(); if (osqueryIntegration) { return navigateToApp(INTEGRATIONS_PLUGIN_ID, { - path: - '#' + - pagePathGetters.integration_details_policies({ - pkgkey: `${osqueryIntegration.name}-${osqueryIntegration.version}`, - })[1], + path: pagePathGetters.integration_details_policies({ + pkgkey: `${osqueryIntegration.name}-${osqueryIntegration.version}`, + })[1], }); } } diff --git a/x-pack/plugins/osquery/public/live_queries/form/index.tsx b/x-pack/plugins/osquery/public/live_queries/form/index.tsx index 987be904c87e6..69b02dee8b9f7 100644 --- a/x-pack/plugins/osquery/public/live_queries/form/index.tsx +++ b/x-pack/plugins/osquery/public/live_queries/form/index.tsx @@ -114,7 +114,7 @@ const LiveQueryFormComponent: React.FC = ({ ), }); - const { setFieldValue, submit } = form; + const { setFieldValue, submit, isSubmitting } = form; const actionId = useMemo(() => data?.actions[0].action_id, [data?.actions]); const agentIds = useMemo(() => data?.actions[0].agents, [data?.actions]); @@ -185,7 +185,10 @@ const LiveQueryFormComponent: React.FC = ({ )} - + = ({ ), [ - agentSelected, - permissions.writeSavedQueries, - handleShowSaveQueryFlout, queryComponentProps, + singleAgentMode, + permissions.writeSavedQueries, + agentSelected, queryValueProvided, resultsStatus, - singleAgentMode, + handleShowSaveQueryFlout, + isSubmitting, submit, ] ); diff --git a/x-pack/plugins/osquery/public/packs/common/add_pack_query.tsx b/x-pack/plugins/osquery/public/packs/common/add_pack_query.tsx index 2d58e2dfe9522..d1115898b4e40 100644 --- a/x-pack/plugins/osquery/public/packs/common/add_pack_query.tsx +++ b/x-pack/plugins/osquery/public/packs/common/add_pack_query.tsx @@ -51,7 +51,7 @@ const AddPackQueryFormComponent = ({ handleSubmit }) => { }, }, }); - const { submit } = form; + const { submit, isSubmitting } = form; const createSavedQueryMutation = useMutation( (payload) => http.post(`/internal/osquery/saved_query`, { body: JSON.stringify(payload) }), @@ -108,7 +108,7 @@ const AddPackQueryFormComponent = ({ handleSubmit }) => { - + {'Add query'} diff --git a/x-pack/plugins/osquery/public/packs/common/pack_form.tsx b/x-pack/plugins/osquery/public/packs/common/pack_form.tsx index 86d4d8dff6ba6..ab0984e808943 100644 --- a/x-pack/plugins/osquery/public/packs/common/pack_form.tsx +++ b/x-pack/plugins/osquery/public/packs/common/pack_form.tsx @@ -40,7 +40,7 @@ const PackFormComponent = ({ data, handleSubmit }) => { }, }, }); - const { submit } = form; + const { submit, isSubmitting } = form; return (
@@ -50,7 +50,7 @@ const PackFormComponent = ({ data, handleSubmit }) => { - + {'Save pack'} diff --git a/x-pack/plugins/osquery/public/routes/saved_queries/edit/form.tsx b/x-pack/plugins/osquery/public/routes/saved_queries/edit/form.tsx index a7596575b90c4..617d83821d08d 100644 --- a/x-pack/plugins/osquery/public/routes/saved_queries/edit/form.tsx +++ b/x-pack/plugins/osquery/public/routes/saved_queries/edit/form.tsx @@ -38,6 +38,7 @@ const EditSavedQueryFormComponent: React.FC = ({ defaultValue, handleSubmit, }); + const { submit, isSubmitting } = form; return (
@@ -58,12 +59,12 @@ const EditSavedQueryFormComponent: React.FC = ({ = ({ defaultValue, handleSubmit, }); + const { submit, isSubmitting } = form; return ( @@ -54,12 +55,12 @@ const NewSavedQueryFormComponent: React.FC = ({ { const { data } = useScheduledQueryGroup({ scheduledQueryGroupId }); - useBreadcrumbs('scheduled_query_group_edit', { scheduledQueryGroupName: data?.name ?? '' }); + useBreadcrumbs('scheduled_query_group_edit', { + scheduledQueryGroupId: data?.id ?? '', + scheduledQueryGroupName: data?.name ?? '', + }); const LeftColumn = useMemo( () => ( diff --git a/x-pack/plugins/osquery/public/saved_queries/constants.ts b/x-pack/plugins/osquery/public/saved_queries/constants.ts index 69ca805e3e8fa..8edcfd00d1788 100644 --- a/x-pack/plugins/osquery/public/saved_queries/constants.ts +++ b/x-pack/plugins/osquery/public/saved_queries/constants.ts @@ -6,3 +6,4 @@ */ export const SAVED_QUERIES_ID = 'savedQueryList'; +export const SAVED_QUERY_ID = 'savedQuery'; diff --git a/x-pack/plugins/osquery/public/saved_queries/saved_query_flyout.tsx b/x-pack/plugins/osquery/public/saved_queries/saved_query_flyout.tsx index 6d14943a6bc84..8c35a359a9baf 100644 --- a/x-pack/plugins/osquery/public/saved_queries/saved_query_flyout.tsx +++ b/x-pack/plugins/osquery/public/saved_queries/saved_query_flyout.tsx @@ -42,6 +42,7 @@ const SavedQueryFlyoutComponent: React.FC = ({ defaultValue defaultValue, handleSubmit, }); + const { submit, isSubmitting } = form; return ( @@ -72,7 +73,7 @@ const SavedQueryFlyoutComponent: React.FC = ({ defaultValue - + { queryClient.invalidateQueries(SAVED_QUERIES_ID); + queryClient.invalidateQueries([SAVED_QUERY_ID, { savedQueryId }]); navigateToApp(PLUGIN_ID, { path: pagePathGetters.saved_queries() }); toasts.addSuccess( i18n.translate('xpack.osquery.editSavedQuery.successToastMessageText', { diff --git a/x-pack/plugins/osquery/public/scheduled_query_groups/form/index.tsx b/x-pack/plugins/osquery/public/scheduled_query_groups/form/index.tsx index 685960ecd202e..3598a9fd2e44c 100644 --- a/x-pack/plugins/osquery/public/scheduled_query_groups/form/index.tsx +++ b/x-pack/plugins/osquery/public/scheduled_query_groups/form/index.tsx @@ -88,7 +88,7 @@ const ScheduledQueryGroupFormComponent: React.FC = `scheduled_query_groups/${editMode ? defaultValue?.id : ''}` ); - const { isLoading, mutateAsync } = useMutation( + const { mutateAsync } = useMutation( (payload: Record) => editMode && defaultValue?.id ? http.put(packagePolicyRouteService.getUpdatePath(defaultValue.id), { @@ -248,7 +248,7 @@ const ScheduledQueryGroupFormComponent: React.FC = ), }); - const { setFieldValue, submit } = form; + const { setFieldValue, submit, isSubmitting } = form; const policyIdEuiFieldProps = useMemo( () => ({ isDisabled: !!defaultValue, options: agentPolicyOptions }), @@ -368,7 +368,7 @@ const ScheduledQueryGroupFormComponent: React.FC = ( ) )(args); - if (fieldRequiredError && (!!(!editForm && args.formData.value?.field.length) || editForm)) { + // @ts-expect-error update types + if (fieldRequiredError && ((!editForm && args.formData['value.field'].length) || editForm)) { return fieldRequiredError; } diff --git a/x-pack/plugins/osquery/public/scheduled_query_groups/queries/query_flyout.tsx b/x-pack/plugins/osquery/public/scheduled_query_groups/queries/query_flyout.tsx index cae9711694f29..d38c1b2118f24 100644 --- a/x-pack/plugins/osquery/public/scheduled_query_groups/queries/query_flyout.tsx +++ b/x-pack/plugins/osquery/public/scheduled_query_groups/queries/query_flyout.tsx @@ -5,6 +5,7 @@ * 2.0. */ +import { isEmpty } from 'lodash'; import { EuiCallOut, EuiFlyout, @@ -66,7 +67,7 @@ const QueryFlyoutComponent: React.FC = ({ if (isValid && ecsFieldValue) { onSave({ ...payload, - ecs_mapping: ecsFieldValue, + ...(isEmpty(ecsFieldValue) ? {} : { ecs_mapping: ecsFieldValue }), }); onClose(); } @@ -81,7 +82,7 @@ const QueryFlyoutComponent: React.FC = ({ [integrationPackageVersion] ); - const { submit, setFieldValue, reset } = form; + const { submit, setFieldValue, reset, isSubmitting } = form; const [{ query }] = useFormData({ form, @@ -245,7 +246,7 @@ const QueryFlyoutComponent: React.FC = ({ - + = ({ toggleErrors, expanded, }) => { + const data = useKibana().services.data; + const [logsIndexPattern, setLogsIndexPattern] = useState(undefined); + const { data: lastResultsData, isFetched } = useScheduledQueryGroupQueryLastResults({ actionId, agentIds, interval, + logsIndexPattern, }); const { data: errorsData, isFetched: errorsFetched } = useScheduledQueryGroupQueryErrors({ actionId, agentIds, interval, + logsIndexPattern, }); const handleErrorsToggle = useCallback(() => toggleErrors({ queryId, interval }), [ @@ -409,20 +414,41 @@ const ScheduledQueryLastResults: React.FC = ({ toggleErrors, ]); + useEffect(() => { + const fetchLogsIndexPattern = async () => { + const indexPattern = await data.indexPatterns.find('logs-*'); + + setLogsIndexPattern(indexPattern[0]); + }; + fetchLogsIndexPattern(); + }, [data.indexPatterns]); + if (!isFetched || !errorsFetched) { return ; } - if (!lastResultsData) { + if (!lastResultsData && !errorsData?.total) { return <>{'-'}; } return ( - {lastResultsData.first_event_ingested_time?.value ? ( - - <>{moment(lastResultsData.first_event_ingested_time?.value).fromNow()} + {lastResultsData?.['@timestamp'] ? ( + + {' '} + + + } + > + ) : ( '-' @@ -432,10 +458,17 @@ const ScheduledQueryLastResults: React.FC = ({ - {lastResultsData?.doc_count ?? 0} + {lastResultsData?.docCount ?? 0} - {'Documents'} + + + @@ -443,10 +476,17 @@ const ScheduledQueryLastResults: React.FC = ({ - {lastResultsData?.unique_agents?.value ?? 0} + {lastResultsData?.uniqueAgentsCount ?? 0} - {'Agents'} + + + @@ -458,7 +498,15 @@ const ScheduledQueryLastResults: React.FC = ({ - {'Errors'} + + {' '} + + { const data = useKibana().services.data; @@ -28,9 +30,8 @@ export const useScheduledQueryGroupQueryErrors = ({ return useQuery( ['scheduledQueryErrors', { actionId, interval }], async () => { - const indexPattern = await data.indexPatterns.find('logs-*'); const searchSource = await data.search.searchSource.create({ - index: indexPattern[0], + index: logsIndexPattern, fields: ['*'], sort: [ { @@ -80,7 +81,7 @@ export const useScheduledQueryGroupQueryErrors = ({ }, { keepPreviousData: true, - enabled: !!(!skip && actionId && interval && agentIds?.length), + enabled: !!(!skip && actionId && interval && agentIds?.length && logsIndexPattern), select: (response) => response.rawResponse.hits ?? [], refetchOnReconnect: false, refetchOnWindowFocus: false, diff --git a/x-pack/plugins/osquery/public/scheduled_query_groups/use_scheduled_query_group_query_last_results.ts b/x-pack/plugins/osquery/public/scheduled_query_groups/use_scheduled_query_group_query_last_results.ts index f972640e25986..7cfd6be461e05 100644 --- a/x-pack/plugins/osquery/public/scheduled_query_groups/use_scheduled_query_group_query_last_results.ts +++ b/x-pack/plugins/osquery/public/scheduled_query_groups/use_scheduled_query_group_query_last_results.ts @@ -6,13 +6,14 @@ */ import { useQuery } from 'react-query'; - +import { IndexPattern } from '../../../../../src/plugins/data/common'; import { useKibana } from '../common/lib/kibana'; interface UseScheduledQueryGroupQueryLastResultsProps { actionId: string; agentIds?: string[]; interval: number; + logsIndexPattern?: IndexPattern; skip?: boolean; } @@ -20,6 +21,7 @@ export const useScheduledQueryGroupQueryLastResults = ({ actionId, agentIds, interval, + logsIndexPattern, skip = false, }: UseScheduledQueryGroupQueryLastResultsProps) => { const data = useKibana().services.data; @@ -27,23 +29,9 @@ export const useScheduledQueryGroupQueryLastResults = ({ return useQuery( ['scheduledQueryLastResults', { actionId }], async () => { - const indexPattern = await data.indexPatterns.find('logs-*'); - const searchSource = await data.search.searchSource.create({ - index: indexPattern[0], - size: 0, - aggs: { - runs: { - terms: { - field: 'response_id', - order: { first_event_ingested_time: 'desc' }, - size: 1, - }, - aggs: { - first_event_ingested_time: { min: { field: '@timestamp' } }, - unique_agents: { cardinality: { field: 'agent.id' } }, - }, - }, - }, + const lastResultsSearchSource = await data.search.searchSource.create({ + index: logsIndexPattern, + size: 1, query: { // @ts-expect-error update types bool: { @@ -59,26 +47,62 @@ export const useScheduledQueryGroupQueryLastResults = ({ action_id: actionId, }, }, - { - range: { - '@timestamp': { - gte: `now-${interval * 2}s`, - lte: 'now', - }, - }, - }, ], }, }, }); - return searchSource.fetch$().toPromise(); + const lastResultsResponse = await lastResultsSearchSource.fetch$().toPromise(); + + const responseId = lastResultsResponse.rawResponse?.hits?.hits[0]?._source?.response_id; + + if (responseId) { + const aggsSearchSource = await data.search.searchSource.create({ + index: logsIndexPattern, + size: 0, + aggs: { + unique_agents: { cardinality: { field: 'agent.id' } }, + }, + query: { + // @ts-expect-error update types + bool: { + should: agentIds?.map((agentId) => ({ + match_phrase: { + 'agent.id': agentId, + }, + })), + minimum_should_match: 1, + filter: [ + { + match_phrase: { + action_id: actionId, + }, + }, + { + match_phrase: { + response_id: responseId, + }, + }, + ], + }, + }, + }); + + const aggsResponse = await aggsSearchSource.fetch$().toPromise(); + + return { + '@timestamp': lastResultsResponse.rawResponse?.hits?.hits[0]?.fields?.['@timestamp'], + // @ts-expect-error update types + uniqueAgentsCount: aggsResponse.rawResponse.aggregations?.unique_agents?.value, + docCount: aggsResponse.rawResponse?.hits?.total, + }; + } + + return null; }, { keepPreviousData: true, - enabled: !!(!skip && actionId && interval && agentIds?.length), - // @ts-expect-error update types - select: (response) => response.rawResponse.aggregations?.runs?.buckets[0] ?? [], + enabled: !!(!skip && actionId && interval && agentIds?.length && logsIndexPattern), refetchOnReconnect: false, refetchOnWindowFocus: false, } diff --git a/x-pack/plugins/osquery/server/routes/privileges_check/privileges_check_route.ts b/x-pack/plugins/osquery/server/routes/privileges_check/privileges_check_route.ts index 80c335c1c46d3..d9683d23deb13 100644 --- a/x-pack/plugins/osquery/server/routes/privileges_check/privileges_check_route.ts +++ b/x-pack/plugins/osquery/server/routes/privileges_check/privileges_check_route.ts @@ -9,7 +9,6 @@ import { OSQUERY_INTEGRATION_NAME, PLUGIN_ID } from '../../../common'; import { IRouter } from '../../../../../../src/core/server'; import { OsqueryAppContext } from '../../lib/osquery_app_context_services'; -// eslint-disable-next-line @typescript-eslint/no-unused-vars export const privilegesCheckRoute = (router: IRouter, osqueryContext: OsqueryAppContext) => { router.get( { @@ -20,23 +19,26 @@ export const privilegesCheckRoute = (router: IRouter, osqueryContext: OsqueryApp }, }, async (context, request, response) => { - const esClient = context.core.elasticsearch.client.asCurrentUser; - - const privileges = ( - await esClient.security.hasPrivileges({ - body: { - index: [ - { - names: [`logs-${OSQUERY_INTEGRATION_NAME}.result*`], - privileges: ['read'], - }, - ], + if (osqueryContext.security.authz.mode.useRbacForRequest(request)) { + const checkPrivileges = osqueryContext.security.authz.checkPrivilegesDynamicallyWithRequest( + request + ); + const { hasAllRequested } = await checkPrivileges({ + elasticsearch: { + cluster: [], + index: { + [`logs-${OSQUERY_INTEGRATION_NAME}.result*`]: ['read'], + }, }, - }) - ).body; + }); + + return response.ok({ + body: `${hasAllRequested}`, + }); + } return response.ok({ - body: privileges, + body: 'true', }); } ); diff --git a/yarn.lock b/yarn.lock index 4d49a2f06e1e9..f0a1ff1278f4e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -23379,10 +23379,10 @@ react-popper@^2.2.4: react-fast-compare "^3.0.1" warning "^4.0.2" -react-query@^3.21.0: - version "3.21.0" - resolved "https://registry.yarnpkg.com/react-query/-/react-query-3.21.0.tgz#2e099a7906c38eeeb750e8b9b12121a21fa8d9ef" - integrity sha512-5rY5J8OD9f4EdkytjSsdCO+pqbJWKwSIMETfh/UyxqyjLURHE0IhlB+IPNPrzzu/dzK0rRxi5p0IkcCdSfizDQ== +react-query@^3.21.1: + version "3.21.1" + resolved "https://registry.yarnpkg.com/react-query/-/react-query-3.21.1.tgz#8fe4df90bf6c6a93e0552ea9baff211d1b28f6e0" + integrity sha512-aKFLfNJc/m21JBXJk7sR9tDUYPjotWA4EHAKvbZ++GgxaY+eI0tqBxXmGBuJo0Pisis1W4pZWlZgoRv9yE8yjA== dependencies: "@babel/runtime" "^7.5.5" broadcast-channel "^3.4.1"