Skip to content
Snippets Groups Projects
Unverified Commit 5f6f1d23 authored by shaun's avatar shaun Committed by GitHub
Browse files

Nudge larger basic instances to Pro (#31630)

Place a nudge card below admin people sidebar if managing over 50 users without a pro account
parent a5d3f0d9
No related merge requests found
import styled from "@emotion/styled";
import { color } from "metabase/lib/colors";
import ExternalLink from "metabase/core/components/ExternalLink";
export const NudgeCard = styled.div`
background-color: ${color("bg-light")};
border-radius: 0.375rem;
padding: 1.25rem 1.5rem;
margin-top: 2rem;
display: flex;
flex-direction: column;
`;
export const Description = styled.div`
margin-top: 1rem;
`;
export const Subtitle = styled.div`
margin-top: 0.5rem;
font-weight: 700;
`;
export const ProLink = styled(ExternalLink)`
margin-top: 1rem;
font-weight: 700;
padding: 0.75rem 1rem;
border: 1px solid ${color("brand")};
border-radius: 0.5rem;
color: ${color("brand")};
width: fit-content;
&:hover {
color: ${color("white")};
background-color: ${color("brand")};
}
`;
import { t } from "ttag";
import { getUpgradeUrl } from "metabase/selectors/settings";
import { useSelector } from "metabase/lib/redux";
import { Icon } from "metabase/core/components/Icon";
import { color } from "metabase/lib/colors";
import { NudgeCard, Description, Subtitle, ProLink } from "./NudgeToPro.styled";
export const NudgeToPro = () => {
const upgradeUrl = useSelector(state =>
getUpgradeUrl(state, { utm_media: "people" }),
);
return (
<NudgeCard>
<Icon name="group" size={40} color={color("brand")} />
<Description>{t`Tired of manually managing people and groups?`}</Description>
<Subtitle>{t`Get single-sign on (SSO) via SAML, JWT, or LDAP with Metabase Pro`}</Subtitle>
<ProLink href={upgradeUrl}>{t`Learn more`}</ProLink>
</NudgeCard>
);
};
......@@ -2,3 +2,5 @@ export const USER_STATUS = {
active: "active",
deactivated: "deactivated",
};
export const ACTIVE_USERS_NUDGE_THRESHOLD = 50;
import styled from "@emotion/styled";
export const LeftNavWrapper = styled.div`
display: flex;
flex-direction: column;
width: 266px;
flex-shrink: 0;
`;
/* eslint "react/prop-types": "warn" */
import { Component } from "react";
import PropTypes from "prop-types";
import * as React from "react";
import { t } from "ttag";
import { LeftNavPane, LeftNavPaneItem } from "metabase/components/LeftNavPane";
import { NudgeToPro } from "metabase/admin/people/components/NudgeToPro";
import AdminLayout from "metabase/components/AdminLayout";
import { shouldNudgeToPro } from "metabase/admin/people/selectors";
import { useSelector } from "metabase/lib/redux";
import { LeftNavWrapper } from "./AdminPeopleApp.styled";
export default class AdminPeopleApp extends Component {
static propTypes = {
children: PropTypes.any,
};
render() {
const { children } = this.props;
return (
<AdminLayout
sidebar={
<LeftNavPane>
export const AdminPeopleApp = ({ children }: { children: React.ReactNode }) => {
const shouldNudge = useSelector(shouldNudgeToPro);
return (
<AdminLayout
sidebar={
<LeftNavWrapper>
<LeftNavPane fullHeight={!shouldNudge}>
<LeftNavPaneItem name={t`People`} path="/admin/people" index />
<LeftNavPaneItem name={t`Groups`} path="/admin/people/groups" />
</LeftNavPane>
}
>
{children}
</AdminLayout>
);
}
}
{shouldNudge && <NudgeToPro />}
</LeftNavWrapper>
}
>
{children}
</AdminLayout>
);
};
import {
createMockTokenFeatures,
createMockUser,
} from "metabase-types/api/mocks";
import {
createMockSettingsState,
createMockState,
} from "metabase-types/store/mocks";
import { renderWithProviders, screen } from "__support__/ui";
import { AdminPeopleApp } from "metabase/admin/people/containers/AdminPeopleApp";
interface SetupOpts {
activeUsersCount: number;
ssoEnabled: boolean;
isSuperUser: boolean;
}
const setup = ({ activeUsersCount, ssoEnabled, isSuperUser }: SetupOpts) => {
const state = createMockState({
currentUser: createMockUser({
is_superuser: isSuperUser,
}),
settings: createMockSettingsState({
"active-users-count": activeUsersCount,
"token-features": createMockTokenFeatures({
sso: ssoEnabled,
}),
}),
});
renderWithProviders(<AdminPeopleApp>empty</AdminPeopleApp>, {
storeInitialState: state,
});
};
describe("AdminPeopleApp", () => {
describe("nudge to pro", () => {
const nudgeText = /Get single-sign on/;
const setupOpts = {
activeUsersCount: 50,
ssoEnabled: false,
isSuperUser: true,
};
it("should be visible when user is admin, has 50 active users, and SSO is not available", () => {
setup(setupOpts);
expect(screen.getByText(nudgeText)).toBeInTheDocument();
const link = screen.getByRole("link", { name: /Learn more/i });
expect(link).toBeInTheDocument();
expect(link).toHaveAttribute(
"href",
expect.stringMatching(/^https:\/\/www.metabase.com\/upgrade/),
);
});
it("should not be visible with less than 50 users", () => {
setup({ ...setupOpts, activeUsersCount: 10 });
expect(screen.queryByText(nudgeText)).not.toBeInTheDocument();
});
it("should not be visible when user is not admin", () => {
setup({ ...setupOpts, isSuperUser: false });
expect(screen.queryByText(nudgeText)).not.toBeInTheDocument();
});
it("should not be visible when SSO is already available", () => {
setup({ ...setupOpts, ssoEnabled: true });
expect(screen.queryByText(nudgeText)).not.toBeInTheDocument();
});
});
});
import { createSelector } from "@reduxjs/toolkit";
import { getSetting } from "metabase/selectors/settings";
import { getUserIsAdmin } from "metabase/selectors/user";
import { ACTIVE_USERS_NUDGE_THRESHOLD } from "metabase/admin/people/constants";
export const getMemberships = state => state.admin.people.memberships;
......@@ -31,3 +34,14 @@ export const getUserMemberships = createSelector(
export const getUserTemporaryPassword = (state, props) =>
state.admin.people.temporaryPasswords[props.userId];
export const shouldNudgeToPro = createSelector(
state => getSetting(state, "token-features").sso,
state => getUserIsAdmin(state),
state => getSetting(state, "active-users-count"),
(ssoEnabled, isAdmin, numActiveUsers) => {
return (
!ssoEnabled && isAdmin && numActiveUsers >= ACTIVE_USERS_NUDGE_THRESHOLD
);
},
);
......@@ -42,7 +42,7 @@ import MetricApp from "metabase/admin/datamodel/containers/MetricApp";
import SegmentListApp from "metabase/admin/datamodel/containers/SegmentListApp";
import SegmentApp from "metabase/admin/datamodel/containers/SegmentApp";
import RevisionHistoryApp from "metabase/admin/datamodel/containers/RevisionHistoryApp";
import AdminPeopleApp from "metabase/admin/people/containers/AdminPeopleApp";
import { AdminPeopleApp } from "metabase/admin/people/containers/AdminPeopleApp";
import TroubleshootingApp from "metabase/admin/tasks/containers/TroubleshootingApp";
import {
......
/* eslint-disable react/prop-types */
import { Link, IndexLink } from "react-router";
import { t } from "ttag";
import cx from "classnames";
export function LeftNavPaneItem({ name, path, index = false }) {
return (
......@@ -39,9 +40,13 @@ export function LeftNavPaneItemBack({ path }) {
);
}
export function LeftNavPane({ children }) {
export function LeftNavPane({ children, fullHeight = true }) {
return (
<div className="MetadataEditor-table-list AdminList flex-no-shrink full-height">
<div
className={cx("MetadataEditor-table-list AdminList flex-no-shrink", {
"full-height": fullHeight,
})}
>
<ul className="AdminList-items pt1">{children}</ul>
</div>
);
......
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