Hash static images through the rspack build (#82726)
* Declare png and gif module imports for the bundler * Hash onboarding, upsell and example images through the build * Assert the Slack app icon link against the bundled asset * Hash the upsell card story illustration through the build * Take image sources as props instead of building paths from a base string * Add a ?url image import that the SDK bundle references instead of inlining * Hash empty state, upsell and driver logo images through the build * Hash the map skeleton sprite and the bridge illustration through the build
This commit is contained in:
@@ -1,5 +1,7 @@
|
|||||||
import { t } from "ttag";
|
import { t } from "ttag";
|
||||||
|
|
||||||
|
import upsellAiCustomizationImage from "assets/img/upsell-ai-customization.png";
|
||||||
|
|
||||||
import { MetabotUpsellPage } from "../MetabotUpsellPage";
|
import { MetabotUpsellPage } from "../MetabotUpsellPage";
|
||||||
|
|
||||||
export function MetabotCustomizationUpsellPage() {
|
export function MetabotCustomizationUpsellPage() {
|
||||||
@@ -11,7 +13,7 @@ export function MetabotCustomizationUpsellPage() {
|
|||||||
title={t`Pick the look and feel of Metabase’s AI agent`}
|
title={t`Pick the look and feel of Metabase’s AI agent`}
|
||||||
// eslint-disable-next-line metabase/no-literal-metabase-strings -- This upsell refers to the Metabase product.
|
// eslint-disable-next-line metabase/no-literal-metabase-strings -- This upsell refers to the Metabase product.
|
||||||
description={t`Customize the name, icon, and illustrations used in AI chat experiences in Metabase.`}
|
description={t`Customize the name, icon, and illustrations used in AI chat experiences in Metabase.`}
|
||||||
image="app/assets/img/upsell-ai-customization.png"
|
image={upsellAiCustomizationImage}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { t } from "ttag";
|
import { t } from "ttag";
|
||||||
|
|
||||||
|
import upsellAiUsageControlsImage from "assets/img/upsell-ai-usage-controls.png";
|
||||||
|
|
||||||
import { MetabotUpsellPage } from "../MetabotUpsellPage";
|
import { MetabotUpsellPage } from "../MetabotUpsellPage";
|
||||||
|
|
||||||
export function MetabotFeatureAccessUpsellPage() {
|
export function MetabotFeatureAccessUpsellPage() {
|
||||||
@@ -9,7 +11,7 @@ export function MetabotFeatureAccessUpsellPage() {
|
|||||||
location="ai-controls-usage-controls-page"
|
location="ai-controls-usage-controls-page"
|
||||||
title={t`Set AI usage limits and controls`}
|
title={t`Set AI usage limits and controls`}
|
||||||
description={t`Decide which groups can use which AI features, and set sensible limits on usage based on tokens, messages, or dollar spend.`}
|
description={t`Decide which groups can use which AI features, and set sensible limits on usage based on tokens, messages, or dollar spend.`}
|
||||||
image="app/assets/img/upsell-ai-usage-controls.png"
|
image={upsellAiUsageControlsImage}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { t } from "ttag";
|
import { t } from "ttag";
|
||||||
|
|
||||||
|
import upsellAiSystemPromptsImage from "assets/img/upsell-ai-system-prompts.png";
|
||||||
|
|
||||||
import { MetabotUpsellPage } from "../MetabotUpsellPage";
|
import { MetabotUpsellPage } from "../MetabotUpsellPage";
|
||||||
|
|
||||||
export function MetabotSystemPromptsUpsellPage() {
|
export function MetabotSystemPromptsUpsellPage() {
|
||||||
@@ -9,7 +11,7 @@ export function MetabotSystemPromptsUpsellPage() {
|
|||||||
location="ai-controls-system-prompts-page"
|
location="ai-controls-system-prompts-page"
|
||||||
title={t`Write custom instructions for each AI feature`}
|
title={t`Write custom instructions for each AI feature`}
|
||||||
description={t`SQL code generation, natural language queries, and agent chat can each be customized with your own prompts.`}
|
description={t`SQL code generation, natural language queries, and agent chat can each be customized with your own prompts.`}
|
||||||
image="app/assets/img/upsell-ai-system-prompts.png"
|
image={upsellAiSystemPromptsImage}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import type { PropsWithChildren } from "react";
|
|||||||
import { match } from "ts-pattern";
|
import { match } from "ts-pattern";
|
||||||
import { t } from "ttag";
|
import { t } from "ttag";
|
||||||
|
|
||||||
|
import googleSheetImage from "assets/img/empty-states/google-sheet.svg?url";
|
||||||
import { skipToken } from "metabase/api";
|
import { skipToken } from "metabase/api";
|
||||||
import {
|
import {
|
||||||
StoragePurchaseButton,
|
StoragePurchaseButton,
|
||||||
@@ -37,7 +38,6 @@ import {
|
|||||||
Text,
|
Text,
|
||||||
Title,
|
Title,
|
||||||
} from "metabase/ui";
|
} from "metabase/ui";
|
||||||
import { getSubpathSafeUrl } from "metabase/urls";
|
|
||||||
import { useGetGsheetsFolderQuery } from "metabase-enterprise/api";
|
import { useGetGsheetsFolderQuery } from "metabase-enterprise/api";
|
||||||
import {
|
import {
|
||||||
DriveConnectionDisplay,
|
DriveConnectionDisplay,
|
||||||
@@ -59,13 +59,9 @@ const PanelWrapper = ({
|
|||||||
isModalOpen?: boolean;
|
isModalOpen?: boolean;
|
||||||
onModalClose?: () => void;
|
onModalClose?: () => void;
|
||||||
}>) => {
|
}>) => {
|
||||||
const illustration = getSubpathSafeUrl(
|
|
||||||
"app/assets/img/empty-states/google-sheet.svg",
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack gap="lg" align="center" justify="center" pt="2.5rem">
|
<Stack gap="lg" align="center" justify="center" pt="2.5rem">
|
||||||
<Center component="img" src={illustration} w="3rem" />
|
<Center component="img" src={googleSheetImage} w="3rem" />
|
||||||
<Box component="header" ta="center" maw={CONTENT_MAX_WIDTH}>
|
<Box component="header" ta="center" maw={CONTENT_MAX_WIDTH}>
|
||||||
<Title order={2} size="h4" mb="sm">
|
<Title order={2} size="h4" mb="sm">
|
||||||
{title}
|
{title}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { t } from "ttag";
|
import { t } from "ttag";
|
||||||
|
|
||||||
|
import upsellAiUsageAuditingImage from "assets/img/upsell-ai-usage-auditing.png";
|
||||||
import { DottedBackground } from "metabase/common/components/upsells/components/DottedBackground";
|
import { DottedBackground } from "metabase/common/components/upsells/components/DottedBackground";
|
||||||
import { LineDecorator } from "metabase/common/components/upsells/components/LineDecorator";
|
import { LineDecorator } from "metabase/common/components/upsells/components/LineDecorator";
|
||||||
import { useUpgradeAction } from "metabase/common/components/upsells/components/UpgradeModal";
|
import { useUpgradeAction } from "metabase/common/components/upsells/components/UpgradeModal";
|
||||||
@@ -31,7 +32,7 @@ export function MetabotAnalyticsUpsellPage() {
|
|||||||
description={t`Track trends of token use, conversations, and messages, and understand what your team is doing with AI in Metabase.`}
|
description={t`Track trends of token use, conversations, and messages, and understand what your team is doing with AI in Metabase.`}
|
||||||
upgradeOnClick={upgradeOnClick}
|
upgradeOnClick={upgradeOnClick}
|
||||||
upgradeUrl={upgradeUrl}
|
upgradeUrl={upgradeUrl}
|
||||||
image="app/assets/img/upsell-ai-usage-auditing.png"
|
image={upsellAiUsageAuditingImage}
|
||||||
/>
|
/>
|
||||||
</LineDecorator>
|
</LineDecorator>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import cx from "classnames";
|
import cx from "classnames";
|
||||||
import { t } from "ttag";
|
import { t } from "ttag";
|
||||||
|
|
||||||
|
import attributesIllustration from "assets/img/attributes_illustration.png";
|
||||||
|
import attributesIllustration2x from "assets/img/attributes_illustration@2x.png";
|
||||||
import CS from "metabase/css/core/index.css";
|
import CS from "metabase/css/core/index.css";
|
||||||
|
|
||||||
interface AttributeOptionsEmptyStateProps {
|
interface AttributeOptionsEmptyStateProps {
|
||||||
@@ -20,11 +22,8 @@ export const AttributeOptionsEmptyState = ({
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src="app/assets/img/attributes_illustration.png"
|
src={attributesIllustration}
|
||||||
srcSet="
|
srcSet={`${attributesIllustration} 1x, ${attributesIllustration2x} 2x`}
|
||||||
app/assets/img/attributes_illustration.png 1x,
|
|
||||||
app/assets/img/attributes_illustration@2x.png 2x,
|
|
||||||
"
|
|
||||||
className={CS.mr2}
|
className={CS.mr2}
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import noResultsSource from "assets/img/no_results.svg";
|
import noResultsSource from "assets/img/no_results.svg";
|
||||||
|
import bridgeImage from "img/bridge.svg?url";
|
||||||
import type { IllustrationValue } from "metabase/plugins";
|
import type { IllustrationValue } from "metabase/plugins";
|
||||||
import type { State } from "metabase/redux/store";
|
import type { State } from "metabase/redux/store";
|
||||||
import {
|
import {
|
||||||
@@ -59,7 +60,7 @@ export function getLoginPageIllustration(state: State): IllustrationValue {
|
|||||||
switch (illustrationOption) {
|
switch (illustrationOption) {
|
||||||
case "default":
|
case "default":
|
||||||
return {
|
return {
|
||||||
src: "app/img/bridge.svg",
|
src: bridgeImage,
|
||||||
isDefault: true,
|
isDefault: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -86,7 +87,7 @@ export function getLandingPageIllustration(state: State): IllustrationValue {
|
|||||||
switch (illustrationOption) {
|
switch (illustrationOption) {
|
||||||
case "default":
|
case "default":
|
||||||
return {
|
return {
|
||||||
src: "app/img/bridge.svg",
|
src: bridgeImage,
|
||||||
isDefault: true,
|
isDefault: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { t } from "ttag";
|
import { t } from "ttag";
|
||||||
|
|
||||||
|
import metabotSlackbotIcon from "assets/img/metabot-slackbot.png";
|
||||||
import { ExternalLink } from "metabase/common/components/ExternalLink";
|
import { ExternalLink } from "metabase/common/components/ExternalLink";
|
||||||
import { useSetting } from "metabase/settings";
|
import { useSetting } from "metabase/settings";
|
||||||
import { Box, Button, Flex, Stack, Text } from "metabase/ui";
|
import { Box, Button, Flex, Stack, Text } from "metabase/ui";
|
||||||
@@ -12,7 +13,7 @@ export const SlackConfiguration = () => {
|
|||||||
const bugReportingEnabled = useSetting("bug-reporting-enabled") ?? false;
|
const bugReportingEnabled = useSetting("bug-reporting-enabled") ?? false;
|
||||||
const { data: appInfo } = useGetSlackAppInfoQuery();
|
const { data: appInfo } = useGetSlackAppInfoQuery();
|
||||||
|
|
||||||
const iconUrl = "/app/assets/img/metabot-slackbot.png";
|
const iconUrl = metabotSlackbotIcon;
|
||||||
const basicInfoUrl = appInfo?.app_id
|
const basicInfoUrl = appInfo?.app_id
|
||||||
? `https://api.slack.com/apps/${appInfo.app_id}/general#edit`
|
? `https://api.slack.com/apps/${appInfo.app_id}/general#edit`
|
||||||
: `https://api.slack.com/apps`;
|
: `https://api.slack.com/apps`;
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
} from "__support__/server-mocks";
|
} from "__support__/server-mocks";
|
||||||
import { createMockSettingsState } from "__support__/state";
|
import { createMockSettingsState } from "__support__/state";
|
||||||
import { renderWithProviders, screen, waitFor } from "__support__/ui";
|
import { renderWithProviders, screen, waitFor } from "__support__/ui";
|
||||||
|
import metabotSlackbotIcon from "assets/img/metabot-slackbot.png";
|
||||||
import { createMockSettings } from "metabase-types/api/mocks";
|
import { createMockSettings } from "metabase-types/api/mocks";
|
||||||
|
|
||||||
import { SlackSetup } from "./SlackSetup";
|
import { SlackSetup } from "./SlackSetup";
|
||||||
@@ -151,10 +152,8 @@ describe("SlackSetup", () => {
|
|||||||
const downloadLink = screen.getByRole("link", {
|
const downloadLink = screen.getByRole("link", {
|
||||||
name: "Download App Icon",
|
name: "Download App Icon",
|
||||||
});
|
});
|
||||||
expect(downloadLink).toHaveAttribute(
|
expect(downloadLink).toHaveAttribute("href", metabotSlackbotIcon);
|
||||||
"href",
|
expect(downloadLink).toHaveAttribute("download", "metabot-slack-icon.png");
|
||||||
"/app/assets/img/metabot-slackbot.png",
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should show link to Slack app settings", async () => {
|
it("should show link to Slack app settings", async () => {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { c, t } from "ttag";
|
import { c, t } from "ttag";
|
||||||
|
|
||||||
|
import upsellCloudImage from "assets/img/upsell-cloud.png?url";
|
||||||
import { ExternalLink } from "metabase/common/components/ExternalLink";
|
import { ExternalLink } from "metabase/common/components/ExternalLink";
|
||||||
import { UpsellBigCard } from "metabase/common/components/upsells/components";
|
import { UpsellBigCard } from "metabase/common/components/upsells/components";
|
||||||
import S from "metabase/common/components/upsells/components/Upsells.module.css";
|
import S from "metabase/common/components/upsells/components/Upsells.module.css";
|
||||||
@@ -25,7 +26,7 @@ export const UpsellCloud = ({
|
|||||||
buttonText={t`Try for free`}
|
buttonText={t`Try for free`}
|
||||||
source={source}
|
source={source}
|
||||||
onClick={onOpenModal}
|
onClick={onOpenModal}
|
||||||
illustrationSrc="app/assets/img/upsell-cloud.png"
|
illustrationSrc={upsellCloudImage}
|
||||||
>
|
>
|
||||||
{c("'restores' and 'upgrades' are nouns in plural in this context")
|
{c("'restores' and 'upgrades' are nouns in plural in this context")
|
||||||
.t`Get automatic backups, restores, and upgrades, built-in network monitoring, unlimited expert help from engineers and more.`}{" "}
|
.t`Get automatic backups, restores, and upgrades, built-in network monitoring, unlimited expert help from engineers and more.`}{" "}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { t } from "ttag";
|
import { t } from "ttag";
|
||||||
|
|
||||||
|
import upsellDataAppsImage from "assets/img/upsell-data-apps.png?url";
|
||||||
import { UpsellBigCard } from "metabase/common/components/upsells/components";
|
import { UpsellBigCard } from "metabase/common/components/upsells/components";
|
||||||
import { UPGRADE_URL } from "metabase/common/components/upsells/constants";
|
import { UPGRADE_URL } from "metabase/common/components/upsells/constants";
|
||||||
import { useHasTokenFeature } from "metabase/common/hooks";
|
import { useHasTokenFeature } from "metabase/common/hooks";
|
||||||
@@ -24,7 +25,7 @@ export const UpsellDataApps = ({ source }: { source: string }) => {
|
|||||||
buttonText={t`Try for free`}
|
buttonText={t`Try for free`}
|
||||||
buttonLink={UPGRADE_URL}
|
buttonLink={UPGRADE_URL}
|
||||||
source={source}
|
source={source}
|
||||||
illustrationSrc="app/assets/img/upsell-data-apps.png"
|
illustrationSrc={upsellDataAppsImage}
|
||||||
onClick={triggerUpsellFlow}
|
onClick={triggerUpsellFlow}
|
||||||
>
|
>
|
||||||
<span>
|
<span>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { t } from "ttag";
|
import { t } from "ttag";
|
||||||
|
|
||||||
|
import upsellThemesImage from "assets/img/upsell-themes.png?url";
|
||||||
import { DottedBackground } from "metabase/common/components/upsells/components/DottedBackground";
|
import { DottedBackground } from "metabase/common/components/upsells/components/DottedBackground";
|
||||||
import { LineDecorator } from "metabase/common/components/upsells/components/LineDecorator";
|
import { LineDecorator } from "metabase/common/components/upsells/components/LineDecorator";
|
||||||
import { useUpgradeAction } from "metabase/common/components/upsells/components/UpgradeModal";
|
import { useUpgradeAction } from "metabase/common/components/upsells/components/UpgradeModal";
|
||||||
@@ -33,7 +34,7 @@ export const UpsellEmbeddingTheme = ({ source }: { source: string }) => {
|
|||||||
description={t`Fine-tune the appearance of your embedded content with colors and fonts.`}
|
description={t`Fine-tune the appearance of your embedded content with colors and fonts.`}
|
||||||
upgradeOnClick={upgradeOnClick}
|
upgradeOnClick={upgradeOnClick}
|
||||||
upgradeUrl={upgradeUrl}
|
upgradeUrl={upgradeUrl}
|
||||||
image="app/assets/img/upsell-themes.png"
|
image={upsellThemesImage}
|
||||||
/>
|
/>
|
||||||
</LineDecorator>
|
</LineDecorator>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { t } from "ttag";
|
import { t } from "ttag";
|
||||||
|
|
||||||
|
import upsellRemoteSyncImage from "assets/img/upsell-remote-sync.png?url";
|
||||||
import { ExternalLink } from "metabase/common/components/ExternalLink";
|
import { ExternalLink } from "metabase/common/components/ExternalLink";
|
||||||
import { UpsellBigCard } from "metabase/common/components/upsells/components";
|
import { UpsellBigCard } from "metabase/common/components/upsells/components";
|
||||||
import S from "metabase/common/components/upsells/components/Upsells.module.css";
|
import S from "metabase/common/components/upsells/components/Upsells.module.css";
|
||||||
@@ -33,7 +34,7 @@ export const UpsellRemoteSync = ({ source }: { source: string }) => {
|
|||||||
buttonLink={UPGRADE_URL}
|
buttonLink={UPGRADE_URL}
|
||||||
source={source}
|
source={source}
|
||||||
onClick={triggerUpsellFlow}
|
onClick={triggerUpsellFlow}
|
||||||
illustrationSrc="app/assets/img/upsell-remote-sync.png"
|
illustrationSrc={upsellRemoteSyncImage}
|
||||||
>
|
>
|
||||||
{t`Keep your most important datasets, metrics, and SQL logic under version control. Sync content to a Git repository to review changes, collaborate, and maintain a production-ready source of truth.`}
|
{t`Keep your most important datasets, metrics, and SQL logic under version control. Sync content to a Git repository to review changes, collaborate, and maintain a production-ready source of truth.`}
|
||||||
<ExternalLink
|
<ExternalLink
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { t } from "ttag";
|
import { t } from "ttag";
|
||||||
|
|
||||||
|
import upsellTenantsImage from "assets/img/upsell-tenants.png?url";
|
||||||
import { DottedBackground } from "metabase/common/components/upsells/components/DottedBackground";
|
import { DottedBackground } from "metabase/common/components/upsells/components/DottedBackground";
|
||||||
import { LineDecorator } from "metabase/common/components/upsells/components/LineDecorator";
|
import { LineDecorator } from "metabase/common/components/upsells/components/LineDecorator";
|
||||||
import { useUpgradeAction } from "metabase/common/components/upsells/components/UpgradeModal";
|
import { useUpgradeAction } from "metabase/common/components/upsells/components/UpgradeModal";
|
||||||
@@ -33,7 +34,7 @@ export const UpsellTenants = () => {
|
|||||||
description={t`Group your customers into tenants, reuse the same dashboards and permissions, and keep each tenant's data isolated.`}
|
description={t`Group your customers into tenants, reuse the same dashboards and permissions, and keep each tenant's data isolated.`}
|
||||||
upgradeOnClick={upgradeOnClick}
|
upgradeOnClick={upgradeOnClick}
|
||||||
upgradeUrl={upgradeUrl}
|
upgradeUrl={upgradeUrl}
|
||||||
image="app/assets/img/upsell-tenants.png"
|
image={upsellTenantsImage}
|
||||||
/>
|
/>
|
||||||
</LineDecorator>
|
</LineDecorator>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { t } from "ttag";
|
import { t } from "ttag";
|
||||||
|
|
||||||
|
import upsellWhitelabelImage from "assets/img/upsell-whitelabel.png?url";
|
||||||
import { ExternalLink } from "metabase/common/components/ExternalLink";
|
import { ExternalLink } from "metabase/common/components/ExternalLink";
|
||||||
import { UpsellBigCard } from "metabase/common/components/upsells/components";
|
import { UpsellBigCard } from "metabase/common/components/upsells/components";
|
||||||
import S from "metabase/common/components/upsells/components/Upsells.module.css";
|
import S from "metabase/common/components/upsells/components/Upsells.module.css";
|
||||||
@@ -43,7 +44,7 @@ export const UpsellWhitelabel = ({ source }: { source: string }) => {
|
|||||||
buttonText={t`Try for free`}
|
buttonText={t`Try for free`}
|
||||||
buttonLink={UPGRADE_URL}
|
buttonLink={UPGRADE_URL}
|
||||||
source={source}
|
source={source}
|
||||||
illustrationSrc="app/assets/img/upsell-whitelabel.png"
|
illustrationSrc={upsellWhitelabelImage}
|
||||||
onClick={triggerUpsellFlow}
|
onClick={triggerUpsellFlow}
|
||||||
>
|
>
|
||||||
{t`Customize your internal or customer-facing analytics with your brand name, logo, colors, font and more, and hide giveaway Metabase elements.`}
|
{t`Customize your internal or customer-facing analytics with your brand name, logo, colors, font and more, and hide giveaway Metabase elements.`}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import cx from "classnames";
|
import cx from "classnames";
|
||||||
import { t } from "ttag";
|
import { t } from "ttag";
|
||||||
|
|
||||||
|
import browseModelsVideoThumbnail from "assets/img/browse-models-video-thumbnail.png";
|
||||||
|
|
||||||
import S from "./ModelsVideo.module.css";
|
import S from "./ModelsVideo.module.css";
|
||||||
|
|
||||||
export const ModelsVideo = ({ autoplay }: { autoplay: 0 | 1 }) => {
|
export const ModelsVideo = ({ autoplay }: { autoplay: 0 | 1 }) => {
|
||||||
@@ -27,7 +29,7 @@ export const ModelsVideoThumbnail = ({ onClick }: { onClick: () => void }) => {
|
|||||||
<img
|
<img
|
||||||
alt={t`Browse models video thumbnail`}
|
alt={t`Browse models video thumbnail`}
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
src="app/assets/img/browse-models-video-thumbnail.png"
|
src={browseModelsVideoThumbnail}
|
||||||
width="100%"
|
width="100%"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import type { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
|
|
||||||
import { EmptyState } from "metabase/common/components/EmptyState";
|
import {
|
||||||
|
EmptyState,
|
||||||
|
type ImageSource,
|
||||||
|
} from "metabase/common/components/EmptyState";
|
||||||
import { getUser } from "metabase/current-user";
|
import { getUser } from "metabase/current-user";
|
||||||
import { useSelector } from "metabase/redux";
|
import { useSelector } from "metabase/redux";
|
||||||
import type { IconName } from "metabase-types/api";
|
import type { IconName } from "metabase-types/api";
|
||||||
@@ -14,7 +17,7 @@ interface AdminAwareEmptyStateProps {
|
|||||||
message?: ReactNode;
|
message?: ReactNode;
|
||||||
adminMessage?: ReactNode;
|
adminMessage?: ReactNode;
|
||||||
icon?: IconName;
|
icon?: IconName;
|
||||||
image?: string;
|
image?: ImageSource;
|
||||||
action?: ReactNode;
|
action?: ReactNode;
|
||||||
adminAction?: ReactNode;
|
adminAction?: ReactNode;
|
||||||
link?: string;
|
link?: string;
|
||||||
|
|||||||
@@ -16,6 +16,14 @@ import type { IconName } from "metabase-types/api";
|
|||||||
|
|
||||||
import S from "./EmptyState.module.css";
|
import S from "./EmptyState.module.css";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A raster illustration and its high-density variant, both resolved by the bundler.
|
||||||
|
*/
|
||||||
|
export type ImageSource = {
|
||||||
|
src: string;
|
||||||
|
srcSet?: string;
|
||||||
|
};
|
||||||
|
|
||||||
// Don't break existing empty states
|
// Don't break existing empty states
|
||||||
// TODO - remove these and update empty states with proper usage of illustrationElement
|
// TODO - remove these and update empty states with proper usage of illustrationElement
|
||||||
const LegacyIcon = ({ icon }: { icon: IconName }) =>
|
const LegacyIcon = ({ icon }: { icon: IconName }) =>
|
||||||
@@ -27,21 +35,20 @@ const LegacyImage = ({
|
|||||||
imageClassName,
|
imageClassName,
|
||||||
message,
|
message,
|
||||||
}: {
|
}: {
|
||||||
image: string;
|
image: ImageSource;
|
||||||
imageHeight?: number;
|
imageHeight?: number;
|
||||||
imageClassName?: string;
|
imageClassName?: string;
|
||||||
message?: string;
|
message?: string;
|
||||||
}) =>
|
}) => (
|
||||||
image ? (
|
<img
|
||||||
<img
|
src={image.src}
|
||||||
src={`${image}.png`}
|
width="300px"
|
||||||
width="300px"
|
height={imageHeight}
|
||||||
height={imageHeight}
|
alt={message}
|
||||||
alt={message}
|
srcSet={image.srcSet}
|
||||||
srcSet={`${image}@2x.png 2x`}
|
className={imageClassName}
|
||||||
className={imageClassName}
|
/>
|
||||||
/>
|
);
|
||||||
) : null;
|
|
||||||
|
|
||||||
type EmptyStateProps = {
|
type EmptyStateProps = {
|
||||||
message?: React.ReactNode;
|
message?: React.ReactNode;
|
||||||
@@ -52,7 +59,7 @@ type EmptyStateProps = {
|
|||||||
onActionClick?: () => void;
|
onActionClick?: () => void;
|
||||||
className?: string;
|
className?: string;
|
||||||
icon?: IconName;
|
icon?: IconName;
|
||||||
image?: string;
|
image?: ImageSource;
|
||||||
spacing?: "sm" | "md";
|
spacing?: "sm" | "md";
|
||||||
actionVariant?: ButtonProps["variant"];
|
actionVariant?: ButtonProps["variant"];
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
export { EmptyState } from "./EmptyState";
|
export { EmptyState, type ImageSource } from "./EmptyState";
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { t } from "ttag";
|
import { t } from "ttag";
|
||||||
|
|
||||||
|
import databasesListImage from "assets/img/databases-list.png";
|
||||||
|
import databasesListImage2x from "assets/img/databases-list@2x.png";
|
||||||
import { AdminAwareEmptyState } from "metabase/common/components/AdminAwareEmptyState";
|
import { AdminAwareEmptyState } from "metabase/common/components/AdminAwareEmptyState";
|
||||||
import { useSelector } from "metabase/redux";
|
import { useSelector } from "metabase/redux";
|
||||||
import { getApplicationName } from "metabase/selectors/whitelabel";
|
import { getApplicationName } from "metabase/selectors/whitelabel";
|
||||||
@@ -11,7 +13,10 @@ export const NoDatabasesEmptyState = () => {
|
|||||||
title={t`${applicationName} is no fun without any data`}
|
title={t`${applicationName} is no fun without any data`}
|
||||||
adminMessage={t`Your databases will appear here once you connect one`}
|
adminMessage={t`Your databases will appear here once you connect one`}
|
||||||
message={t`Databases will appear here once your admins have added some`}
|
message={t`Databases will appear here once your admins have added some`}
|
||||||
image="app/assets/img/databases-list"
|
image={{
|
||||||
|
src: databasesListImage,
|
||||||
|
srcSet: `${databasesListImage2x} 2x`,
|
||||||
|
}}
|
||||||
adminAction={t`Connect a database`}
|
adminAction={t`Connect a database`}
|
||||||
adminLink="/admin/databases/create"
|
adminLink="/admin/databases/create"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { t } from "ttag";
|
import { t } from "ttag";
|
||||||
|
|
||||||
|
import usageAnalyticsIllustrationSource from "assets/img/usage-analytics.png?url";
|
||||||
import { ExternalLink } from "metabase/common/components/ExternalLink";
|
import { ExternalLink } from "metabase/common/components/ExternalLink";
|
||||||
import { PLUGIN_ADMIN_SETTINGS } from "metabase/plugins";
|
import { PLUGIN_ADMIN_SETTINGS } from "metabase/plugins";
|
||||||
import { useSelector } from "metabase/redux";
|
import { useSelector } from "metabase/redux";
|
||||||
@@ -9,8 +10,6 @@ import { Box, type BoxProps, Text } from "metabase/ui";
|
|||||||
import { UpsellCard, type UpsellCardProps } from "./UpsellCard";
|
import { UpsellCard, type UpsellCardProps } from "./UpsellCard";
|
||||||
import { UPGRADE_URL } from "./constants";
|
import { UPGRADE_URL } from "./constants";
|
||||||
|
|
||||||
const usageAnalyticsIllustrationSource = "app/assets/img/usage-analytics.png";
|
|
||||||
|
|
||||||
export const UpsellUsageAnalytics = (
|
export const UpsellUsageAnalytics = (
|
||||||
props: BoxProps &
|
props: BoxProps &
|
||||||
Omit<
|
Omit<
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
import { ReduxProvider } from "__support__/storybook";
|
import { ReduxProvider } from "__support__/storybook";
|
||||||
|
import upsellPerformanceToolsImage from "assets/img/upsell-performance-tools.png";
|
||||||
import { ExternalLink } from "metabase/common/components/ExternalLink";
|
import { ExternalLink } from "metabase/common/components/ExternalLink";
|
||||||
import { Box, Modal } from "metabase/ui";
|
import { Box, Modal } from "metabase/ui";
|
||||||
|
|
||||||
@@ -14,7 +15,7 @@ const args = {
|
|||||||
campaign: "upsell-big-card",
|
campaign: "upsell-big-card",
|
||||||
source: "storybook",
|
source: "storybook",
|
||||||
title: "Troubleshoot faster",
|
title: "Troubleshoot faster",
|
||||||
illustrationSrc: "app/assets/img/upsell-performance-tools.png",
|
illustrationSrc: upsellPerformanceToolsImage,
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
const argTypes = {
|
const argTypes = {
|
||||||
|
|||||||
@@ -1 +1,3 @@
|
|||||||
export const LOAD_COMPLETE_FAVICON = "app/assets/img/blue_check.png";
|
import blueCheckImage from "assets/img/blue_check.png?url";
|
||||||
|
|
||||||
|
export const LOAD_COMPLETE_FAVICON = blueCheckImage;
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { useCallback } from "react";
|
import { useCallback } from "react";
|
||||||
|
|
||||||
|
import favicon32Image from "assets/img/favicon-32x32.png";
|
||||||
|
|
||||||
const hasNotificationAPI = "Notification" in window;
|
const hasNotificationAPI = "Notification" in window;
|
||||||
|
|
||||||
export function useWebNotification() {
|
export function useWebNotification() {
|
||||||
@@ -18,7 +20,7 @@ export function useWebNotification() {
|
|||||||
|
|
||||||
const notification = new Notification(title, {
|
const notification = new Notification(title, {
|
||||||
body,
|
body,
|
||||||
icon: "app/assets/img/favicon-32x32.png",
|
icon: favicon32Image,
|
||||||
});
|
});
|
||||||
|
|
||||||
const closeNotification = (e: Event) => {
|
const closeNotification = (e: Event) => {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { t } from "ttag";
|
import { t } from "ttag";
|
||||||
|
|
||||||
|
import dependenciesUpsellImage from "assets/img/data-studio-dependencies-upsell.png";
|
||||||
import { usePageTitle } from "metabase/hooks/use-page-title";
|
import { usePageTitle } from "metabase/hooks/use-page-title";
|
||||||
|
|
||||||
import { BaseUpsellPage } from "./BaseUpsellPage";
|
import { BaseUpsellPage } from "./BaseUpsellPage";
|
||||||
@@ -14,7 +15,7 @@ export function DependenciesUpsellPage() {
|
|||||||
header={t`Dependency graph`}
|
header={t`Dependency graph`}
|
||||||
title={t`See how everything connects`}
|
title={t`See how everything connects`}
|
||||||
description={t`Better manage your data transformation and entity graph by inspecting it visually, understanding relationships and dependencies.`}
|
description={t`Better manage your data transformation and entity graph by inspecting it visually, understanding relationships and dependencies.`}
|
||||||
image="app/assets/img/data-studio-dependencies-upsell.png"
|
image={dependenciesUpsellImage}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { t } from "ttag";
|
import { t } from "ttag";
|
||||||
|
|
||||||
|
import libraryUpsellImage from "assets/img/data-studio-library-upsell.svg";
|
||||||
|
|
||||||
import { BaseUpsellPage } from "./BaseUpsellPage";
|
import { BaseUpsellPage } from "./BaseUpsellPage";
|
||||||
|
|
||||||
export function LibraryUpsellPage() {
|
export function LibraryUpsellPage() {
|
||||||
@@ -10,7 +12,7 @@ export function LibraryUpsellPage() {
|
|||||||
header={t`Semantic layer`}
|
header={t`Semantic layer`}
|
||||||
title={t`Bring more order to your analytics`}
|
title={t`Bring more order to your analytics`}
|
||||||
description={t`Create a shared semantic layer of datasets and metrics your team can rely on, so things stay consistent as your data and downstream content grows.`}
|
description={t`Create a shared semantic layer of datasets and metrics your team can rely on, so things stay consistent as your data and downstream content grows.`}
|
||||||
image="app/assets/img/data-studio-library-upsell.svg"
|
image={libraryUpsellImage}
|
||||||
variant="image-full-height"
|
variant="image-full-height"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { t } from "ttag";
|
import { t } from "ttag";
|
||||||
|
|
||||||
|
import remoteSyncUpsellImage from "assets/img/data-studio-remote-sync-upsell.svg";
|
||||||
import { DottedBackground } from "metabase/common/components/upsells/components/DottedBackground";
|
import { DottedBackground } from "metabase/common/components/upsells/components/DottedBackground";
|
||||||
import { LineDecorator } from "metabase/common/components/upsells/components/LineDecorator";
|
import { LineDecorator } from "metabase/common/components/upsells/components/LineDecorator";
|
||||||
import { useUpgradeAction } from "metabase/common/components/upsells/components/UpgradeModal";
|
import { useUpgradeAction } from "metabase/common/components/upsells/components/UpgradeModal";
|
||||||
@@ -36,7 +37,7 @@ export function RemoteSyncUpsellPage() {
|
|||||||
location={LOCATION}
|
location={LOCATION}
|
||||||
title={t`Manage your ${applicationName} content in Git`}
|
title={t`Manage your ${applicationName} content in Git`}
|
||||||
description={t`Keep your most important datasets, metrics, and SQL logic under version control. Sync content to a Git repository to review changes, collaborate, and maintain a production-ready source of truth.`}
|
description={t`Keep your most important datasets, metrics, and SQL logic under version control. Sync content to a Git repository to review changes, collaborate, and maintain a production-ready source of truth.`}
|
||||||
image="app/assets/img/data-studio-remote-sync-upsell.svg"
|
image={remoteSyncUpsellImage}
|
||||||
upgradeOnClick={upgradeOnClick}
|
upgradeOnClick={upgradeOnClick}
|
||||||
upgradeUrl={upgradeUrl}
|
upgradeUrl={upgradeUrl}
|
||||||
variant="image-full-height"
|
variant="image-full-height"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { t } from "ttag";
|
import { t } from "ttag";
|
||||||
|
|
||||||
|
import schemaViewerUpsellImage from "assets/img/schema-viewer.svg";
|
||||||
import { usePageTitle } from "metabase/hooks/use-page-title";
|
import { usePageTitle } from "metabase/hooks/use-page-title";
|
||||||
|
|
||||||
import { BaseUpsellPage } from "./BaseUpsellPage";
|
import { BaseUpsellPage } from "./BaseUpsellPage";
|
||||||
@@ -14,7 +15,7 @@ export function SchemaViewerUpsellPage() {
|
|||||||
header={t`Schema viewer`}
|
header={t`Schema viewer`}
|
||||||
title={t`Visualize your database structure`}
|
title={t`Visualize your database structure`}
|
||||||
description={t`Explore tables and their relationships at a glance. Follow foreign keys, expand into related tables, and see how your schema fits together.`}
|
description={t`Explore tables and their relationships at a glance. Follow foreign keys, expand into related tables, and see how your schema fits together.`}
|
||||||
image="app/assets/img/schema-viewer.svg"
|
image={schemaViewerUpsellImage}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,23 @@
|
|||||||
import { t } from "ttag";
|
import { t } from "ttag";
|
||||||
|
|
||||||
|
import athenaLogo from "assets/img/drivers/athena.svg?url";
|
||||||
|
import bigqueryLogo from "assets/img/drivers/bigquery.svg?url";
|
||||||
|
import clickhouseLogo from "assets/img/drivers/clickhouse.svg?url";
|
||||||
|
import databricksLogo from "assets/img/drivers/databricks.svg?url";
|
||||||
|
import druidLogo from "assets/img/drivers/druid.svg?url";
|
||||||
|
import h2Logo from "assets/img/drivers/h2.svg?url";
|
||||||
|
import mongoLogo from "assets/img/drivers/mongo.svg?url";
|
||||||
|
import mysqlLogo from "assets/img/drivers/mysql.svg?url";
|
||||||
|
import oracleLogo from "assets/img/drivers/oracle.svg?url";
|
||||||
|
import postgresLogo from "assets/img/drivers/postgres.svg?url";
|
||||||
|
import prestoLogo from "assets/img/drivers/presto.svg?url";
|
||||||
|
import redshiftLogo from "assets/img/drivers/redshift.svg?url";
|
||||||
|
import snowflakeLogo from "assets/img/drivers/snowflake.svg?url";
|
||||||
|
import sparksqlLogo from "assets/img/drivers/sparksql.svg?url";
|
||||||
|
import sqliteLogo from "assets/img/drivers/sqlite.svg?url";
|
||||||
|
import sqlserverLogo from "assets/img/drivers/sqlserver.svg?url";
|
||||||
|
import starburstLogo from "assets/img/drivers/starburst.svg?url";
|
||||||
|
import verticaLogo from "assets/img/drivers/vertica.svg?url";
|
||||||
import { SAVED_QUESTIONS_VIRTUAL_DB_ID } from "metabase-lib/v1/metadata/utils/saved-questions";
|
import { SAVED_QUESTIONS_VIRTUAL_DB_ID } from "metabase-lib/v1/metadata/utils/saved-questions";
|
||||||
|
|
||||||
import DatabaseAuthCodeDescription from "./components/DatabaseAuthCodeDescription";
|
import DatabaseAuthCodeDescription from "./components/DatabaseAuthCodeDescription";
|
||||||
@@ -46,27 +64,27 @@ export const ELEVATED_ENGINES = [
|
|||||||
export const ALLOWED_ENGINES_FOR_TABLE_EDITING = ["postgres", "mysql"];
|
export const ALLOWED_ENGINES_FOR_TABLE_EDITING = ["postgres", "mysql"];
|
||||||
|
|
||||||
export const ENGINE_LOGO: Record<string, string> = {
|
export const ENGINE_LOGO: Record<string, string> = {
|
||||||
athena: "athena.svg",
|
athena: athenaLogo,
|
||||||
bigquery: "bigquery.svg",
|
bigquery: bigqueryLogo,
|
||||||
"bigquery-cloud-sdk": "bigquery.svg",
|
"bigquery-cloud-sdk": bigqueryLogo,
|
||||||
clickhouse: "clickhouse.svg",
|
clickhouse: clickhouseLogo,
|
||||||
databricks: "databricks.svg",
|
databricks: databricksLogo,
|
||||||
druid: "druid.svg",
|
druid: druidLogo,
|
||||||
"druid-jdbc": "druid.svg",
|
"druid-jdbc": druidLogo,
|
||||||
h2: "h2.svg",
|
h2: h2Logo,
|
||||||
mongo: "mongo.svg",
|
mongo: mongoLogo,
|
||||||
mysql: "mysql.svg",
|
mysql: mysqlLogo,
|
||||||
oracle: "oracle.svg",
|
oracle: oracleLogo,
|
||||||
postgres: "postgres.svg",
|
postgres: postgresLogo,
|
||||||
presto: "presto.svg",
|
presto: prestoLogo,
|
||||||
"presto-jdbc": "presto.svg",
|
"presto-jdbc": prestoLogo,
|
||||||
redshift: "redshift.svg",
|
redshift: redshiftLogo,
|
||||||
snowflake: "snowflake.svg",
|
snowflake: snowflakeLogo,
|
||||||
sparksql: "sparksql.svg",
|
sparksql: sparksqlLogo,
|
||||||
starburst: "starburst.svg",
|
starburst: starburstLogo,
|
||||||
sqlite: "sqlite.svg",
|
sqlite: sqliteLogo,
|
||||||
sqlserver: "sqlserver.svg",
|
sqlserver: sqlserverLogo,
|
||||||
vertica: "vertica.svg",
|
vertica: verticaLogo,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ADVANCED_FIELDS = [
|
export const ADVANCED_FIELDS = [
|
||||||
|
|||||||
@@ -40,8 +40,7 @@ const getEngineOption = (engineKey: string, engine: Engine) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const getEngineLogo = (engine: string): string | undefined => {
|
export const getEngineLogo = (engine: string): string | undefined => {
|
||||||
const logo = ENGINE_LOGO[engine];
|
return ENGINE_LOGO[engine];
|
||||||
return logo ? `app/assets/img/drivers/${logo}` : undefined;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getDefaultEngineKey = (engines: Record<string, Engine>) => {
|
export const getDefaultEngineKey = (engines: Record<string, Engine>) => {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import { useCallback } from "react";
|
import { useCallback } from "react";
|
||||||
import { t } from "ttag";
|
import { t } from "ttag";
|
||||||
|
|
||||||
|
import multiTenantUserStrategyImage from "assets/img/embedding-onboarding/multi-tenant-user-strategy.svg";
|
||||||
import {
|
import {
|
||||||
useCreateCollectionMutation,
|
useCreateCollectionMutation,
|
||||||
useListCollectionsTreeQuery,
|
useListCollectionsTreeQuery,
|
||||||
@@ -65,7 +66,7 @@ export const EnableTenantsStepContent = ({
|
|||||||
return (
|
return (
|
||||||
<Stack gap="xl">
|
<Stack gap="xl">
|
||||||
<img
|
<img
|
||||||
src="app/assets/img/embedding-onboarding/multi-tenant-user-strategy.svg"
|
src={multiTenantUserStrategyImage}
|
||||||
alt=""
|
alt=""
|
||||||
className={S.illustration}
|
className={S.illustration}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import styled from "@emotion/styled";
|
|||||||
import { match } from "ts-pattern";
|
import { match } from "ts-pattern";
|
||||||
import { t } from "ttag";
|
import { t } from "ttag";
|
||||||
|
|
||||||
|
import embedJsExampleImage from "assets/img/embed-js-example.png";
|
||||||
import { ExternalLink } from "metabase/common/components/ExternalLink";
|
import { ExternalLink } from "metabase/common/components/ExternalLink";
|
||||||
import { Link } from "metabase/common/components/Link";
|
import { Link } from "metabase/common/components/Link";
|
||||||
import { Box, Button, Group, Text } from "metabase/ui";
|
import { Box, Button, Group, Text } from "metabase/ui";
|
||||||
@@ -48,7 +49,7 @@ export const EmbedJsContent = ({
|
|||||||
</Text>
|
</Text>
|
||||||
{showImage && (
|
{showImage && (
|
||||||
<EmbedJsImage
|
<EmbedJsImage
|
||||||
src="/app/assets/img/embed-js-example.png"
|
src={embedJsExampleImage}
|
||||||
alt="Modular embedding example"
|
alt="Modular embedding example"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import styled from "@emotion/styled";
|
import styled from "@emotion/styled";
|
||||||
import { t } from "ttag";
|
import { t } from "ttag";
|
||||||
|
|
||||||
|
import staticEmbeddingExampleImage from "assets/img/static-embedding-example.png";
|
||||||
import { ExternalLink } from "metabase/common/components/ExternalLink";
|
import { ExternalLink } from "metabase/common/components/ExternalLink";
|
||||||
import { Link } from "metabase/common/components/Link";
|
import { Link } from "metabase/common/components/Link";
|
||||||
import { Box, Button, Group, Text } from "metabase/ui";
|
import { Box, Button, Group, Text } from "metabase/ui";
|
||||||
@@ -33,7 +34,7 @@ export const StaticEmbedContent = ({
|
|||||||
</Text>
|
</Text>
|
||||||
{showImage && (
|
{showImage && (
|
||||||
<StaticEmbedImage
|
<StaticEmbedImage
|
||||||
src="/app/assets/img/static-embedding-example.png"
|
src={staticEmbeddingExampleImage}
|
||||||
alt="Static embedding example"
|
alt="Static embedding example"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { t } from "ttag";
|
import { t } from "ttag";
|
||||||
|
|
||||||
|
import onboardingAiImage from "assets/img/onboarding_ai.png";
|
||||||
|
import onboardingAiImage2x from "assets/img/onboarding_ai@2x.png";
|
||||||
import { useSelector } from "metabase/redux";
|
import { useSelector } from "metabase/redux";
|
||||||
import { getApplicationName } from "metabase/selectors/whitelabel";
|
import { getApplicationName } from "metabase/selectors/whitelabel";
|
||||||
import { Text } from "metabase/ui";
|
import { Text } from "metabase/ui";
|
||||||
@@ -36,8 +38,8 @@ export const AiItem = ({ value, itemRef }: OnboardingItemProps) => {
|
|||||||
>
|
>
|
||||||
<ChecklistImage
|
<ChecklistImage
|
||||||
alt={t`Connecting an AI provider in the admin settings`}
|
alt={t`Connecting an AI provider in the admin settings`}
|
||||||
src="app/assets/img/onboarding_ai.png"
|
src={onboardingAiImage}
|
||||||
srcSet="app/assets/img/onboarding_ai@2x.png 2x"
|
srcSet={`${onboardingAiImage2x} 2x`}
|
||||||
style={ILLUSTRATION_STYLE}
|
style={ILLUSTRATION_STYLE}
|
||||||
/>
|
/>
|
||||||
<Text>
|
<Text>
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { jt, t } from "ttag";
|
import { jt, t } from "ttag";
|
||||||
|
|
||||||
|
import onboardingDataStudioImage from "assets/img/onboarding_data_studio.png";
|
||||||
|
import onboardingDataStudioImage2x from "assets/img/onboarding_data_studio@2x.png";
|
||||||
import { canAccessDataStudio } from "metabase/common/data-studio/selectors";
|
import { canAccessDataStudio } from "metabase/common/data-studio/selectors";
|
||||||
import { useSelector } from "metabase/redux";
|
import { useSelector } from "metabase/redux";
|
||||||
import { Text } from "metabase/ui";
|
import { Text } from "metabase/ui";
|
||||||
@@ -42,8 +44,8 @@ export const DataStudioItem = ({ value, itemRef }: OnboardingItemProps) => {
|
|||||||
>
|
>
|
||||||
<ChecklistImage
|
<ChecklistImage
|
||||||
alt={t`A table in the Data Studio library`}
|
alt={t`A table in the Data Studio library`}
|
||||||
src="app/assets/img/onboarding_data_studio.png"
|
src={onboardingDataStudioImage}
|
||||||
srcSet="app/assets/img/onboarding_data_studio@2x.png 2x"
|
srcSet={`${onboardingDataStudioImage2x} 2x`}
|
||||||
style={ILLUSTRATION_STYLE}
|
style={ILLUSTRATION_STYLE}
|
||||||
/>
|
/>
|
||||||
<Text>
|
<Text>
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { t } from "ttag";
|
import { t } from "ttag";
|
||||||
|
|
||||||
|
import onboardingDataDiagramImage from "assets/img/onboarding_data_diagram.png";
|
||||||
|
import onboardingDataDiagramImage2x from "assets/img/onboarding_data_diagram@2x.png";
|
||||||
import { useSelector } from "metabase/redux";
|
import { useSelector } from "metabase/redux";
|
||||||
import { getApplicationName } from "metabase/selectors/whitelabel";
|
import { getApplicationName } from "metabase/selectors/whitelabel";
|
||||||
import { Text } from "metabase/ui";
|
import { Text } from "metabase/ui";
|
||||||
@@ -26,8 +28,8 @@ export const DatabaseItem = ({ value, itemRef }: OnboardingItemProps) => {
|
|||||||
>
|
>
|
||||||
<ChecklistImage
|
<ChecklistImage
|
||||||
alt={`${applicationName} ${t`data stack`}`}
|
alt={`${applicationName} ${t`data stack`}`}
|
||||||
src="app/assets/img/onboarding_data_diagram.png"
|
src={onboardingDataDiagramImage}
|
||||||
srcSet="app/assets/img/onboarding_data_diagram@2x.png 2x"
|
srcSet={`${onboardingDataDiagramImage2x} 2x`}
|
||||||
/>
|
/>
|
||||||
<Text>
|
<Text>
|
||||||
{t`Connect one or more databases. You can query these databases directly, either with the query builder or the native SQL editor.`}
|
{t`Connect one or more databases. You can query these databases directly, either with the query builder or the native SQL editor.`}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { t } from "ttag";
|
import { t } from "ttag";
|
||||||
|
|
||||||
|
import onboardingInviteImage from "assets/img/onboarding_invite.png";
|
||||||
|
import onboardingInviteImage2x from "assets/img/onboarding_invite@2x.png";
|
||||||
import { useSelector } from "metabase/redux";
|
import { useSelector } from "metabase/redux";
|
||||||
import { getApplicationName } from "metabase/selectors/whitelabel";
|
import { getApplicationName } from "metabase/selectors/whitelabel";
|
||||||
import { Text } from "metabase/ui";
|
import { Text } from "metabase/ui";
|
||||||
@@ -40,8 +42,8 @@ export const InviteItem = ({ value, itemRef }: OnboardingItemProps) => {
|
|||||||
>
|
>
|
||||||
<ChecklistImage
|
<ChecklistImage
|
||||||
alt={t`Admin panel with the "Invite someone" button`}
|
alt={t`Admin panel with the "Invite someone" button`}
|
||||||
src="app/assets/img/onboarding_invite.png"
|
src={onboardingInviteImage}
|
||||||
srcSet="app/assets/img/onboarding_invite@2x.png 2x"
|
srcSet={`${onboardingInviteImage2x} 2x`}
|
||||||
style={ILLUSTRATION_STYLE}
|
style={ILLUSTRATION_STYLE}
|
||||||
/>
|
/>
|
||||||
<Text>
|
<Text>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { t } from "ttag";
|
import { t } from "ttag";
|
||||||
|
|
||||||
|
import lineChartImage from "assets/img/empty-states/visualizations/line.svg?url";
|
||||||
import CS from "metabase/css/core/index.css";
|
import CS from "metabase/css/core/index.css";
|
||||||
import { Box, Center, Stack, Text, Title } from "metabase/ui";
|
import { Box, Center, Stack, Text, Title } from "metabase/ui";
|
||||||
import { getSubpathSafeUrl } from "metabase/urls";
|
|
||||||
|
|
||||||
function MetricsViewerEmptyStateLayout({
|
function MetricsViewerEmptyStateLayout({
|
||||||
title,
|
title,
|
||||||
@@ -11,17 +11,13 @@ function MetricsViewerEmptyStateLayout({
|
|||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
}): JSX.Element {
|
}): JSX.Element {
|
||||||
const imgSrc = getSubpathSafeUrl(
|
|
||||||
"app/assets/img/empty-states/visualizations/line.svg",
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Center flex={1}>
|
<Center flex={1}>
|
||||||
<Stack>
|
<Stack>
|
||||||
<Box maw="20rem" mb="1.5rem">
|
<Box maw="20rem" mb="1.5rem">
|
||||||
<img
|
<img
|
||||||
className={CS.pointerEventsNone}
|
className={CS.pointerEventsNone}
|
||||||
src={imgSrc}
|
src={lineChartImage}
|
||||||
alt={t`Line chart illustration`}
|
alt={t`Line chart illustration`}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import type { ImageSource } from "metabase/common/components/EmptyState";
|
||||||
import { Link, type LinkProps } from "metabase/common/components/Link";
|
import { Link, type LinkProps } from "metabase/common/components/Link";
|
||||||
import { Box, Flex, Text, Title } from "metabase/ui";
|
import { Box, Flex, Text, Title } from "metabase/ui";
|
||||||
|
|
||||||
@@ -6,7 +7,7 @@ import S from "./NewModelOption.module.css";
|
|||||||
const DEFAULT_IMAGE_WIDTH = 210;
|
const DEFAULT_IMAGE_WIDTH = 210;
|
||||||
|
|
||||||
type NewModelOptionProps = LinkProps & {
|
type NewModelOptionProps = LinkProps & {
|
||||||
image: string;
|
image: ImageSource;
|
||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
width?: number;
|
width?: number;
|
||||||
@@ -22,7 +23,7 @@ export function NewModelOption({
|
|||||||
return (
|
return (
|
||||||
<Link {...props} className={S.linkWrapper}>
|
<Link {...props} className={S.linkWrapper}>
|
||||||
<Flex align="center" justify="center" h="10rem">
|
<Flex align="center" justify="center" h="10rem">
|
||||||
<img src={`${image}.png`} srcSet={`${image}@2x.png 2x`} width={width} />
|
<img src={image.src} srcSet={image.srcSet} width={width} />
|
||||||
</Flex>
|
</Flex>
|
||||||
<Box my="lg">
|
<Box my="lg">
|
||||||
<Title order={2} className={S.modelTitle}>
|
<Title order={2} className={S.modelTitle}>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { Route } from "metabase/router";
|
|||||||
import { NewModelOption } from "./NewModelOption";
|
import { NewModelOption } from "./NewModelOption";
|
||||||
|
|
||||||
const defaultProps = {
|
const defaultProps = {
|
||||||
image: "app/img/sql_illustration",
|
image: { src: "sql_illustration.png", srcSet: "sql_illustration@2x.png 2x" },
|
||||||
title: "Use a native query",
|
title: "Use a native query",
|
||||||
description: "Fall back to SQL.",
|
description: "Fall back to SQL.",
|
||||||
to: "/model/new/native",
|
to: "/model/new/native",
|
||||||
@@ -42,8 +42,8 @@ describe("NewModelOption", () => {
|
|||||||
setup({ width: 180 });
|
setup({ width: 180 });
|
||||||
|
|
||||||
const img = screen.getByRole("img");
|
const img = screen.getByRole("img");
|
||||||
expect(img).toHaveAttribute("src", "app/img/sql_illustration.png");
|
expect(img).toHaveAttribute("src", defaultProps.image.src);
|
||||||
expect(img).toHaveAttribute("srcset", "app/img/sql_illustration@2x.png 2x");
|
expect(img).toHaveAttribute("srcset", defaultProps.image.srcSet);
|
||||||
expect(img).toHaveAttribute("width", "180");
|
expect(img).toHaveAttribute("width", "180");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import cx from "classnames";
|
import cx from "classnames";
|
||||||
import { t } from "ttag";
|
import { t } from "ttag";
|
||||||
|
|
||||||
|
import notebookModeIllustration from "img/notebook_mode_illustration.png";
|
||||||
|
import notebookModeIllustration2x from "img/notebook_mode_illustration@2x.png";
|
||||||
|
import sqlIllustration from "img/sql_illustration.png";
|
||||||
|
import sqlIllustration2x from "img/sql_illustration@2x.png";
|
||||||
import { ExternalLink } from "metabase/common/components/ExternalLink";
|
import { ExternalLink } from "metabase/common/components/ExternalLink";
|
||||||
import { NoDatabasesEmptyState } from "metabase/common/components/NoDatabasesEmptyState";
|
import { NoDatabasesEmptyState } from "metabase/common/components/NoDatabasesEmptyState";
|
||||||
import CS from "metabase/css/core/index.css";
|
import CS from "metabase/css/core/index.css";
|
||||||
@@ -60,7 +64,10 @@ const NewModelOptions = () => {
|
|||||||
{hasDataAccess && (
|
{hasDataAccess && (
|
||||||
<div>
|
<div>
|
||||||
<NewModelOption
|
<NewModelOption
|
||||||
image="app/img/notebook_mode_illustration"
|
image={{
|
||||||
|
src: notebookModeIllustration,
|
||||||
|
srcSet: `${notebookModeIllustration2x} 2x`,
|
||||||
|
}}
|
||||||
title={t`Use the notebook editor`}
|
title={t`Use the notebook editor`}
|
||||||
description={t`This automatically inherits metadata from your source tables, and gives your models drill-through.`}
|
description={t`This automatically inherits metadata from your source tables, and gives your models drill-through.`}
|
||||||
width={180}
|
width={180}
|
||||||
@@ -76,7 +83,10 @@ const NewModelOptions = () => {
|
|||||||
{hasNativeWrite && (
|
{hasNativeWrite && (
|
||||||
<div>
|
<div>
|
||||||
<NewModelOption
|
<NewModelOption
|
||||||
image="app/img/sql_illustration"
|
image={{
|
||||||
|
src: sqlIllustration,
|
||||||
|
srcSet: `${sqlIllustration2x} 2x`,
|
||||||
|
}}
|
||||||
title={t`Use a native query`}
|
title={t`Use a native query`}
|
||||||
description={t`You can always fall back to a SQL or native query, which is a bit more manual.`}
|
description={t`You can always fall back to a SQL or native query, which is a bit more manual.`}
|
||||||
to={Urls.newQuestion({
|
to={Urls.newQuestion({
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { t } from "ttag";
|
import { t } from "ttag";
|
||||||
|
|
||||||
|
import dependencyDiagnosticsUpsellImage from "assets/img/data-studio-dependency-diagnostics-upsell.svg";
|
||||||
import { usePageTitle } from "metabase/hooks/use-page-title";
|
import { usePageTitle } from "metabase/hooks/use-page-title";
|
||||||
import { BaseUpsellPage } from "metabase/monitor/upsells";
|
import { BaseUpsellPage } from "metabase/monitor/upsells";
|
||||||
|
|
||||||
@@ -13,7 +14,7 @@ export function DependencyDiagnosticsUpsellPage() {
|
|||||||
header={t`Dependency diagnostics`}
|
header={t`Dependency diagnostics`}
|
||||||
title={t`Find and fix broken dependencies without hunting them down`}
|
title={t`Find and fix broken dependencies without hunting them down`}
|
||||||
description={t`Detect broken dependencies across datasets and dashboards, and resolve issues without checking everything manually.`}
|
description={t`Detect broken dependencies across datasets and dashboards, and resolve issues without checking everything manually.`}
|
||||||
image="app/assets/img/data-studio-dependency-diagnostics-upsell.svg"
|
image={dependencyDiagnosticsUpsellImage}
|
||||||
variant="image-full-height"
|
variant="image-full-height"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { t } from "ttag";
|
import { t } from "ttag";
|
||||||
|
|
||||||
|
import upsellPerformanceToolsImage from "assets/img/upsell-performance-tools.png";
|
||||||
import { usePageTitle } from "metabase/hooks/use-page-title";
|
import { usePageTitle } from "metabase/hooks/use-page-title";
|
||||||
import { BaseUpsellPage } from "metabase/monitor/upsells";
|
import { BaseUpsellPage } from "metabase/monitor/upsells";
|
||||||
|
|
||||||
@@ -13,7 +14,7 @@ export const MonitorUpsell = () => {
|
|||||||
header={t`Erroring questions`}
|
header={t`Erroring questions`}
|
||||||
title={t`Troubleshoot faster`}
|
title={t`Troubleshoot faster`}
|
||||||
description={t`Find and fix issues fast, with an overview of all errors and model caching logs.`}
|
description={t`Find and fix issues fast, with an overview of all errors and model caching logs.`}
|
||||||
image="app/assets/img/upsell-performance-tools.png"
|
image={upsellPerformanceToolsImage}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import type { ReactNode } from "react";
|
|||||||
import { match } from "ts-pattern";
|
import { match } from "ts-pattern";
|
||||||
import { c, t } from "ttag";
|
import { c, t } from "ttag";
|
||||||
|
|
||||||
|
import databasesImage from "assets/img/empty-states/databases.svg?url";
|
||||||
import { Link } from "metabase/common/components/Link";
|
import { Link } from "metabase/common/components/Link";
|
||||||
import { StoragePurchaseButton } from "metabase/common/components/upsells/StoragePurchaseModal";
|
import { StoragePurchaseButton } from "metabase/common/components/upsells/StoragePurchaseModal";
|
||||||
import { useSelector } from "metabase/redux";
|
import { useSelector } from "metabase/redux";
|
||||||
@@ -18,7 +19,6 @@ import {
|
|||||||
Text,
|
Text,
|
||||||
Title,
|
Title,
|
||||||
} from "metabase/ui";
|
} from "metabase/ui";
|
||||||
import { getSubpathSafeUrl } from "metabase/urls";
|
|
||||||
|
|
||||||
import IconCSV from "./illustrations/csv.svg?component";
|
import IconCSV from "./illustrations/csv.svg?component";
|
||||||
|
|
||||||
@@ -160,15 +160,11 @@ const AddDataEmptyState = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const DatabasePanelEmptyState = () => {
|
export const DatabasePanelEmptyState = () => {
|
||||||
const illustration = getSubpathSafeUrl(
|
|
||||||
"app/assets/img/empty-states/databases.svg",
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AddDataEmptyState
|
<AddDataEmptyState
|
||||||
title={t`Add a database`}
|
title={t`Add a database`}
|
||||||
subtitle={t`Start exploring in minutes. We support more than 20 data connectors.`}
|
subtitle={t`Start exploring in minutes. We support more than 20 data connectors.`}
|
||||||
illustration={<Center component="img" src={illustration} w="3rem" />}
|
illustration={<Center component="img" src={databasesImage} w="3rem" />}
|
||||||
contactAdminReason="add-database"
|
contactAdminReason="add-database"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import type { ComponentType, ReactNode } from "react";
|
|||||||
import { t } from "ttag";
|
import { t } from "ttag";
|
||||||
|
|
||||||
import noResultsSource from "assets/img/no_results.svg";
|
import noResultsSource from "assets/img/no_results.svg";
|
||||||
|
import bridgeImage from "img/bridge.svg?url";
|
||||||
import type { AdminPathKey, State } from "metabase/redux/store";
|
import type { AdminPathKey, State } from "metabase/redux/store";
|
||||||
import type { UiParameter } from "metabase-lib/v1/parameters/types";
|
import type { UiParameter } from "metabase-lib/v1/parameters/types";
|
||||||
import type { Dashboard, DraftDashboardSubscription } from "metabase-types/api";
|
import type { Dashboard, DraftDashboardSubscription } from "metabase-types/api";
|
||||||
@@ -33,12 +34,12 @@ interface PluginDashboardSubscriptionParametersSectionOverride {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const defaultLandingPageIllustration = {
|
const defaultLandingPageIllustration = {
|
||||||
src: "app/img/bridge.svg",
|
src: bridgeImage,
|
||||||
isDefault: true,
|
isDefault: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultLoginPageIllustration = {
|
const defaultLoginPageIllustration = {
|
||||||
src: "app/img/bridge.svg",
|
src: bridgeImage,
|
||||||
isDefault: true,
|
isDefault: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import cx from "classnames";
|
|||||||
import type { CSSProperties } from "react";
|
import type { CSSProperties } from "react";
|
||||||
import { t } from "ttag";
|
import { t } from "ttag";
|
||||||
|
|
||||||
|
import segmentsListImage from "assets/img/segments-list.png";
|
||||||
|
import segmentsListImage2x from "assets/img/segments-list@2x.png";
|
||||||
import { useListSegmentsQuery } from "metabase/api";
|
import { useListSegmentsQuery } from "metabase/api";
|
||||||
import { AdminAwareEmptyState } from "metabase/common/components/AdminAwareEmptyState";
|
import { AdminAwareEmptyState } from "metabase/common/components/AdminAwareEmptyState";
|
||||||
import { LoadingAndErrorWrapper } from "metabase/common/components/LoadingAndErrorWrapper";
|
import { LoadingAndErrorWrapper } from "metabase/common/components/LoadingAndErrorWrapper";
|
||||||
@@ -26,7 +28,10 @@ const emptyStateData = {
|
|||||||
get message() {
|
get message() {
|
||||||
return t`Segments will appear here once your admins have created some`;
|
return t`Segments will appear here once your admins have created some`;
|
||||||
},
|
},
|
||||||
image: "app/assets/img/segments-list",
|
image: {
|
||||||
|
src: segmentsListImage,
|
||||||
|
srcSet: `${segmentsListImage2x} 2x`,
|
||||||
|
},
|
||||||
get adminAction() {
|
get adminAction() {
|
||||||
return t`Learn how to create segments`;
|
return t`Learn how to create segments`;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,22 @@
|
|||||||
import { t } from "ttag";
|
import { t } from "ttag";
|
||||||
|
|
||||||
|
import areaImage from "assets/img/empty-states/visualizations/area.svg?url";
|
||||||
|
import barImage from "assets/img/empty-states/visualizations/bar.svg?url";
|
||||||
|
import comboImage from "assets/img/empty-states/visualizations/combo.svg?url";
|
||||||
|
import funnelImage from "assets/img/empty-states/visualizations/funnel.svg?url";
|
||||||
|
import gaugeImage from "assets/img/empty-states/visualizations/gauge.svg?url";
|
||||||
|
import lineImage from "assets/img/empty-states/visualizations/line.svg?url";
|
||||||
|
import mapRegionImage from "assets/img/empty-states/visualizations/map-region.svg?url";
|
||||||
|
import pieImage from "assets/img/empty-states/visualizations/pie.svg?url";
|
||||||
|
import pivotImage from "assets/img/empty-states/visualizations/pivot.svg?url";
|
||||||
|
import progressImage from "assets/img/empty-states/visualizations/progress.svg?url";
|
||||||
|
import rowImage from "assets/img/empty-states/visualizations/row.svg?url";
|
||||||
|
import sankeyImage from "assets/img/empty-states/visualizations/sankey.svg?url";
|
||||||
|
import scalarImage from "assets/img/empty-states/visualizations/scalar.svg?url";
|
||||||
|
import scatterImage from "assets/img/empty-states/visualizations/scatter.svg?url";
|
||||||
|
import smartscalarImage from "assets/img/empty-states/visualizations/smartscalar.svg?url";
|
||||||
|
import treemapImage from "assets/img/empty-states/visualizations/treemap.svg?url";
|
||||||
|
import waterfallImage from "assets/img/empty-states/visualizations/waterfall.svg?url";
|
||||||
import { getSubpathSafeUrl } from "metabase/urls";
|
import { getSubpathSafeUrl } from "metabase/urls";
|
||||||
import type { CardDisplayType } from "metabase-types/api";
|
import type { CardDisplayType } from "metabase-types/api";
|
||||||
|
|
||||||
@@ -24,16 +41,9 @@ type EmptyVizConfig = {
|
|||||||
docsLink?: string;
|
docsLink?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
// We should not import these large empty-state images directly,
|
|
||||||
// because we don't need to inline them as base64 to the SDK bundle.
|
|
||||||
// Instead we just define paths to them that is passed to the <img> tag directly.
|
|
||||||
const emptyVizConfig: Record<SupportedDisplayType, EmptyVizConfig> = {
|
const emptyVizConfig: Record<SupportedDisplayType, EmptyVizConfig> = {
|
||||||
area: {
|
area: {
|
||||||
get imgSrc() {
|
imgSrc: areaImage,
|
||||||
return getSubpathSafeUrl(
|
|
||||||
"app/assets/img/empty-states/visualizations/area.svg",
|
|
||||||
);
|
|
||||||
},
|
|
||||||
get primaryText() {
|
get primaryText() {
|
||||||
return t`Then pick a metric and multiple columns to group by.`;
|
return t`Then pick a metric and multiple columns to group by.`;
|
||||||
},
|
},
|
||||||
@@ -42,11 +52,7 @@ const emptyVizConfig: Record<SupportedDisplayType, EmptyVizConfig> = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
bar: {
|
bar: {
|
||||||
get imgSrc() {
|
imgSrc: barImage,
|
||||||
return getSubpathSafeUrl(
|
|
||||||
"app/assets/img/empty-states/visualizations/bar.svg",
|
|
||||||
);
|
|
||||||
},
|
|
||||||
get primaryText() {
|
get primaryText() {
|
||||||
return t`Then pick a metric and a column to group by.`;
|
return t`Then pick a metric and a column to group by.`;
|
||||||
},
|
},
|
||||||
@@ -55,11 +61,7 @@ const emptyVizConfig: Record<SupportedDisplayType, EmptyVizConfig> = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
combo: {
|
combo: {
|
||||||
get imgSrc() {
|
imgSrc: comboImage,
|
||||||
return getSubpathSafeUrl(
|
|
||||||
"app/assets/img/empty-states/visualizations/combo.svg",
|
|
||||||
);
|
|
||||||
},
|
|
||||||
get primaryText() {
|
get primaryText() {
|
||||||
return t`Then pick two or more metrics and one or two columns to group by.`;
|
return t`Then pick two or more metrics and one or two columns to group by.`;
|
||||||
},
|
},
|
||||||
@@ -68,11 +70,7 @@ const emptyVizConfig: Record<SupportedDisplayType, EmptyVizConfig> = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
funnel: {
|
funnel: {
|
||||||
get imgSrc() {
|
imgSrc: funnelImage,
|
||||||
return getSubpathSafeUrl(
|
|
||||||
"app/assets/img/empty-states/visualizations/funnel.svg",
|
|
||||||
);
|
|
||||||
},
|
|
||||||
get primaryText() {
|
get primaryText() {
|
||||||
return t`Funnel charts visualize how a value is broken out by a series of steps, and the percent change between steps.`;
|
return t`Funnel charts visualize how a value is broken out by a series of steps, and the percent change between steps.`;
|
||||||
},
|
},
|
||||||
@@ -82,11 +80,7 @@ const emptyVizConfig: Record<SupportedDisplayType, EmptyVizConfig> = {
|
|||||||
docsLink: getSubpathSafeUrl("questions/visualizations/funnel"),
|
docsLink: getSubpathSafeUrl("questions/visualizations/funnel"),
|
||||||
},
|
},
|
||||||
gauge: {
|
gauge: {
|
||||||
get imgSrc() {
|
imgSrc: gaugeImage,
|
||||||
return getSubpathSafeUrl(
|
|
||||||
"app/assets/img/empty-states/visualizations/gauge.svg",
|
|
||||||
);
|
|
||||||
},
|
|
||||||
get primaryText() {
|
get primaryText() {
|
||||||
return t`Then pick an aggregate metric (such as Average or Sum) and customize the gauge in the visualization settings.`;
|
return t`Then pick an aggregate metric (such as Average or Sum) and customize the gauge in the visualization settings.`;
|
||||||
},
|
},
|
||||||
@@ -95,11 +89,7 @@ const emptyVizConfig: Record<SupportedDisplayType, EmptyVizConfig> = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
line: {
|
line: {
|
||||||
get imgSrc() {
|
imgSrc: lineImage,
|
||||||
return getSubpathSafeUrl(
|
|
||||||
"app/assets/img/empty-states/visualizations/line.svg",
|
|
||||||
);
|
|
||||||
},
|
|
||||||
get primaryText() {
|
get primaryText() {
|
||||||
return t`Then pick one or more metrics and a time column to group by.`;
|
return t`Then pick one or more metrics and a time column to group by.`;
|
||||||
},
|
},
|
||||||
@@ -108,11 +98,7 @@ const emptyVizConfig: Record<SupportedDisplayType, EmptyVizConfig> = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
map: {
|
map: {
|
||||||
get imgSrc() {
|
imgSrc: mapRegionImage,
|
||||||
return getSubpathSafeUrl(
|
|
||||||
"app/assets/img/empty-states/visualizations/map-region.svg",
|
|
||||||
);
|
|
||||||
},
|
|
||||||
get primaryText() {
|
get primaryText() {
|
||||||
return t`Build map visualizations with geospatial data: Pin and Grid maps require longitude and latitude columns, Region maps require a column with region names.`;
|
return t`Build map visualizations with geospatial data: Pin and Grid maps require longitude and latitude columns, Region maps require a column with region names.`;
|
||||||
},
|
},
|
||||||
@@ -122,11 +108,7 @@ const emptyVizConfig: Record<SupportedDisplayType, EmptyVizConfig> = {
|
|||||||
docsLink: "questions/visualizations/map",
|
docsLink: "questions/visualizations/map",
|
||||||
},
|
},
|
||||||
pie: {
|
pie: {
|
||||||
get imgSrc() {
|
imgSrc: pieImage,
|
||||||
return getSubpathSafeUrl(
|
|
||||||
"app/assets/img/empty-states/visualizations/pie.svg",
|
|
||||||
);
|
|
||||||
},
|
|
||||||
get primaryText() {
|
get primaryText() {
|
||||||
return t`Then pick a metric and a column to group by.`;
|
return t`Then pick a metric and a column to group by.`;
|
||||||
},
|
},
|
||||||
@@ -135,11 +117,7 @@ const emptyVizConfig: Record<SupportedDisplayType, EmptyVizConfig> = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
pivot: {
|
pivot: {
|
||||||
get imgSrc() {
|
imgSrc: pivotImage,
|
||||||
return getSubpathSafeUrl(
|
|
||||||
"app/assets/img/empty-states/visualizations/pivot.svg",
|
|
||||||
);
|
|
||||||
},
|
|
||||||
get primaryText() {
|
get primaryText() {
|
||||||
return t`Then pick an aggregate metric (such as Average or Sum) and multiple columns to group by.`;
|
return t`Then pick an aggregate metric (such as Average or Sum) and multiple columns to group by.`;
|
||||||
},
|
},
|
||||||
@@ -148,11 +126,7 @@ const emptyVizConfig: Record<SupportedDisplayType, EmptyVizConfig> = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
progress: {
|
progress: {
|
||||||
get imgSrc() {
|
imgSrc: progressImage,
|
||||||
return getSubpathSafeUrl(
|
|
||||||
"app/assets/img/empty-states/visualizations/progress.svg",
|
|
||||||
);
|
|
||||||
},
|
|
||||||
get primaryText() {
|
get primaryText() {
|
||||||
return t`Then pick an aggregate metric (such as Count or Sum) and customize the progress bar in the visualization settings.`;
|
return t`Then pick an aggregate metric (such as Count or Sum) and customize the progress bar in the visualization settings.`;
|
||||||
},
|
},
|
||||||
@@ -161,11 +135,7 @@ const emptyVizConfig: Record<SupportedDisplayType, EmptyVizConfig> = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
row: {
|
row: {
|
||||||
get imgSrc() {
|
imgSrc: rowImage,
|
||||||
return getSubpathSafeUrl(
|
|
||||||
"app/assets/img/empty-states/visualizations/row.svg",
|
|
||||||
);
|
|
||||||
},
|
|
||||||
get primaryText() {
|
get primaryText() {
|
||||||
return t`Then pick a metric and a column to group by.`;
|
return t`Then pick a metric and a column to group by.`;
|
||||||
},
|
},
|
||||||
@@ -174,11 +144,7 @@ const emptyVizConfig: Record<SupportedDisplayType, EmptyVizConfig> = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
sankey: {
|
sankey: {
|
||||||
get imgSrc() {
|
imgSrc: sankeyImage,
|
||||||
return getSubpathSafeUrl(
|
|
||||||
"app/assets/img/empty-states/visualizations/sankey.svg",
|
|
||||||
);
|
|
||||||
},
|
|
||||||
get primaryText() {
|
get primaryText() {
|
||||||
return t`Sankey charts show how data flows through multi-dimensional steps. They're useful for showing which elements, called nodes, contribute to the overall flow.`;
|
return t`Sankey charts show how data flows through multi-dimensional steps. They're useful for showing which elements, called nodes, contribute to the overall flow.`;
|
||||||
},
|
},
|
||||||
@@ -188,22 +154,14 @@ const emptyVizConfig: Record<SupportedDisplayType, EmptyVizConfig> = {
|
|||||||
docsLink: "questions/visualizations/sankey",
|
docsLink: "questions/visualizations/sankey",
|
||||||
},
|
},
|
||||||
scalar: {
|
scalar: {
|
||||||
get imgSrc() {
|
imgSrc: scalarImage,
|
||||||
return getSubpathSafeUrl(
|
|
||||||
"app/assets/img/empty-states/visualizations/scalar.svg",
|
|
||||||
);
|
|
||||||
},
|
|
||||||
get primaryText() {
|
get primaryText() {
|
||||||
return t`Then pick an aggregate metric (such as Average or Sum).`;
|
return t`Then pick an aggregate metric (such as Average or Sum).`;
|
||||||
},
|
},
|
||||||
secondaryText: `E.g. Average star rating`,
|
secondaryText: `E.g. Average star rating`,
|
||||||
},
|
},
|
||||||
scatter: {
|
scatter: {
|
||||||
get imgSrc() {
|
imgSrc: scatterImage,
|
||||||
return getSubpathSafeUrl(
|
|
||||||
"app/assets/img/empty-states/visualizations/scatter.svg",
|
|
||||||
);
|
|
||||||
},
|
|
||||||
get primaryText() {
|
get primaryText() {
|
||||||
return t`Then pick one or more metrics and a column to group by.`;
|
return t`Then pick one or more metrics and a column to group by.`;
|
||||||
},
|
},
|
||||||
@@ -212,11 +170,7 @@ const emptyVizConfig: Record<SupportedDisplayType, EmptyVizConfig> = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
smartscalar: {
|
smartscalar: {
|
||||||
get imgSrc() {
|
imgSrc: smartscalarImage,
|
||||||
return getSubpathSafeUrl(
|
|
||||||
"app/assets/img/empty-states/visualizations/smartscalar.svg",
|
|
||||||
);
|
|
||||||
},
|
|
||||||
get primaryText() {
|
get primaryText() {
|
||||||
return t`Then pick an aggregate metric (such as the Average or Sum) and a time column to group by.`;
|
return t`Then pick an aggregate metric (such as the Average or Sum) and a time column to group by.`;
|
||||||
},
|
},
|
||||||
@@ -225,11 +179,7 @@ const emptyVizConfig: Record<SupportedDisplayType, EmptyVizConfig> = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
treemap: {
|
treemap: {
|
||||||
get imgSrc() {
|
imgSrc: treemapImage,
|
||||||
return getSubpathSafeUrl(
|
|
||||||
"app/assets/img/empty-states/visualizations/treemap.svg",
|
|
||||||
);
|
|
||||||
},
|
|
||||||
get primaryText() {
|
get primaryText() {
|
||||||
return t`Then pick a metric and one or two columns to group by.`;
|
return t`Then pick a metric and one or two columns to group by.`;
|
||||||
},
|
},
|
||||||
@@ -238,11 +188,7 @@ const emptyVizConfig: Record<SupportedDisplayType, EmptyVizConfig> = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
waterfall: {
|
waterfall: {
|
||||||
get imgSrc() {
|
imgSrc: waterfallImage,
|
||||||
return getSubpathSafeUrl(
|
|
||||||
"app/assets/img/empty-states/visualizations/waterfall.svg",
|
|
||||||
);
|
|
||||||
},
|
|
||||||
get primaryText() {
|
get primaryText() {
|
||||||
return t`Then pick a metric and a single column to group by: either time or category.`;
|
return t`Then pick a metric and a single column to group by: either time or category.`;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
|
import mapImage from "assets/img/map.svg?url";
|
||||||
import { ChartSkeletonImage } from "metabase/visualizations/components/skeletons/ChartSkeleton/ChartSkeletonImage";
|
import { ChartSkeletonImage } from "metabase/visualizations/components/skeletons/ChartSkeleton/ChartSkeletonImage";
|
||||||
|
|
||||||
const MapSkeleton = (): JSX.Element => {
|
const MapSkeleton = (): JSX.Element => {
|
||||||
return (
|
return (
|
||||||
<ChartSkeletonImage viewBox="0 0 242 157" preserveAspectRatio="xMidYMid">
|
<ChartSkeletonImage viewBox="0 0 242 157" preserveAspectRatio="xMidYMid">
|
||||||
<use xlinkHref="/app/assets/img/map.svg" />
|
<use xlinkHref={mapImage} />
|
||||||
</ChartSkeletonImage>
|
</ChartSkeletonImage>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,9 +3,10 @@ import { useCallback, useMemo } from "react";
|
|||||||
import { useLatest } from "react-use";
|
import { useLatest } from "react-use";
|
||||||
import { t } from "ttag";
|
import { t } from "ttag";
|
||||||
|
|
||||||
|
import hiddenFieldImage from "assets/img/hidden-field.png?url";
|
||||||
|
import hiddenFieldImage2x from "assets/img/hidden-field@2x.png?url";
|
||||||
import CS from "metabase/css/core/index.css";
|
import CS from "metabase/css/core/index.css";
|
||||||
import { useQuestionFromCardBuilder } from "metabase/metadata-store";
|
import { useQuestionFromCardBuilder } from "metabase/metadata-store";
|
||||||
import { getSubpathSafeUrl } from "metabase/urls";
|
|
||||||
import {
|
import {
|
||||||
type VisibleTableData,
|
type VisibleTableData,
|
||||||
getVisibleTableData,
|
getVisibleTableData,
|
||||||
@@ -97,13 +98,6 @@ function useSyncedQuestion(series: VisualizationProps["series"]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function AllFieldsHiddenMessage({ isDashboard }: { isDashboard: boolean }) {
|
function AllFieldsHiddenMessage({ isDashboard }: { isDashboard: boolean }) {
|
||||||
const allFieldsHiddenImageUrl = getSubpathSafeUrl(
|
|
||||||
"app/assets/img/hidden-field.png",
|
|
||||||
);
|
|
||||||
const allFieldsHiddenImage2xUrl = getSubpathSafeUrl(
|
|
||||||
"app/assets/img/hidden-field@2x.png",
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cx(
|
className={cx(
|
||||||
@@ -120,10 +114,10 @@ function AllFieldsHiddenMessage({ isDashboard }: { isDashboard: boolean }) {
|
|||||||
<img
|
<img
|
||||||
data-testid="Table-all-fields-hidden-image"
|
data-testid="Table-all-fields-hidden-image"
|
||||||
width={99}
|
width={99}
|
||||||
src={allFieldsHiddenImageUrl}
|
src={hiddenFieldImage}
|
||||||
srcSet={`
|
srcSet={`
|
||||||
${allFieldsHiddenImageUrl} 1x,
|
${hiddenFieldImage} 1x,
|
||||||
${allFieldsHiddenImage2xUrl} 2x
|
${hiddenFieldImage2x} 2x
|
||||||
`}
|
`}
|
||||||
className={CS.mb2}
|
className={CS.mb2}
|
||||||
/>
|
/>
|
||||||
|
|||||||
20
frontend/src/types/import-image.d.ts
vendored
Normal file
20
frontend/src/types/import-image.d.ts
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
declare module "*.png" {
|
||||||
|
const value: string;
|
||||||
|
|
||||||
|
// eslint-disable-next-line import/no-default-export
|
||||||
|
export default value;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module "*.png?url" {
|
||||||
|
const value: string;
|
||||||
|
|
||||||
|
// eslint-disable-next-line import/no-default-export
|
||||||
|
export default value;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module "*.gif" {
|
||||||
|
const value: string;
|
||||||
|
|
||||||
|
// eslint-disable-next-line import/no-default-export
|
||||||
|
export default value;
|
||||||
|
}
|
||||||
7
frontend/src/types/import-svg.d.ts
vendored
7
frontend/src/types/import-svg.d.ts
vendored
@@ -5,6 +5,13 @@ declare module "*.svg" {
|
|||||||
export default value;
|
export default value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare module "*.svg?url" {
|
||||||
|
const value: string;
|
||||||
|
|
||||||
|
// eslint-disable-next-line import/no-default-export
|
||||||
|
export default value;
|
||||||
|
}
|
||||||
|
|
||||||
declare module "*.svg?source" {
|
declare module "*.svg?source" {
|
||||||
const value: string;
|
const value: string;
|
||||||
|
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ const baseConfig = {
|
|||||||
"^remend$": "<rootDir>/node_modules/remend/dist/index.js",
|
"^remend$": "<rootDir>/node_modules/remend/dist/index.js",
|
||||||
"^build-configs/(.*)$": "<rootDir>/frontend/build/$1",
|
"^build-configs/(.*)$": "<rootDir>/frontend/build/$1",
|
||||||
"\\.(css|less)$": "<rootDir>/frontend/test/__mocks__/styleMock.js",
|
"\\.(css|less)$": "<rootDir>/frontend/test/__mocks__/styleMock.js",
|
||||||
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$":
|
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)(\\?url)?$":
|
||||||
"<rootDir>/frontend/test/__mocks__/fileMock.js",
|
"<rootDir>/frontend/test/__mocks__/fileMock.js",
|
||||||
"^cljs/(.*)$": "<rootDir>/target/cljs_dev/$1",
|
"^cljs/(.*)$": "<rootDir>/target/cljs_dev/$1",
|
||||||
// The real catalogues are a build artifact, so tests resolve a small fixture
|
// The real catalogues are a build artifact, so tests resolve a small fixture
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ const esmPackages = require("./jest.esm-packages.js");
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
moduleNameMapper: {
|
moduleNameMapper: {
|
||||||
"\\.(css|less)$": "<rootDir>/frontend/test/__mocks__/styleMock.js",
|
"\\.(css|less)$": "<rootDir>/frontend/test/__mocks__/styleMock.js",
|
||||||
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$":
|
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)(\\?url)?$":
|
||||||
"<rootDir>/frontend/test/__mocks__/fileMock.js",
|
"<rootDir>/frontend/test/__mocks__/fileMock.js",
|
||||||
"^cljs/(.*)$": "<rootDir>/target/cljs_dev/$1",
|
"^cljs/(.*)$": "<rootDir>/target/cljs_dev/$1",
|
||||||
"\\.svg\\?(component|source)":
|
"\\.svg\\?(component|source)":
|
||||||
|
|||||||
@@ -134,7 +134,19 @@ const config = {
|
|||||||
{
|
{
|
||||||
test: /\.(svg|png)$/,
|
test: /\.(svg|png)$/,
|
||||||
type: "asset/inline",
|
type: "asset/inline",
|
||||||
resourceQuery: { not: [/component|source/] },
|
resourceQuery: { not: [/component|source|url/] },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// `?url` opts an image out of inlining. The app build owns the emitted
|
||||||
|
// file, this build only needs its URL: `../dist/` resolves from the
|
||||||
|
// SDK's publicPath (`app/embedding-sdk/`) to the app's `app/dist/`.
|
||||||
|
test: /\.(svg|png)$/,
|
||||||
|
resourceQuery: /url/,
|
||||||
|
type: "asset/resource",
|
||||||
|
generator: {
|
||||||
|
emit: false,
|
||||||
|
filename: "../dist/[hash][ext]",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.css$/,
|
test: /\.css$/,
|
||||||
|
|||||||
@@ -238,6 +238,9 @@ const config = {
|
|||||||
test: /\.(svg|png)$/,
|
test: /\.(svg|png)$/,
|
||||||
type: "asset/resource",
|
type: "asset/resource",
|
||||||
resourceQuery: { not: [/component|source/] },
|
resourceQuery: { not: [/component|source/] },
|
||||||
|
// No `[query]`: a `?url` import must emit the same name as the SDK
|
||||||
|
// build references.
|
||||||
|
generator: { filename: "[hash][ext]" },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.css$/,
|
test: /\.css$/,
|
||||||
|
|||||||
Reference in New Issue
Block a user