Skip to content
Snippets Groups Projects
Unverified Commit 784d3c69 authored by Emmad Usmani's avatar Emmad Usmani Committed by GitHub
Browse files

add button to duplicate dashcard (#38186)

Part of https://github.com/metabase/metabase/issues/38208

### Description

Adds another button to the dash card action panel that duplicates that dashcard.

### How to verify

Describe the steps to verify that the changes are working as expected.

1. Open a dashboard that you can edit
2. Mouseover a dashcard
3. Click the duplicate icon

### Demo

https://www.loom.com/share/d73aad469074440992618ee179518d39

### Checklist

~~- [ ] Tests have been added/updated to cover changes in this PR~~

e2e tests are added further down the stack
parent 164f0f5d
No related branches found
No related tags found
No related merge requests found
......@@ -72,7 +72,7 @@ export type VirtualCard = Partial<Card> & {
};
export type DashboardCard = BaseDashboardCard & {
card_id: CardId | null;
card_id: CardId | null; // will be null for virtual card
card: Card;
parameter_mappings?: DashboardParameterMapping[] | null;
series?: Card[];
......
......@@ -30,7 +30,7 @@ export const markNewCardSeen = createAction(MARK_NEW_CARD_SEEN);
let tempId = -1;
function generateTemporaryDashcardId() {
export function generateTemporaryDashcardId() {
return tempId--;
}
......
......@@ -16,6 +16,7 @@ import type {
import { isActionDashCard } from "metabase/actions/utils";
import { isLinkDashCard, isVirtualDashCard } from "metabase/dashboard/utils";
import { useDuplicateDashCard } from "./use-duplicate-dashcard";
import { ChartSettingsButton } from "./ChartSettingsButton/ChartSettingsButton";
import { DashCardTabMenu } from "./DashCardTabMenu/DashCardTabMenu";
import { DashCardActionButton } from "./DashCardActionButton/DashCardActionButton";
......@@ -71,10 +72,10 @@ export function DashCardActionsPanel({
disableClickBehavior,
} = getVisualizationRaw(series) ?? {};
const [isDashCardTabMenuOpen, setIsDashCardTabMenuOpen] = useState(false);
const buttons = [];
const [isDashCardTabMenuOpen, setIsDashCardTabMenuOpen] = useState(false);
if (dashcard) {
buttons.push(
<DashCardTabMenu
......@@ -145,6 +146,20 @@ export function DashCardActionsPanel({
);
}
const duplicateDashcard = useDuplicateDashCard({ dashboard, dashcard });
if (!isLoading && dashcard) {
buttons.push(
<DashCardActionButton
key="duplicate-question"
aria-label={t`Duplicate`}
tooltip={t`Duplicate`}
onClick={duplicateDashcard}
>
<Icon name="copy" />
</DashCardActionButton>,
);
}
if (!isLoading && !hasError) {
if (supportsSeries) {
buttons.push(
......
import { useCallback } from "react";
import { createAction, useDispatch, useSelector } from "metabase/lib/redux";
import { getPositionForNewDashCard } from "metabase/lib/dashboard_grid";
import {
getCardData,
getDashboards,
getDashcards,
getSelectedTabId,
} from "metabase/dashboard/selectors";
import {
FETCH_CARD_DATA,
addDashCardToDashboard,
generateTemporaryDashcardId,
} from "metabase/dashboard/actions";
import { getExistingDashCards } from "metabase/dashboard/actions/utils";
import { isVirtualDashCard } from "metabase/dashboard/utils";
import type { Dashboard, DashboardCard } from "metabase-types/api";
export function useDuplicateDashCard({
dashboard,
dashcard,
}: {
dashboard: Dashboard;
dashcard: DashboardCard | undefined;
}) {
const dispatch = useDispatch();
const dashboards = useSelector(getDashboards);
const dashcards = useSelector(getDashcards);
const selectedTabId = useSelector(getSelectedTabId);
const dashcardDataMap = useSelector(getCardData);
return useCallback(() => {
if (!dashcard) {
return () => {
throw new Error(
"duplicateDashcard was called with an undefined dashcard",
);
};
}
const newId = generateTemporaryDashcardId();
const { id: _id, ...newDashcard } = dashcard;
const position = getPositionForNewDashCard(
getExistingDashCards(dashboards, dashcards, dashboard.id, selectedTabId),
dashcard.size_x,
dashcard.size_y,
);
dispatch(
addDashCardToDashboard({
dashId: dashboard.id,
dashcardOverrides: { id: newId, ...newDashcard, ...position },
tabId: selectedTabId,
}),
);
// We don't have card (question) data for virtual dashcards (text, heading, link, action)
if (!isVirtualDashCard(dashcard) && dashcard.card_id !== null) {
dispatch(
// Manually copying the card data by dispatching the `FETCH_CARD_DATA` action directly,
// as opposed to using the `fetchCardData` thunk, will send a request to re-fetch the data
createAction(FETCH_CARD_DATA)({
dashcard_id: newId,
card_id: dashcard.card_id,
result: dashcardDataMap[dashcard.id][dashcard?.card_id],
}),
);
}
}, [
dispatch,
dashboard.id,
dashboards,
dashcard,
dashcards,
dashcardDataMap,
selectedTabId,
]);
}
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