From ef256e31086dd76ab744ab8898583369cf1dbc60 Mon Sep 17 00:00:00 2001
From: Benoit Vinay <ben@benoitvinay.com>
Date: Tue, 22 Mar 2022 20:56:19 +0100
Subject: [PATCH] Various fixes for errors seen in the console while browsing
 (#21060)

* shouldNotForwardTransientProp method created for emotion

* BadgeIcon html attributes clean up

* Usage of transient property $hasMargin in Badge for BadgeIcon

* HTML attribute cleanup for LinkRoot

* React key added to ButtonBar child if not defined

* Properties for TableInfoPopover properly typed

* Misc

* analyticsContext property set as non required as non of the parents enforce it or pass a value

* Clean up attributes for SVG in Icon

* CSS fix for share icon special case in Icon

* Checkbox controlled component default value fix

* Misc clean up

* index file for collection side bar

* Collections collectionID type fix

* collection type in NewCollectionItemMenu fix

* Misc

* TokenField fix for value

* noPopover passed properly to WidgetStatusIcon

* Updates to controlled input/checkbox

* VS Code workspace file added to gitignore

* shouldNotForwardTransientProp renamed to shouldForwardNonTransientProp

* AddAggregationButton defaultProps updates

* propTypes for TableInfoPopover added back
---
 .gitignore                                       |  1 +
 .../Collections/Collections.jsx                  |  2 +-
 .../components/NewCollectionItemMenu.jsx         | 10 +++++-----
 .../containers/CollectionSidebar/index.ts        |  1 +
 frontend/src/metabase/components/Badge.jsx       |  2 +-
 .../src/metabase/components/Badge.styled.jsx     |  7 +++++--
 frontend/src/metabase/components/ButtonBar.jsx   |  4 +++-
 .../CollectionLanding/CollectionLanding.jsx      |  6 +++---
 frontend/src/metabase/components/Icon.tsx        | 13 +++++++++----
 .../TableInfoPopover/TableInfoPopover.tsx        | 16 +++++++++-------
 frontend/src/metabase/components/TokenField.jsx  |  6 ++++--
 .../core/components/CheckBox/CheckBox.tsx        |  6 ++++--
 .../core/components/Link/Link.styled.tsx         |  5 ++++-
 frontend/src/metabase/lib/styling/emotion.ts     |  5 +++++
 .../components/ParameterValueWidget.jsx          |  4 ++--
 .../AddAggregationButton.jsx                     |  2 +-
 .../questions/components/CollectionBadge.jsx     |  3 ++-
 17 files changed, 60 insertions(+), 33 deletions(-)
 create mode 100644 frontend/src/metabase/collections/containers/CollectionSidebar/index.ts
 create mode 100644 frontend/src/metabase/lib/styling/emotion.ts

diff --git a/.gitignore b/.gitignore
index f2e037e9e25..c60848bbec3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -80,3 +80,4 @@ dev/src/dev/nocommit/
 .lsp/*
 !.lsp/config.edn
 .clj-kondo/.cache
+*.code-workspace
diff --git a/frontend/src/metabase/collections/components/CollectionSidebar/Collections/Collections.jsx b/frontend/src/metabase/collections/components/CollectionSidebar/Collections/Collections.jsx
index 071d3cf25d2..cae353b349f 100644
--- a/frontend/src/metabase/collections/components/CollectionSidebar/Collections/Collections.jsx
+++ b/frontend/src/metabase/collections/components/CollectionSidebar/Collections/Collections.jsx
@@ -10,7 +10,7 @@ import {
 import { Container } from "./Collections.styled";
 
 const propTypes = {
-  collectionId: PropTypes.number,
+  collectionId: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
   currentUserId: PropTypes.number,
   handleToggleMobileSidebar: PropTypes.func.isRequired,
   list: PropTypes.array,
diff --git a/frontend/src/metabase/collections/components/NewCollectionItemMenu.jsx b/frontend/src/metabase/collections/components/NewCollectionItemMenu.jsx
index 772e54fe7b7..1fe9df69969 100644
--- a/frontend/src/metabase/collections/components/NewCollectionItemMenu.jsx
+++ b/frontend/src/metabase/collections/components/NewCollectionItemMenu.jsx
@@ -2,13 +2,13 @@ import React from "react";
 import PropTypes from "prop-types";
 import { t } from "ttag";
 
-import * as Urls from "metabase/lib/urls";
+import { newQuestion, newDashboard, newCollection } from "metabase/lib/urls";
 
 import EntityMenu from "metabase/components/EntityMenu";
 import { ANALYTICS_CONTEXT } from "metabase/collections/constants";
 
 const propTypes = {
-  collection: PropTypes.func,
+  collection: PropTypes.object,
   list: PropTypes.arrayOf(PropTypes.object),
 };
 
@@ -17,19 +17,19 @@ function NewCollectionItemMenu({ collection }) {
     {
       icon: "insight",
       title: t`Question`,
-      link: Urls.newQuestion({ mode: "notebook", collectionId: collection.id }),
+      link: newQuestion({ mode: "notebook", collectionId: collection.id }),
       event: `${ANALYTICS_CONTEXT};New Item Menu;Question Click`,
     },
     {
       icon: "dashboard",
       title: t`Dashboard`,
-      link: Urls.newDashboard(collection.id),
+      link: newDashboard(collection.id),
       event: `${ANALYTICS_CONTEXT};New Item Menu;Dashboard Click`,
     },
     {
       icon: "folder",
       title: t`Collection`,
-      link: Urls.newCollection(collection.id),
+      link: newCollection(collection.id),
       event: `${ANALYTICS_CONTEXT};New Item Menu;Collection Click`,
     },
   ];
diff --git a/frontend/src/metabase/collections/containers/CollectionSidebar/index.ts b/frontend/src/metabase/collections/containers/CollectionSidebar/index.ts
new file mode 100644
index 00000000000..04f8db55b43
--- /dev/null
+++ b/frontend/src/metabase/collections/containers/CollectionSidebar/index.ts
@@ -0,0 +1 @@
+export { default } from "./CollectionSidebar";
diff --git a/frontend/src/metabase/components/Badge.jsx b/frontend/src/metabase/components/Badge.jsx
index c29c2f46c17..7f9ae0d0718 100644
--- a/frontend/src/metabase/components/Badge.jsx
+++ b/frontend/src/metabase/components/Badge.jsx
@@ -45,7 +45,7 @@ function Badge({
       activeColor={activeColor}
       {...props}
     >
-      {icon && <BadgeIcon {...getIconProps(icon)} hasMargin={!!children} />}
+      {icon && <BadgeIcon {...getIconProps(icon)} $hasMargin={!!children} />}
       {children && <span className="text-wrap">{children}</span>}
     </MaybeLink>
   );
diff --git a/frontend/src/metabase/components/Badge.styled.jsx b/frontend/src/metabase/components/Badge.styled.jsx
index 292f3128e2b..7d1e9bf1ace 100644
--- a/frontend/src/metabase/components/Badge.styled.jsx
+++ b/frontend/src/metabase/components/Badge.styled.jsx
@@ -5,6 +5,7 @@ import { css } from "@emotion/react";
 import { color } from "metabase/lib/colors";
 import Icon from "metabase/components/Icon";
 import Link from "metabase/core/components/Link";
+import { shouldForwardNonTransientProp } from "metabase/lib/styling/emotion";
 
 const propTypes = {
   to: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
@@ -33,6 +34,8 @@ export const MaybeLink = styled(RawMaybeLink)`
   }
 `;
 
-export const BadgeIcon = styled(Icon)`
-  margin-right: ${props => (props.hasMargin ? "5px" : 0)};
+export const BadgeIcon = styled(Icon, {
+  shouldForwardProp: shouldForwardNonTransientProp,
+})`
+  margin-right: ${props => (props.$hasMargin ? "5px" : 0)};
 `;
diff --git a/frontend/src/metabase/components/ButtonBar.jsx b/frontend/src/metabase/components/ButtonBar.jsx
index 64ed57a4e0c..a10147b60e5 100644
--- a/frontend/src/metabase/components/ButtonBar.jsx
+++ b/frontend/src/metabase/components/ButtonBar.jsx
@@ -1,5 +1,5 @@
 /* eslint-disable react/prop-types */
-import React from "react";
+import React, { Children } from "react";
 import {
   ButtonBarCenter,
   ButtonBarLeft,
@@ -12,6 +12,8 @@ function normalizeArray(array) {
     array = array.filter(a => a);
     if (array.length === 0) {
       array = null;
+    } else {
+      array = Children.toArray(array);
     }
   }
   return array;
diff --git a/frontend/src/metabase/components/CollectionLanding/CollectionLanding.jsx b/frontend/src/metabase/components/CollectionLanding/CollectionLanding.jsx
index b099bfa4041..4852a23e0f6 100644
--- a/frontend/src/metabase/components/CollectionLanding/CollectionLanding.jsx
+++ b/frontend/src/metabase/components/CollectionLanding/CollectionLanding.jsx
@@ -1,11 +1,11 @@
 /* eslint-disable react/prop-types */
 import React, { useState } from "react";
 
-import * as Urls from "metabase/lib/urls";
+import { extractCollectionId } from "metabase/lib/urls";
 
 import { PageWrapper } from "metabase/collections/components/Layout";
 import CollectionContent from "metabase/collections/containers/CollectionContent";
-import CollectionSidebar from "metabase/collections/containers/CollectionSidebar/CollectionSidebar";
+import CollectionSidebar from "metabase/collections/containers/CollectionSidebar";
 import { ContentBox } from "./CollectionLanding.styled";
 
 const CollectionLanding = ({ params: { slug }, children }) => {
@@ -16,7 +16,7 @@ const CollectionLanding = ({ params: { slug }, children }) => {
   const handleToggleMobileSidebar = () =>
     setShouldDisplayMobileSidebar(!shouldDisplayMobileSidebar);
 
-  const collectionId = Urls.extractCollectionId(slug);
+  const collectionId = extractCollectionId(slug);
   const isRoot = collectionId === "root";
 
   return (
diff --git a/frontend/src/metabase/components/Icon.tsx b/frontend/src/metabase/components/Icon.tsx
index 6fb2138e5cd..3e40b460db8 100644
--- a/frontend/src/metabase/components/Icon.tsx
+++ b/frontend/src/metabase/components/Icon.tsx
@@ -7,6 +7,7 @@ import Tooltip from "metabase/components/Tooltip";
 import { loadIcon } from "metabase/icon_paths";
 import { color as c } from "metabase/lib/colors";
 import { stripLayoutProps } from "metabase/lib/utils";
+import { shouldForwardNonTransientProp } from "metabase/lib/styling/emotion";
 
 const MISSING_ICON_NAME = "unknown";
 
@@ -28,7 +29,7 @@ export const IconWrapper = styled.div<IconWrapperProps>`
   // special cases for certain icons
   // Icon-share has a taller viewbox than most so to optically center
   // the icon we need to translate it upwards
-  "> .icon.icon-share": {
+  & > .icon.icon-share {
     transform: translateY(-2px);
   }
   ${hover};
@@ -115,7 +116,7 @@ class BaseIcon extends Component<IconProps> {
       );
     } else if (icon.svg) {
       return (
-        <svg
+        <StyledSVG
           {...svgProps}
           dangerouslySetInnerHTML={{ __html: icon.svg }}
           ref={forwardedRef}
@@ -123,9 +124,9 @@ class BaseIcon extends Component<IconProps> {
       );
     } else if (icon.path) {
       return (
-        <svg {...svgProps} ref={forwardedRef}>
+        <StyledSVG {...svgProps} ref={forwardedRef}>
           <path d={icon.path} />
-        </svg>
+        </StyledSVG>
       );
     } else {
       console.warn(`Icon "${name}" must have an img, svg, or path`);
@@ -134,6 +135,10 @@ class BaseIcon extends Component<IconProps> {
   }
 }
 
+const StyledSVG = styled("svg", {
+  shouldForwardProp: shouldForwardNonTransientProp,
+})``;
+
 const BaseIconWithRef = forwardRef<HTMLElement, IconProps>(
   function BaseIconWithRef(props, ref) {
     return <BaseIcon {...props} forwardedRef={ref} />;
diff --git a/frontend/src/metabase/components/MetadataInfo/TableInfoPopover/TableInfoPopover.tsx b/frontend/src/metabase/components/MetadataInfo/TableInfoPopover/TableInfoPopover.tsx
index 91083a71880..51f6688dc1e 100644
--- a/frontend/src/metabase/components/MetadataInfo/TableInfoPopover/TableInfoPopover.tsx
+++ b/frontend/src/metabase/components/MetadataInfo/TableInfoPopover/TableInfoPopover.tsx
@@ -1,5 +1,4 @@
-import React from "react";
-import PropTypes from "prop-types";
+import React, { ReactElement } from "react";
 import { hideAll } from "tippy.js";
 
 import TippyPopover, {
@@ -8,6 +7,7 @@ import TippyPopover, {
 import { isVirtualCardId } from "metabase/lib/saved-questions/saved-questions";
 
 import { WidthBoundTableInfo } from "./TableInfoPopover.styled";
+import PropTypes from "prop-types";
 
 export const POPOVER_DELAY: [number, number] = [500, 300];
 
@@ -18,7 +18,7 @@ interface TableSubset {
 
 const propTypes = {
   table: PropTypes.shape({
-    id: PropTypes.oneOf([PropTypes.number, PropTypes.string]),
+    id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
     description: PropTypes.string,
   }).isRequired,
   children: PropTypes.node,
@@ -26,10 +26,12 @@ const propTypes = {
   offset: PropTypes.arrayOf(PropTypes.number),
 };
 
-type Props = { table: TableSubset } & Pick<
-  ITippyPopoverProps,
-  "children" | "placement" | "offset" | "delay"
->;
+type Props = {
+  table: TableSubset;
+  children: ReactElement;
+  placement: string;
+  offset: number[];
+} & Pick<ITippyPopoverProps, "children" | "placement" | "offset" | "delay">;
 
 const className = "table-info-popover";
 
diff --git a/frontend/src/metabase/components/TokenField.jsx b/frontend/src/metabase/components/TokenField.jsx
index 8e36a715b9e..04891d52695 100644
--- a/frontend/src/metabase/components/TokenField.jsx
+++ b/frontend/src/metabase/components/TokenField.jsx
@@ -495,6 +495,7 @@ export default class TokenField extends Component {
       placeholder = null;
     }
 
+    const isControlledInput = !!this.onInputChange;
     const valuesList = (
       <ul
         className={cx(
@@ -534,9 +535,10 @@ export default class TokenField extends Component {
             // set size to be small enough that it fits in a parameter.
             size={10}
             placeholder={placeholder}
-            value={inputValue}
+            value={isControlledInput ? inputValue : undefined}
+            defaultValue={isControlledInput ? undefined : inputValue}
             onKeyDown={this.onInputKeyDown}
-            onChange={this.onInputChange}
+            onChange={isControlledInput ? this.onInputChange : undefined}
             onFocus={this.onInputFocus}
             onBlur={this.onInputBlur}
             onPaste={this.onInputPaste}
diff --git a/frontend/src/metabase/core/components/CheckBox/CheckBox.tsx b/frontend/src/metabase/core/components/CheckBox/CheckBox.tsx
index 9c05786cf93..f5913babf8e 100644
--- a/frontend/src/metabase/core/components/CheckBox/CheckBox.tsx
+++ b/frontend/src/metabase/core/components/CheckBox/CheckBox.tsx
@@ -54,15 +54,17 @@ const CheckBox = forwardRef(function Checkbox(
   }: CheckBoxProps,
   ref: Ref<HTMLLabelElement>,
 ): JSX.Element {
+  const isControlledCheckBoxInput = !!onChange;
   return (
     <CheckBoxRoot ref={ref} {...props}>
       <CheckBoxInput
         type="checkbox"
-        checked={checked}
+        checked={isControlledCheckBoxInput ? !!checked : undefined}
+        defaultChecked={isControlledCheckBoxInput ? undefined : !!checked}
         size={size}
         disabled={disabled}
         autoFocus={autoFocus}
-        onChange={onChange}
+        onChange={isControlledCheckBoxInput ? onChange : undefined}
         onFocus={onFocus}
         onBlur={onBlur}
       />
diff --git a/frontend/src/metabase/core/components/Link/Link.styled.tsx b/frontend/src/metabase/core/components/Link/Link.styled.tsx
index 7e681006d4e..5c468a87af5 100644
--- a/frontend/src/metabase/core/components/Link/Link.styled.tsx
+++ b/frontend/src/metabase/core/components/Link/Link.styled.tsx
@@ -2,8 +2,11 @@ import styled from "@emotion/styled";
 import { display, space, hover, color } from "styled-system";
 import { Link } from "react-router";
 import { color as colors } from "metabase/lib/colors";
+import { shouldForwardNonTransientProp } from "metabase/lib/styling/emotion";
 
-export const LinkRoot = styled(Link)`
+export const LinkRoot = styled(Link, {
+  shouldForwardProp: shouldForwardNonTransientProp,
+})`
   ${display};
   ${space};
   ${hover};
diff --git a/frontend/src/metabase/lib/styling/emotion.ts b/frontend/src/metabase/lib/styling/emotion.ts
new file mode 100644
index 00000000000..9fd3588ba7b
--- /dev/null
+++ b/frontend/src/metabase/lib/styling/emotion.ts
@@ -0,0 +1,5 @@
+import isPropValid from "@emotion/is-prop-valid";
+
+export function shouldForwardNonTransientProp(propName: string): boolean {
+  return !propName.startsWith("$") && isPropValid(propName);
+}
diff --git a/frontend/src/metabase/parameters/components/ParameterValueWidget.jsx b/frontend/src/metabase/parameters/components/ParameterValueWidget.jsx
index bb84372d66e..b60831c896e 100644
--- a/frontend/src/metabase/parameters/components/ParameterValueWidget.jsx
+++ b/frontend/src/metabase/parameters/components/ParameterValueWidget.jsx
@@ -181,7 +181,7 @@ export default class ParameterValueWidget extends Component {
               isFullscreen={isFullscreen}
               hasValue={hasValue}
               noReset={noReset}
-              noPopover={noPopover}
+              noPopover={!!noPopover}
               isFocused={isFocused}
               setValue={setValue}
             />
@@ -216,7 +216,7 @@ export default class ParameterValueWidget extends Component {
                   isFullscreen={isFullscreen}
                   hasValue={hasValue}
                   noReset={noReset}
-                  noPopover={noPopover}
+                  noPopover={!!noPopover}
                   isFocused={isFocused}
                   setValue={setValue}
                 />
diff --git a/frontend/src/metabase/query_builder/components/view/sidebars/SummarizeSidebar/AddAggregationButton/AddAggregationButton.jsx b/frontend/src/metabase/query_builder/components/view/sidebars/SummarizeSidebar/AddAggregationButton/AddAggregationButton.jsx
index 9b3e36078c9..46ca1b6fbd6 100644
--- a/frontend/src/metabase/query_builder/components/view/sidebars/SummarizeSidebar/AddAggregationButton/AddAggregationButton.jsx
+++ b/frontend/src/metabase/query_builder/components/view/sidebars/SummarizeSidebar/AddAggregationButton/AddAggregationButton.jsx
@@ -17,7 +17,7 @@ const propTypes = {
 
 const LABEL = t`Add a metric`;
 
-export const AddAggregationButton = ({ query, shouldShowLabel }) => {
+export const AddAggregationButton = ({ query, shouldShowLabel = false }) => {
   return (
     <PopoverWithTrigger
       triggerElement={
diff --git a/frontend/src/metabase/questions/components/CollectionBadge.jsx b/frontend/src/metabase/questions/components/CollectionBadge.jsx
index 9576363e161..39d7c3cef6a 100644
--- a/frontend/src/metabase/questions/components/CollectionBadge.jsx
+++ b/frontend/src/metabase/questions/components/CollectionBadge.jsx
@@ -8,7 +8,7 @@ import { PLUGIN_COLLECTIONS } from "metabase/plugins";
 
 const propTypes = {
   collection: PropTypes.object,
-  analyticsContext: PropTypes.string.isRequired,
+  analyticsContext: PropTypes.string,
   className: PropTypes.string,
 };
 
@@ -25,6 +25,7 @@ function CollectionBadge({ collection, analyticsContext, className }) {
   if (!collection) {
     return null;
   }
+
   const isRegular = PLUGIN_COLLECTIONS.isRegularCollection(collection);
   const icon = {
     ...collection.getIcon(),
-- 
GitLab