Skip to content
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,9 @@ impl BillingCycleUsageSectionView {
.finish(),
);

if is_admin && let Some(banner) = self.render_visibility_cta_banner(workspace, appearance) {
if is_admin
&& let Some(banner) = self.render_visibility_cta_banner(workspace, appearance, app)
{
column.add_child(Container::new(banner).with_margin_top(16.).finish());
}

Expand Down Expand Up @@ -644,28 +646,44 @@ impl BillingCycleUsageSectionView {
.finish()
}

/// Whether the viewer is an admin of this workspace on a tier that has
/// native workspaces enabled -- the case where the admin panel owns the
/// full workspace settings surface, not just per-user spend limits.
fn viewer_is_native_workspaces_admin(&self, workspace: &Workspace, app: &AppContext) -> bool {
workspace.is_native_workspaces_enabled()
&& Self::resolved_viewer_email(app)
.as_deref()
.is_some_and(|email| workspace.is_workspace_admin(email))
}

/// Renders the CTA banner that sits between the team-totals block and
/// the per-member rows. The copy and action vary by visibility tier:
/// non-FullBreakdown admins see an upgrade nudge; FullBreakdown admins
/// see a pointer to the admin panel where per-user spend limits actually
/// get configured.
/// the per-member rows. Workspace admins on a native-workspaces tier are
/// pointed at the admin panel for the whole workspace settings surface.
/// Otherwise the copy and action vary by visibility tier: non-FullBreakdown
/// admins see an upgrade nudge; FullBreakdown admins see a pointer to the
/// admin panel where per-user spend limits actually get configured.
fn render_visibility_cta_banner(
&self,
workspace: &Workspace,
appearance: &Appearance,
app: &AppContext,
) -> Option<Box<dyn Element>> {
let admin_granularity = workspace
.billing_metadata
.tier
.usage_visibility_policy?
.admin_granularity;
if admin_granularity == UsageVisibilityGranularity::FullBreakdown
&& !workspace.billing_metadata.is_enterprise_plan()
{
return None;
}
let (link_text, trailing_copy, action, leading_icon) =
visibility_cta_for(admin_granularity)?;
if self.viewer_is_native_workspaces_admin(workspace, app) {
NATIVE_WORKSPACES_ADMIN_CTA
} else {
let admin_granularity = workspace
.billing_metadata
.tier
.usage_visibility_policy?
.admin_granularity;
if admin_granularity == UsageVisibilityGranularity::FullBreakdown
&& !workspace.billing_metadata.is_enterprise_plan()
{
return None;
}
visibility_cta_for(admin_granularity)?
};

// Only show when there are teammates -- a single-member workspace
// doesn't benefit from any of the team-level visibility CTAs.
Expand Down Expand Up @@ -718,6 +736,16 @@ impl BillingCycleUsageSectionView {
}
}

/// The CTA shown to workspace admins whose tier has native workspaces
/// enabled: the admin panel owns workspace settings as a whole there, so the
/// copy points at that rather than only at per-user spend limits.
const NATIVE_WORKSPACES_ADMIN_CTA: (&str, &str, BillingCycleUsageAction, Icon) = (
"Open the admin panel",
"to view and edit workspace settings and spend limits.",
BillingCycleUsageAction::OpenAdminPanel,
Icon::Users,
);

/// Returns the (link text, trailing copy, action, icon) tuple for the
/// visibility CTA banner, or `None` to suppress the banner entirely.
fn visibility_cta_for(
Expand Down
13 changes: 12 additions & 1 deletion app/src/workspaces/gql_convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use warp_graphql::billing::{
EnterpriseCreditsAutoReloadPolicy as GqlEnterpriseCreditsAutoReloadPolicy,
EnterprisePayAsYouGoPolicy as GqlEnterprisePayAsYouGoPolicy, InstanceShape as GqlInstanceShape,
ManagedByokByoePolicy as GqlManagedByokByoePolicy, MultiAdminPolicy as GqlMultiAdminPolicy,
NativeWorkspacesPolicy as GqlNativeWorkspacesPolicy,
PurchaseAddOnCreditsPolicy as GqlPurchaseAddOnCreditsPolicy, ServiceAgreementType,
SessionSharingPolicy as GqlSessionSharingPolicy,
SharedNotebooksPolicy as GqlSharedNotebooksPolicy,
Expand Down Expand Up @@ -76,7 +77,8 @@ use crate::settings::AgentModeCommandExecutionPredicate;
use crate::workspaces::workspace::{
AiOverages, BonusGrantsPurchased, ByoApiKeyPolicy, ByoEndpointPolicy, CodebaseContextPolicy,
EnterpriseCreditsAutoReloadPolicy, EnterprisePayAsYouGoPolicy, ManagedByokByoePolicy,
MultiAdminPolicy, PurchaseAddOnCreditsPolicy, UsageBasedPricingSettings,
MultiAdminPolicy, NativeWorkspacesPolicy, PurchaseAddOnCreditsPolicy,
UsageBasedPricingSettings,
};

pub const PLACEHOLDER_WORKSPACE_UID: &str = "NOT_A_REAL_WORKSPACE_UID";
Expand Down Expand Up @@ -504,6 +506,14 @@ impl From<GqlMultiAdminPolicy> for MultiAdminPolicy {
}
}

impl From<GqlNativeWorkspacesPolicy> for NativeWorkspacesPolicy {
fn from(gql_policy: GqlNativeWorkspacesPolicy) -> NativeWorkspacesPolicy {
Self {
enabled: gql_policy.enabled,
}
}
}

impl From<GqlInstanceShape> for InstanceShape {
fn from(gql_instance_shape: GqlInstanceShape) -> InstanceShape {
Self {
Expand Down Expand Up @@ -629,6 +639,7 @@ impl From<GqlTier> for Tier {
.enterprise_credits_auto_reload_policy
.map(From::from),
multi_admin_policy: gql_tier.multi_admin_policy.map(From::from),
native_workspaces_policy: gql_tier.native_workspaces_policy.map(From::from),
ambient_agents_policy: gql_tier.ambient_agents_policy.map(From::from),
usage_visibility_policy: gql_tier.usage_visibility_policy.map(From::from),
}
Expand Down
15 changes: 15 additions & 0 deletions app/src/workspaces/workspace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,15 @@ impl Workspace {
.is_some_and(|member| member.role.is_admin_or_owner())
}

/// Whether the workspace's tier enables native workspaces, which move
/// workspace-level settings (including spend limits) into the admin panel.
pub fn is_native_workspaces_enabled(&self) -> bool {
self.billing_metadata
.tier
.native_workspaces_policy
.is_some_and(|policy| policy.enabled)
}

pub fn resolve_usage_visibility(&self, is_admin: bool) -> UsageVisibility {
let Some(policy) = self.billing_metadata.tier.usage_visibility_policy else {
return UsageVisibility::default();
Expand Down Expand Up @@ -405,6 +414,11 @@ pub struct MultiAdminPolicy {
pub enabled: bool,
}

#[derive(Clone, Debug, Copy, Serialize, Deserialize)]
pub struct NativeWorkspacesPolicy {
pub enabled: bool,
}

#[derive(Clone, Debug, Copy, Serialize, Deserialize)]
pub struct AmbientAgentsPolicy {
pub max_concurrent_agents: i32,
Expand Down Expand Up @@ -489,6 +503,7 @@ pub struct Tier {
pub enterprise_pay_as_you_go_policy: Option<EnterprisePayAsYouGoPolicy>,
pub enterprise_credits_auto_reload_policy: Option<EnterpriseCreditsAutoReloadPolicy>,
pub multi_admin_policy: Option<MultiAdminPolicy>,
pub native_workspaces_policy: Option<NativeWorkspacesPolicy>,
pub ambient_agents_policy: Option<AmbientAgentsPolicy>,
pub usage_visibility_policy: Option<UsageVisibilityPolicy>,
}
Expand Down
6 changes: 6 additions & 0 deletions crates/graphql/src/api/billing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ pub struct Tier {
pub enterprise_pay_as_you_go_policy: Option<EnterprisePayAsYouGoPolicy>,
pub enterprise_credits_auto_reload_policy: Option<EnterpriseCreditsAutoReloadPolicy>,
pub multi_admin_policy: Option<MultiAdminPolicy>,
pub native_workspaces_policy: Option<NativeWorkspacesPolicy>,
pub ambient_agents_policy: Option<AmbientAgentsPolicy>,
pub usage_visibility_policy: Option<UsageVisibilityPolicy>,
}
Expand Down Expand Up @@ -220,6 +221,11 @@ pub struct MultiAdminPolicy {
pub enabled: bool,
}

#[derive(cynic::QueryFragment, Debug, Clone)]
pub struct NativeWorkspacesPolicy {
pub enabled: bool,
}

#[derive(cynic::QueryFragment, Debug, Clone)]
pub struct AmbientAgentsPolicy {
pub enabled: bool,
Expand Down
5 changes: 5 additions & 0 deletions crates/warp_graphql_schema/api/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -2403,6 +2403,10 @@ type MultiAdminPolicy {
enabled: Boolean!
}

type NativeWorkspacesPolicy {
enabled: Boolean!
}

scalar NodeHash

type Notebook {
Expand Down Expand Up @@ -3799,6 +3803,7 @@ type Tier {
managedByokByoePolicy: ManagedByokByoePolicy
multiAdminPolicy: MultiAdminPolicy
name: String!
nativeWorkspacesPolicy: NativeWorkspacesPolicy
pricing: TierPricing
purchaseAddOnCreditsPolicy: PurchaseAddOnCreditsPolicy
sessionSharingPolicy: SessionSharingPolicy
Expand Down