Skip to content
Snippets Groups Projects
Unverified Commit 1c2b5cc3 authored by Mahatthana (Kelvin) Nomsawadi's avatar Mahatthana (Kelvin) Nomsawadi Committed by GitHub
Browse files

refactor: Create static embed view components (#44555)


* Remove `this` reference from the markup

* Move functions inline

* Introduce a view component making it purer

* Introduce question embed view component

* Address review: Don't lie about types

Co-authored-by: default avatarNicolò Pretto <info@npretto.com>

* Address review: Make code consistent

---------

Co-authored-by: default avatarNicolò Pretto <info@npretto.com>
parent f135ea4f
No related branches found
No related tags found
No related merge requests found
import cx from "classnames";
import type { ReactNode, JSX } from "react";
import type { ReactNode } from "react";
import { useEffect, useState } from "react";
import { useMount } from "react-use";
import _ from "underscore";
......@@ -54,7 +54,7 @@ export type EmbedFrameBaseProps = Partial<{
description: string | null;
question: Question;
dashboard: Dashboard | null;
actionButtons: JSX.Element | null;
actionButtons: ReactNode;
footerVariant: FooterVariant;
parameters: Parameter[];
parameterValues: ParameterValuesMap;
......
import cx from "classnames";
import type { Query } from "history";
import { assoc } from "icepick";
import { type ComponentType, Component } from "react";
import type { ConnectedProps } from "react-redux";
import { connect } from "react-redux";
import _ from "underscore";
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
import ColorS from "metabase/css/core/colors.module.css";
import CS from "metabase/css/core/index.css";
import DashboardS from "metabase/css/dashboard.module.css";
import {
cancelFetchDashboardCardData,
fetchDashboard,
......@@ -19,10 +13,6 @@ import {
setParameterValueToDefault,
} from "metabase/dashboard/actions";
import type { NavigateToNewCardFromDashboardOpts } from "metabase/dashboard/components/DashCard/types";
import { DashboardEmptyStateWithoutAddPrompt } from "metabase/dashboard/components/Dashboard/DashboardEmptyState/DashboardEmptyState";
import { getDashboardActions } from "metabase/dashboard/components/DashboardActions";
import { DashboardGridConnected } from "metabase/dashboard/components/DashboardGrid";
import { DashboardTabs } from "metabase/dashboard/components/DashboardTabs";
import { DashboardControls } from "metabase/dashboard/hoc/DashboardControls";
import {
getDashboardComplete,
......@@ -38,20 +28,13 @@ import type {
FetchDashboardResult,
SuccessfulFetchDashboardResult,
} from "metabase/dashboard/types";
import { isActionDashCard } from "metabase/dashboard/utils";
import title from "metabase/hoc/Title";
import { isWithinIframe } from "metabase/lib/dom";
import ParametersS from "metabase/parameters/components/ParameterValueWidget.module.css";
import { WithPublicDashboardEndpoints } from "metabase/public/containers/PublicOrEmbeddedDashboard/WithPublicDashboardEndpoints";
import { setErrorPage } from "metabase/redux/app";
import { EmbeddingSdkMode } from "metabase/visualizations/click-actions/modes/EmbeddingSdkMode";
import { PublicMode } from "metabase/visualizations/click-actions/modes/PublicMode";
import type { Dashboard, DashboardCard, DashboardId } from "metabase-types/api";
import type { Dashboard, DashboardId } from "metabase-types/api";
import type { State } from "metabase-types/store";
import { EmbedFrame } from "../../components/EmbedFrame";
import { DashboardContainer } from "./PublicOrEmbeddedDashboard.styled";
import { PublicOrEmbeddedDashboardView } from "./PublicOrEmbeddedDashboardView";
const mapStateToProps = (state: State) => {
return {
......@@ -160,33 +143,6 @@ class PublicOrEmbeddedDashboardInner extends Component<PublicOrEmbeddedDashboard
}
}
getCurrentTabDashcards = () => {
const { dashboard, selectedTabId } = this.props;
if (!Array.isArray(dashboard?.dashcards)) {
return [];
}
if (!selectedTabId) {
return dashboard?.dashcards;
}
return dashboard?.dashcards.filter(
dashcard => dashcard.dashboard_tab_id === selectedTabId,
);
};
getHiddenParameterSlugs = () => {
const { parameters } = this.props;
const currentTabParameterIds =
this.getCurrentTabDashcards()?.flatMap(
dashcard =>
dashcard.parameter_mappings?.map(mapping => mapping.parameter_id) ??
[],
) ?? [];
const hiddenParameters = parameters.filter(
parameter => !currentTabParameterIds.includes(parameter.id),
);
return hiddenParameters.map(parameter => parameter.slug).join(",");
};
render() {
const {
dashboard,
......@@ -209,111 +165,39 @@ class PublicOrEmbeddedDashboardInner extends Component<PublicOrEmbeddedDashboard
hideParameters,
navigateToNewCardFromDashboard,
selectedTabId,
setParameterValue,
slowCards,
dashboardId,
cardTitled,
} = this.props;
const buttons = !isWithinIframe()
? getDashboardActions({
dashboard,
hasNightModeToggle,
isFullscreen,
isNightMode,
onFullscreenChange,
onNightModeChange,
onRefreshPeriodChange,
refreshPeriod,
setRefreshElapsedHook,
isPublic: true,
})
: [];
const visibleDashcards = (dashboard?.dashcards ?? []).filter(
dashcard => !isActionDashCard(dashcard),
);
const dashboardHasCards = dashboard && visibleDashcards.length > 0;
const tabHasCards =
visibleDashcards.filter(
(dc: DashboardCard) => dc.dashboard_tab_id === selectedTabId,
).length > 0;
return (
<EmbedFrame
name={dashboard && dashboard.name}
description={dashboard && dashboard.description}
<PublicOrEmbeddedDashboardView
dashboard={dashboard}
hasNightModeToggle={hasNightModeToggle}
isFullscreen={isFullscreen}
isNightMode={isNightMode}
onFullscreenChange={onFullscreenChange}
onNightModeChange={onNightModeChange}
onRefreshPeriodChange={onRefreshPeriodChange}
refreshPeriod={refreshPeriod}
setRefreshElapsedHook={setRefreshElapsedHook}
selectedTabId={selectedTabId}
parameters={parameters}
parameterValues={parameterValues}
draftParameterValues={draftParameterValues}
hiddenParameterSlugs={this.getHiddenParameterSlugs()}
setParameterValue={this.props.setParameterValue}
setParameterValue={setParameterValue}
setParameterValueToDefault={setParameterValueToDefault}
enableParameterRequiredBehavior
actionButtons={
buttons.length > 0 ? <div className={CS.flex}>{buttons}</div> : null
}
dashboardTabs={
dashboard?.tabs &&
dashboard.tabs.length > 1 && (
<DashboardTabs dashboardId={this.props.dashboardId} />
)
}
dashboardId={dashboardId}
bordered={bordered}
titled={titled}
theme={theme}
hide_parameters={hideParameters}
hide_download_button={hideDownloadButton}
>
<LoadingAndErrorWrapper
className={cx({
[DashboardS.DashboardFullscreen]: isFullscreen,
[DashboardS.DashboardNight]: isNightMode,
[ParametersS.DashboardNight]: isNightMode,
[ColorS.DashboardNight]: isNightMode,
})}
loading={!dashboard}
>
{() => {
if (!dashboard) {
return null;
}
if (!dashboardHasCards || !tabHasCards) {
return (
<DashboardEmptyStateWithoutAddPrompt
isNightMode={isNightMode}
/>
);
}
return (
<DashboardContainer>
<DashboardGridConnected
dashboard={assoc(dashboard, "dashcards", visibleDashcards)}
isPublicOrEmbedded
mode={
navigateToNewCardFromDashboard
? EmbeddingSdkMode
: PublicMode
}
selectedTabId={selectedTabId}
slowCards={this.props.slowCards}
isEditing={false}
isEditingParameter={false}
isXray={false}
isFullscreen={isFullscreen}
isNightMode={isNightMode}
withCardTitle={this.props.cardTitled}
clickBehaviorSidebarDashcard={null}
navigateToNewCardFromDashboard={
navigateToNewCardFromDashboard
}
/>
</DashboardContainer>
);
}}
</LoadingAndErrorWrapper>
</EmbedFrame>
hideParameters={hideParameters}
hideDownloadButton={hideDownloadButton}
navigateToNewCardFromDashboard={navigateToNewCardFromDashboard}
slowCards={slowCards}
cardTitled={cardTitled}
/>
);
}
}
......
import cx from "classnames";
import { assoc } from "icepick";
import type { HandleThunkActionCreator } from "react-redux";
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
import ColorS from "metabase/css/core/colors.module.css";
import CS from "metabase/css/core/index.css";
import DashboardS from "metabase/css/dashboard.module.css";
import type {
setParameterValueToDefault as setParameterValueToDefaultDashboardAction,
setParameterValue as setParameterValueDashboardAction,
} from "metabase/dashboard/actions";
import type { NavigateToNewCardFromDashboardOpts } from "metabase/dashboard/components/DashCard/types";
import { DashboardEmptyStateWithoutAddPrompt } from "metabase/dashboard/components/Dashboard/DashboardEmptyState/DashboardEmptyState";
import { getDashboardActions } from "metabase/dashboard/components/DashboardActions";
import { DashboardGridConnected } from "metabase/dashboard/components/DashboardGrid";
import { DashboardTabs } from "metabase/dashboard/components/DashboardTabs";
import type {
DashboardFullscreenControls,
DashboardRefreshPeriodControls,
EmbedHideDownloadButton,
EmbedHideParameters,
EmbedThemeControls,
RefreshPeriod,
} from "metabase/dashboard/types";
import { isActionDashCard } from "metabase/dashboard/utils";
import { isWithinIframe } from "metabase/lib/dom";
import ParametersS from "metabase/parameters/components/ParameterValueWidget.module.css";
import type { DisplayTheme } from "metabase/public/lib/types";
import { EmbeddingSdkMode } from "metabase/visualizations/click-actions/modes/EmbeddingSdkMode";
import { PublicMode } from "metabase/visualizations/click-actions/modes/PublicMode";
import type { UiParameter } from "metabase-lib/v1/parameters/types";
import type {
Dashboard,
DashboardCard,
DashboardId,
ParameterValueOrArray,
} from "metabase-types/api";
import type { SelectedTabId } from "metabase-types/store";
import { EmbedFrame } from "../../components/EmbedFrame";
import { DashboardContainer } from "./PublicOrEmbeddedDashboard.styled";
export function PublicOrEmbeddedDashboardView({
dashboard,
hasNightModeToggle,
isFullscreen,
isNightMode,
onFullscreenChange,
onNightModeChange,
onRefreshPeriodChange,
refreshPeriod,
setRefreshElapsedHook,
selectedTabId,
parameters,
parameterValues,
draftParameterValues,
setParameterValue,
setParameterValueToDefault,
dashboardId,
bordered,
titled,
theme,
hideParameters,
hideDownloadButton,
navigateToNewCardFromDashboard,
slowCards,
cardTitled,
}: {
dashboard: Dashboard | null;
hasNightModeToggle?: boolean;
isFullscreen: boolean;
isNightMode: boolean;
onFullscreenChange: DashboardFullscreenControls["onFullscreenChange"];
onNightModeChange: EmbedThemeControls["onNightModeChange"];
onRefreshPeriodChange: DashboardRefreshPeriodControls["onRefreshPeriodChange"];
refreshPeriod: RefreshPeriod;
setRefreshElapsedHook: DashboardRefreshPeriodControls["setRefreshElapsedHook"];
selectedTabId: SelectedTabId;
parameters: UiParameter[];
parameterValues: Record<string, ParameterValueOrArray>;
draftParameterValues: Record<string, ParameterValueOrArray | null>;
setParameterValue: HandleThunkActionCreator<
typeof setParameterValueDashboardAction
>;
setParameterValueToDefault: HandleThunkActionCreator<
typeof setParameterValueToDefaultDashboardAction
>;
dashboardId: DashboardId;
bordered: boolean;
titled: boolean;
theme: DisplayTheme;
hideParameters: EmbedHideParameters;
hideDownloadButton: EmbedHideDownloadButton;
navigateToNewCardFromDashboard?: (
opts: NavigateToNewCardFromDashboardOpts,
) => void;
slowCards: Record<number, boolean>;
cardTitled: boolean;
}) {
const buttons = !isWithinIframe()
? getDashboardActions({
dashboard,
hasNightModeToggle,
isFullscreen,
isNightMode,
onFullscreenChange,
onNightModeChange,
onRefreshPeriodChange,
refreshPeriod,
setRefreshElapsedHook,
isPublic: true,
})
: [];
const visibleDashcards = (dashboard?.dashcards ?? []).filter(
dashcard => !isActionDashCard(dashcard),
);
const dashboardHasCards = dashboard && visibleDashcards.length > 0;
const tabHasCards =
visibleDashcards.filter(
(dc: DashboardCard) => dc.dashboard_tab_id === selectedTabId,
).length > 0;
const hiddenParameterSlugs = getHiddenParameterSlugs({
parameters,
dashboard,
selectedTabId,
});
return (
<EmbedFrame
name={dashboard && dashboard.name}
description={dashboard && dashboard.description}
dashboard={dashboard}
parameters={parameters}
parameterValues={parameterValues}
draftParameterValues={draftParameterValues}
hiddenParameterSlugs={hiddenParameterSlugs}
setParameterValue={setParameterValue}
setParameterValueToDefault={setParameterValueToDefault}
enableParameterRequiredBehavior
actionButtons={
buttons.length > 0 && <div className={CS.flex}>{buttons}</div>
}
dashboardTabs={
dashboard?.tabs &&
dashboard.tabs.length > 1 && <DashboardTabs dashboardId={dashboardId} />
}
bordered={bordered}
titled={titled}
theme={theme}
hide_parameters={hideParameters}
hide_download_button={hideDownloadButton}
>
<LoadingAndErrorWrapper
className={cx({
[DashboardS.DashboardFullscreen]: isFullscreen,
[DashboardS.DashboardNight]: isNightMode,
[ParametersS.DashboardNight]: isNightMode,
[ColorS.DashboardNight]: isNightMode,
})}
loading={!dashboard}
>
{() => {
if (!dashboard) {
return null;
}
if (!dashboardHasCards || !tabHasCards) {
return (
<DashboardEmptyStateWithoutAddPrompt isNightMode={isNightMode} />
);
}
return (
<DashboardContainer>
<DashboardGridConnected
dashboard={assoc(dashboard, "dashcards", visibleDashcards)}
isPublicOrEmbedded
mode={
navigateToNewCardFromDashboard ? EmbeddingSdkMode : PublicMode
}
selectedTabId={selectedTabId}
slowCards={slowCards}
isEditing={false}
isEditingParameter={false}
isXray={false}
isFullscreen={isFullscreen}
isNightMode={isNightMode}
withCardTitle={cardTitled}
clickBehaviorSidebarDashcard={null}
navigateToNewCardFromDashboard={navigateToNewCardFromDashboard}
/>
</DashboardContainer>
);
}}
</LoadingAndErrorWrapper>
</EmbedFrame>
);
}
function getHiddenParameterSlugs({
parameters,
dashboard,
selectedTabId,
}: {
parameters: UiParameter[];
dashboard: Dashboard | null;
selectedTabId: SelectedTabId;
}) {
const currentTabParameterIds =
getCurrentTabDashcards({ dashboard, selectedTabId })?.flatMap(
dashcard =>
dashcard.parameter_mappings?.map(mapping => mapping.parameter_id) ?? [],
) ?? [];
const hiddenParameters = parameters.filter(
parameter => !currentTabParameterIds.includes(parameter.id),
);
return hiddenParameters.map(parameter => parameter.slug).join(",");
}
function getCurrentTabDashcards({
dashboard,
selectedTabId,
}: {
dashboard: Dashboard | null;
selectedTabId: SelectedTabId;
}) {
if (!Array.isArray(dashboard?.dashcards)) {
return [];
}
if (!selectedTabId) {
return dashboard?.dashcards;
}
return dashboard?.dashcards.filter(
dashcard => dashcard.dashboard_tab_id === selectedTabId,
);
}
import cx from "classnames";
import type { Location } from "history";
import { updateIn } from "icepick";
import { useCallback, useEffect, useState } from "react";
import { useMount } from "react-use";
import _ from "underscore";
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
import CS from "metabase/css/core/index.css";
import { useDispatch, useSelector } from "metabase/lib/redux";
import { getParameterValuesByIdFromQueryParams } from "metabase/parameters/utils/parameter-values";
import { EmbedFrame } from "metabase/public/components/EmbedFrame";
import { useEmbedFrameOptions } from "metabase/public/hooks";
import QueryDownloadWidget from "metabase/query_builder/components/QueryDownloadWidget";
import { setErrorPage } from "metabase/redux/app";
import { addParamValues, addFields } from "metabase/redux/metadata";
import { getMetadata } from "metabase/selectors/metadata";
......@@ -22,21 +16,19 @@ import {
setEmbedQuestionEndpoints,
maybeUsePivotEndpoint,
} from "metabase/services";
import { PublicMode } from "metabase/visualizations/click-actions/modes/PublicMode";
import Visualization from "metabase/visualizations/components/Visualization";
import Question from "metabase-lib/v1/Question";
import { getCardUiParameters } from "metabase-lib/v1/parameters/utils/cards";
import { getParameterValuesBySlug } from "metabase-lib/v1/parameters/utils/parameter-values";
import { getParametersFromCard } from "metabase-lib/v1/parameters/utils/template-tags";
import { applyParameters } from "metabase-lib/v1/queries/utils/card";
import type {
Card,
VisualizationSettings,
Dataset,
ParameterId,
ParameterValuesMap,
} from "metabase-types/api";
import { PublicOrEmbeddedQuestionView } from "./PublicOrEmbeddedQuestionView";
export const PublicOrEmbeddedQuestion = ({
params: { uuid, token },
location,
......@@ -173,69 +165,27 @@ export const PublicOrEmbeddedQuestion = ({
);
};
const question = new Question(card, metadata);
const actionButtons = result && (
<QueryDownloadWidget
className={cx(CS.m1, CS.textMediumHover)}
question={question}
result={result}
uuid={uuid}
token={token}
/>
);
const { bordered, hide_download_button, hide_parameters, theme, titled } =
useEmbedFrameOptions({ location });
return (
<EmbedFrame
name={card && card.name}
description={card && card.description}
actionButtons={actionButtons}
question={question}
parameters={getParameters()}
<PublicOrEmbeddedQuestionView
initialized={initialized}
card={card}
metadata={metadata}
result={result}
uuid={uuid}
token={token}
getParameters={getParameters}
parameterValues={parameterValues}
setParameterValue={setParameterValue}
enableParameterRequiredBehavior
setParameterValueToDefault={setParameterValueToDefault}
bordered={bordered}
hide_download_button={hide_download_button}
hide_parameters={hide_parameters}
theme={theme}
titled={titled}
>
<LoadingAndErrorWrapper
className={CS.flexFull}
loading={!result}
error={typeof result === "string" ? result : null}
noWrapper
>
{() => (
<Visualization
error={result && result.error}
rawSeries={[{ card: card, data: result && result.data }]}
className={cx(CS.full, CS.flexFull, CS.z1)}
onUpdateVisualizationSettings={(
settings: VisualizationSettings,
) => {
setCard(prevCard =>
updateIn(
prevCard,
["visualization_settings"],
previousSettings => ({ ...previousSettings, ...settings }),
),
);
}}
gridUnit={12}
showTitle={false}
isDashboard
mode={PublicMode}
metadata={metadata}
onChangeCardAndRun={() => {}}
/>
)}
</LoadingAndErrorWrapper>
</EmbedFrame>
setCard={setCard}
/>
);
};
import cx from "classnames";
import { updateIn } from "icepick";
import type { Dispatch, SetStateAction } from "react";
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
import CS from "metabase/css/core/index.css";
import { EmbedFrame } from "metabase/public/components/EmbedFrame";
import type { DisplayTheme } from "metabase/public/lib/types";
import QueryDownloadWidget from "metabase/query_builder/components/QueryDownloadWidget";
import { PublicMode } from "metabase/visualizations/click-actions/modes/PublicMode";
import Visualization from "metabase/visualizations/components/Visualization";
import Question from "metabase-lib/v1/Question";
import type Metadata from "metabase-lib/v1/metadata/Metadata";
import type { UiParameter } from "metabase-lib/v1/parameters/types";
import type {
Card,
VisualizationSettings,
Dataset,
ParameterId,
ParameterValuesMap,
DatasetQuery,
} from "metabase-types/api";
export function PublicOrEmbeddedQuestionView({
card,
metadata,
result,
uuid,
token,
getParameters,
parameterValues,
setParameterValue,
setParameterValueToDefault,
bordered,
hide_download_button,
hide_parameters,
theme,
titled,
setCard,
}: {
initialized: boolean;
card: Card<DatasetQuery> | null;
metadata: Metadata;
result: Dataset | null;
uuid: string;
token: string;
getParameters: () => UiParameter[];
parameterValues: ParameterValuesMap;
setParameterValue: (parameterId: ParameterId, value: any) => Promise<void>;
setParameterValueToDefault: (parameterId: ParameterId) => void;
bordered: boolean;
hide_download_button: boolean | null;
hide_parameters: string | null;
theme: DisplayTheme | undefined;
titled: boolean;
setCard: Dispatch<SetStateAction<Card<DatasetQuery> | null>>;
}) {
const question = new Question(card, metadata);
const actionButtons = result && (
<QueryDownloadWidget
className={cx(CS.m1, CS.textMediumHover)}
question={question}
result={result}
uuid={uuid}
token={token}
/>
);
return (
<EmbedFrame
name={card && card.name}
description={card && card.description}
actionButtons={actionButtons}
question={question}
parameters={getParameters()}
parameterValues={parameterValues}
setParameterValue={setParameterValue}
enableParameterRequiredBehavior
setParameterValueToDefault={setParameterValueToDefault}
bordered={bordered}
hide_download_button={hide_download_button}
hide_parameters={hide_parameters}
theme={theme}
titled={titled}
>
<LoadingAndErrorWrapper
className={CS.flexFull}
loading={!result}
error={typeof result === "string" ? result : null}
noWrapper
>
{() => (
<Visualization
error={result && result.error}
rawSeries={[{ card: card, data: result && result.data }]}
className={cx(CS.full, CS.flexFull, CS.z1)}
onUpdateVisualizationSettings={(
settings: VisualizationSettings,
) => {
setCard(prevCard =>
updateIn(
prevCard,
["visualization_settings"],
previousSettings => ({ ...previousSettings, ...settings }),
),
);
}}
gridUnit={12}
showTitle={false}
isDashboard
mode={PublicMode}
metadata={metadata}
onChangeCardAndRun={() => {}}
/>
)}
</LoadingAndErrorWrapper>
</EmbedFrame>
);
}
......@@ -9,8 +9,8 @@ export const useEmbedFrameOptions = ({ location }: { location: Location }) => {
bordered = isWithinIframe(),
titled = true,
theme,
hide_parameters,
hide_download_button,
hide_parameters = null,
hide_download_button = null,
} = parseHashOptions(location.hash) as DashboardUrlHashOptions;
return {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment