diff --git a/lib/js/app/components/APIResource/APIResource.styles.ts b/lib/js/app/components/APIResource/APIResource.styles.ts deleted file mode 100644 index ebc2a269a..000000000 --- a/lib/js/app/components/APIResource/APIResource.styles.ts +++ /dev/null @@ -1,12 +0,0 @@ -import styled from 'styled-components'; - -export const Container = styled.div` - position: relative; -`; - -export const TooltipContent = styled.div` - font-family: 'Lato Regular', sans-serif; - font-size: 12px; - line-height: 14px; - width: 110px; -`; diff --git a/lib/js/app/components/APIResource/APIResource.tsx b/lib/js/app/components/APIResource/APIResource.tsx deleted file mode 100644 index 28c1ba439..000000000 --- a/lib/js/app/components/APIResource/APIResource.tsx +++ /dev/null @@ -1,113 +0,0 @@ -import React, { FC, useRef, useState, useContext, useCallback } from 'react'; -import { stringify } from 'qs'; -import { AnimatePresence, motion } from 'framer-motion'; -import { Button, Tooltip } from '@keen.io/ui-core'; - -import { Container, TooltipContent } from './APIResource.styles'; -import text from './text.json'; - -import { useTooltipHandler } from '../../hooks'; -import { AppContext } from '../../contexts'; - -import { HIDE_TIME, API_VERSION } from './constants'; - -type Props = { - /** Query definition */ - query: Record; - /** Click event handler */ - onClick: (resourceUrl: string) => void; -}; - -export const tooltipMotion = { - transition: { duration: 0.3 }, - exit: { opacity: 0 }, -}; - -const APIResource: FC = ({ query, onClick }) => { - const { keenAnalysis } = useContext(AppContext); - - const containerRef = useRef(null); - const hideTooltip = useRef(null); - const [tooltip, setTooltip] = useState<{ - visible: boolean; - x: number; - y: number; - }>({ - visible: false, - x: 0, - y: 0, - }); - - const { calculateTooltipPosition } = useTooltipHandler(containerRef); - - const createResourceUrl = useCallback(() => { - const { analysis_type: analysisType, ...queryParams } = query; - const { - config: { protocol, host, projectId, masterKey }, - } = keenAnalysis; - - const queryString = stringify(queryParams, { - indices: false, - arrayFormat: 'repeat', - skipNulls: true, - }); - - return `${protocol}://${host}/${API_VERSION}/projects/${projectId}/queries/${analysisType}?api_key=${masterKey}&${queryString}`; - }, [keenAnalysis, query]); - - return ( - - - {tooltip.visible && ( - - - {text.copyMessage} - - - )} - - - - ); -}; - -export default APIResource; diff --git a/lib/js/app/components/APIResource/constants.ts b/lib/js/app/components/APIResource/constants.ts deleted file mode 100644 index a8864ad8b..000000000 --- a/lib/js/app/components/APIResource/constants.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const HIDE_TIME = 2000; -export const API_VERSION = '3.0'; diff --git a/lib/js/app/components/APIResource/index.ts b/lib/js/app/components/APIResource/index.ts deleted file mode 100644 index d04f337a9..000000000 --- a/lib/js/app/components/APIResource/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import APIResource from './APIResource'; - -export default APIResource; diff --git a/lib/js/app/components/APIResource/text.json b/lib/js/app/components/APIResource/text.json deleted file mode 100644 index c0607e52c..000000000 --- a/lib/js/app/components/APIResource/text.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "API Resource", - "copyMessage": "Resource URL copied to clipboard" -} diff --git a/lib/js/app/components/ActionsMenu/ActionsMenu.test.tsx b/lib/js/app/components/ActionsMenu/ActionsMenu.test.tsx index cbbb00a7d..9ba1dab4b 100644 --- a/lib/js/app/components/ActionsMenu/ActionsMenu.test.tsx +++ b/lib/js/app/components/ActionsMenu/ActionsMenu.test.tsx @@ -3,6 +3,8 @@ import { Provider } from 'react-redux'; import { render as rtlRender, fireEvent } from '@testing-library/react'; import configureStore from 'redux-mock-store'; +import { AppContext } from '../../contexts'; + import ActionsMenu from './ActionsMenu'; import text from './text.json'; @@ -18,9 +20,11 @@ const render = (overProps: any = {}) => { const store = mockStore({ queries: {} }); const wrapper = rtlRender( - - - + + + + + ); return { @@ -161,3 +165,24 @@ test('allows user to embed HTML code', () => { ] `); }); + +test('allows user to copy API Resource', () => { + const { + wrapper: { getByText }, + store, + } = render(); + + const copyApiResource = getByText(text.apiResource); + fireEvent.click(copyApiResource); + + expect(store.getActions()).toMatchInlineSnapshot(` + Array [ + Object { + "payload": Object { + "config": Object {}, + }, + "type": "@app/COPY_API_RESOURCE_URL", + }, + ] + `); +}); diff --git a/lib/js/app/components/ActionsMenu/ActionsMenu.tsx b/lib/js/app/components/ActionsMenu/ActionsMenu.tsx index a3b8304ca..b818408ff 100644 --- a/lib/js/app/components/ActionsMenu/ActionsMenu.tsx +++ b/lib/js/app/components/ActionsMenu/ActionsMenu.tsx @@ -1,8 +1,9 @@ -import React, { FC, useState } from 'react'; +import React, { FC, useState, useContext } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { DropdownMenu, Tooltip } from '@keen.io/ui-core'; import { AnimatePresence } from 'framer-motion'; +import { AppContext } from '../../contexts'; import { getQueryResults } from '../../modules/queries'; import { @@ -22,6 +23,7 @@ import { exportChartToJson, exportDataToCsv, showEmbedModal, + copyApiResourceUrl, } from '../../modules/app'; type Props = { @@ -43,6 +45,9 @@ const ActionsMenu: FC = ({ isNewQuery, onRemoveQuery, onHideMenu }) => { const dispatch = useDispatch(); const queryResults = useSelector(getQueryResults); const [tooltip, showTooltip] = useState(false); + const { + keenAnalysis: { config }, + } = useContext(AppContext); return ( @@ -51,15 +56,15 @@ const ActionsMenu: FC = ({ isNewQuery, onRemoveQuery, onHideMenu }) => { onMouseEnter={() => !queryResults && showTooltip(true)} onMouseLeave={() => tooltip && showTooltip(false)} > - - {tooltip && ( + {tooltip && ( + {text.tooltip} - )} - + + )} { @@ -107,6 +112,14 @@ const ActionsMenu: FC = ({ isNewQuery, onRemoveQuery, onHideMenu }) => { > {text.shareQuery} + { + dispatch(copyApiResourceUrl(config)); + onHideMenu(); + }} + > + {text.apiResource} + { dispatch(showEmbedModal()); diff --git a/lib/js/app/components/ActionsMenu/text.json b/lib/js/app/components/ActionsMenu/text.json index b1bfe2e02..36103699e 100644 --- a/lib/js/app/components/ActionsMenu/text.json +++ b/lib/js/app/components/ActionsMenu/text.json @@ -6,5 +6,6 @@ "json": "JSON", "csv": "CSV", "tooltip": "Run query to download result", - "embedHtml": "Embed HTML" + "embedHtml": "Embed HTML", + "apiResource": "API Resource" } diff --git a/lib/js/app/constants.ts b/lib/js/app/constants.ts index 9a2f61c1c..ae7658a7b 100644 --- a/lib/js/app/constants.ts +++ b/lib/js/app/constants.ts @@ -40,6 +40,8 @@ export const ERRORS = { TOO_MANY_QUERIES: 'TooManyCachedQueriesInTheCurrentBillingPeriod', }; +export const API_VERSION = '3.0'; + export const EXTRACTION_PREVIEW_EVENTS_DEFAULT = 100; export const EXTRACTION_PREVIEW_EVENTS_LIMIT = 100000; export const EXTRACTION_BULK_EVENTS_DEFAULT = 1000; diff --git a/lib/js/app/modules/app/actions.ts b/lib/js/app/modules/app/actions.ts index d7f4687eb..6f5b817f0 100644 --- a/lib/js/app/modules/app/actions.ts +++ b/lib/js/app/modules/app/actions.ts @@ -36,6 +36,7 @@ import { DOWNLOAD_CODE_SNIPPET, SHOW_EMAIL_EXTRACTION_MODAL, HIDE_EMAIL_EXTRACTION_MODAL, + COPY_API_RESOURCE_URL, } from './constants'; import { @@ -212,3 +213,12 @@ export const downloadCodeSnippet = ( readKey, }, }); + +export const copyApiResourceUrl = ( + config: Record +): AppActions => ({ + type: COPY_API_RESOURCE_URL, + payload: { + config, + }, +}); diff --git a/lib/js/app/modules/app/constants.ts b/lib/js/app/modules/app/constants.ts index 8ed92cb74..bd4c172f5 100644 --- a/lib/js/app/modules/app/constants.ts +++ b/lib/js/app/modules/app/constants.ts @@ -29,5 +29,6 @@ export const DOWNLOAD_CODE_SNIPPET = '@app/DOWNLOAD_CODE_SNIPPET'; export const APP_START = '@app/APP_START'; export const SHOW_EMAIL_EXTRACTION_MODAL = '@app/SHOW_EMAIL_EXTRACTION_MODAL'; export const HIDE_EMAIL_EXTRACTION_MODAL = '@app/HIDE_EMAIL_EXTRACTION_MODAL'; +export const COPY_API_RESOURCE_URL = '@app/COPY_API_RESOURCE_URL'; export const URL_STATE = 'keen_explorer_state'; diff --git a/lib/js/app/modules/app/index.ts b/lib/js/app/modules/app/index.ts index 03554fe50..c2b8f53bf 100644 --- a/lib/js/app/modules/app/index.ts +++ b/lib/js/app/modules/app/index.ts @@ -29,6 +29,7 @@ import { downloadCodeSnippet, showEmailExtractionModal, hideEmailExtractionModal, + copyApiResourceUrl, } from './actions'; import { getConfirmation, @@ -87,6 +88,7 @@ export { copyEmbeddedCode, downloadCodeSnippet, exportDataToCsv, + copyApiResourceUrl, ReducerState, SettingsModalSource, }; diff --git a/lib/js/app/modules/app/saga.ts b/lib/js/app/modules/app/saga.ts index 5122d2b31..658845510 100644 --- a/lib/js/app/modules/app/saga.ts +++ b/lib/js/app/modules/app/saga.ts @@ -55,6 +55,7 @@ import { exportToCsv, createCodeSnippet, exportToHtml, + createResourceUrl, } from '../../utils'; import { SET_QUERY_EVENT, NEW_QUERY_EVENT } from '../../queryCreator'; @@ -67,6 +68,7 @@ import { AppStartAction, CopyEmbeddedCodeAction, DownloadCodeSnippetAction, + CopyApiResourceUrlAction, } from './types'; import { @@ -88,6 +90,7 @@ import { EXPORT_DATA_TO_CSV, COPY_EMBEDDED_CODE, DOWNLOAD_CODE_SNIPPET, + COPY_API_RESOURCE_URL, } from './constants'; const createScreenResizeChannel = () => @@ -417,6 +420,29 @@ export function* downloadCodeSnippet({ payload }: DownloadCodeSnippetAction) { } } +export function* copyApiResourceUrl({ payload }: CopyApiResourceUrlAction) { + const { config } = payload; + const query = yield select(getQuerySettings); + const notificationManager = yield getContext(NOTIFICATION_MANAGER_CONTEXT); + try { + const url = createResourceUrl({ query, config }); + console.log(url); + yield copyToClipboard(url); + yield notificationManager.showNotification({ + type: 'success', + message: text.copyApiResourceUrl, + autoDismiss: true, + }); + } catch (err) { + yield notificationManager.showNotification({ + type: 'error', + message: text.copyApiResourceUrlError, + showDismissButton: true, + autoDismiss: false, + }); + } +} + export function* appSaga() { yield takeLatest(APP_START, appStart); yield takeLatest(SHARE_QUERY_URL, shareQueryUrl); @@ -432,5 +458,6 @@ export function* appSaga() { yield takeLatest(EXPORT_DATA_TO_CSV, exportDataToCsv); yield takeLatest(COPY_EMBEDDED_CODE, copyEmbeddedCode); yield takeLatest(DOWNLOAD_CODE_SNIPPET, downloadCodeSnippet); + yield takeLatest(COPY_API_RESOURCE_URL, copyApiResourceUrl); yield debounce(200, SCREEN_RESIZE, resizeBrowserScreen); } diff --git a/lib/js/app/modules/app/text.json b/lib/js/app/modules/app/text.json index 633ff76de..a64338792 100644 --- a/lib/js/app/modules/app/text.json +++ b/lib/js/app/modules/app/text.json @@ -5,5 +5,7 @@ "downloadChartError": "download failed. Please try again later.", "downloadInProgress": "download in progress", "copyEmbeddedCode": "HTML code copied to clipboard", - "copyEmbeddedCodeError": "We were not able to copy HTML code. Try again later." + "copyEmbeddedCodeError": "We were not able to copy HTML code. Try again later.", + "copyApiResourceUrl": "API Resource copied to clipboard", + "copyApiResourceUrlError": "We were not able to copy API Resource. Try again later." } diff --git a/lib/js/app/modules/app/types.ts b/lib/js/app/modules/app/types.ts index b49962964..575e06780 100644 --- a/lib/js/app/modules/app/types.ts +++ b/lib/js/app/modules/app/types.ts @@ -33,6 +33,7 @@ import { DOWNLOAD_CODE_SNIPPET, SHOW_EMAIL_EXTRACTION_MODAL, HIDE_EMAIL_EXTRACTION_MODAL, + COPY_API_RESOURCE_URL, } from './constants'; export type Confirmation = 'delete'; @@ -220,6 +221,13 @@ export interface DownloadCodeSnippetAction { }; } +export interface CopyApiResourceUrlAction { + type: typeof COPY_API_RESOURCE_URL; + payload: { + config: Record; + }; +} + export type AppActions = | AppStartAction | ResizeScreenAction @@ -249,4 +257,5 @@ export type AppActions = | SelectFirstSavedQueryAction | SelectFirstSavedQueryAction | ShowEmailExtractionModalction - | HideEmailExtractionModalction; + | HideEmailExtractionModalction + | CopyApiResourceUrlAction; diff --git a/lib/js/app/utils/__snapshots__/createResourceUrl.test.ts.snap b/lib/js/app/utils/__snapshots__/createResourceUrl.test.ts.snap new file mode 100644 index 000000000..d15c3384c --- /dev/null +++ b/lib/js/app/utils/__snapshots__/createResourceUrl.test.ts.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`stringify query params 1`] = `"http://host/3.0/projects/projectId/queries/undefined?api_key=masterKey&timezone=0&timeframe=%22this_14_weeks%22&order_by=%5B%7B%22direction%22%3A%22ASC%22%2C%22property_name%22%3A%22result%22%7D%2C%7B%22direction%22%3A%22ASC%22%2C%22property_name%22%3A%22platform%22%7D%5D&group_by=%5B%22referrer%22%2C%22platform%22%5D&event_collection=%22purchases%22"`; diff --git a/lib/js/app/utils/__snapshots__/stringify.test.ts.snap b/lib/js/app/utils/__snapshots__/stringify.test.ts.snap new file mode 100644 index 000000000..793d14969 --- /dev/null +++ b/lib/js/app/utils/__snapshots__/stringify.test.ts.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`stringify query params 1`] = `"timezone=0&timeframe=%22this_14_weeks%22&order_by=%5B%7B%22direction%22%3A%22ASC%22%2C%22property_name%22%3A%22result%22%7D%2C%7B%22direction%22%3A%22ASC%22%2C%22property_name%22%3A%22platform%22%7D%5D&group_by=%5B%22referrer%22%2C%22platform%22%5D&event_collection=%22purchases%22"`; diff --git a/lib/js/app/utils/createResourceUrl.test.ts b/lib/js/app/utils/createResourceUrl.test.ts new file mode 100644 index 000000000..8d1348bc2 --- /dev/null +++ b/lib/js/app/utils/createResourceUrl.test.ts @@ -0,0 +1,27 @@ +/* eslint-disable @typescript-eslint/camelcase */ +import { createResourceUrl } from './createResourceUrl'; + +test('stringify query params', () => { + const query = { + filters: [], + timezone: 'UTC', + timeframe: 'this_14_weeks', + zero_fill: null, + order_by: [ + { direction: 'ASC', property_name: 'result' }, + { direction: 'ASC', property_name: 'platform' }, + ], + interval: null, + group_by: ['referrer', 'platform'], + limit: null, + event_collection: 'purchases', + }; + const config = { + protocol: 'http', + host: 'host', + projectId: 'projectId', + masterKey: 'masterKey', + }; + + expect(createResourceUrl({ query, config })).toMatchSnapshot(); +}); diff --git a/lib/js/app/utils/createResourceUrl.ts b/lib/js/app/utils/createResourceUrl.ts new file mode 100644 index 000000000..70155403c --- /dev/null +++ b/lib/js/app/utils/createResourceUrl.ts @@ -0,0 +1,10 @@ +import { stringify } from './stringify'; +import { API_VERSION } from '../constants'; + +export const createResourceUrl = ({ query, config }) => { + const { analysis_type: analysisType, ...queryParams } = query; + const { protocol, host, projectId, masterKey } = config; + const urlParams = stringify(queryParams); + + return `${protocol}://${host}/${API_VERSION}/projects/${projectId}/queries/${analysisType}?api_key=${masterKey}&${urlParams}`; +}; diff --git a/lib/js/app/utils/index.ts b/lib/js/app/utils/index.ts index bd21c30ff..ad2ec2796 100644 --- a/lib/js/app/utils/index.ts +++ b/lib/js/app/utils/index.ts @@ -5,6 +5,8 @@ import { exportToCsv } from './exportToCsv'; import { setVisualization } from './setVisualization'; import { exportToHtml } from './exportToHtml'; import { createCodeSnippet } from './createCodeSnippet'; +import { createResourceUrl } from './createResourceUrl'; +import { stringify } from './stringify'; export { setVisualization, @@ -14,4 +16,6 @@ export { exportToCsv, exportToHtml, createCodeSnippet, + createResourceUrl, + stringify, }; diff --git a/lib/js/app/utils/stringify.test.ts b/lib/js/app/utils/stringify.test.ts new file mode 100644 index 000000000..29b14e058 --- /dev/null +++ b/lib/js/app/utils/stringify.test.ts @@ -0,0 +1,21 @@ +/* eslint-disable @typescript-eslint/camelcase */ +import { stringify } from './stringify'; + +test('stringify query params', () => { + const queryParams = { + filters: [], + timezone: 'UTC', + timeframe: 'this_14_weeks', + zero_fill: null, + order_by: [ + { direction: 'ASC', property_name: 'result' }, + { direction: 'ASC', property_name: 'platform' }, + ], + interval: null, + group_by: ['referrer', 'platform'], + limit: null, + event_collection: 'purchases', + }; + + expect(stringify(queryParams)).toMatchSnapshot(); +}); diff --git a/lib/js/app/utils/stringify.ts b/lib/js/app/utils/stringify.ts new file mode 100644 index 000000000..cacd607f3 --- /dev/null +++ b/lib/js/app/utils/stringify.ts @@ -0,0 +1,51 @@ +import { TIMEZONES } from '../queryCreator'; + +export const stringify = (queryParams) => { + console.log(JSON.stringify(queryParams)); + return Object.keys(queryParams) + .map((k) => { + let queryParamValue = queryParams[k]; + if (!queryParamValue) return null; + if (Array.isArray(queryParamValue) && !queryParamValue.length) + return null; + + if (k === 'timezone') { + const timezoneOption = TIMEZONES.find( + (item) => item.name === queryParamValue + ) || { + label: 'UTC', + value: 0, + }; + queryParamValue = timezoneOption.value; + } + + if (Array.isArray(queryParamValue)) { + queryParamValue = queryParamValue.map((value) => { + if (typeof value === 'object' && value !== null) { + const underscoredObject = {}; + Object.keys(value).forEach((objkey) => { + const underscoredK = objkey + .replace(/(?:^|\.?)([A-Z])/g, (x, y) => `_${y.toLowerCase()}`) + .replace(/^_/, ''); + underscoredObject[underscoredK] = value[objkey]; + }); + return underscoredObject; + } + return value; + }); + } + + queryParamValue = JSON.stringify(queryParamValue); + + const underscoredK = k + .replace(/(?:^|\.?)([A-Z])/g, (x, y) => `_${y.toLowerCase()}`) + .replace(/^_/, ''); + return `${encodeURIComponent(underscoredK)}=${encodeURIComponent( + queryParamValue + )}`; + }) + .filter((item) => !!item) + .join('&') + .replace(/%22true%22/gi, 'true') + .replace(/%22false%22/gi, 'false'); +}; diff --git a/package.json b/package.json index 36d96fcf3..ae60785f9 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,6 @@ "moment": "^2.22.2", "moment-timezone": "^0.5.31", "polished": "^3.4.4", - "qs": "^6.9.4", "rc-time-picker": "^3.6.2", "react": "^16.13.1", "react-dates": "^21.8.0", @@ -102,7 +101,6 @@ "@types/dom-to-image": "^2.6.1", "@types/file-saver": "^2.0.1", "@types/jest": "^26.0.14", - "@types/qs": "^6.9.3", "@types/rc-time-picker": "^3.4.1", "@types/react": "^16.9.35", "@types/react-dates": "^17.1.12", diff --git a/yarn.lock b/yarn.lock index ca7955aec..5f43bdbfd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1633,10 +1633,10 @@ styled-system "5.1.5" webfontloader "^1.6.28" -"@keen.io/ui-core@^2.5.6": - version "2.5.6" - resolved "https://registry.yarnpkg.com/@keen.io/ui-core/-/ui-core-2.5.6.tgz#65025b88b903832b9740d6f87eaa14b2cb4d4e49" - integrity sha512-fTv/L3XNHmNdaXoqRgEN7IPXCahiJvoQimN3X4DfQddusFdpiDG0qDXgJgEKnOQ5p9xZMoHZ6iwZHXw0SGHbjw== +"@keen.io/ui-core@^2.5.5": + version "2.5.5" + resolved "https://registry.yarnpkg.com/@keen.io/ui-core/-/ui-core-2.5.5.tgz#8ac362988c0884da4bce82070eea00655efb8bfc" + integrity sha512-K+PDRGSNVzciGRa9jgmkiRweDM7zAccK6ATPj5zciAfoXX2gzk3WuG1m55iFlg6NXie5xzPS2CW292+uQiDTpA== dependencies: "@keen.io/colors" "^1.1.0" "@keen.io/icons" "^1.2.1" @@ -1652,10 +1652,10 @@ styled-system "5.1.5" webfontloader "^1.6.28" -"@keen.io/ui-core@^2.5.5": - version "2.5.5" - resolved "https://registry.yarnpkg.com/@keen.io/ui-core/-/ui-core-2.5.5.tgz#8ac362988c0884da4bce82070eea00655efb8bfc" - integrity sha512-K+PDRGSNVzciGRa9jgmkiRweDM7zAccK6ATPj5zciAfoXX2gzk3WuG1m55iFlg6NXie5xzPS2CW292+uQiDTpA== +"@keen.io/ui-core@^2.5.6": + version "2.5.6" + resolved "https://registry.yarnpkg.com/@keen.io/ui-core/-/ui-core-2.5.6.tgz#65025b88b903832b9740d6f87eaa14b2cb4d4e49" + integrity sha512-fTv/L3XNHmNdaXoqRgEN7IPXCahiJvoQimN3X4DfQddusFdpiDG0qDXgJgEKnOQ5p9xZMoHZ6iwZHXw0SGHbjw== dependencies: "@keen.io/colors" "^1.1.0" "@keen.io/icons" "^1.2.1" @@ -2101,11 +2101,6 @@ resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7" integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw== -"@types/qs@^6.9.3": - version "6.9.5" - resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.5.tgz#434711bdd49eb5ee69d90c1d67c354a9a8ecb18b" - integrity sha512-/JHkVHtx/REVG0VVToGRGH2+23hsYLHdyG+GrvoUGlGAd0ErauXDyvHtRI/7H7mzLm+tBCKA7pfcpkQ1lf58iQ== - "@types/rc-time-picker@^3.4.1": version "3.4.1" resolved "https://registry.yarnpkg.com/@types/rc-time-picker/-/rc-time-picker-3.4.1.tgz#e35f7738e5c7b94dc1c22b21c2606ff92adac8d2" @@ -10661,11 +10656,6 @@ qs@6.7.0: resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== -qs@^6.9.4: - version "6.9.4" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.4.tgz#9090b290d1f91728d3c22e54843ca44aea5ab687" - integrity sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ== - qs@~6.5.2: version "6.5.2" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"