Skip to content

[UBP] Nudge users toward pay-as-you-go in workspace class preferences #15190

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Dec 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion components/dashboard/src/components/Alert.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ const infoMap: Record<AlertType, AlertInfo> = {
iconColor: "text-gray-400",
},
message: {
bgCls: "bg-blue-50 dark:bg-blue-700",
bgCls: "bg-blue-50 dark:bg-blue-800",
txtCls: "text-blue-800 dark:text-blue-100",
icon: <InfoSvg className="w-4 h-4"></InfoSvg>,
iconColor: "text-blue-400",
Expand Down
52 changes: 43 additions & 9 deletions components/dashboard/src/projects/ProjectSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import PillLabel from "../components/PillLabel";
import { ProjectContext } from "./project-context";
import SelectWorkspaceClass from "../settings/selectClass";
import { BillingMode } from "@gitpod/gitpod-protocol/lib/billing-mode";
import Alert from "../components/Alert";
import { Link } from "react-router-dom";

export function getProjectSettingsMenu(project?: Project, team?: Team) {
const teamOrUserSlug = !!team ? "t/" + team.slug : "projects";
Expand Down Expand Up @@ -48,12 +50,14 @@ export function ProjectSettingsPage(props: { project?: Project; children?: React

export default function () {
const { project, setProject } = useContext(ProjectContext);
const [teamBillingMode, setTeamBillingMode] = useState<BillingMode | undefined>(undefined);
const [billingMode, setBillingMode] = useState<BillingMode | undefined>(undefined);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: Probably not the best line to comment on this, but the Usage tab is visible for teams even if you haven't enabled UBP for your account. Is this known or expected?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch! But yes, I believe this is expected. The reason is that viewing the Usage for your team makes sense even if you're not yet on UBP.

const { teams } = useContext(TeamsContext);
const team = getCurrentTeam(useLocation(), teams);
useEffect(() => {
if (team) {
getGitpodService().server.getBillingModeForTeam(team.id).then(setTeamBillingMode);
getGitpodService().server.getBillingModeForTeam(team.id).then(setBillingMode);
} else {
getGitpodService().server.getBillingModeForUser().then(setBillingMode);
}
}, [team]);

Expand Down Expand Up @@ -156,13 +160,43 @@ export default function () {
</div>
</div>
</div>
{BillingMode.canSetWorkspaceClass(teamBillingMode) && (
<SelectWorkspaceClass
workspaceClass={project.settings?.workspaceClasses?.regular}
enabled={BillingMode.canSetWorkspaceClass(teamBillingMode)}
setWorkspaceClass={setWorkspaceClass}
/>
)}
<div>
<h3 className="mt-12">Workspaces</h3>
<p className="text-base text-gray-500 dark:text-gray-400">
Choose the workspace machine type for your workspaces.
</p>
Comment on lines +164 to +167
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thought: I find the section heading here unnecessary as if we're trying to fool users by advertizing a feature that does not exist, or they can not reach, especially since there are no other workspace settings, and we're not allowing users to close this alert. I'd drop the section heading, but I'll leave you and @jldec decide because of the relevant comment in #14831 (comment). 🏓

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @gtsiolis. I agree that we wouldn't want to fool our users about anything.

Ping @jldec for final decision about the heading 🙂 🙏

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's clearer with the heading Let's leave it in.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DEAL—Let's go with that and change later if needed. 🤝

{BillingMode.canSetWorkspaceClass(billingMode) ? (
<SelectWorkspaceClass
workspaceClass={project.settings?.workspaceClasses?.regular}
setWorkspaceClass={setWorkspaceClass}
/>
) : (
<Alert type="message" className="mt-4">
<div className="flex flex-col">
<span>
To access{" "}
<a
className="gp-link"
href="https://www.gitpod.io/docs/configure/workspaces/workspace-classes"
>
large workspaces
</a>{" "}
and{" "}
<a
className="gp-link"
href="https://www.gitpod.io/docs/configure/billing/pay-as-you-go"
>
pay-as-you-go
</a>
, first cancel your existing plan.
</span>
<Link className="mt-2" to={project.teamId ? "../billing" : "/plans"}>
<button>Go to {project.teamId ? "Team" : "Personal"} Billing</button>
</Link>
</div>
</Alert>
)}
</div>
</ProjectSettingsPage>
);
}
45 changes: 17 additions & 28 deletions components/dashboard/src/settings/selectClass.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import WorkspaceClass from "../components/WorkspaceClass";
import { SupportedWorkspaceClass } from "@gitpod/gitpod-protocol/lib/workspace-class";

interface SelectWorkspaceClassProps {
enabled: boolean;
workspaceClass?: string;
setWorkspaceClass: (value: string) => Promise<string | undefined>;
}
Expand Down Expand Up @@ -44,31 +43,21 @@ export default function SelectWorkspaceClass(props: SelectWorkspaceClassProps) {
fetchClasses().catch(console.error);
}, []);

if (!props.enabled) {
return <div></div>;
} else {
return (
<div>
<h3 className="mt-12">Workspaces</h3>
<p className="text-base text-gray-500 dark:text-gray-400">
Choose the workspace machine type for your workspaces.
</p>
<div className="mt-4 space-x-3 flex">
{supportedClasses.map((c) => {
return (
<WorkspaceClass
additionalStyles="w-80 h-32"
selected={workspaceClass === c.id}
onClick={() => actuallySetWorkspaceClass(c.id)}
category={c.category}
friendlyName={c.displayName}
description={c.description}
powerUps={c.powerups}
/>
);
})}
</div>
</div>
);
}
return (
<div className="mt-4 space-x-3 flex">
{supportedClasses.map((c) => {
return (
<WorkspaceClass
additionalStyles="w-80 h-32"
selected={workspaceClass === c.id}
onClick={() => actuallySetWorkspaceClass(c.id)}
category={c.category}
friendlyName={c.displayName}
description={c.description}
powerUps={c.powerups}
/>
);
})}
</div>
);
}
31 changes: 31 additions & 0 deletions components/dashboard/src/teams/TeamBilling.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import TeamUsageBasedBilling from "./TeamUsageBasedBilling";
import { UserContext } from "../user-context";
import { FeatureFlagContext } from "../contexts/FeatureFlagContext";
import { publicApiTeamMembersToProtocol, teamsService } from "../service/public-api";
import Alert from "../components/Alert";
import { getExperimentsClient } from "../experiments/client";

type PendingPlan = Plan & { pendingSince: number };

Expand All @@ -38,6 +40,7 @@ export default function TeamBilling() {
const [isUserOwner, setIsUserOwner] = useState(true);
const [teamSubscription, setTeamSubscription] = useState<TeamSubscription2 | undefined>();
const { currency, setCurrency } = useContext(PaymentContext);
const [isUsageBasedBillingEnabled, setIsUsageBasedBillingEnabled] = useState<boolean>(false);
const [teamBillingMode, setTeamBillingMode] = useState<BillingMode | undefined>(undefined);
const [pendingTeamPlan, setPendingTeamPlan] = useState<PendingPlan | undefined>();
const [pollTeamSubscriptionTimeout, setPollTeamSubscriptionTimeout] = useState<NodeJS.Timeout | undefined>();
Expand Down Expand Up @@ -114,6 +117,20 @@ export default function TeamBilling() {
};
}, [pendingTeamPlan, pollTeamSubscriptionTimeout, team, teamSubscription]);

useEffect(() => {
if (!team || !user) {
return;
}
(async () => {
const isEnabled = await getExperimentsClient().getValueAsync("isUsageBasedBillingEnabled", false, {
user,
teamId: team.id,
teamName: team.name,
});
setIsUsageBasedBillingEnabled(isEnabled);
})();
}, [team, user]);

const availableTeamPlans = Plans.getAvailableTeamPlans(currency || "USD").filter((p) => p.type !== "student");

const checkout = async (plan: Plan) => {
Expand Down Expand Up @@ -161,6 +178,20 @@ export default function TeamBilling() {
function renderTeamBilling(): JSX.Element {
return (
<>
{isUsageBasedBillingEnabled && (
<Alert type="message" className="mb-4">
To access{" "}
<a className="gp-link" href="https://www.gitpod.io/docs/configure/workspaces/workspace-classes">
large workspaces
</a>{" "}
and{" "}
<a className="gp-link" href="https://www.gitpod.io/docs/configure/billing/pay-as-you-go">
pay-as-you-go
</a>
, first cancel your existing plan. Existing plans will keep working until the end of March,
2023.
</Alert>
)}
<h3>{!teamPlan ? "Select Team Plan" : "Team Plan"}</h3>
<h2 className="text-gray-500">
{!teamPlan ? (
Expand Down