From 1ba18765d06416ee00ba7d2a4b6fadee50962382 Mon Sep 17 00:00:00 2001
From: Oisin Coveney <oisin@metabase.com>
Date: Tue, 26 Mar 2024 14:20:58 +0200
Subject: [PATCH] Convert `link.module.css` to CSS modules (#40584)

---
 ...or-question-with-filter-causes-error.cy.spec.js |  2 +-
 .../ImpersonationModal/ImpersonationModalView.tsx  |  3 ++-
 .../ImpersonationWarning/ImpersonationWarning.tsx  |  3 ++-
 .../components/DatabaseList/DatabaseList.jsx       |  4 ++--
 .../metabase/admin/people/components/AddRow.jsx    |  2 +-
 .../admin/people/components/GroupsListing.jsx      |  7 ++-----
 .../admin/people/containers/UserSuccessModal.jsx   |  6 ++++--
 .../CollectionPermissionsModal.jsx                 |  2 +-
 .../components/UploadSettings/UploadSettings.tsx   |  3 ++-
 .../PublicLinksListing/PublicLinksListing.jsx      |  2 +-
 .../metabase/admin/tasks/containers/JobInfoApp.jsx |  2 +-
 .../metabase/admin/tasks/containers/TasksApp.jsx   |  2 +-
 .../components/CollectionCopyEntityModal.jsx       |  2 +-
 .../ChannelSetupMessage/ChannelSetupMessage.jsx    |  2 +-
 .../components/ErrorDetails/ErrorDetails.tsx       |  4 +++-
 .../components/LeftNavPane/LeftNavPane.jsx         |  3 +--
 .../components/PasswordReveal/PasswordReveal.jsx   |  2 +-
 .../components/Triggerable/Triggerable.jsx         |  3 ++-
 .../core/components/ExternalLink/ExternalLink.tsx  |  3 ++-
 frontend/src/metabase/css/core/link.module.css     | 12 +++++-------
 .../dashboard/containers/AutomaticDashboardApp.jsx |  5 ++++-
 .../DatabaseClientIdDescription.tsx                |  3 ++-
 frontend/src/metabase/lib/formatting/url.tsx       |  5 ++++-
 frontend/src/metabase/lib/formatting/value.tsx     |  7 ++++++-
 .../nav/components/PaymentBanner/PaymentBanner.tsx |  5 +++--
 .../components/QuestionLineage/QuestionLineage.tsx |  3 ++-
 .../components/AlertListPopoverContent.jsx         |  6 +++---
 .../DataSelectorTablePicker.tsx                    |  4 +++-
 .../components/DataSelector/TriggerComponents.tsx  | 14 ++++++++++----
 .../query_builder/components/ExpandableString.jsx  |  7 +++++--
 .../VisualizationError/VisualizationError.tsx      |  2 +-
 .../components/VisualizationResult.jsx             |  2 +-
 .../template_tags/TagEditorHelp/TagEditorHelp.tsx  |  4 +++-
 .../TagEditorParamParts/FilterWidgetTypeSelect.tsx |  3 ++-
 .../setup/components/SetupHelp/SetupHelp.tsx       |  3 ++-
 .../AddEditSidebar/CaveatMessage/CaveatMessage.jsx |  3 ++-
 .../sharing/components/NewPulseSidebar.tsx         | 12 ++++++++++--
 frontend/test/metabase/lib/formatting.unit.spec.js |  8 ++++----
 38 files changed, 103 insertions(+), 62 deletions(-)

diff --git a/e2e/test/scenarios/dashboard-cards/reproductions/15993-click-behavior-question-with-filter-causes-error.cy.spec.js b/e2e/test/scenarios/dashboard-cards/reproductions/15993-click-behavior-question-with-filter-causes-error.cy.spec.js
index 7774f43d0f4..d2d3ba2e983 100644
--- a/e2e/test/scenarios/dashboard-cards/reproductions/15993-click-behavior-question-with-filter-causes-error.cy.spec.js
+++ b/e2e/test/scenarios/dashboard-cards/reproductions/15993-click-behavior-question-with-filter-causes-error.cy.spec.js
@@ -39,7 +39,7 @@ describe("issue 15993", () => {
     });
 
     // Drill-through
-    cy.findAllByTestId("cell-data").get(".link").contains("0").realClick();
+    cy.findAllByTestId("cell-data").contains("0").realClick();
 
     // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
     cy.contains("117.03").should("not.exist"); // Total for the order in which quantity wasn't 0
diff --git a/enterprise/frontend/src/metabase-enterprise/advanced_permissions/components/ImpersonationModal/ImpersonationModalView.tsx b/enterprise/frontend/src/metabase-enterprise/advanced_permissions/components/ImpersonationModal/ImpersonationModalView.tsx
index a341d79b747..df2efce4bf9 100644
--- a/enterprise/frontend/src/metabase-enterprise/advanced_permissions/components/ImpersonationModal/ImpersonationModalView.tsx
+++ b/enterprise/frontend/src/metabase-enterprise/advanced_permissions/components/ImpersonationModal/ImpersonationModalView.tsx
@@ -10,6 +10,7 @@ import FormFooter from "metabase/core/components/FormFooter";
 import FormSelect from "metabase/core/components/FormSelect";
 import FormSubmitButton from "metabase/core/components/FormSubmitButton";
 import Link from "metabase/core/components/Link/Link";
+import CS from "metabase/css/core/index.css";
 import { Form, FormProvider } from "metabase/forms";
 import * as Errors from "metabase/lib/errors";
 import MetabaseSettings from "metabase/lib/settings";
@@ -93,7 +94,7 @@ export const ImpersonationModalView = ({
       <ImpersonationDescription>
         {modalMessage}{" "}
         <ExternalLink
-          className="link"
+          className={CS.link}
           // eslint-disable-next-line no-unconditional-metabase-links-render -- Admin settings
           href={MetabaseSettings.docsUrl("permissions/data")}
         >{t`Learn More`}</ExternalLink>
diff --git a/enterprise/frontend/src/metabase-enterprise/advanced_permissions/components/ImpersonationWarning/ImpersonationWarning.tsx b/enterprise/frontend/src/metabase-enterprise/advanced_permissions/components/ImpersonationWarning/ImpersonationWarning.tsx
index 1fedad4415b..3bc90ddc01d 100644
--- a/enterprise/frontend/src/metabase-enterprise/advanced_permissions/components/ImpersonationWarning/ImpersonationWarning.tsx
+++ b/enterprise/frontend/src/metabase-enterprise/advanced_permissions/components/ImpersonationWarning/ImpersonationWarning.tsx
@@ -2,6 +2,7 @@ import { t, jt } from "ttag";
 
 import { BoldCode } from "metabase/components/Code";
 import Link from "metabase/core/components/Link";
+import CS from "metabase/css/core/index.css";
 import * as Urls from "metabase/lib/urls";
 import { isEmpty } from "metabase/lib/validate";
 import type Database from "metabase-lib/v1/metadata/Database";
@@ -61,7 +62,7 @@ export const ImpersonationWarning = ({
     <ImpersonationAlert icon="warning" variant="warning">
       {isEmpty(databaseUser) ? emptyText : warningText}{" "}
       <Link
-        className="link"
+        className={CS.link}
         to={Urls.editDatabase(database.id) + (databaseUser ? "#user" : "")}
       >{t`Edit settings`}</Link>
     </ImpersonationAlert>
diff --git a/frontend/src/metabase/admin/databases/components/DatabaseList/DatabaseList.jsx b/frontend/src/metabase/admin/databases/components/DatabaseList/DatabaseList.jsx
index 3eb2a55d1ba..d4d693150fc 100644
--- a/frontend/src/metabase/admin/databases/components/DatabaseList/DatabaseList.jsx
+++ b/frontend/src/metabase/admin/databases/components/DatabaseList/DatabaseList.jsx
@@ -120,7 +120,7 @@ export default class DatabaseList extends Component {
                             )}
                             <Link
                               to={"/admin/databases/" + database.id}
-                              className="text-bold link"
+                              className={cx("text-bold", CS.link)}
                             >
                               {database.name}
                             </Link>
@@ -153,7 +153,7 @@ export default class DatabaseList extends Component {
                 })}
               >
                 {isAddingSampleDatabase ? (
-                  <span className="text-light no-decoration">
+                  <span className={cx("text-light", CS.noDecoration)}>
                     {t`Restoring the sample database...`}
                   </span>
                 ) : (
diff --git a/frontend/src/metabase/admin/people/components/AddRow.jsx b/frontend/src/metabase/admin/people/components/AddRow.jsx
index 7cb53103de3..5076d658c48 100644
--- a/frontend/src/metabase/admin/people/components/AddRow.jsx
+++ b/frontend/src/metabase/admin/people/components/AddRow.jsx
@@ -44,7 +44,7 @@ export const AddRow = forwardRef(function AddRow(
         onKeyDown={onKeyDown}
         onChange={onChange}
       />
-      <span className="link no-decoration cursor-pointer" onClick={onCancel}>
+      <span className={CS.link} onClick={onCancel}>
         {t`Cancel`}
       </span>
       <button
diff --git a/frontend/src/metabase/admin/people/components/GroupsListing.jsx b/frontend/src/metabase/admin/people/components/GroupsListing.jsx
index 16ee454937a..11cc11c0e72 100644
--- a/frontend/src/metabase/admin/people/components/GroupsListing.jsx
+++ b/frontend/src/metabase/admin/people/components/GroupsListing.jsx
@@ -164,10 +164,7 @@ function EditingGroupRow({
       </td>
       <td />
       <td className="text-right">
-        <span
-          className="link no-decoration cursor-pointer"
-          onClick={onCancelClicked}
-        >{t`Cancel`}</span>
+        <span className={CS.link} onClick={onCancelClicked}>{t`Cancel`}</span>
         <button
           className={cx(ButtonsS.Button, CS.ml2, {
             [ButtonsS.ButtonPrimary]: textIsValid && textHasChanged,
@@ -213,7 +210,7 @@ function GroupRow({
       <td>
         <Link
           to={"/admin/people/groups/" + group.id}
-          className={cx("link", CS.noDecoration, CS.flex, CS.alignCenter)}
+          className={cx(CS.link, CS.flex, CS.alignCenter)}
         >
           <span className="text-white">
             <UserAvatar
diff --git a/frontend/src/metabase/admin/people/containers/UserSuccessModal.jsx b/frontend/src/metabase/admin/people/containers/UserSuccessModal.jsx
index bbe6b8769be..ee65d603ba7 100644
--- a/frontend/src/metabase/admin/people/containers/UserSuccessModal.jsx
+++ b/frontend/src/metabase/admin/people/containers/UserSuccessModal.jsx
@@ -1,4 +1,5 @@
 /* eslint-disable react/prop-types */
+import cx from "classnames";
 import { Component } from "react";
 import { connect } from "react-redux";
 import { push } from "react-router-redux";
@@ -9,6 +10,7 @@ import ModalContent from "metabase/components/ModalContent";
 import PasswordReveal from "metabase/components/PasswordReveal";
 import Button from "metabase/core/components/Button";
 import Link from "metabase/core/components/Link";
+import CS from "metabase/css/core/index.css";
 import User from "metabase/entities/users";
 import MetabaseSettings from "metabase/lib/settings";
 
@@ -50,7 +52,7 @@ const EmailSuccess = ({ user, isSsoEnabled }) => {
       )} with instructions to log in. If this user is unable to authenticate then you can ${(
         <Link
           to={`/admin/people/${user.id}/reset`}
-          className="link"
+          className={CS.link}
         >{t`reset their password.`}</Link>
       )}`}</div>
     );
@@ -76,7 +78,7 @@ const PasswordSuccess = ({ user, temporaryPassword }) => (
       className="pt4 text-centered"
     >
       {jt`If you want to be able to send email invites, just go to the ${(
-        <Link to="/admin/settings/email" className="link text-bold">
+        <Link to="/admin/settings/email" className={cx(CS.link, "text-bold")}>
           Email Settings
         </Link>
       )} page.`}
diff --git a/frontend/src/metabase/admin/permissions/components/CollectionPermissionsModal/CollectionPermissionsModal.jsx b/frontend/src/metabase/admin/permissions/components/CollectionPermissionsModal/CollectionPermissionsModal.jsx
index 70aa92a8a6b..58720d32318 100644
--- a/frontend/src/metabase/admin/permissions/components/CollectionPermissionsModal/CollectionPermissionsModal.jsx
+++ b/frontend/src/metabase/admin/permissions/components/CollectionPermissionsModal/CollectionPermissionsModal.jsx
@@ -130,7 +130,7 @@ const CollectionPermissionsModal = ({
           : [
               <Link
                 key="all-permissions"
-                className="link"
+                className={CS.link}
                 to="/admin/permissions/collections"
               >
                 {t`See all collection permissions`}
diff --git a/frontend/src/metabase/admin/settings/components/UploadSettings/UploadSettings.tsx b/frontend/src/metabase/admin/settings/components/UploadSettings/UploadSettings.tsx
index 9d3f91c7fd8..a3db423291d 100644
--- a/frontend/src/metabase/admin/settings/components/UploadSettings/UploadSettings.tsx
+++ b/frontend/src/metabase/admin/settings/components/UploadSettings/UploadSettings.tsx
@@ -12,6 +12,7 @@ import Input from "metabase/core/components/Input";
 import Link from "metabase/core/components/Link";
 import type { SelectChangeEvent } from "metabase/core/components/Select";
 import Select from "metabase/core/components/Select";
+import CS from "metabase/css/core/index.css";
 import Databases from "metabase/entities/databases";
 import Schemas from "metabase/entities/schemas";
 import { useDispatch } from "metabase/lib/redux";
@@ -71,7 +72,7 @@ const Header = () => (
       display_name: t`Allow people to upload data to Collections`,
       description: jt`People will be able to upload CSV files that will be stored in the ${(
         <Link
-          className="link"
+          className={CS.link}
           key="db-link"
           to="/admin/databases"
         >{t`database`}</Link>
diff --git a/frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing/PublicLinksListing.jsx b/frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing/PublicLinksListing.jsx
index 416b14b6437..39a0a9a762f 100644
--- a/frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing/PublicLinksListing.jsx
+++ b/frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing/PublicLinksListing.jsx
@@ -100,7 +100,7 @@ class PublicLinksListing extends Component {
                         <ExternalLink
                           href={getPublicUrl(link)}
                           onClick={() => this.trackEvent("Public Link Clicked")}
-                          className="link text-wrap"
+                          className={cx(CS.link, "text-wrap")}
                         >
                           {getPublicUrl(link)}
                         </ExternalLink>
diff --git a/frontend/src/metabase/admin/tasks/containers/JobInfoApp.jsx b/frontend/src/metabase/admin/tasks/containers/JobInfoApp.jsx
index f7db719f188..a1410cfc27b 100644
--- a/frontend/src/metabase/admin/tasks/containers/JobInfoApp.jsx
+++ b/frontend/src/metabase/admin/tasks/containers/JobInfoApp.jsx
@@ -50,7 +50,7 @@ const renderJobsTable = jobs => {
                 <td>{job.durable}</td>
                 <td>
                   <Link
-                    className="link"
+                    className={CS.link}
                     to={`/admin/troubleshooting/jobs/${job.key}`}
                   >
                     {t`View triggers`}
diff --git a/frontend/src/metabase/admin/tasks/containers/TasksApp.jsx b/frontend/src/metabase/admin/tasks/containers/TasksApp.jsx
index e092749b3a2..abcfb72fd64 100644
--- a/frontend/src/metabase/admin/tasks/containers/TasksApp.jsx
+++ b/frontend/src/metabase/admin/tasks/containers/TasksApp.jsx
@@ -91,7 +91,7 @@ class TasksAppInner extends Component {
                   <td>{task.duration}</td>
                   <td>
                     <Link
-                      className="link text-bold"
+                      className={cx(CS.link, "text-bold")}
                       to={`/admin/troubleshooting/tasks/${task.id}`}
                     >{t`View`}</Link>
                   </td>
diff --git a/frontend/src/metabase/collections/components/CollectionCopyEntityModal.jsx b/frontend/src/metabase/collections/components/CollectionCopyEntityModal.jsx
index e200b0fef43..0dcd86b4ea9 100644
--- a/frontend/src/metabase/collections/components/CollectionCopyEntityModal.jsx
+++ b/frontend/src/metabase/collections/components/CollectionCopyEntityModal.jsx
@@ -59,7 +59,7 @@ function CollectionCopyEntityModal({
         {newEntityObject.uncopied?.length > 0
           ? t`Duplicated ${entityObject.model}, but couldn't duplicate some questions`
           : t`Duplicated ${entityObject.model}`}
-        <Link className="link text-bold ml1" to={newEntityUrl}>
+        <Link className={cx(CS.link, "text-bold ml1")} to={newEntityUrl}>
           {t`See it`}
         </Link>
       </div>,
diff --git a/frontend/src/metabase/components/ChannelSetupMessage/ChannelSetupMessage.jsx b/frontend/src/metabase/components/ChannelSetupMessage/ChannelSetupMessage.jsx
index bc83fe8585b..6a2e779f8af 100644
--- a/frontend/src/metabase/components/ChannelSetupMessage/ChannelSetupMessage.jsx
+++ b/frontend/src/metabase/components/ChannelSetupMessage/ChannelSetupMessage.jsx
@@ -42,7 +42,7 @@ export default class ChannelSetupMessage extends Component {
       content = (
         <div className="mb1">
           <h4 className="text-medium">{t`Your admin's email address`}:</h4>
-          <a className="h2 link no-decoration" href={"mailto:" + adminEmail}>
+          <a className={cx("h2", CS.link)} href={"mailto:" + adminEmail}>
             {adminEmail}
           </a>
         </div>
diff --git a/frontend/src/metabase/components/ErrorDetails/ErrorDetails.tsx b/frontend/src/metabase/components/ErrorDetails/ErrorDetails.tsx
index 625a7618af4..56f227894c3 100644
--- a/frontend/src/metabase/components/ErrorDetails/ErrorDetails.tsx
+++ b/frontend/src/metabase/components/ErrorDetails/ErrorDetails.tsx
@@ -2,6 +2,8 @@ import cx from "classnames";
 import { useState } from "react";
 import { t } from "ttag";
 
+import CS from "metabase/css/core/index.css";
+
 import { ErrorBox } from "./ErrorBox";
 import type { ErrorDetailsProps } from "./types";
 
@@ -24,7 +26,7 @@ export default function ErrorDetails({
   return (
     <div className={className}>
       <div className={centered ? "text-centered" : "text-left"}>
-        <a onClick={toggleShowError} className="link cursor-pointer">
+        <a onClick={toggleShowError} className={cx(CS.link)}>
           {showError ? t`Hide error details` : t`Show error details`}
         </a>
       </div>
diff --git a/frontend/src/metabase/components/LeftNavPane/LeftNavPane.jsx b/frontend/src/metabase/components/LeftNavPane/LeftNavPane.jsx
index 12565f9df6c..e777f0968e6 100644
--- a/frontend/src/metabase/components/LeftNavPane/LeftNavPane.jsx
+++ b/frontend/src/metabase/components/LeftNavPane/LeftNavPane.jsx
@@ -54,10 +54,9 @@ export function LeftNavPaneItemBack({ path }) {
           AdminS.AdminListItem,
           CS.flex,
           CS.alignCenter,
-          CS.noDecoration,
           CS.textBold,
           CS.justifyBetween,
-          "link",
+          CS.link,
         )}
       >
         &lt; {t`Back`}
diff --git a/frontend/src/metabase/components/PasswordReveal/PasswordReveal.jsx b/frontend/src/metabase/components/PasswordReveal/PasswordReveal.jsx
index b2c7a7faa97..4f8a4bc8078 100644
--- a/frontend/src/metabase/components/PasswordReveal/PasswordReveal.jsx
+++ b/frontend/src/metabase/components/PasswordReveal/PasswordReveal.jsx
@@ -76,7 +76,7 @@ export default class PasswordReveal extends Component {
 
         <div className={cx(CS.mlAuto, CS.flex, CS.alignCenter)}>
           <a
-            className={cx("link", CS.textBold, CS.mr2)}
+            className={cx(CS.link, CS.textBold, CS.mr2)}
             onClick={() => this.setState({ visible: !visible })}
           >
             {visible ? t`Hide` : t`Show`}
diff --git a/frontend/src/metabase/components/Triggerable/Triggerable.jsx b/frontend/src/metabase/components/Triggerable/Triggerable.jsx
index d87290491ed..99c677da067 100644
--- a/frontend/src/metabase/components/Triggerable/Triggerable.jsx
+++ b/frontend/src/metabase/components/Triggerable/Triggerable.jsx
@@ -4,6 +4,7 @@ import cx from "classnames";
 import { createRef, cloneElement, Children, Component } from "react";
 
 import Tooltip from "metabase/core/components/Tooltip";
+import CS from "metabase/css/core/index.css";
 import { isObscured } from "metabase/lib/dom";
 
 const Trigger = styled.a``;
@@ -153,7 +154,7 @@ const Triggerable = ComposedComponent =>
               triggerClasses,
               isOpen && triggerClassesOpen,
               !isOpen && triggerClassesClose,
-              "no-decoration",
+              CS.noDecoration,
               {
                 "cursor-default": this.props.disabled,
               },
diff --git a/frontend/src/metabase/core/components/ExternalLink/ExternalLink.tsx b/frontend/src/metabase/core/components/ExternalLink/ExternalLink.tsx
index 608438a43a2..90813728595 100644
--- a/frontend/src/metabase/core/components/ExternalLink/ExternalLink.tsx
+++ b/frontend/src/metabase/core/components/ExternalLink/ExternalLink.tsx
@@ -1,6 +1,7 @@
 import type { AnchorHTMLAttributes, ReactNode, Ref } from "react";
 import { forwardRef } from "react";
 
+import CS from "metabase/css/core/index.css";
 import { getUrlTarget } from "metabase/lib/dom";
 
 import { LinkRoot } from "./ExternalLink.styled";
@@ -20,7 +21,7 @@ const ExternalLink = forwardRef(function ExternalLink(
     <LinkRoot
       ref={ref}
       href={href}
-      className={className || "link"}
+      className={className || CS.link}
       target={target}
       // prevent malicious pages from navigating us away
       rel="noopener noreferrer"
diff --git a/frontend/src/metabase/css/core/link.module.css b/frontend/src/metabase/css/core/link.module.css
index 03bec2f0e78..3495c97d760 100644
--- a/frontend/src/metabase/css/core/link.module.css
+++ b/frontend/src/metabase/css/core/link.module.css
@@ -1,27 +1,25 @@
-:global(.no-decoration),
-.no-decoration,
 .noDecoration {
   text-decoration: none;
 }
 
-:global(.link) {
+.link {
   cursor: pointer;
   text-decoration: none;
   color: var(--color-brand);
 }
 
-:global(.link:hover) {
+.link:hover {
   text-decoration: underline;
 }
 
-:global(.link:focus) {
+.link:focus {
   outline: 2px solid var(--color-focus);
 }
 
-:global(.link:focus:not(:focus-visible)) {
+.link:focus:not(:focus-visible) {
   outline: none;
 }
 
-:global(.link--wrappable) {
+.linkWrappable {
   word-break: break-all;
 }
diff --git a/frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx b/frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx
index 2a2457dcf85..6c732e46395 100644
--- a/frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx
+++ b/frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx
@@ -79,7 +79,10 @@ class AutomaticDashboardAppInner extends Component {
     triggerToast(
       <div className={cx(CS.flex, CS.alignCenter)}>
         {t`Your dashboard was saved`}
-        <Link className="link text-bold ml1" to={Urls.dashboard(newDashboard)}>
+        <Link
+          className={cx(CS.link, "text-bold ml1")}
+          to={Urls.dashboard(newDashboard)}
+        >
           {t`See it`}
         </Link>
       </div>,
diff --git a/frontend/src/metabase/databases/components/DatabaseClientIdDescription/DatabaseClientIdDescription.tsx b/frontend/src/metabase/databases/components/DatabaseClientIdDescription/DatabaseClientIdDescription.tsx
index 6948f82bbaa..71b2f23cada 100644
--- a/frontend/src/metabase/databases/components/DatabaseClientIdDescription/DatabaseClientIdDescription.tsx
+++ b/frontend/src/metabase/databases/components/DatabaseClientIdDescription/DatabaseClientIdDescription.tsx
@@ -2,6 +2,7 @@ import { useFormikContext } from "formik";
 import { jt, t } from "ttag";
 
 import ExternalLink from "metabase/core/components/ExternalLink";
+import CS from "metabase/css/core/index.css";
 import type { DatabaseData } from "metabase-types/api";
 
 const CREDENTIAL_URLS: Record<string, string> = {
@@ -24,7 +25,7 @@ const DatabaseClientIdDescription = (): JSX.Element | null => {
   return (
     <span>
       {jt`${(
-        <ExternalLink className="link" href={projectUrl.href}>
+        <ExternalLink className={CS.link} href={projectUrl.href}>
           {t`Click here`}
         </ExternalLink>
       )} to generate a Client ID and Client Secret for your project.`}{" "}
diff --git a/frontend/src/metabase/lib/formatting/url.tsx b/frontend/src/metabase/lib/formatting/url.tsx
index 4d9ad10b937..cf820fb2a86 100644
--- a/frontend/src/metabase/lib/formatting/url.tsx
+++ b/frontend/src/metabase/lib/formatting/url.tsx
@@ -1,4 +1,7 @@
+import cx from "classnames";
+
 import ExternalLink from "metabase/core/components/ExternalLink";
+import CS from "metabase/css/core/index.css";
 import { getDataFromClicked } from "metabase-lib/v1/parameters/utils/click-behavior";
 import { isURL } from "metabase-lib/v1/types/utils/isa";
 
@@ -35,7 +38,7 @@ export function formatUrl(value: string, options: OptionsType = {}) {
   if (jsx && rich && url) {
     const text = getLinkText(value, options);
     return (
-      <ExternalLink className="link link--wrappable" href={url}>
+      <ExternalLink className={cx(CS.link, CS.linkWrappable)} href={url}>
         {text}
       </ExternalLink>
     );
diff --git a/frontend/src/metabase/lib/formatting/value.tsx b/frontend/src/metabase/lib/formatting/value.tsx
index 86f39280aba..0584672d568 100644
--- a/frontend/src/metabase/lib/formatting/value.tsx
+++ b/frontend/src/metabase/lib/formatting/value.tsx
@@ -1,3 +1,4 @@
+import cx from "classnames";
 import type { Moment } from "moment-timezone"; // eslint-disable-line no-restricted-imports -- deprecated usage
 import moment from "moment-timezone"; // eslint-disable-line no-restricted-imports -- deprecated usage
 import Mustache from "mustache";
@@ -5,6 +6,7 @@ import type * as React from "react";
 import ReactMarkdown from "react-markdown";
 
 import ExternalLink from "metabase/core/components/ExternalLink";
+import CS from "metabase/css/core/index.css";
 import { NULL_DISPLAY_VALUE, NULL_NUMERIC_VALUE } from "metabase/lib/constants";
 import { renderLinkTextForClick } from "metabase/lib/formatting/link";
 import {
@@ -155,7 +157,10 @@ export function formatValueRaw(
     // Style this like a link if we're in a jsx context.
     // It's not actually a link since we handle the click differently for dashboard and question targets.
     return (
-      <div className="link link--wrappable">
+      <div
+        data-testid="link-formatted-text"
+        className={cx(CS.link, CS.linkWrappable)}
+      >
         {formatValueRaw(value, { ...options, jsx: false })}
       </div>
     );
diff --git a/frontend/src/metabase/nav/components/PaymentBanner/PaymentBanner.tsx b/frontend/src/metabase/nav/components/PaymentBanner/PaymentBanner.tsx
index 9ae99d6b790..13bf516c5a5 100644
--- a/frontend/src/metabase/nav/components/PaymentBanner/PaymentBanner.tsx
+++ b/frontend/src/metabase/nav/components/PaymentBanner/PaymentBanner.tsx
@@ -2,6 +2,7 @@ import { jt, t } from "ttag";
 
 import Banner from "metabase/components/Banner";
 import ExternalLink from "metabase/core/components/ExternalLink";
+import CS from "metabase/css/core/index.css";
 import MetabaseSettings from "metabase/lib/settings";
 import type { TokenStatus } from "metabase-types/api";
 
@@ -17,7 +18,7 @@ export const PaymentBanner = ({ isAdmin, tokenStatus }: PaymentBannerProps) => {
         {jt`⚠️ We couldn't process payment for your account. Please ${(
           <ExternalLink
             key="payment-past-due"
-            className="link"
+            className={CS.link}
             href={MetabaseSettings.storeUrl()}
           >
             {t`review your payment settings`}
@@ -31,7 +32,7 @@ export const PaymentBanner = ({ isAdmin, tokenStatus }: PaymentBannerProps) => {
         {jt`⚠️ Pro features won’t work right now due to lack of payment. ${(
           <ExternalLink
             key="payment-unpaid"
-            className="link"
+            className={CS.link}
             href={MetabaseSettings.storeUrl()}
           >
             {t`Review your payment settings`}
diff --git a/frontend/src/metabase/nav/components/QuestionLineage/QuestionLineage.tsx b/frontend/src/metabase/nav/components/QuestionLineage/QuestionLineage.tsx
index 0dad2c89a8b..afc3445313a 100644
--- a/frontend/src/metabase/nav/components/QuestionLineage/QuestionLineage.tsx
+++ b/frontend/src/metabase/nav/components/QuestionLineage/QuestionLineage.tsx
@@ -2,6 +2,7 @@ import { t } from "ttag";
 
 import Badge from "metabase/components/Badge";
 import Link from "metabase/core/components/Link/Link";
+import CS from "metabase/css/core/index.css";
 import type { IconName } from "metabase/ui";
 import type Question from "metabase-lib/v1/Question";
 import * as ML_Urls from "metabase-lib/v1/urls";
@@ -24,7 +25,7 @@ const QuestionLineage = ({
   return (
     <Badge icon={icon} isSingleLine>
       {t`Started from`}{" "}
-      <Link className="link" to={ML_Urls.getUrl(originalQuestion)}>
+      <Link className={CS.link} to={ML_Urls.getUrl(originalQuestion)}>
         {originalQuestion.displayName()}
       </Link>
     </Badge>
diff --git a/frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx b/frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx
index 3d0179597e5..c768d90968a 100644
--- a/frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx
+++ b/frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx
@@ -87,7 +87,7 @@ class AlertListPopoverContent extends Component {
           <div className={cx(CS.borderTop, CS.p2, "bg-light-blue")}>
             <a
               className={cx(
-                "link",
+                CS.link,
                 CS.flex,
                 CS.alignCenter,
                 CS.textBold,
@@ -186,11 +186,11 @@ class AlertListItemInner extends Component {
               }}
             >
               {(isAdmin || isCurrentUser) && (
-                <a className="link" onClick={this.onEdit}>{jt`Edit`}</a>
+                <a className={CS.link} onClick={this.onEdit}>{jt`Edit`}</a>
               )}
               {!isAdmin && !unsubscribingProgress && (
                 <a
-                  className="link ml2"
+                  className={cx(CS.link, "ml2")}
                   onClick={this.onUnsubscribe}
                 >{jt`Unsubscribe`}</a>
               )}
diff --git a/frontend/src/metabase/query_builder/components/DataSelector/DataSelectorTablePicker/DataSelectorTablePicker.tsx b/frontend/src/metabase/query_builder/components/DataSelector/DataSelectorTablePicker/DataSelectorTablePicker.tsx
index b3a6b68bde4..d0afdfa88be 100644
--- a/frontend/src/metabase/query_builder/components/DataSelector/DataSelectorTablePicker/DataSelectorTablePicker.tsx
+++ b/frontend/src/metabase/query_builder/components/DataSelector/DataSelectorTablePicker/DataSelectorTablePicker.tsx
@@ -1,3 +1,4 @@
+import cx from "classnames";
 import type { ReactNode } from "react";
 import { t } from "ttag";
 
@@ -7,6 +8,7 @@ import {
 } from "metabase/components/MetadataInfo/TableInfoIcon/TableInfoIcon";
 import AccordionList from "metabase/core/components/AccordionList";
 import ExternalLink from "metabase/core/components/ExternalLink";
+import CS from "metabase/css/core/index.css";
 import { color } from "metabase/lib/colors";
 import MetabaseSettings from "metabase/lib/settings";
 import { isSyncCompleted } from "metabase/lib/syncing";
@@ -162,7 +164,7 @@ const LinkToDocsOnReferencingSavedQuestionsInQueries = () => (
         "questions/native-editor/referencing-saved-questions-in-queries",
       )}
       target="_blank"
-      className="block link"
+      className={cx("block", CS.link)}
     >
       {t`Learn more about nested queries`}
     </ExternalLink>
diff --git a/frontend/src/metabase/query_builder/components/DataSelector/TriggerComponents.tsx b/frontend/src/metabase/query_builder/components/DataSelector/TriggerComponents.tsx
index 166eef94a38..b725f9222f3 100644
--- a/frontend/src/metabase/query_builder/components/DataSelector/TriggerComponents.tsx
+++ b/frontend/src/metabase/query_builder/components/DataSelector/TriggerComponents.tsx
@@ -1,7 +1,9 @@
+import cx from "classnames";
 import type { CSSProperties, ReactNode } from "react";
 import { t } from "ttag";
 import _ from "underscore";
 
+import CS from "metabase/css/core/index.css";
 import { Icon, Text } from "metabase/ui";
 import type Database from "metabase-lib/v1/metadata/Database";
 import type Field from "metabase-lib/v1/metadata/Field";
@@ -81,22 +83,26 @@ export function FieldTrigger({
 export function DatabaseTrigger({ database }: { database: Database }) {
   return database ? (
     <span
-      className="text-wrap text-grey no-decoration"
+      className={cx("text-wrap text-grey", CS.noDecoration)}
       data-testid="selected-database"
     >
       {database.name}
     </span>
   ) : (
-    <span className="text-medium no-decoration">{t`Select a database`}</span>
+    <span
+      className={cx("text-medium", CS.noDecoration)}
+    >{t`Select a database`}</span>
   );
 }
 
 export function TableTrigger({ table }: { table: Table }) {
   return table ? (
-    <span className="text-wrap text-grey no-decoration">
+    <span className={cx("text-wrap text-grey", CS.noDecoration)}>
       {table.display_name || table.name}
     </span>
   ) : (
-    <span className="text-medium no-decoration">{t`Select a table`}</span>
+    <span
+      className={cx("text-medium", CS.noDecoration)}
+    >{t`Select a table`}</span>
   );
 }
diff --git a/frontend/src/metabase/query_builder/components/ExpandableString.jsx b/frontend/src/metabase/query_builder/components/ExpandableString.jsx
index ba542ce6288..539268aed8e 100644
--- a/frontend/src/metabase/query_builder/components/ExpandableString.jsx
+++ b/frontend/src/metabase/query_builder/components/ExpandableString.jsx
@@ -1,8 +1,11 @@
 /* eslint-disable react/prop-types */
+import cx from "classnames";
 import Humanize from "humanize-plus";
 import { Component } from "react";
 import { t } from "ttag";
 
+import CS from "metabase/css/core/index.css";
+
 export default class ExpandableString extends Component {
   constructor(props, context) {
     super(props, context);
@@ -43,7 +46,7 @@ export default class ExpandableString extends Component {
         <span>
           {this.props.str}{" "}
           <span
-            className="block mt1 link"
+            className={cx("block mt1", CS.link)}
             onClick={this.toggleExpansion}
           >{t`View less`}</span>
         </span>
@@ -53,7 +56,7 @@ export default class ExpandableString extends Component {
         <span>
           {truncated}{" "}
           <span
-            className="block mt1 link"
+            className={cx("block mt1", CS.link)}
             onClick={this.toggleExpansion}
           >{t`View more`}</span>
         </span>
diff --git a/frontend/src/metabase/query_builder/components/VisualizationError/VisualizationError.tsx b/frontend/src/metabase/query_builder/components/VisualizationError/VisualizationError.tsx
index 7f8d29b8ad8..024152ec234 100644
--- a/frontend/src/metabase/query_builder/components/VisualizationError/VisualizationError.tsx
+++ b/frontend/src/metabase/query_builder/components/VisualizationError/VisualizationError.tsx
@@ -31,7 +31,7 @@ function EmailAdmin(): JSX.Element | null {
   const hasAdminEmail = isNotNull(MetabaseSettings.adminEmail());
   return hasAdminEmail ? (
     <span className={QueryBuilderS.QueryErrorAdminEmail}>
-      <a className="no-decoration" href={`mailto:${hasAdminEmail}`}>
+      <a className={CS.noDecoration} href={`mailto:${hasAdminEmail}`}>
         {hasAdminEmail}
       </a>
     </span>
diff --git a/frontend/src/metabase/query_builder/components/VisualizationResult.jsx b/frontend/src/metabase/query_builder/components/VisualizationResult.jsx
index 9b89e6c6af1..00452d780bb 100644
--- a/frontend/src/metabase/query_builder/components/VisualizationResult.jsx
+++ b/frontend/src/metabase/query_builder/components/VisualizationResult.jsx
@@ -78,7 +78,7 @@ export default class VisualizationResult extends Component {
                   <p>
                     {jt`You can also ${(
                       <a
-                        className="link"
+                        className={CS.link}
                         key="link"
                         onClick={this.showCreateAlertModal}
                       >
diff --git a/frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp/TagEditorHelp.tsx b/frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp/TagEditorHelp.tsx
index 7fa023d36f8..6f26acc08cf 100644
--- a/frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp/TagEditorHelp.tsx
+++ b/frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp/TagEditorHelp.tsx
@@ -1,8 +1,10 @@
+import cx from "classnames";
 import { t, jt } from "ttag";
 
 import Code from "metabase/components/Code";
 import Button from "metabase/core/components/Button";
 import ExternalLink from "metabase/core/components/ExternalLink";
+import CS from "metabase/css/core/index.css";
 import { useSelector } from "metabase/lib/redux";
 import MetabaseSettings from "metabase/lib/settings";
 import { uuid } from "metabase/lib/utils";
@@ -312,7 +314,7 @@ export const TagEditorHelp = ({
       />
 
       {showMetabaseLinks && (
-        <p className="pt2 link">
+        <p className={cx("pt2", CS.link)}>
           <ExternalLink
             href={MetabaseSettings.docsUrl(
               "questions/native-editor/sql-parameters",
diff --git a/frontend/src/metabase/query_builder/components/template_tags/TagEditorParamParts/FilterWidgetTypeSelect.tsx b/frontend/src/metabase/query_builder/components/template_tags/TagEditorParamParts/FilterWidgetTypeSelect.tsx
index 81a421fea8e..3c6309bb272 100644
--- a/frontend/src/metabase/query_builder/components/template_tags/TagEditorParamParts/FilterWidgetTypeSelect.tsx
+++ b/frontend/src/metabase/query_builder/components/template_tags/TagEditorParamParts/FilterWidgetTypeSelect.tsx
@@ -2,6 +2,7 @@ import { useMemo } from "react";
 import { Link } from "react-router";
 import { t } from "ttag";
 
+import CS from "metabase/css/core/index.css";
 import MetabaseSettings from "metabase/lib/settings";
 import { Select } from "metabase/ui";
 import type { TemplateTag } from "metabase-types/api";
@@ -65,7 +66,7 @@ export function FilterWidgetTypeSelect({
               "the-field-filter-variable-type",
             )}
             target="_blank"
-            className="link"
+            className={CS.link}
           >
             {t`Learn more`}
           </Link>
diff --git a/frontend/src/metabase/setup/components/SetupHelp/SetupHelp.tsx b/frontend/src/metabase/setup/components/SetupHelp/SetupHelp.tsx
index 8286019e82a..6e8af10cea9 100644
--- a/frontend/src/metabase/setup/components/SetupHelp/SetupHelp.tsx
+++ b/frontend/src/metabase/setup/components/SetupHelp/SetupHelp.tsx
@@ -1,6 +1,7 @@
 import { t } from "ttag";
 
 import ExternalLink from "metabase/core/components/ExternalLink";
+import CS from "metabase/css/core/index.css";
 import MetabaseSettings from "metabase/lib/settings";
 
 import { SetupFooterRoot } from "./SetupHelp.styled";
@@ -10,7 +11,7 @@ export const SetupHelp = (): JSX.Element => {
     <SetupFooterRoot>
       {t`If you feel stuck`},{" "}
       <ExternalLink
-        className="link"
+        className={CS.link}
         href={MetabaseSettings.docsUrl(
           "configuring-metabase/setting-up-metabase",
         )}
diff --git a/frontend/src/metabase/sharing/components/AddEditSidebar/CaveatMessage/CaveatMessage.jsx b/frontend/src/metabase/sharing/components/AddEditSidebar/CaveatMessage/CaveatMessage.jsx
index 302f8647103..293ebbe9874 100644
--- a/frontend/src/metabase/sharing/components/AddEditSidebar/CaveatMessage/CaveatMessage.jsx
+++ b/frontend/src/metabase/sharing/components/AddEditSidebar/CaveatMessage/CaveatMessage.jsx
@@ -1,6 +1,7 @@
 import { t } from "ttag";
 
 import ExternalLink from "metabase/core/components/ExternalLink";
+import CS from "metabase/css/core/index.css";
 import { useSelector } from "metabase/lib/redux";
 import MetabaseSettings from "metabase/lib/settings";
 import { getShowMetabaseLinks } from "metabase/selectors/whitelabel";
@@ -16,7 +17,7 @@ export function CaveatMessage() {
         <>
           &nbsp;
           <ExternalLink
-            className="link"
+            className={CS.link}
             target="_blank"
             href={MetabaseSettings.docsUrl("dashboards/subscriptions")}
           >
diff --git a/frontend/src/metabase/sharing/components/NewPulseSidebar.tsx b/frontend/src/metabase/sharing/components/NewPulseSidebar.tsx
index 3ed66590fc4..e6338bd7d97 100644
--- a/frontend/src/metabase/sharing/components/NewPulseSidebar.tsx
+++ b/frontend/src/metabase/sharing/components/NewPulseSidebar.tsx
@@ -63,7 +63,11 @@ export function NewPulseSidebar({
             >
               {!emailConfigured &&
                 jt`You'll need to ${(
-                  <Link key="link" to="/admin/settings/email" className="link">
+                  <Link
+                    key="link"
+                    to="/admin/settings/email"
+                    className={CS.link}
+                  >
                     {t`set up email`}
                   </Link>
                 )} first.`}
@@ -101,7 +105,11 @@ export function NewPulseSidebar({
             >
               {!slackConfigured &&
                 jt`First, you'll have to ${(
-                  <Link key="link" to="/admin/settings/slack" className="link">
+                  <Link
+                    key="link"
+                    to="/admin/settings/slack"
+                    className={CS.link}
+                  >
                     {t`configure Slack`}
                   </Link>
                 )}.`}
diff --git a/frontend/test/metabase/lib/formatting.unit.spec.js b/frontend/test/metabase/lib/formatting.unit.spec.js
index f00b913b5ab..ec4090fd875 100644
--- a/frontend/test/metabase/lib/formatting.unit.spec.js
+++ b/frontend/test/metabase/lib/formatting.unit.spec.js
@@ -268,8 +268,8 @@ describe("formatting", () => {
       });
       // it's not actually a link
       expect(isElementOfType(formatted, ExternalLink)).toEqual(false);
-      // but it's formatted as a link
-      expect(formatted.props.className).toEqual("link link--wrappable");
+      // expect the text to be in a div (which has link formatting) rather than ExternalLink
+      expect(formatted.props["data-testid"]).toEqual("link-formatted-text");
     });
     it("should render image", () => {
       const formatted = formatValue("http://metabase.com/logo.png", {
@@ -498,8 +498,8 @@ describe("formatting", () => {
 
         // it is not a link set on the question level
         expect(isElementOfType(formatted, ExternalLink)).toEqual(false);
-        // it is formatted as a link cell for the dashboard level click behavior
-        expect(formatted.props.className).toEqual("link link--wrappable");
+        // expect the text to be in a div (which has link formatting) rather than ExternalLink
+        expect(formatted.props["data-testid"]).toEqual("link-formatted-text");
       });
     });
 
-- 
GitLab