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

Better communicate embedding milestone 2 (#23571)

* WIP embedding changes

* Simplify logic to not render nested setting page in the sidebar

* Add sub setting for embedding for admin

* Update embedding copies for OSS users

* Fix E2E tests
parent 0381866b
No related branches found
No related tags found
No related merge requests found
Showing
with 261 additions and 85 deletions
......@@ -69,7 +69,6 @@ PLUGIN_ADMIN_SETTINGS_UPDATES.push(sections =>
PLUGIN_ADMIN_SETTINGS_UPDATES.push(sections => ({
...sections,
"authentication/saml": {
sidebar: false,
component: SettingsSAMLForm,
settings: [
{
......@@ -165,7 +164,6 @@ PLUGIN_ADMIN_SETTINGS_UPDATES.push(sections => ({
],
},
"authentication/jwt": {
sidebar: false,
component: SettingsJWTForm,
settings: [
{
......@@ -290,7 +288,6 @@ PLUGIN_ADMIN_SETTINGS_UPDATES.push(sections => ({
...sections,
"authentication/google": {
component: SettingsGoogleForm,
sidebar: false,
settings: [
{
key: "google-auth-client-id",
......
......@@ -5,6 +5,7 @@ import { updateIn } from "icepick";
import MetabaseSettings from "metabase/lib/settings";
import { hasPremiumFeature } from "metabase-enterprise/settings";
import { PLUGIN_ADMIN_SETTINGS_UPDATES } from "metabase/plugins";
import ExternalLink from "metabase/core/components/ExternalLink";
if (hasPremiumFeature("embedding")) {
MetabaseSettings.hideEmbedBranding = () => true;
......@@ -13,37 +14,35 @@ if (hasPremiumFeature("embedding")) {
const APP_ORIGIN_SETTING = {
key: "embedding-app-origin",
display_name: t`Embedding the entire Metabase app`,
description: jt`If you want to embed all of Metabase, enter the origins of the websites or web apps where you want to allow embedding in an iframe, separated by a space. Here are the ${(
<a
href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-ancestors"
className="link"
>
exact specifications
</a>
)} for what can be entered.`,
description: (
<>
{jt`With this Pro/Enterprise feature you can embed the full Metabase app. Enable your users to drill-through to charts, browse collections, and use the graphical query builder. ${(
<ExternalLink href="https://www.metabase.com/learn/embedding/multi-tenant-self-service-analytics.html">
{t`Learn more.`}
</ExternalLink>
)}`}
<div className="my4">
<strong className="block text-dark mb1">{t`Authorized origins`}</strong>
{jt`Enter the origins for the websites or web apps where you want to allow embedding, separated by a space. Here are the ${(
<ExternalLink href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-ancestors">
{t`exact specifications`}
</ExternalLink>
)} for what can be entered.`}
</div>
</>
),
placeholder: "https://*.example.com",
type: "string",
getHidden: settings => !settings["enable-embedding"],
getHidden: settings =>
!settings["enable-embedding"] || !MetabaseSettings.isEnterprise(),
};
PLUGIN_ADMIN_SETTINGS_UPDATES.push(sections =>
updateIn(
sections,
["embedding_in_other_applications", "settings"],
["embedding_in_other_applications/full-app", "settings"],
settings => {
// insert the app origin setting right after the secret key widget
const itemIndex = settings.findIndex(
s => s.key === "embedding-secret-key",
);
const sliceIndex = itemIndex === -1 ? settings.length : itemIndex + 1;
settings = [
...settings.slice(0, sliceIndex),
APP_ORIGIN_SETTING,
...settings.slice(sliceIndex),
];
return settings;
return [...settings, APP_ORIGIN_SETTING];
},
),
);
......@@ -10,6 +10,7 @@ export const SettingTitle = styled.label`
`;
export const SettingDescription = styled.div`
line-height: 1.5;
color: ${color("text-medium")};
margin: ${space(1)} 0;
max-width: 38.75rem;
......
import React from "react";
import { jt } from "ttag";
import { t, jt } from "ttag";
import MetabaseSettings from "metabase/lib/settings";
import ExternalLink from "metabase/core/components/ExternalLink";
import SettingHeader from "../SettingHeader";
export const EmbeddingCustomizationInfo = () => {
const setting = {
display_name: "Customization",
description: (
<p style={{ maxWidth: "460px" }}>
{jt`Looking to remove the “Powered by Metabase” logo, customize colors
and make it your own? ${(
<ExternalLink href={MetabaseSettings.upgradeUrl()}>
Explore our paid plans.
</ExternalLink>
)}`}
</p>
),
description: jt`In order to remove the Metabase logo from embeds, you can always upgrade to one of our ${(
<ExternalLink href={MetabaseSettings.upgradeUrl()}>
{t`paid plans.`}
</ExternalLink>
)}`,
};
return <SettingHeader id="embedding-customization-info" setting={setting} />;
......
import styled from "@emotion/styled";
import { color } from "metabase/lib/colors";
import { space } from "metabase/styled-components/theme";
import Card from "metabase/components/Card";
import Icon from "metabase/components/Icon";
export const StyledCard = styled(Card)`
padding: 2rem;
width: 100%;
max-width: 31.25rem;
`;
export const Label = styled.span`
margin-bottom: ${space(2)};
padding: ${space(0)} ${space(1)};
display: inline-block;
line-height: 1.3;
font-size: 0.75rem;
font-weight: 700;
border-radius: 0.25rem;
text-transform: uppercase;
color: ${color("brand")};
background: ${color("brand-light")};
`;
export const Header = styled.h2`
font-size: 0.875rem;
margin-bottom: ${space(1)};
`;
export const Description = styled.p`
margin-top: 0;
margin-bottom: ${space(2)};
color: ${color("text-medium")};
`;
export const MoreDetails = styled.span`
font-weight: 700;
`;
export const StyledIcon = styled(Icon)`
margin-left: ${space(1)};
`;
import React from "react";
import { t } from "ttag";
import { Link } from "react-router";
import {
Description,
Header,
Label,
StyledCard,
StyledIcon,
MoreDetails,
} from "./EmbeddingOption.styled";
interface EmbeddingOptionProps {
setting: {
embedName: string;
embedDescription: string;
embedType: "standalone" | "full-app";
};
}
export default function EmbeddingOption({ setting }: EmbeddingOptionProps) {
return (
<Link
to={`/admin/settings/embedding_in_other_applications/${setting.embedType}`}
>
<StyledCard compact>
{setting.embedType === "full-app" && <Label>{t`Paid`}</Label>}
<Header>{setting.embedName}</Header>
<Description>{setting.embedDescription}</Description>
<div>
<MoreDetails className="link">
More details
<StyledIcon name="triangle_right" size={7} />
</MoreDetails>
</div>
</StyledCard>
</Link>
);
}
export { default } from "./EmbeddingOption";
import styled from "@emotion/styled";
import { color } from "metabase/lib/colors";
export const PremiumEmbeddingLinkWidgetRoot = styled.div`
color: ${color("text-medium")};
padding-top: 1rem;
width: 100%;
max-width: 38.75rem;
border-top: 1px solid ${color("border")};
`;
import React from "react";
import { t } from "ttag";
import Link from "metabase/core/components/Link";
import { PremiumEmbeddingLinkWidgetRoot } from "./PremiumEmbeddingLinkWidget.styled";
import { jt, t } from "ttag";
import MetabaseSettings from "metabase/lib/settings";
import ExternalLink from "metabase/core/components/ExternalLink";
import SettingHeader from "../../SettingHeader";
export const PremiumEmbeddingLinkWidget = () => {
return (
<PremiumEmbeddingLinkWidgetRoot>
{t`Have a Premium Embedding license?`}{" "}
<Link
to="/admin/settings/premium-embedding-license"
className="link"
>{t`Activate it here.`}</Link>
</PremiumEmbeddingLinkWidgetRoot>
);
const setting = {
display_name: t`embedding the entire metabase app`,
description: jt`With some of our ${(
<ExternalLink href={MetabaseSettings.upgradeUrl()}>
{t`paid plans,`}
</ExternalLink>
)} you can embed the full Metabase app and enable your users to drill-through to charts, browse collections, and use the graphical query builder. You can also get priority support, more tools to help you share your insights with your teams and powerful options to help you create seamless, interactive data experiences for your customers.`,
};
return <SettingHeader id="embedding-customization-info" setting={setting} />;
};
import { useEffect } from "react";
import { connect } from "react-redux";
import { LocationAction, replace } from "react-router-redux";
interface RedirectWidgetProps {
to: string;
replace: LocationAction;
}
function RedirectWidget({ to, replace }: RedirectWidgetProps) {
useEffect(() => {
replace(to);
}, [replace, to]);
return null;
}
const mapDispatchToProps = {
replace,
};
export default connect(null, mapDispatchToProps)(RedirectWidget);
export { default } from "./RedirectWidget";
......@@ -193,8 +193,9 @@ class SettingsEditorApp extends Component {
// HACK - This is used to hide specific items in the sidebar and is currently
// only used as a way to fake the multi page auth settings pages without
// requiring a larger refactor.
if (section.sidebar === false) {
return false;
const isNestedSettingPage = Boolean(slug.split("/")[1]);
if (isNestedSettingPage) {
return null;
}
// The nested authentication routes should be matched just on the prefix:
......
......@@ -33,6 +33,9 @@ import { trackTrackingPermissionChanged } from "./analytics";
import { PersistedModelsApi, UtilApi } from "metabase/services";
import { PLUGIN_ADMIN_SETTINGS_UPDATES } from "metabase/plugins";
import { getUserIsAdmin } from "metabase/selectors/user";
import Breadcrumbs from "metabase/components/Breadcrumbs";
import EmbeddingOption from "./components/widgets/EmbeddingOption";
import RedirectWidget from "./components/widgets/RedirectWidget";
// This allows plugins to update the settings sections
function updateSectionsWithPlugins(sections) {
......@@ -369,14 +372,42 @@ const SECTIONS = updateSectionsWithPlugins({
getHidden: (_, derivedSettings) => !derivedSettings["enable-embedding"],
},
{
widget: EmbeddingCustomizationInfo,
getHidden: (_, derivedSettings) =>
!derivedSettings["enable-embedding"] ||
MetabaseSettings.isEnterprise(),
widget: EmbeddingOption,
getHidden: (_, derivedSettings) => !derivedSettings["enable-embedding"],
embedName: t`Standalone embeds`,
embedDescription: t`Securely embed individual questions and dashboards within other applications.`,
embedType: "standalone",
},
{
widget: EmbeddingOption,
getHidden: (_, derivedSettings) => !derivedSettings["enable-embedding"],
embedName: t`Full-app embedding`,
embedDescription: t`With this Pro/Enterprise feature you can embed the full Metabase app. Enable your users to drill-through to charts, browse collections, and use the graphical query builder.`,
embedType: "full-app",
},
],
},
"embedding_in_other_applications/standalone": {
settings: [
{
widget: () => {
return (
<Breadcrumbs
crumbs={[
[
t`Embedding`,
"/admin/settings/embedding_in_other_applications",
],
[t`Standalone embeds`],
]}
/>
);
},
},
{
key: "embedding-secret-key",
display_name: t`Embedding secret key`,
description: t`Standalone Embed Secret Key used to sign JSON Web Tokens for requests to /api/embed endpoints. This lets you create a secure environment limited to specific users or organizations.`,
widget: SecretKeyWidget,
getHidden: (_, derivedSettings) => !derivedSettings["enable-embedding"],
},
......@@ -392,12 +423,49 @@ const SECTIONS = updateSectionsWithPlugins({
widget: EmbeddedQuestionListing,
getHidden: (_, derivedSettings) => !derivedSettings["enable-embedding"],
},
{
widget: EmbeddingCustomizationInfo,
getHidden: (_, derivedSettings) =>
!derivedSettings["enable-embedding"] ||
MetabaseSettings.isEnterprise(),
},
{
widget: () => (
<RedirectWidget to="/admin/settings/embedding_in_other_applications" />
),
getHidden: (_, derivedSettings) => derivedSettings["enable-embedding"],
},
],
},
"embedding_in_other_applications/full-app": {
settings: [
{
widget: () => {
return (
<Breadcrumbs
crumbs={[
[
t`Embedding`,
"/admin/settings/embedding_in_other_applications",
],
[t`Full-app embedding`],
]}
/>
);
},
},
{
widget: PremiumEmbeddingLinkWidget,
getHidden: (_, derivedSettings) =>
!derivedSettings["enable-embedding"] ||
MetabaseSettings.isEnterprise(),
},
{
widget: () => (
<RedirectWidget to="/admin/settings/embedding_in_other_applications" />
),
getHidden: (_, derivedSettings) => derivedSettings["enable-embedding"],
},
],
},
license: {
......
......@@ -41,7 +41,6 @@ PLUGIN_ADMIN_SETTINGS_UPDATES.push(sections => ({
...sections,
"authentication/google": {
component: SettingsGoogleForm,
sidebar: false,
settings: [
{
key: "google-auth-client-id",
......
......@@ -25,7 +25,6 @@ PLUGIN_ADMIN_SETTINGS_UPDATES.push(
sections => ({
...sections,
"authentication/ldap": {
sidebar: false,
component: SettingsLdapForm,
settings: [
{
......
......@@ -30,11 +30,14 @@ describe(
);
cy.visit(embeddingPage);
cy.findByText("Full-app embedding").click();
cy.contains("Have a Premium Embedding license?");
cy.contains("Activate it here.").click();
cy.contains(
"With some of our paid plans, you can embed the full Metabase app and enable your users to drill-through to charts, browse collections, and use the graphical query builder. You can also get priority support, more tools to help you share your insights with your teams and powerful options to help you create seamless, interactive data experiences for your customers.",
);
cy.location("pathname").should("eq", licensePage);
// Old premium embedding page
cy.visit(licensePage);
cy.findByRole("heading")
.invoke("text")
......
......@@ -31,7 +31,9 @@ describe("scenarios > embedding > smoke tests", () => {
it("should display the embedding page correctly", { tags: "@OSS" }, () => {
cy.visit("/admin/settings/setup");
cy.findByText("Embedding").click();
sidebar().within(() => {
cy.findByText("Embedding").click();
});
cy.location("pathname").should("eq", embeddingPage);
......@@ -52,39 +54,46 @@ describe("scenarios > embedding > smoke tests", () => {
cy.location("pathname").should("eq", embeddingPage);
cy.findByText("Enabled");
cy.findByText("Standalone embeds").click();
if (isOSS) {
cy.findByText(/Customization/i);
cy.findByText(
"Looking to remove the “Powered by Metabase” logo, customize colors and make it your own?",
cy.contains(
"In order to remove the Metabase logo from embeds, you can always upgrade to one of our paid plans.",
);
assertLinkMatchesUrl("Explore our paid plans.", upgradeUrl);
assertLinkMatchesUrl("paid plans.", upgradeUrl);
}
cy.findByText(/Embedding secret key/i);
cy.findByText(
"Secret key used to sign JSON Web Tokens for requests to `/api/embed` endpoints.",
"Standalone Embed Secret Key used to sign JSON Web Tokens for requests to /api/embed endpoints. This lets you create a secure environment limited to specific users or organizations.",
);
getTokenValue().should("have.length", 64);
cy.button("Regenerate key");
// List of all embedded dashboards and questions
cy.findByText(/Embedded dashboards/i);
cy.findByText("No dashboards have been embedded yet.");
cy.findByText(/Embedded questions/i);
cy.findByText("No questions have been embedded yet.");
// Full app embedding section (available only for EE version and in PRO hosted plans)
if (isEE) {
sidebar().within(() => {
cy.findByText("Embedding").click();
});
cy.findByText("Full-app embedding").click();
cy.findByText(/Embedding the entire Metabase app/i);
cy.contains(
"If you want to embed all of Metabase, enter the origins of the websites or web apps where you want to allow embedding in an iframe, separated by a space. Here are the exact specifications for what can be entered.",
"With this Pro/Enterprise feature you can embed the full Metabase app. Enable your users to drill-through to charts, browse collections, and use the graphical query builder. Learn more.",
);
cy.contains(
"Enter the origins for the websites or web apps where you want to allow embedding, separated by a space. Here are the exact specifications for what can be entered.",
);
cy.findByPlaceholderText("https://*.example.com").should("be.empty");
}
// List of all embedded dashboards and questions
cy.findByText(/Embedded dashboards/i);
cy.findByText("No dashboards have been embedded yet.");
cy.findByText(/Embedded questions/i);
cy.findByText("No questions have been embedded yet.");
});
it("should not let you embed the question", () => {
......@@ -154,6 +163,7 @@ describe("scenarios > embedding > smoke tests", () => {
cy.signInAsAdmin();
cy.visit(embeddingPage);
cy.findByText("Standalone embeds").click();
cy.wait("@currentlyEmbeddedObject");
const sectionName = new RegExp(`Embedded ${object}s`, "i");
......@@ -180,6 +190,7 @@ describe("scenarios > embedding > smoke tests", () => {
cy.signInAsAdmin();
cy.visit(embeddingPage);
cy.findByText("Standalone embeds").click();
cy.wait("@currentlyEmbeddedObject");
cy.contains(/No (questions|dashboards) have been embedded yet./);
......@@ -250,3 +261,7 @@ function visitAndEnableSharing(object) {
cy.findByText(/Embed this (question|dashboard) in an application/).click();
}
}
function sidebar() {
return cy.get(".AdminList");
}
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