diff --git a/components/dashboard/src/start/CreateWorkspace.tsx b/components/dashboard/src/start/CreateWorkspace.tsx
index 26d775aacd8adf..b645df3e8238d6 100644
--- a/components/dashboard/src/start/CreateWorkspace.tsx
+++ b/components/dashboard/src/start/CreateWorkspace.tsx
@@ -11,6 +11,7 @@ import {
RunningWorkspacePrebuildStarting,
ContextURL,
DisposableCollection,
+ Team,
} from "@gitpod/gitpod-protocol";
import { ErrorCodes } from "@gitpod/gitpod-protocol/lib/messaging/error";
import Modal from "../components/Modal";
@@ -26,6 +27,8 @@ import CodeText from "../components/CodeText";
import FeedbackComponent from "../feedback-form/FeedbackComponent";
import { isGitpodIo } from "../utils";
import { BillingAccountSelector } from "../components/BillingAccountSelector";
+import { AttributionId } from "@gitpod/gitpod-protocol/lib/attribution";
+import { TeamsContext } from "../teams/teams-context";
export interface CreateWorkspaceProps {
contextUrl: string;
@@ -199,6 +202,11 @@ export default class CreateWorkspace extends React.Component
);
break;
+ case ErrorCodes.PAYMENT_SPENDING_LIMIT_REACHED:
+ error = undefined; // to hide the error (otherwise rendered behind the modal)
+ phase = StartPhase.Stopped;
+ statusMessage = ;
+ break;
default:
statusMessage = (
@@ -358,6 +366,44 @@ function LimitReachedOutOfHours() {
);
}
+function SpendingLimitReachedModal(p: { hints: any }) {
+ const { teams } = useContext(TeamsContext);
+ // const [attributionId, setAttributionId] = useState();
+ const [attributedTeam, setAttributedTeam] = useState();
+
+ useEffect(() => {
+ const attributionId: AttributionId | undefined =
+ p.hints && p.hints.attributionId && AttributionId.parse(p.hints.attributionId);
+ if (attributionId) {
+ // setAttributionId(attributionId);
+ if (attributionId.kind === "team") {
+ const team = teams?.find((t) => t.id === attributionId.teamId);
+ setAttributedTeam(team);
+ }
+ }
+ }, []);
+
+ return (
+ {}}>
+
+ Spending Limit Reached
+
+
+
Please increase the spending limit and retry.
+
+
+
+ );
+}
function RepositoryNotFoundView(p: { error: StartWorkspaceError }) {
const [statusMessage, setStatusMessage] = useState();
diff --git a/components/dashboard/src/start/StartWorkspace.tsx b/components/dashboard/src/start/StartWorkspace.tsx
index 7915b7be1001a6..ab3161c661c35b 100644
--- a/components/dashboard/src/start/StartWorkspace.tsx
+++ b/components/dashboard/src/start/StartWorkspace.tsx
@@ -528,7 +528,7 @@ export default class StartWorkspace extends React.Component {
await super.mayStartWorkspace(ctx, user, runningInstances);
+ // TODO(at) replace the naive implementation based on usage service
+ // with a proper call check against the upcoming invoice.
+ // For now this should just enable the work on fronend.
+ if (await this.isUsageBasedFeatureFlagEnabled(user)) {
+ // dummy implementation to test frontend bits
+ const attributionId = await this.userService.getWorkspaceUsageAttributionId(user);
+ const costCenter = !!attributionId && (await this.costCenterDB.findById(attributionId));
+ if (costCenter) {
+ const allSessions = await this.listBilledUsage(ctx, {
+ attributionId,
+ startedTimeOrder: SortOrder.Descending,
+ });
+ const totalUsage = allSessions.map((s) => s.credits).reduce((a, b) => a + b, 0);
+
+ if (totalUsage >= costCenter.spendingLimit) {
+ throw new ResponseError(
+ ErrorCodes.PAYMENT_SPENDING_LIMIT_REACHED,
+ "Increase spending limit and try again.",
+ {
+ attributionId: user.usageAttributionId,
+ },
+ );
+ }
+ }
+ }
+
const result = await this.entitlementService.mayStartWorkspace(user, new Date(), runningInstances);
if (!result.enoughCredits) {
throw new ResponseError(
@@ -1926,16 +1952,16 @@ export class GitpodServerEEImpl extends GitpodServerImpl {
return subscription;
}
- protected async ensureIsUsageBasedFeatureFlagEnabled(user: User): Promise {
+ protected async isUsageBasedFeatureFlagEnabled(user: User): Promise {
const teams = await this.teamDB.findTeamsByUser(user.id);
- const isUsageBasedBillingEnabled = await getExperimentsClientForBackend().getValueAsync(
- "isUsageBasedBillingEnabled",
- false,
- {
- user,
- teams: teams,
- },
- );
+ return await getExperimentsClientForBackend().getValueAsync("isUsageBasedBillingEnabled", false, {
+ user,
+ teams: teams,
+ });
+ }
+
+ protected async ensureIsUsageBasedFeatureFlagEnabled(user: User): Promise {
+ const isUsageBasedBillingEnabled = await this.isUsageBasedFeatureFlagEnabled(user);
if (!isUsageBasedBillingEnabled) {
throw new ResponseError(ErrorCodes.PERMISSION_DENIED, "not allowed");
}
@@ -2084,7 +2110,7 @@ export class GitpodServerEEImpl extends GitpodServerImpl {
if (costCenter) {
if (totalUsage > costCenter.spendingLimit) {
result.unshift("The spending limit is reached.");
- } else if (totalUsage > 0.8 * costCenter.spendingLimit * 0.8) {
+ } else if (totalUsage > costCenter.spendingLimit * 0.8) {
result.unshift("The spending limit is almost reached.");
}
} else {