From 38dcee804c0009f14e51a077b7338df6a5846dea Mon Sep 17 00:00:00 2001 From: Alexander Polyankin <alexander.polyankin@metabase.com> Date: Fri, 6 Sep 2024 13:49:01 -0400 Subject: [PATCH] Add docs url to the metric editor (#47702) * Add metric sidebar * Add tests * Rename ResizableNotebook to DatasetNotebook * Add tests * Add tests * Add tests * Add tests --- .../DatasetNotebook.jsx} | 23 ++++-- .../DatasetNotebook.unit.spec.tsx | 71 +++++++++++++++++++ .../MetricSidebar/MetricSidebar.tsx | 32 +++++++++ .../DatasetNotebook/MetricSidebar/index.ts | 1 + .../MetricSidebar/tests/common.unit.spec.tsx | 16 +++++ .../tests/enterprise.unit.spec.tsx | 19 +++++ .../MetricSidebar/tests/setup.tsx | 33 +++++++++ .../tests/whitelabel.unit.spec.tsx | 25 +++++++ .../DatasetEditor/DatasetNotebook/index.ts | 1 + .../DatasetEditor/DatasetQueryEditor.jsx | 4 +- .../DatasetEditor/ResizableNotebook/index.js | 1 - 11 files changed, 216 insertions(+), 10 deletions(-) rename frontend/src/metabase/query_builder/components/DatasetEditor/{ResizableNotebook/ResizableNotebook.jsx => DatasetNotebook/DatasetNotebook.jsx} (78%) create mode 100644 frontend/src/metabase/query_builder/components/DatasetEditor/DatasetNotebook/DatasetNotebook.unit.spec.tsx create mode 100644 frontend/src/metabase/query_builder/components/DatasetEditor/DatasetNotebook/MetricSidebar/MetricSidebar.tsx create mode 100644 frontend/src/metabase/query_builder/components/DatasetEditor/DatasetNotebook/MetricSidebar/index.ts create mode 100644 frontend/src/metabase/query_builder/components/DatasetEditor/DatasetNotebook/MetricSidebar/tests/common.unit.spec.tsx create mode 100644 frontend/src/metabase/query_builder/components/DatasetEditor/DatasetNotebook/MetricSidebar/tests/enterprise.unit.spec.tsx create mode 100644 frontend/src/metabase/query_builder/components/DatasetEditor/DatasetNotebook/MetricSidebar/tests/setup.tsx create mode 100644 frontend/src/metabase/query_builder/components/DatasetEditor/DatasetNotebook/MetricSidebar/tests/whitelabel.unit.spec.tsx create mode 100644 frontend/src/metabase/query_builder/components/DatasetEditor/DatasetNotebook/index.ts delete mode 100644 frontend/src/metabase/query_builder/components/DatasetEditor/ResizableNotebook/index.js diff --git a/frontend/src/metabase/query_builder/components/DatasetEditor/ResizableNotebook/ResizableNotebook.jsx b/frontend/src/metabase/query_builder/components/DatasetEditor/DatasetNotebook/DatasetNotebook.jsx similarity index 78% rename from frontend/src/metabase/query_builder/components/DatasetEditor/ResizableNotebook/ResizableNotebook.jsx rename to frontend/src/metabase/query_builder/components/DatasetEditor/DatasetNotebook/DatasetNotebook.jsx index 5dbf8f7cbe3..d175ea2b6e5 100644 --- a/frontend/src/metabase/query_builder/components/DatasetEditor/ResizableNotebook/ResizableNotebook.jsx +++ b/frontend/src/metabase/query_builder/components/DatasetEditor/DatasetNotebook/DatasetNotebook.jsx @@ -8,7 +8,10 @@ import { darken } from "metabase/lib/colors"; import { Notebook } from "metabase/querying/notebook/components/Notebook"; import { Box, Flex, rem } from "metabase/ui"; +import { MetricSidebar } from "./MetricSidebar"; + const propTypes = { + question: PropTypes.object.isRequired, isResizing: PropTypes.bool.isRequired, resizableBoxProps: PropTypes.object.isRequired, onResizeStop: PropTypes.func.isRequired, @@ -50,7 +53,8 @@ const Handle = forwardRef(function Handle(props, ref) { ); }); -function ResizableNotebook({ +export function DatasetNotebook({ + question, isResizing, onResizeStop, resizableBoxProps, @@ -67,13 +71,18 @@ function ResizableNotebook({ onResizeStop(...args); }} > - <Box w="100%" style={{ overflowY: getOverflow(isResizing) }}> - <Notebook {...notebookProps} hasVisualizeButton={false} /> - </Box> + <Flex w="100%" style={{ overflowY: getOverflow(isResizing) }}> + <Box w="100%"> + <Notebook + {...notebookProps} + question={question} + hasVisualizeButton={false} + /> + </Box> + {question.type() === "metric" && <MetricSidebar />} + </Flex> </ResizableBox> ); } -ResizableNotebook.propTypes = propTypes; - -export default ResizableNotebook; +DatasetNotebook.propTypes = propTypes; diff --git a/frontend/src/metabase/query_builder/components/DatasetEditor/DatasetNotebook/DatasetNotebook.unit.spec.tsx b/frontend/src/metabase/query_builder/components/DatasetEditor/DatasetNotebook/DatasetNotebook.unit.spec.tsx new file mode 100644 index 00000000000..15e20d07ce0 --- /dev/null +++ b/frontend/src/metabase/query_builder/components/DatasetEditor/DatasetNotebook/DatasetNotebook.unit.spec.tsx @@ -0,0 +1,71 @@ +import type { ResizableBoxProps } from "react-resizable"; + +import { renderWithProviders, screen } from "__support__/ui"; +import Question from "metabase-lib/v1/Question"; +import { createMockCard } from "metabase-types/api/mocks"; + +import { DatasetNotebook } from "./DatasetNotebook"; + +type SetupOpts = { + question: Question; + reportTimezone?: string; + isDirty?: boolean; + isRunnable?: boolean; + isResultDirty?: boolean; + isResizing?: boolean; + resizableBoxProps?: ResizableBoxProps; +}; + +function setup({ + question, + reportTimezone = "UTC", + isDirty = false, + isRunnable = false, + isResultDirty = false, + isResizing = false, + resizableBoxProps = { + axis: "y", + height: 100, + }, +}: SetupOpts) { + const updateQuestion = jest.fn(); + const setQueryBuilderMode = jest.fn(); + const onResizeStop = jest.fn(); + + renderWithProviders( + <DatasetNotebook + question={question} + reportTimezone={reportTimezone} + isDirty={isDirty} + isRunnable={isRunnable} + isResultDirty={isResultDirty} + isResizing={isResizing} + resizableBoxProps={resizableBoxProps} + updateQuestion={updateQuestion} + setQueryBuilderMode={setQueryBuilderMode} + onResizeStop={onResizeStop} + />, + ); +} + +describe("DatasetNotebook", () => { + it("should render a metric docs link for metrics", () => { + const question = new Question(createMockCard({ type: "metric" })); + setup({ question }); + const link = screen.getByRole("link", { name: /Docs/ }); + expect(link).toBeInTheDocument(); + expect(link).toHaveAttribute("target", "_blank"); + expect(link).toHaveAttribute( + "href", + expect.stringContaining("data-modeling/segments-and-metrics"), + ); + }); + + it("should not render a metric docs link for non-metrics", () => { + const question = new Question(createMockCard({ type: "question" })); + setup({ question }); + expect( + screen.queryByRole("link", { name: /Docs/ }), + ).not.toBeInTheDocument(); + }); +}); diff --git a/frontend/src/metabase/query_builder/components/DatasetEditor/DatasetNotebook/MetricSidebar/MetricSidebar.tsx b/frontend/src/metabase/query_builder/components/DatasetEditor/DatasetNotebook/MetricSidebar/MetricSidebar.tsx new file mode 100644 index 00000000000..720f358f9f2 --- /dev/null +++ b/frontend/src/metabase/query_builder/components/DatasetEditor/DatasetNotebook/MetricSidebar/MetricSidebar.tsx @@ -0,0 +1,32 @@ +import { t } from "ttag"; + +import ExternalLink from "metabase/core/components/ExternalLink"; +import { useSelector } from "metabase/lib/redux"; +import { getDocsUrl } from "metabase/selectors/settings"; +import { getShowMetabaseLinks } from "metabase/selectors/whitelabel"; +import { Box, Button, Icon } from "metabase/ui"; + +export function MetricSidebar() { + const showMetabaseLinks = useSelector(getShowMetabaseLinks); + const docsUrl = useSelector(state => + getDocsUrl(state, { + page: "data-modeling/segments-and-metrics", + anchor: "creating-a-metric", + }), + ); + + return ( + <Box pt="md" pr={{ sm: "sm", lg: "md" }}> + {showMetabaseLinks && ( + <Button + component={ExternalLink} + href={docsUrl} + variant="subtle" + rightIcon={<Icon name="external" size={16} />} + > + {t`Docs`} + </Button> + )} + </Box> + ); +} diff --git a/frontend/src/metabase/query_builder/components/DatasetEditor/DatasetNotebook/MetricSidebar/index.ts b/frontend/src/metabase/query_builder/components/DatasetEditor/DatasetNotebook/MetricSidebar/index.ts new file mode 100644 index 00000000000..f4f5e14386a --- /dev/null +++ b/frontend/src/metabase/query_builder/components/DatasetEditor/DatasetNotebook/MetricSidebar/index.ts @@ -0,0 +1 @@ +export * from "./MetricSidebar"; diff --git a/frontend/src/metabase/query_builder/components/DatasetEditor/DatasetNotebook/MetricSidebar/tests/common.unit.spec.tsx b/frontend/src/metabase/query_builder/components/DatasetEditor/DatasetNotebook/MetricSidebar/tests/common.unit.spec.tsx new file mode 100644 index 00000000000..94aa8a3f82e --- /dev/null +++ b/frontend/src/metabase/query_builder/components/DatasetEditor/DatasetNotebook/MetricSidebar/tests/common.unit.spec.tsx @@ -0,0 +1,16 @@ +import { screen } from "__support__/ui"; + +import { setup } from "./setup"; + +describe("MetricSidebar (OSS)", () => { + it("should render the metric docs link", () => { + setup(); + const link = screen.getByRole("link", { name: /Docs/ }); + expect(link).toBeInTheDocument(); + expect(link).toHaveAttribute("target", "_blank"); + expect(link).toHaveAttribute( + "href", + expect.stringContaining("data-modeling/segments-and-metrics"), + ); + }); +}); diff --git a/frontend/src/metabase/query_builder/components/DatasetEditor/DatasetNotebook/MetricSidebar/tests/enterprise.unit.spec.tsx b/frontend/src/metabase/query_builder/components/DatasetEditor/DatasetNotebook/MetricSidebar/tests/enterprise.unit.spec.tsx new file mode 100644 index 00000000000..6f31d75d4fe --- /dev/null +++ b/frontend/src/metabase/query_builder/components/DatasetEditor/DatasetNotebook/MetricSidebar/tests/enterprise.unit.spec.tsx @@ -0,0 +1,19 @@ +import { screen } from "__support__/ui"; + +import { type SetupOpts, setup as baseSetup } from "./setup"; + +function setup(opts: SetupOpts = {}) { + baseSetup({ hasEnterprisePlugins: true, ...opts }); +} + +describe("MetricSidebar (EE without a token)", () => { + it("should render the metric docs link by default", () => { + setup({ showMetabaseLinks: true }); + expect(screen.getByRole("link", { name: /Docs/ })).toBeInTheDocument(); + }); + + it("should render the metric docs link even if the setting is turned off", () => { + setup({ showMetabaseLinks: false }); + expect(screen.getByRole("link", { name: /Docs/ })).toBeInTheDocument(); + }); +}); diff --git a/frontend/src/metabase/query_builder/components/DatasetEditor/DatasetNotebook/MetricSidebar/tests/setup.tsx b/frontend/src/metabase/query_builder/components/DatasetEditor/DatasetNotebook/MetricSidebar/tests/setup.tsx new file mode 100644 index 00000000000..a67c1a22b80 --- /dev/null +++ b/frontend/src/metabase/query_builder/components/DatasetEditor/DatasetNotebook/MetricSidebar/tests/setup.tsx @@ -0,0 +1,33 @@ +import { setupEnterprisePlugins } from "__support__/enterprise"; +import { mockSettings } from "__support__/settings"; +import { renderWithProviders } from "__support__/ui"; +import type { TokenFeatures } from "metabase-types/api"; +import { createMockTokenFeatures } from "metabase-types/api/mocks"; +import { createMockState } from "metabase-types/store/mocks"; + +import { MetricSidebar } from "../MetricSidebar"; + +export type SetupOpts = { + tokenFeatures?: Partial<TokenFeatures>; + showMetabaseLinks?: boolean; + hasEnterprisePlugins?: boolean; +}; + +export function setup({ + tokenFeatures, + showMetabaseLinks, + hasEnterprisePlugins, +}: SetupOpts = {}) { + const state = createMockState({ + settings: mockSettings({ + "show-metabase-links": showMetabaseLinks, + "token-features": createMockTokenFeatures(tokenFeatures), + }), + }); + + if (hasEnterprisePlugins) { + setupEnterprisePlugins(); + } + + renderWithProviders(<MetricSidebar />, { storeInitialState: state }); +} diff --git a/frontend/src/metabase/query_builder/components/DatasetEditor/DatasetNotebook/MetricSidebar/tests/whitelabel.unit.spec.tsx b/frontend/src/metabase/query_builder/components/DatasetEditor/DatasetNotebook/MetricSidebar/tests/whitelabel.unit.spec.tsx new file mode 100644 index 00000000000..a6c50309e78 --- /dev/null +++ b/frontend/src/metabase/query_builder/components/DatasetEditor/DatasetNotebook/MetricSidebar/tests/whitelabel.unit.spec.tsx @@ -0,0 +1,25 @@ +import { screen } from "__support__/ui"; + +import { type SetupOpts, setup as baseSetup } from "./setup"; + +function setup(opts: SetupOpts = {}) { + baseSetup({ + tokenFeatures: { whitelabel: true }, + hasEnterprisePlugins: true, + ...opts, + }); +} + +describe("MetricSidebar (EE with a whitelabel token)", () => { + it("should render the metric docs link by default", () => { + setup({ showMetabaseLinks: true }); + expect(screen.getByRole("link", { name: /Docs/ })).toBeInTheDocument(); + }); + + it("should not render the metric docs link when the setting is turned off", () => { + setup({ showMetabaseLinks: false }); + expect( + screen.queryByRole("link", { name: /Docs/ }), + ).not.toBeInTheDocument(); + }); +}); diff --git a/frontend/src/metabase/query_builder/components/DatasetEditor/DatasetNotebook/index.ts b/frontend/src/metabase/query_builder/components/DatasetEditor/DatasetNotebook/index.ts new file mode 100644 index 00000000000..fa20edb2966 --- /dev/null +++ b/frontend/src/metabase/query_builder/components/DatasetEditor/DatasetNotebook/index.ts @@ -0,0 +1 @@ +export * from "./DatasetNotebook"; diff --git a/frontend/src/metabase/query_builder/components/DatasetEditor/DatasetQueryEditor.jsx b/frontend/src/metabase/query_builder/components/DatasetEditor/DatasetQueryEditor.jsx index aafb7760e77..801b904cfe3 100644 --- a/frontend/src/metabase/query_builder/components/DatasetEditor/DatasetQueryEditor.jsx +++ b/frontend/src/metabase/query_builder/components/DatasetEditor/DatasetQueryEditor.jsx @@ -6,7 +6,7 @@ import { isReducedMotionPreferred } from "metabase/lib/dom"; import NativeQueryEditor from "metabase/query_builder/components/NativeQueryEditor"; import * as Lib from "metabase-lib"; -import ResizableNotebook from "./ResizableNotebook"; +import { DatasetNotebook } from "./DatasetNotebook"; const QueryEditorContainer = styled.div` visibility: ${props => (props.isActive ? "visible" : "hidden")}; @@ -83,7 +83,7 @@ function DatasetQueryEditor({ onSetDatabaseId={onSetDatabaseId} /> ) : ( - <ResizableNotebook + <DatasetNotebook {...props} question={question} isResizing={isResizing} diff --git a/frontend/src/metabase/query_builder/components/DatasetEditor/ResizableNotebook/index.js b/frontend/src/metabase/query_builder/components/DatasetEditor/ResizableNotebook/index.js deleted file mode 100644 index ab9a6783494..00000000000 --- a/frontend/src/metabase/query_builder/components/DatasetEditor/ResizableNotebook/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from "./ResizableNotebook"; -- GitLab