Add hidden DOM when dashboard UI is complete to indicate completion to headless/WebDriver clients. Closes #2467. (#2469)

This commit is contained in:
Mike Burgess
2022-09-28 11:37:07 +01:00
committed by GitHub
parent 6ea97ef604
commit dd40d8a596
9 changed files with 229 additions and 61 deletions

View File

@@ -64,11 +64,13 @@
"@tailwindcss/forms": "0.5.3",
"@tailwindcss/line-clamp": "0.4.2",
"@tailwindcss/typography": "0.5.7",
"@testing-library/jest-dom": "5.16.5",
"@testing-library/react": "13.4.0",
"@tsconfig/create-react-app": "1.0.2",
"@types/echarts": "4.9.16",
"@types/jest": "29.0.3",
"@types/lodash": "4.14.185",
"@types/node": "18.7.22",
"@types/node": "18.7.23",
"@types/react": "18.0.21",
"@types/react-dom": "18.0.6",
"autoprefixer": "10.4.12",

View File

@@ -1,11 +1,13 @@
import { DashboardRunState } from "../../../../hooks/useDashboard";
import { useDashboard } from "../../../../hooks/useDashboard";
interface DashboardProgressProps {
state?: DashboardRunState;
progress?: number;
}
const DashboardProgress = () => {
const { dataMode, progress, state } = useDashboard();
// We only show a progress indicator in live mode
if (dataMode === "snapshot") {
return null;
}
const DashboardProgress = ({ state, progress }: DashboardProgressProps) => {
return (
<div className="w-full h-[4px] bg-dashboard print:hidden">
{state === "ready" ? (

View File

@@ -2,9 +2,9 @@ import Children from "../common/Children";
import DashboardProgress from "./DashboardProgress";
import LayoutPanel from "../common/LayoutPanel";
import PanelDetail from "../PanelDetail";
import SnapshotRenderComplete from "../../../snapshot/SnapshotRenderComplete";
import {
DashboardDefinition,
DashboardRunState,
useDashboard,
} from "../../../../hooks/useDashboard";
import { registerComponent } from "../../index";
@@ -13,8 +13,6 @@ interface DashboardProps {
allowPanelExpand?: boolean;
definition: DashboardDefinition;
isRoot?: boolean;
progress?: number;
state?: DashboardRunState;
withPadding?: boolean;
}
@@ -26,13 +24,11 @@ interface DashboardWrapperProps {
const Dashboard = ({
allowPanelExpand = true,
definition,
progress = 0,
isRoot = true,
state = "ready",
withPadding = false,
}: DashboardProps) => (
<>
{isRoot ? <DashboardProgress state={state} progress={progress} /> : <></>}
{isRoot ? <DashboardProgress /> : <></>}
<LayoutPanel
className={isRoot ? "h-full overflow-y-auto" : undefined}
definition={definition}
@@ -50,15 +46,8 @@ const Dashboard = ({
const DashboardWrapper = ({
allowPanelExpand = true,
}: DashboardWrapperProps) => {
const {
dashboard,
dataMode,
progress,
search,
selectedDashboard,
selectedPanel,
state,
} = useDashboard();
const { dashboard, dataMode, search, selectedDashboard, selectedPanel } =
useDashboard();
if (
search.value ||
@@ -73,13 +62,14 @@ const DashboardWrapper = ({
}
return (
<Dashboard
allowPanelExpand={allowPanelExpand}
definition={dashboard}
progress={progress}
withPadding={true}
state={state}
/>
<>
<Dashboard
allowPanelExpand={allowPanelExpand}
definition={dashboard}
withPadding={true}
/>
<SnapshotRenderComplete />
</>
);
};

View File

@@ -0,0 +1,33 @@
import React from "react";
import SnapshotRenderComplete from "./index.tsx";
import { DashboardContext } from "../../../hooks/useDashboard";
import { render } from "@testing-library/react";
import "@testing-library/jest-dom";
test("return null when should not render snapshot complete div", async () => {
// ARRANGE
const { container } = render(
<DashboardContext.Provider
value={{ render: { snapshotCompleteDiv: false } }}
>
<SnapshotRenderComplete />
</DashboardContext.Provider>
);
// ASSERT
expect(container).toBeEmptyDOMElement();
});
test("return null when should not render snapshot complete div", async () => {
// ARRANGE
render(
<DashboardContext.Provider
value={{ render: { snapshotCompleteDiv: true } }}
>
<SnapshotRenderComplete />
</DashboardContext.Provider>
);
// ASSERT
expect(document.querySelector("#snapshot-complete")).toBeTruthy();
});

View File

@@ -0,0 +1,15 @@
import { useDashboard } from "../../../hooks/useDashboard";
const SnapshotRenderComplete = () => {
const {
render: { snapshotCompleteDiv },
} = useDashboard();
if (!snapshotCompleteDiv) {
return null;
}
return <div id="snapshot-complete" className="hidden" />;
};
export default SnapshotRenderComplete;

View File

@@ -2,15 +2,7 @@ import get from "lodash/get";
import has from "lodash/has";
import isEqual from "lodash/isEqual";
import paths from "deepdash/paths";
import set from "lodash/set";
import sortBy from "lodash/sortBy";
import useDashboardWebSocket, {
SocketActions,
SocketURLFactory,
} from "./useDashboardWebSocket";
import usePrevious from "./usePrevious";
import { buildComponentsMap } from "../components";
import {
import React, {
createContext,
Ref,
useCallback,
@@ -19,6 +11,14 @@ import {
useReducer,
useState,
} from "react";
import set from "lodash/set";
import sortBy from "lodash/sortBy";
import useDashboardWebSocket, {
SocketActions,
SocketURLFactory,
} from "./useDashboardWebSocket";
import usePrevious from "./usePrevious";
import { buildComponentsMap } from "../components";
import { GlobalHotKeys } from "react-hotkeys";
import { LeafNodeData, Width } from "../components/dashboards/common";
import { noop } from "../utils/func";
@@ -95,6 +95,10 @@ interface IDashboardContext {
progress: number;
state: DashboardRunState;
render: {
headless: boolean;
snapshotCompleteDiv: boolean;
};
}
export interface IActions {
@@ -314,6 +318,10 @@ export interface DashboardDataOptions {
snapshotId?: string;
}
export interface DashboardRenderOptions {
headless?: boolean;
}
interface DashboardProviderProps {
analyticsContext: any;
breakpointContext: any;
@@ -322,6 +330,7 @@ interface DashboardProviderProps {
dataOptions?: DashboardDataOptions;
eventHooks?: {};
featureFlags?: string[];
renderOptions?: DashboardRenderOptions;
socketUrlFactory?: SocketURLFactory;
stateDefaults?: {};
themeContext: any;
@@ -842,6 +851,9 @@ const DashboardProvider = ({
},
eventHooks,
featureFlags = [],
renderOptions = {
headless: false,
},
socketUrlFactory,
stateDefaults = {},
themeContext,
@@ -851,7 +863,11 @@ const DashboardProvider = ({
const [searchParams, setSearchParams] = useSearchParams();
const [state, dispatchInner] = useReducer(
reducer,
getInitialState(searchParams, { ...stateDefaults, ...dataOptions })
getInitialState(searchParams, {
...stateDefaults,
...dataOptions,
...renderOptions,
})
);
const dispatch = useCallback((action) => {
// console.log(action.type, action);
@@ -923,11 +939,6 @@ const DashboardProvider = ({
searchParams.get("tag") ||
get(stateDefaults, "search.groupBy.tag", "service");
const inputs = buildSelectedDashboardInputsFromSearchParams(searchParams);
// console.log({
// // @ts-ignore
// previous: previousSelectedDashboardStates?.selectedDashboardInputs,
// current: inputs,
// });
dispatch({
type: DashboardActions.SET_DASHBOARD_SEARCH_VALUE,
value: goneFromDashboardToDashboard ? "" : search,
@@ -943,7 +954,6 @@ const DashboardProvider = ({
previousSelectedDashboardStates?.selectedDashboardInputs
) !== JSON.stringify(inputs)
) {
// console.log("dispatching inputs", inputs);
dispatch({
type: DashboardActions.SET_DASHBOARD_INPUTS,
value: inputs,
@@ -1282,6 +1292,16 @@ const DashboardProvider = ({
});
}, [closePanelDetail]);
const [renderSnapshotCompleteDiv, setRenderSnapshotCompleteDiv] =
useState(false);
useEffect(() => {
if (dataOptions?.dataMode !== "snapshot" || state.state !== "complete") {
return;
}
setRenderSnapshotCompleteDiv(true);
}, [dataOptions?.dataMode, state.state]);
return (
<DashboardContext.Provider
value={{
@@ -1292,6 +1312,10 @@ const DashboardProvider = ({
dispatch,
closePanelDetail,
themeContext,
render: {
headless: renderOptions?.headless,
snapshotCompleteDiv: renderSnapshotCompleteDiv,
},
}}
>
<GlobalHotKeys

View File

@@ -83,7 +83,7 @@
--color-error: red;
--color-warning: #f9a825;
--color-dashboard: #1e2329;
--color-dashboard-panel: #262B32; /* #2d333b;*/ /*var(--color-black-scale-1);*/
--color-dashboard-panel: #262b32; /* #2d333b;*/ /*var(--color-black-scale-1);*/
--color-foreground: #fff;
--color-foreground-light: #ccc;
--color-foreground-lighter: #888;
@@ -216,14 +216,13 @@
}
.dashboard-loading-animate {
background-image:
repeating-linear-gradient(
-45deg,
var(--color-skip-animate),
var(--color-skip-animate) 0.5rem,
var(--color-skip) 0.5rem,
var(--color-skip) 1.25rem
);
background-image: repeating-linear-gradient(
-45deg,
var(--color-skip-animate),
var(--color-skip-animate) 0.5rem,
var(--color-skip) 0.5rem,
var(--color-skip) 1.25rem
);
background-size: 200% 200%;
animation: barberpole 10s linear infinite;
}
@@ -280,6 +279,10 @@
}
@media print {
@page {
size: 2550px 3300px;
}
#root {
-webkit-print-color-adjust: exact;
}

View File

@@ -113,6 +113,7 @@ export const PanelStoryDecorator = ({
refetchDashboard: false,
state: "complete",
progress: 100,
render: { headless: false, snapshotCompleteDiv: false },
}}
>
<Dashboard allowPanelExpand={false} />

View File

@@ -2,6 +2,11 @@
# yarn lockfile v1
"@adobe/css-tools@^4.0.1":
version "4.0.1"
resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.0.1.tgz#b38b444ad3aa5fedbb15f2f746dcd934226a12dd"
integrity sha512-+u76oB43nOHrF4DDWRLWDCtci7f3QJoEBigemIdIeTi1ODqjx6Tad9NCVnPRwewWlKkVab5PlK8DCtPTyX7S8g==
"@apideck/better-ajv-errors@^0.3.1":
version "0.3.1"
resolved "https://registry.npmjs.org/@apideck/better-ajv-errors/-/better-ajv-errors-0.3.1.tgz"
@@ -2547,6 +2552,13 @@
dependencies:
regenerator-runtime "^0.13.4"
"@babel/runtime@^7.9.2":
version "7.19.0"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.19.0.tgz#22b11c037b094d27a8a2504ea4dcff00f50e2259"
integrity sha512-eR8Lo9hnDS7tqkO7NsV+mKvCmv5boaXFSZ70DnfhcgiEne8hv9oCEd36Klw74EtizEqLsy4YnW8UWwpBVolHZA==
dependencies:
regenerator-runtime "^0.13.4"
"@babel/template@^7.12.13", "@babel/template@^7.12.7", "@babel/template@^7.3.3":
version "7.12.13"
resolved "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz"
@@ -4338,6 +4350,44 @@
lodash.merge "^4.6.2"
postcss-selector-parser "6.0.10"
"@testing-library/dom@^8.5.0":
version "8.18.1"
resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-8.18.1.tgz#80f91be02bc171fe5a3a7003f88207be31ac2cf3"
integrity sha512-oEvsm2B/WtcHKE+IcEeeCqNU/ltFGaVyGbpcm4g/2ytuT49jrlH9x5qRKL/H3A6yfM4YAbSbC0ceT5+9CEXnLg==
dependencies:
"@babel/code-frame" "^7.10.4"
"@babel/runtime" "^7.12.5"
"@types/aria-query" "^4.2.0"
aria-query "^5.0.0"
chalk "^4.1.0"
dom-accessibility-api "^0.5.9"
lz-string "^1.4.4"
pretty-format "^27.0.2"
"@testing-library/jest-dom@5.16.5":
version "5.16.5"
resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.16.5.tgz#3912846af19a29b2dbf32a6ae9c31ef52580074e"
integrity sha512-N5ixQ2qKpi5OLYfwQmUb/5mSV9LneAcaUfp32pn4yCnpb8r/Yz0pXFPck21dIicKmi+ta5WRAknkZCfA8refMA==
dependencies:
"@adobe/css-tools" "^4.0.1"
"@babel/runtime" "^7.9.2"
"@types/testing-library__jest-dom" "^5.9.1"
aria-query "^5.0.0"
chalk "^3.0.0"
css.escape "^1.5.1"
dom-accessibility-api "^0.5.6"
lodash "^4.17.15"
redent "^3.0.0"
"@testing-library/react@13.4.0":
version "13.4.0"
resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-13.4.0.tgz#6a31e3bf5951615593ad984e96b9e5e2d9380966"
integrity sha512-sXOGON+WNTh3MLE9rve97ftaZukN3oNf2KjDy7YTx6hcTO2uuLHuCGynMDhFwGw/jYf4OJ2Qk0i4i79qMNNkyw==
dependencies:
"@babel/runtime" "^7.12.5"
"@testing-library/dom" "^8.5.0"
"@types/react-dom" "^18.0.0"
"@tootallnate/once@1":
version "1.1.2"
resolved "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz"
@@ -4378,6 +4428,11 @@
resolved "https://registry.npmjs.org/@types/anymatch/-/anymatch-1.3.1.tgz"
integrity sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA==
"@types/aria-query@^4.2.0":
version "4.2.2"
resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-4.2.2.tgz#ed4e0ad92306a704f9fb132a0cfcf77486dbe2bc"
integrity sha512-HnYpAE1Y6kRyKM/XkEuiRQhTHvkzMBurTHnpFLYLBGPIylZNPs9jJcuOOYWxPLJCSEtmZT0Y8rHDokKN7rRTig==
"@types/babel__core@^7.0.0", "@types/babel__core@^7.1.7":
version "7.1.12"
resolved "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.12.tgz"
@@ -4773,7 +4828,7 @@
dependencies:
"@types/istanbul-lib-report" "*"
"@types/jest@29.0.3":
"@types/jest@*", "@types/jest@29.0.3":
version "29.0.3"
resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.0.3.tgz#b61a5ed100850686b8d3c5e28e3a1926b2001b59"
integrity sha512-F6ukyCTwbfsEX5F2YmVYmM5TcTHy1q9P5rWlRbrk56KyMh3v9xRGUO3aa8+SkvMi0SHXtASJv1283enXimC0Og==
@@ -4848,10 +4903,10 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.25.tgz#527051f3c2f77aa52e5dc74e45a3da5fb2301448"
integrity sha512-wANk6fBrUwdpY4isjWrKTufkrXdu1D2YHCot2fD/DfWxF5sMrVSA+KN7ydckvaTCh0HiqX9IVl0L5/ZoXg5M7w==
"@types/node@18.7.22":
version "18.7.22"
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.7.22.tgz#76f7401362ad63d9d7eefa7dcdfa5fcd9baddff3"
integrity sha512-TsmoXYd4zrkkKjJB0URF/mTIKPl+kVcbqClB2F/ykU7vil1BfWZVndOnpEIozPv4fURD28gyPFeIkW2G+KXOvw==
"@types/node@18.7.23":
version "18.7.23"
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.7.23.tgz#75c580983846181ebe5f4abc40fe9dfb2d65665f"
integrity sha512-DWNcCHolDq0ZKGizjx2DZjR/PqsYwAcYUJmfMWqtVU2MBMG5Mo+xFZrhGId5r/O5HOuMPyQEcM6KUBp5lBZZBg==
"@types/node@^14.0.10 || ^16.0.0", "@types/node@^14.14.20 || ^16.0.0":
version "16.11.15"
@@ -4910,7 +4965,7 @@
resolved "https://registry.npmjs.org/@types/qs/-/qs-6.9.6.tgz"
integrity sha512-0/HnwIfW4ki2D8L8c9GVcG5I72s9jP5GSLVF0VIXDW00kmIpA6O33G7a8n59Tmh7Nz0WUC3rSb7PTY/sdW2JzA==
"@types/react-dom@18.0.6":
"@types/react-dom@18.0.6", "@types/react-dom@^18.0.0":
version "18.0.6"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.0.6.tgz#36652900024842b74607a17786b6662dd1e103a1"
integrity sha512-/5OFZgfIPSwy+YuIBP/FgJnQnsxhZhjjrnxudMddeblOouIodEQ75X14Rr4wGSG/bknL+Omy9iWlLo1u/9GzAA==
@@ -4984,6 +5039,13 @@
resolved "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.7.tgz"
integrity sha512-0VBprVqfgFD7Ehb2vd8Lh9TG3jP98gvr8rgehQqzztZNI7o8zS8Ad4jyZneKELphpuE212D8J70LnSNQSyO6bQ==
"@types/testing-library__jest-dom@^5.9.1":
version "5.14.5"
resolved "https://registry.yarnpkg.com/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.5.tgz#d113709c90b3c75fdb127ec338dad7d5f86c974f"
integrity sha512-SBwbxYoyPIvxHbeHxTZX2Pe/74F/tX2/D3mMvzabdeJ25bBojfW0TyB8BHrbq/9zaaKICJZjLP+8r6AeZMFCuQ==
dependencies:
"@types/jest" "*"
"@types/trusted-types@^2.0.2":
version "2.0.2"
resolved "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.2.tgz"
@@ -5755,6 +5817,11 @@ aria-query@^4.2.2:
"@babel/runtime" "^7.10.2"
"@babel/runtime-corejs3" "^7.10.2"
aria-query@^5.0.0:
version "5.0.2"
resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.0.2.tgz#0b8a744295271861e1d933f8feca13f9b70cfdc1"
integrity sha512-eigU3vhqSO+Z8BKDnVLN/ompjhf3pYzecKXz8+whRy+9gZu8n1TCGfwzQUUPnqdHl9ax1Hr9031orZ+UOEYr7Q==
arr-diff@^4.0.0:
version "4.0.0"
resolved "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz"
@@ -6756,6 +6823,14 @@ chalk@^2.0.0, chalk@^2.4.1, chalk@^2.4.2:
escape-string-regexp "^1.0.5"
supports-color "^5.3.0"
chalk@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4"
integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==
dependencies:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
chalk@^4.0.0, chalk@^4.1.0:
version "4.1.0"
resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz"
@@ -7625,6 +7700,11 @@ css-what@^5.1.0:
resolved "https://registry.npmjs.org/css-what/-/css-what-5.1.0.tgz"
integrity sha512-arSMRWIIFY0hV8pIxZMEfmMI47Wj3R/aWpZDDxWYCPEiOMv6tfOrnpDtgxBYPEQD4V0Y/958+1TdC3iWTFcUPw==
css.escape@^1.5.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb"
integrity sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==
cssdb@^5.0.0:
version "5.0.0"
resolved "https://registry.npmjs.org/cssdb/-/cssdb-5.0.0.tgz"
@@ -8146,6 +8226,11 @@ doctrine@^3.0.0:
dependencies:
esutils "^2.0.2"
dom-accessibility-api@^0.5.6, dom-accessibility-api@^0.5.9:
version "0.5.14"
resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.14.tgz#56082f71b1dc7aac69d83c4285eef39c15d93f56"
integrity sha512-NMt+m9zFMPZe0JcY9gN224Qvk6qLIdqex29clBvc/y75ZBX9YA9wNK3frsYvu2DI1xcCIwxwnX+TlsJ2DSOADg==
dom-converter@^0.2, dom-converter@^0.2.0:
version "0.2.0"
resolved "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz"
@@ -12057,6 +12142,11 @@ lru-cache@^6.0.0:
dependencies:
yallist "^4.0.0"
lz-string@^1.4.4:
version "1.4.4"
resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.4.4.tgz#c0d8eaf36059f705796e1e344811cf4c498d3a26"
integrity sha512-0ckx7ZHRPqb0oUm8zNr+90mtf9DQB60H1wMCjBtfi62Kl3a7JbHob6gA2bC+xRvZoOL+1hzUK8jeuEIQE8svEQ==
magic-string@^0.25.0, magic-string@^0.25.7:
version "0.25.7"
resolved "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz"
@@ -14608,7 +14698,7 @@ pretty-error@^4.0.0:
lodash "^4.17.20"
renderkid "^3.0.0"
pretty-format@^27.4.2, pretty-format@^27.5.1:
pretty-format@^27.0.2, pretty-format@^27.4.2, pretty-format@^27.5.1:
version "27.5.1"
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.5.1.tgz#2181879fdea51a7a5851fb39d920faa63f01d88e"
integrity sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==
@@ -15317,6 +15407,14 @@ redent@^1.0.0:
indent-string "^2.1.0"
strip-indent "^1.0.1"
redent@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f"
integrity sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==
dependencies:
indent-string "^4.0.0"
strip-indent "^3.0.0"
reduce-css-calc@1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz#747c914e049614a4c9cfbba629871ad1d2927716"