Skip to content
Snippets Groups Projects
Unverified Commit b01a41bb authored by Ryan Laurie's avatar Ryan Laurie Committed by GitHub
Browse files

fade headings in parameter mapping mode (#32232)

* fade headings in parameter mapping mode

* add dashcard tests

* fix some virtual card types
parent 792c8711
No related branches found
No related tags found
No related merge requests found
Showing
with 234 additions and 19 deletions
......@@ -198,8 +198,10 @@ class QuestionInner {
}
}
const isVirtualDashcard = !this._card.id;
// `dataset_query` is null for questions on a dashboard the user don't have access to
console.warn("Unknown query type: " + datasetQuery?.type);
!isVirtualDashcard &&
console.warn("Unknown query type: " + datasetQuery?.type);
}
isNative(): boolean {
......
......@@ -54,7 +54,7 @@ export type BaseDashboardOrderedCard = {
updated_at: string;
};
export type VirtualCardDisplay = "text" | "action" | "link";
export type VirtualCardDisplay = "text" | "action" | "link" | "heading";
export type VirtualCard = Partial<Card> & {
display: VirtualCardDisplay;
......
......@@ -61,12 +61,49 @@ export const createMockActionDashboardCard = (
...opts,
});
export const createMockTextDashboardCard = (
opts?: Partial<DashboardOrderedCard> & { text?: string },
): DashboardOrderedCard => ({
...createMockDashboardCardWithVirtualCard({
visualization_settings: {
text: opts?.text ?? "Body Text",
virtual_card: {
archived: false,
dataset_query: {},
display: "text",
name: "",
visualization_settings: {},
} as VirtualCard,
},
}),
...opts,
});
export const createMockHeadingDashboardCard = (
opts?: Partial<DashboardOrderedCard> & { text?: string },
): DashboardOrderedCard => ({
...createMockDashboardCardWithVirtualCard({
visualization_settings: {
text: opts?.text ?? "Heading Text",
virtual_card: {
archived: false,
dataset_query: {},
display: "heading",
name: "",
visualization_settings: {},
} as VirtualCard,
},
}),
...opts,
});
export const createMockDashboardCardWithVirtualCard = (
opts?: Partial<DashboardOrderedCard>,
): DashboardOrderedCard => ({
...createMockDashboardOrderedCard(),
card: {
query_average_duration: null,
display: opts?.visualization_settings?.virtual_card?.display ?? "text",
} as Card,
card_id: null,
visualization_settings: {
......
import { useCallback, useMemo, useRef, useState } from "react";
import * as React from "react";
import { getIn } from "icepick";
import type { LocationDescriptor } from "history";
......@@ -51,7 +50,7 @@ function preventDragging(event: React.SyntheticEvent) {
event.stopPropagation();
}
interface DashCardProps {
export interface DashCardProps {
dashboard: Dashboard;
dashcard: DashboardOrderedCard & { justAdded?: boolean };
gridItemWidth: number;
......
......@@ -41,6 +41,7 @@ import {
VirtualDashCardOverlayRoot,
VirtualDashCardOverlayText,
} from "./DashCard.styled";
import { shouldShowParameterMapper } from "./utils";
interface DashCardVisualizationProps {
dashboard: Dashboard;
......@@ -138,6 +139,7 @@ function DashCardVisualization({
link: t`Link`,
action: t`Action Button`,
text: t`Text Card`,
heading: t`Heading Card`,
}[virtualDashcardType] ??
t`This card does not support click mappings`;
......@@ -159,7 +161,7 @@ function DashCardVisualization({
);
}
if (isEditingParameter) {
if (shouldShowParameterMapper({ dashcard, isEditingParameter })) {
return (
<DashCardParameterMapper dashcard={dashcard} isMobile={isMobile} />
);
......
import { renderWithProviders, screen } from "__support__/ui";
import {
createMockCard,
createMockDashboard,
createMockDashboardOrderedCard,
createMockSettings,
createMockDatasetData,
createMockTextDashboardCard,
createMockHeadingDashboardCard,
createMockParameter,
} from "metabase-types/api/mocks";
import { createMockMetadata } from "__support__/metadata";
import { createMockState } from "metabase-types/store/mocks";
import Dashcard, { DashCardProps } from "./DashCard";
const dashboard = createMockDashboard();
const tableDashcard = createMockDashboardOrderedCard({
card: createMockCard({
name: "My Card",
display: "table",
}),
});
const metadata = createMockMetadata({}, createMockSettings());
const store = createMockState({});
function setup(
options?: Partial<DashCardProps>,
storeOptions?: Partial<typeof store>,
) {
return renderWithProviders(
<Dashcard
dashboard={dashboard}
dashcard={tableDashcard}
gridItemWidth={4}
totalNumGridCols={24}
dashcardData={{
1: {
1: {
data: createMockDatasetData({
rows: [["Davy Crocket"], ["Daniel Boone"]],
}),
database_id: 1,
context: "dashboard",
running_time: 50,
row_count: 2,
status: "completed",
},
},
}}
slowCards={{}}
parameterValues={{}}
metadata={metadata}
isEditing={false}
isEditingParameter={false}
onAddSeries={jest.fn()}
onRemove={jest.fn()}
markNewCardSeen={jest.fn()}
navigateToNewCardFromDashboard={jest.fn()}
onReplaceAllVisualizationSettings={jest.fn()}
onUpdateVisualizationSettings={jest.fn()}
showClickBehaviorSidebar={jest.fn()}
onChangeLocation={jest.fn()}
{...options}
/>,
{ storeInitialState: { ...store, ...storeOptions } },
);
}
describe("DashCard", () => {
it("shows a dashcard title", () => {
setup();
expect(screen.getByText("My Card")).toBeVisible();
});
it("shows a table visualization", () => {
setup();
expect(screen.getByText("My Card")).toBeVisible();
expect(screen.getByRole("table")).toBeVisible();
expect(screen.getByText("NAME")).toBeVisible();
expect(screen.getByText("Davy Crocket")).toBeVisible();
expect(screen.getByText("Daniel Boone")).toBeVisible();
});
it("shows a text visualization", () => {
const textCard = createMockTextDashboardCard({ text: "Hello, world!" });
const board = {
...dashboard,
ordered_cards: [textCard],
};
setup({
dashboard: board,
dashcard: textCard,
dashcardData: {},
});
expect(screen.getByText("Hello, world!")).toBeVisible();
});
it("shows a heading visualization", () => {
const textCard = createMockHeadingDashboardCard({
text: "What a cool section",
});
const board = {
...dashboard,
ordered_cards: [textCard],
};
setup({
dashboard: board,
dashcard: textCard,
dashcardData: {},
});
expect(screen.getByText("What a cool section")).toBeVisible();
});
it("in parameter editing mode, shows faded heading text", () => {
const textCard = createMockHeadingDashboardCard({
text: "What a cool section",
});
const board = {
...dashboard,
ordered_cards: [textCard],
parameters: [createMockParameter()],
};
setup({
dashboard: board,
dashcard: textCard,
dashcardData: {},
isEditing: true,
isEditingParameter: true,
});
expect(screen.getByText("What a cool section")).toBeVisible();
expect(screen.getByText("What a cool section")).toHaveStyle({
opacity: 0.25,
});
});
});
import type { DashboardOrderedCard } from "metabase-types/api";
export function shouldShowParameterMapper({
dashcard,
isEditingParameter,
}: {
dashcard: DashboardOrderedCard;
isEditingParameter?: boolean;
}) {
return isEditingParameter && dashcard?.card?.display !== "heading";
}
......@@ -61,7 +61,7 @@ export const TextInput = styled.input`
width: 100%;
`;
export const HeadingContainer = styled.div`
export const HeadingContainer = styled.div<{ fade?: boolean }>`
display: flex;
flex-direction: column;
height: 100%;
......@@ -73,6 +73,7 @@ export const HeadingContainer = styled.div`
interface HeadingContentProps {
isEditing?: boolean;
fade?: boolean;
}
export const HeadingContent = styled.h2<HeadingContentProps>`
......@@ -84,6 +85,7 @@ export const HeadingContent = styled.h2<HeadingContentProps>`
padding: 0;
margin: 0.25rem 0;
pointer-events: all;
opacity: ${({ fade }) => (fade ? 0.25 : 1)};
${({ isEditing }) =>
isEditing &&
css`
......
......@@ -18,6 +18,7 @@ import {
interface HeadingProps {
isEditing: boolean;
isEditingParameter: boolean;
onUpdateVisualizationSettings: ({ text }: { text: string }) => void;
dashcard: BaseDashboardOrderedCard;
settings: VisualizationSettings;
......@@ -26,6 +27,7 @@ interface HeadingProps {
export function Heading({
settings,
isEditing,
isEditingParameter,
onUpdateVisualizationSettings,
dashcard,
}: HeadingProps) {
......@@ -44,7 +46,7 @@ export function Heading({
const hasContent = !isEmpty(content);
const placeholder = t`Heading`;
if (isEditing) {
if (isEditing && !isEditingParameter) {
return (
<InputContainer
data-testid="editing-dashboard-heading-container"
......@@ -78,7 +80,10 @@ export function Heading({
return (
<HeadingContainer>
<HeadingContent data-testid="saved-dashboard-heading-content">
<HeadingContent
data-testid="saved-dashboard-heading-content"
fade={isEditingParameter}
>
{content}
</HeadingContent>
</HeadingContainer>
......
......@@ -17,6 +17,7 @@ interface Settings {
interface Options {
dashcard?: BaseDashboardOrderedCard;
isEditing?: boolean;
isEditingParameter?: boolean;
onUpdateVisualizationSettings?: ({ text }: { text: string }) => void;
settings?: VisualizationSettings;
}
......@@ -24,6 +25,7 @@ interface Options {
const defaultProps = {
dashcard: createMockDashboardCardWithVirtualCard(),
isEditing: false,
isEditingParameter: false,
onUpdateVisualizationSettings: () => {
return;
},
......@@ -119,6 +121,20 @@ describe("Text", () => {
);
expect(screen.getByDisplayValue("Example Heading")).toBeInTheDocument();
});
it("should render the read-only heading content (at reduced opacity) in parameter editing mode", () => {
setup({
settings: getSettingsWithText("Example Heading"),
isEditing: true,
isEditingParameter: true,
});
expect(screen.getByText("Example Heading")).toBeVisible();
expect(screen.getByText("Example Heading")).toHaveStyle(
"opacity: 0.25",
);
expect(screen.queryByRole("input")).not.toBeInTheDocument();
});
});
});
});
......
......@@ -151,16 +151,16 @@ export const ReactMarkdownStyleWrapper = styled.div`
margin: 0.375em 0 0.25em 0;
}
.text-card-markdown h1:first-child,
.text-card-markdown h2:first-child,
.text-card-markdown h3:first-child,
.text-card-markdown h4:first-child,
.text-card-markdown h5:first-child,
.text-card-markdown h6:first-child,
.text-card-markdown p:first-child,
.text-card-markdown ul:first-child,
.text-card-markdown ol:first-child,
.text-card-markdown table:first-child {
.text-card-markdown h1:first-of-type,
.text-card-markdown h2:first-of-type,
.text-card-markdown h3:first-of-type,
.text-card-markdown h4:first-of-type,
.text-card-markdown h5:first-of-type,
.text-card-markdown h6:first-of-type,
.text-card-markdown p:first-of-type,
.text-card-markdown ul:first-of-type,
.text-card-markdown ol:first-of-type,
.text-card-markdown table:first-of-type {
margin-top: 0.125em;
}
......@@ -251,7 +251,7 @@ export const ReactMarkdownStyleWrapper = styled.div`
.text-card-markdown tr {
border-bottom: 1px solid color-mod(${color("border")} alpha(-70%));
}
.text-card-markdown tr:nth-child(even) {
.text-card-markdown tr:nth-of-type(even) {
background-color: color-mod(${color("bg-black")} alpha(-98%));
}
.text-card-markdown th,
......
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