From 003fd28505501299b9b73a1def426d023a925f3f Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Thu, 27 Aug 2026 16:55:40 -0700 Subject: [PATCH 01/13] feat(usage): add enterprise organization usage monitoring MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Enterprise org admins had no way to see where their pooled credits go. The billing page hides the usage-limit field and the credit-usage drill-down for enterprise, and the credit-usage view is personal-scope only — so the one audience that negotiates a pooled commitment was the one audience that could not watch it burn. Everything needed already existed in `usage_log`, which is the single source of cost truth and is stamped `billing_entity_type='organization'` at charge time. This adds the read surface over it, plus one narrow write so BYOK usage is captured rather than discarded. Settings → Organization → Usage tracking, gated on hosted + enterprise with a `USAGE_MONITORING_ENABLED` self-hosted override, matching the other enterprise features. Five tabs, each answering one question: Overview (how much, and what kind of work), Members, Workspaces (drill into one for its Sources and Workflows), Models, BYOK. Only the visible tab's dimension is fetched — half the dimensions heap-scan the ledger, so a tab nobody opens never pays for one. Every ranked list closes with an explicit `Other (N more)` row so it reconciles to the headline figure. Track unbilled (BYOK) model usage --------------------------------- BYOK spans already reached `costSummary` with real token counts and were discarded at a single `if (modelData.total > 0)` gate. A new `model_unbilled` usage_log category records them at zero cost, written only at the terminal execution boundary and only when billing attribution is already resolved, so a BYOK-only run with no billable target still bails safely instead of hitting the attribution requirement. Every billing read over `usage_log` is `SUM(cost)`, so zero-cost rows change no total anywhere. Collapse organization settings onto the workspace plane ------------------------------------------------------ `/organization/[id]/settings/*` was a second plane with no UI entry point anywhere, whose nine sections all already render on the workspace plane. It is deleted, along with its renderer and unavailable page. `planes.organization` is replaced by a single `unified.organizationSection` marker that now derives both `ORGANIZATION_PLANE_UNIFIED_SECTIONS` and the section map in the workspace gate, removing a hand-maintained duplicate. Usage-threshold emails now link to the workspace-scoped billing page, and the two org-provisioning paths that could strand an admin with zero workspaces now backfill one. Shared chart module ------------------- `LineChart` moves to `components/charts/` with a matching `BarChart` sibling built from the same geometry, tooltip, and theme modules. The move also breaks the import edge from the chart to `logs/utils` → `@/blocks/registry`, which would otherwise have pulled the entire block registry into the settings chunk. Logs dashboard renders identically. Co-Authored-By: Claude Opus 5 (1M context) --- .../docs/en/platform/enterprise/index.mdx | 6 + .../docs/en/platform/enterprise/meta.json | 1 + .../en/platform/enterprise/usage-tracking.mdx | 161 ++++++++ .../enterprise/usage-tracking-overview.png | Bin 0 -> 126096 bytes .../usage-tracking-workspace-detail.png | Bin 0 -> 124836 bytes apps/sim/app/api/emails/preview/route.ts | 2 +- .../[id]/usage/breakdown/route.ts | 38 ++ .../organizations/[id]/usage/events/route.ts | 39 ++ .../organizations/[id]/usage/export/route.ts | 81 ++++ .../organizations/[id]/usage/summary/route.ts | 36 ++ .../users/me/usage-logs/export/route.test.ts | 41 +- .../api/users/me/usage-logs/export/route.ts | 21 +- .../app/api/v1/admin/organizations/route.ts | 21 + .../settings/[section]/page.tsx | 72 ---- .../[organizationId]/settings/layout.tsx | 35 -- .../[organizationId]/settings/page.tsx | 11 - .../settings/unavailable/page.tsx | 5 - .../components/dashboard/components/index.ts | 1 - .../dashboard/components/line-chart/index.ts | 1 - .../logs/components/dashboard/dashboard.tsx | 3 +- .../app/workspace/[workspaceId]/logs/utils.ts | 12 +- .../settings/[section]/page.test.tsx | 12 + .../[workspaceId]/settings/[section]/page.tsx | 23 +- .../settings/[section]/settings.tsx | 6 + .../manage-credits-modal/index.ts | 0 .../manage-credits-modal.tsx | 7 +- .../components/segmented-meter/index.ts | 1 + .../segmented-meter/segmented-meter.tsx | 49 +++ .../team-management/components/index.ts | 1 - .../organization-member-lists.tsx | 8 +- .../team-seats-overview.tsx | 23 +- .../team-management/team-management.test.tsx | 6 +- .../team-management/team-management.tsx | 11 +- .../[workspaceId]/settings/navigation.test.ts | 2 + .../settings/usage/events/layout.tsx | 18 + .../settings/usage/events/loading.tsx | 22 ++ .../settings/usage/events/page.tsx | 47 +++ apps/sim/components/charts/bar-chart.tsx | 338 ++++++++++++++++ apps/sim/components/charts/chart-format.ts | 31 ++ .../components/charts/chart-geometry.test.ts | 80 ++++ apps/sim/components/charts/chart-geometry.ts | 67 ++++ apps/sim/components/charts/chart-tooltip.tsx | 89 +++++ apps/sim/components/charts/index.ts | 12 + .../charts}/line-chart.tsx | 246 ++++-------- apps/sim/components/charts/use-chart-theme.ts | 84 ++++ .../emails/render-notifications.test.ts | 2 +- .../components/settings/navigation.test.ts | 73 ++-- apps/sim/components/settings/navigation.ts | 159 ++++---- .../organization-settings-renderer.tsx | 79 ---- .../settings/settings-header-shell.test.tsx | 4 +- .../components/settings/settings-header.tsx | 2 +- .../settings/settings-unavailable.tsx | 28 -- .../standalone-settings-shell.test.ts | 13 - .../settings/standalone-settings-shell.tsx | 61 +-- .../components/usage-consumers.tsx | 193 +++++++++ .../components/usage-events-view.tsx | 117 ++++++ .../components/usage-monitoring.tsx | 341 ++++++++++++++++ .../components/usage-summary.tsx | 84 ++++ apps/sim/ee/organization-usage/constants.ts | 74 ++++ .../hooks/use-usage-window.ts | 55 +++ .../ee/organization-usage/search-params.ts | 40 ++ apps/sim/hooks/queries/organization-usage.ts | 116 ++++++ .../queries/utils/organization-usage-keys.ts | 45 +++ .../lib/api/contracts/organization-usage.ts | 179 +++++++++ .../authorized-organization-usage-use-case.ts | 107 +++++ .../export-organization-usage-events.ts | 94 +++++ .../get-organization-usage-breakdown.ts | 176 +++++++++ .../get-organization-usage-summary.ts | 85 ++++ .../list-organization-usage-events.ts | 82 ++++ .../organization-usage/operations.ts | 53 +++ .../organization-usage-use-cases.test.ts | 128 ++++++ .../billing/core/usage-analytics-queries.ts | 216 ++++++++++ .../lib/billing/core/usage-analytics.test.ts | 291 ++++++++++++++ apps/sim/lib/billing/core/usage-analytics.ts | 370 ++++++++++++++++++ apps/sim/lib/billing/core/usage-log.test.ts | 47 +++ apps/sim/lib/billing/core/usage-log.ts | 64 ++- apps/sim/lib/billing/core/usage.ts | 10 +- apps/sim/lib/billing/usage-sources.ts | 9 + .../core/config/enterprise-entitlements.ts | 5 + apps/sim/lib/core/config/env-flags.ts | 13 + apps/sim/lib/core/config/env.ts | 3 + apps/sim/lib/core/utils/timezone.ts | 15 + apps/sim/lib/logs/cost-ledger.ts | 15 +- apps/sim/lib/logs/execution/logger.ts | 114 +++++- .../logs/execution/logging-factory.test.ts | 28 ++ apps/sim/lib/logs/stats-logs.ts | 9 +- packages/db/schema.ts | 17 +- packages/emcn/src/icons/chart-column.tsx | 28 ++ packages/emcn/src/icons/index.ts | 1 + packages/sim-setup/src/twins.ts | 6 + packages/testing/src/mocks/env-flags.mock.ts | 2 + 91 files changed, 4716 insertions(+), 707 deletions(-) create mode 100644 apps/docs/content/docs/en/platform/enterprise/usage-tracking.mdx create mode 100644 apps/docs/public/static/enterprise/usage-tracking-overview.png create mode 100644 apps/docs/public/static/enterprise/usage-tracking-workspace-detail.png create mode 100644 apps/sim/app/api/organizations/[id]/usage/breakdown/route.ts create mode 100644 apps/sim/app/api/organizations/[id]/usage/events/route.ts create mode 100644 apps/sim/app/api/organizations/[id]/usage/export/route.ts create mode 100644 apps/sim/app/api/organizations/[id]/usage/summary/route.ts delete mode 100644 apps/sim/app/organization/[organizationId]/settings/[section]/page.tsx delete mode 100644 apps/sim/app/organization/[organizationId]/settings/layout.tsx delete mode 100644 apps/sim/app/organization/[organizationId]/settings/page.tsx delete mode 100644 apps/sim/app/organization/[organizationId]/settings/unavailable/page.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/logs/components/dashboard/components/line-chart/index.ts rename apps/sim/app/workspace/[workspaceId]/settings/components/{team-management/components => }/manage-credits-modal/index.ts (100%) rename apps/sim/app/workspace/[workspaceId]/settings/components/{team-management/components => }/manage-credits-modal/manage-credits-modal.tsx (94%) create mode 100644 apps/sim/app/workspace/[workspaceId]/settings/components/segmented-meter/index.ts create mode 100644 apps/sim/app/workspace/[workspaceId]/settings/components/segmented-meter/segmented-meter.tsx create mode 100644 apps/sim/app/workspace/[workspaceId]/settings/usage/events/layout.tsx create mode 100644 apps/sim/app/workspace/[workspaceId]/settings/usage/events/loading.tsx create mode 100644 apps/sim/app/workspace/[workspaceId]/settings/usage/events/page.tsx create mode 100644 apps/sim/components/charts/bar-chart.tsx create mode 100644 apps/sim/components/charts/chart-format.ts create mode 100644 apps/sim/components/charts/chart-geometry.test.ts create mode 100644 apps/sim/components/charts/chart-geometry.ts create mode 100644 apps/sim/components/charts/chart-tooltip.tsx create mode 100644 apps/sim/components/charts/index.ts rename apps/sim/{app/workspace/[workspaceId]/logs/components/dashboard/components/line-chart => components/charts}/line-chart.tsx (75%) create mode 100644 apps/sim/components/charts/use-chart-theme.ts delete mode 100644 apps/sim/components/settings/organization-settings-renderer.tsx delete mode 100644 apps/sim/components/settings/settings-unavailable.tsx create mode 100644 apps/sim/ee/organization-usage/components/usage-consumers.tsx create mode 100644 apps/sim/ee/organization-usage/components/usage-events-view.tsx create mode 100644 apps/sim/ee/organization-usage/components/usage-monitoring.tsx create mode 100644 apps/sim/ee/organization-usage/components/usage-summary.tsx create mode 100644 apps/sim/ee/organization-usage/constants.ts create mode 100644 apps/sim/ee/organization-usage/hooks/use-usage-window.ts create mode 100644 apps/sim/ee/organization-usage/search-params.ts create mode 100644 apps/sim/hooks/queries/organization-usage.ts create mode 100644 apps/sim/hooks/queries/utils/organization-usage-keys.ts create mode 100644 apps/sim/lib/api/contracts/organization-usage.ts create mode 100644 apps/sim/lib/billing/application/organization-usage/authorized-organization-usage-use-case.ts create mode 100644 apps/sim/lib/billing/application/organization-usage/export-organization-usage-events.ts create mode 100644 apps/sim/lib/billing/application/organization-usage/get-organization-usage-breakdown.ts create mode 100644 apps/sim/lib/billing/application/organization-usage/get-organization-usage-summary.ts create mode 100644 apps/sim/lib/billing/application/organization-usage/list-organization-usage-events.ts create mode 100644 apps/sim/lib/billing/application/organization-usage/operations.ts create mode 100644 apps/sim/lib/billing/application/organization-usage/organization-usage-use-cases.test.ts create mode 100644 apps/sim/lib/billing/core/usage-analytics-queries.ts create mode 100644 apps/sim/lib/billing/core/usage-analytics.test.ts create mode 100644 apps/sim/lib/billing/core/usage-analytics.ts create mode 100644 packages/emcn/src/icons/chart-column.tsx diff --git a/apps/docs/content/docs/en/platform/enterprise/index.mdx b/apps/docs/content/docs/en/platform/enterprise/index.mdx index fd3e46e4e1a..fa3f62af3b1 100644 --- a/apps/docs/content/docs/en/platform/enterprise/index.mdx +++ b/apps/docs/content/docs/en/platform/enterprise/index.mdx @@ -53,6 +53,12 @@ Track configuration and security-relevant actions across your organization for c --- +## Usage Tracking + +See where your organization's credits go — by member, workspace, model, and platform feature. See the [usage tracking guide](/platform/enterprise/usage-tracking). + +--- + ## Data Retention Configure how long execution logs, soft-deleted resources, and Chat data are kept before permanent deletion. See the [data retention guide](/platform/enterprise/data-retention). diff --git a/apps/docs/content/docs/en/platform/enterprise/meta.json b/apps/docs/content/docs/en/platform/enterprise/meta.json index cdf420f1ff3..48e14db8d21 100644 --- a/apps/docs/content/docs/en/platform/enterprise/meta.json +++ b/apps/docs/content/docs/en/platform/enterprise/meta.json @@ -10,6 +10,7 @@ "custom-blocks", "whitelabeling", "audit-logs", + "usage-tracking", "data-retention", "data-drains", "forks" diff --git a/apps/docs/content/docs/en/platform/enterprise/usage-tracking.mdx b/apps/docs/content/docs/en/platform/enterprise/usage-tracking.mdx new file mode 100644 index 00000000000..4dc90dfb12c --- /dev/null +++ b/apps/docs/content/docs/en/platform/enterprise/usage-tracking.mdx @@ -0,0 +1,161 @@ +--- +title: Usage Tracking +description: See where your organization's credits go, by member, workspace, and model +--- + +import { Callout } from 'fumadocs-ui/components/callout' +import { FAQ } from '@/components/ui/faq' +import { Image } from '@/components/ui/image' + +Usage tracking shows how your organization consumes credits across every part of the platform — which members, which workspaces, which models, and which product features. Use it to monitor spend against your commitment, find what is driving it, and export the underlying events for chargeback. + +All figures are in **credits** (1 credit = $0.005). See [cost calculation](/platform/costs) for how credits are derived. + +--- + +## Viewing usage + +Go to **Settings → Organization → Usage tracking** in your workspace. + +Usage tracking Overview tab showing the period selector, credits used against the organization limit, a daily usage chart, and a Sources breakdown listing Sim Chat, Workflow, and Agent block + +The period selector applies to every tab: + +| Period | What it covers | +|--------|----------------| +| **Current period** | Your organization's current billing period | +| **Previous period** | The period immediately before it | +| **Last 7 days** / **Last 30 days** | A rolling window ending now | +| **Custom range** | Any range up to 92 days | + + + Daily bars are drawn in your browser's timezone, while a billing period begins at a fixed UTC instant. The first and last bar of a period can therefore be partial, and two admins in different timezones will see the same total split across slightly different days. + + +### Tabs + +| Tab | Answers | +|-----|---------| +| **Overview** | How much have we used, against what limit, and what kind of work was it | +| **Members** | Which people are driving usage | +| **Workspaces** | Which workspaces are driving usage — select one to drill in | +| **Models** | Which models we are paying for | +| **BYOK** | What ran on our own provider keys | + +Selecting a workspace opens its detail view, which splits that workspace's usage into **Sources** (what kind of work) and **Workflows** (the individual workflow runs). **Open logs** jumps to that workspace's execution logs. + +A workspace's detail view with a Sources section listing Sim Chat and Workflow, and a Workflows section ranking individual workflows by credits + +The two sections answer different questions, and the difference is the point: **Sources** adds up to the workspace's total, while **Workflows** covers only the workflow-run part of it. In the example above, Sources totals 4,435 credits but the workflows list only accounts for the 161 credits under Workflow — the other 4,274 came from Chat, which no workflow produced. + +--- + +## What each source means + +A **source** is the part of the platform that consumed the credits. Every charge belongs to exactly one source, so the Sources breakdown always adds up to your total. + +| Source | Platform features that bill to it | +|--------|-----------------------------------| +| **Workflow** | Every workflow run — model calls made on Sim's hosted keys, hosted-key tool calls (web search, scraping, and similar), and the per-run base charge | +| **Sim Chat** | The Chat panel, agent calls made through the API, and the email Inbox | +| **Agent block** | The Agent block running inside a workflow | +| **Knowledge Base** | Embedding documents on upload or connector sync, and semantic search queries | +| **Wand** | Inline AI generation in editors | +| **Enrichment** | Table column enrichment | +| **Voice input** | Voice sessions | +| **Voice output** | Spoken responses in deployed chats | +| **Sim Chat (MCP)** | Retired. Appears only for historical periods | + +### Nuances worth knowing + +These follow from how charges are recorded, and they explain most questions about why a number looks the way it does. + +**Workflow covers the whole run, not just the base charge.** A workflow row includes three things: the models it called on Sim's hosted keys, any hosted-key tool calls, and the per-run base charge. Model usage typically dominates, but the per-run charge is not negligible — across a high-volume organization it commonly accounts for around a fifth of workflow spend, because it applies to every run whether or not the workflow calls a model. + +**Agent block is billed separately from the workflow that contains it.** An Agent block runs inside a workflow but is metered on its own, so its credits appear under **Agent block** rather than **Workflow**. Nothing is counted twice — but it also means Workflow alone understates what your workflows cost. Read the two lines together. + +**Sim Chat covers three surfaces.** The Chat panel, headless agent calls through the API, and the Inbox all record as **Sim Chat** and cannot be separated from one another. If you need per-surface attribution today, separate them by workspace instead. + +**Only workflow runs carry workflow attribution.** Chat, Agent block, Wand, Knowledge Base, and voice usage are not produced by a workflow and have no workflow attached. That is the gap between a workspace's **Sources** and **Workflows** sections shown above. + +**Some usage has no workspace.** Agent calls made through the API may not name a workspace, particularly from headless or self-hosted callers. That usage is grouped under **No workspace** on the Workspaces tab. It is still counted in your total. + +**Models excludes non-model charges.** The Models tab covers model usage only, so it will read lower than your period total — the difference is per-run base charges, hosted-key tool charges, and fixed charges such as voice sessions. + + + Each tab is a different slice of the same charges, not an additional set of them. Members, Workspaces, and Sources each add up to your period total on their own; adding two tabs together double-counts. + + +--- + +## Bring your own keys (BYOK) + +When a workspace or organization supplies its own provider key, Sim does not charge for that model usage. Those calls are still recorded so you can see the volume. + +The **BYOK** tab groups this usage by provider and reports **tokens** rather than credits, because the credit cost is zero by definition. Tokens on your own keys are not included in the credit totals anywhere else in the panel. + + + Tool calls and Chat usage made on your own keys are not yet recorded. BYOK currently covers model usage in workflow runs. + + +--- + +## Exporting + +**Export** downloads the events behind the current period and filters as a CSV with columns `Date, Source, Description, Workflow, Credits`. + +**All events** opens the full ledger — every credit-consuming event, newest first, with its own filters and export. + +Very large exports are capped. When that happens the download still succeeds and Sim tells you it was truncated; narrow the date range to capture everything. + +--- + + + +--- + +## Self-hosted setup + +Self-hosted deployments use environment variables instead of the billing/plan check. + +### Environment variables + +```bash +USAGE_MONITORING_ENABLED=true +NEXT_PUBLIC_USAGE_MONITORING_ENABLED=true +``` + +Once enabled, usage tracking is viewable in **Settings → Organization → Usage tracking** for organization owners and admins. diff --git a/apps/docs/public/static/enterprise/usage-tracking-overview.png b/apps/docs/public/static/enterprise/usage-tracking-overview.png new file mode 100644 index 0000000000000000000000000000000000000000..d55e13d0d231a222d1592c118b2053fcc61c93b3 GIT binary patch literal 126096 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
$!33v2Fd*DIqiLTMB9BZ| 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 P@PqmX_E7Nw@3{W~nb8jQ literal 0 HcmV?d00001 diff --git a/apps/docs/public/static/enterprise/usage-tracking-workspace-detail.png b/apps/docs/public/static/enterprise/usage-tracking-workspace-detail.png new file mode 100644 index 0000000000000000000000000000000000000000..70003a33efb6c7e31be5153441bf514c9ebbbf0c GIT binary patch literal 124836 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

>GMh3jd{cB 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 literal 0 HcmV?d00001 diff --git a/apps/sim/app/api/emails/preview/route.ts b/apps/sim/app/api/emails/preview/route.ts index 63cfb4994dd..d880aaf1dae 100644 --- a/apps/sim/app/api/emails/preview/route.ts +++ b/apps/sim/app/api/emails/preview/route.ts @@ -160,7 +160,7 @@ const emailTemplates = { scope: 'organization', currentUsage: 500, limit: 500, - ctaLink: 'https://sim.ai/organization/org_123/settings/billing', + ctaLink: 'https://sim.ai/workspace/ws_123/settings/billing', }), // Operational notification emails diff --git a/apps/sim/app/api/organizations/[id]/usage/breakdown/route.ts b/apps/sim/app/api/organizations/[id]/usage/breakdown/route.ts new file mode 100644 index 00000000000..edc3c7e5b48 --- /dev/null +++ b/apps/sim/app/api/organizations/[id]/usage/breakdown/route.ts @@ -0,0 +1,38 @@ +import { getOrganizationUsageBreakdownContract } from '@/lib/api/contracts/organization-usage' +import { + defineInternalJsonRoute, + internalOrchestrationErrorPolicy, + internalRateLimits, + internalSessionAuth, +} from '@/lib/api/server/routes' +import { getOrganizationUsageBreakdown } from '@/lib/billing/application/organization-usage/get-organization-usage-breakdown' +import { organizationUsageOperations } from '@/lib/billing/application/organization-usage/operations' + +export const dynamic = 'force-dynamic' + +/** + * One route for all five dimensions: they share a scope, a window, a row shape, and + * authorization, so five routes would be five copies of the same mapping. Separate + * from the summary because three of the five heap-scan the ledger. + */ +export const GET = defineInternalJsonRoute({ + contract: getOrganizationUsageBreakdownContract, + auth: internalSessionAuth, + operation: organizationUsageOperations.readBreakdown, + rateLimit: internalRateLimits.none({ + reason: + 'Authenticated org-admin settings read, gated on enterprise entitlement and billing authority', + }), + errorPolicy: internalOrchestrationErrorPolicy, + mapInput: ({ params, query }) => ({ + organizationId: params.id, + dimension: query.dimension, + workspaceId: query.workspaceId, + preset: query.preset, + startDate: query.startDate ? new Date(query.startDate) : undefined, + endDate: query.endDate ? new Date(query.endDate) : undefined, + limit: query.limit, + }), + useCase: getOrganizationUsageBreakdown, + present: (result) => result, +}) diff --git a/apps/sim/app/api/organizations/[id]/usage/events/route.ts b/apps/sim/app/api/organizations/[id]/usage/events/route.ts new file mode 100644 index 00000000000..ff221a0cf3e --- /dev/null +++ b/apps/sim/app/api/organizations/[id]/usage/events/route.ts @@ -0,0 +1,39 @@ +import { listOrganizationUsageEventsContract } from '@/lib/api/contracts/organization-usage' +import { + defineInternalJsonRoute, + internalOrchestrationErrorPolicy, + internalRateLimits, + internalSessionAuth, +} from '@/lib/api/server/routes' +import { listOrganizationUsageEvents } from '@/lib/billing/application/organization-usage/list-organization-usage-events' +import { organizationUsageOperations } from '@/lib/billing/application/organization-usage/operations' +import type { InternalUsageLogSource } from '@/lib/billing/usage-sources' + +export const dynamic = 'force-dynamic' + +/** + * The raw ledger, paged. Separate from the summary because it owns a cursor + * lifecycle and its own staleness — folding it in would re-run the headline + * aggregate on every scroll. + */ +export const GET = defineInternalJsonRoute({ + contract: listOrganizationUsageEventsContract, + auth: internalSessionAuth, + operation: organizationUsageOperations.listEvents, + rateLimit: internalRateLimits.none({ + reason: + 'Authenticated org-admin settings read, gated on enterprise entitlement and billing authority', + }), + errorPolicy: internalOrchestrationErrorPolicy, + mapInput: ({ params, query }) => ({ + organizationId: params.id, + preset: query.preset, + startDate: query.startDate ? new Date(query.startDate) : undefined, + endDate: query.endDate ? new Date(query.endDate) : undefined, + source: query.source as InternalUsageLogSource[] | undefined, + limit: query.limit, + cursor: query.cursor, + }), + useCase: listOrganizationUsageEvents, + present: (result) => result, +}) diff --git a/apps/sim/app/api/organizations/[id]/usage/export/route.ts b/apps/sim/app/api/organizations/[id]/usage/export/route.ts new file mode 100644 index 00000000000..398f8b75a1f --- /dev/null +++ b/apps/sim/app/api/organizations/[id]/usage/export/route.ts @@ -0,0 +1,81 @@ +import { createLogger } from '@sim/logger' +import { getErrorMessage } from '@sim/utils/errors' +import { type NextRequest, NextResponse } from 'next/server' +import { exportOrganizationUsageContract } from '@/lib/api/contracts/organization-usage' +import { getValidationErrorMessage, parseRequest } from '@/lib/api/server' +import { getSession } from '@/lib/auth' +import { + exportOrganizationUsageEvents, + type OrganizationUsageExportRow, +} from '@/lib/billing/application/organization-usage/export-organization-usage-events' +import { formatCreditsLabel } from '@/lib/billing/credits/conversion' +import type { InternalUsageLogSource } from '@/lib/billing/usage-sources' +import { ForbiddenOperationError } from '@/lib/core/application' +import { formatCsvValue, toCsvRow } from '@/lib/core/utils/csv' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' + +const logger = createLogger('OrganizationUsageExportAPI') + +const CSV_HEADER = toCsvRow(['Date', 'Source', 'Description', 'Workflow', 'Credits']) + +/** `formatCsvValue` neutralizes formula injection — model and workflow names are user-controlled. */ +function toCsvLine(row: OrganizationUsageExportRow): string { + return toCsvRow([ + formatCsvValue(row.createdAt), + formatCsvValue(row.source), + formatCsvValue(row.description), + formatCsvValue(row.workflowName ?? ''), + formatCsvValue(formatCreditsLabel(row.credits)), + ]) +} + +/** + * A raw handler rather than a JSON builder: the body is `text/csv`, and the response + * carries `X-Export-Truncated` so the client can tell the user their range was capped + * rather than silently handing them a partial file. + */ +export const GET = withRouteHandler(async (request: NextRequest, context) => { + try { + const session = await getSession() + const sessionId = session?.session?.id + if (!session?.user?.id || !sessionId) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) + } + + const parsed = await parseRequest(exportOrganizationUsageContract, request, context, { + validationErrorResponse: (error) => + NextResponse.json( + { error: getValidationErrorMessage(error, 'Invalid query parameters') }, + { status: 400 } + ), + }) + if (!parsed.success) return parsed.response + + const { query, params } = parsed.data + const result = await exportOrganizationUsageEvents.execute({ + principal: { kind: 'session', userId: session.user.id, sessionId }, + input: { + organizationId: params.id, + preset: query.preset, + startDate: query.startDate ? new Date(query.startDate) : undefined, + endDate: query.endDate ? new Date(query.endDate) : undefined, + source: query.source as InternalUsageLogSource[] | undefined, + }, + }) + + const csv = [CSV_HEADER, ...result.rows.map(toCsvLine)].join('\n') + return new NextResponse(csv, { + headers: { + 'Content-Type': 'text/csv; charset=utf-8', + 'Content-Disposition': `attachment; filename="organization-usage-${params.id}.csv"`, + ...(result.truncated ? { 'X-Export-Truncated': '1' } : {}), + }, + }) + } catch (error) { + if (error instanceof ForbiddenOperationError) { + return NextResponse.json({ error: error.message }, { status: 403 }) + } + logger.error('Failed to export organization usage', { error: getErrorMessage(error) }) + return NextResponse.json({ error: 'Failed to export usage' }, { status: 500 }) + } +}) diff --git a/apps/sim/app/api/organizations/[id]/usage/summary/route.ts b/apps/sim/app/api/organizations/[id]/usage/summary/route.ts new file mode 100644 index 00000000000..566db746d12 --- /dev/null +++ b/apps/sim/app/api/organizations/[id]/usage/summary/route.ts @@ -0,0 +1,36 @@ +import { getOrganizationUsageSummaryContract } from '@/lib/api/contracts/organization-usage' +import { + defineInternalJsonRoute, + internalOrchestrationErrorPolicy, + internalRateLimits, + internalSessionAuth, +} from '@/lib/api/server/routes' +import { getOrganizationUsageSummary } from '@/lib/billing/application/organization-usage/get-organization-usage-summary' +import { organizationUsageOperations } from '@/lib/billing/application/organization-usage/operations' + +export const dynamic = 'force-dynamic' + +/** + * Everything above the fold in one round trip. Kept separate from the breakdown + * route because every read here is index-covered, and folding in a dimension that + * heap-scans would put that cost on first paint. + */ +export const GET = defineInternalJsonRoute({ + contract: getOrganizationUsageSummaryContract, + auth: internalSessionAuth, + operation: organizationUsageOperations.readSummary, + rateLimit: internalRateLimits.none({ + reason: + 'Authenticated org-admin settings read, gated on enterprise entitlement and billing authority', + }), + errorPolicy: internalOrchestrationErrorPolicy, + mapInput: ({ params, query }) => ({ + organizationId: params.id, + preset: query.preset, + startDate: query.startDate ? new Date(query.startDate) : undefined, + endDate: query.endDate ? new Date(query.endDate) : undefined, + timezone: query.timezone, + }), + useCase: getOrganizationUsageSummary, + present: (result) => result, +}) diff --git a/apps/sim/app/api/users/me/usage-logs/export/route.test.ts b/apps/sim/app/api/users/me/usage-logs/export/route.test.ts index 87854ca15c7..fd4f01a2588 100644 --- a/apps/sim/app/api/users/me/usage-logs/export/route.test.ts +++ b/apps/sim/app/api/users/me/usage-logs/export/route.test.ts @@ -7,6 +7,7 @@ import { apportionCredits } from '@/lib/billing/credits/conversion' const { mockGetUserUsageLogs, mockGetUsageCreditsByLogId } = vi.hoisted(() => ({ mockGetUserUsageLogs: vi.fn(), + /** Still mocked because the module exports it; the export route must never call it. */ mockGetUsageCreditsByLogId: vi.fn(), })) @@ -47,7 +48,6 @@ describe('GET /api/users/me/usage-logs/export', () => { summary: { totalCost: 0.5, bySource: { copilot: 0.5 } }, pagination: { hasMore: false }, }) - mockGetUsageCreditsByLogId.mockResolvedValue(apportionCredits([{ key: 'log-1', dollars: 0.5 }])) const response = await GET(createMockRequest('GET')) const csv = await response.text() @@ -108,10 +108,6 @@ describe('GET /api/users/me/usage-logs/export', () => { 'user-1', expect.objectContaining({ source: ['copilot', 'workspace-chat'] }) ) - expect(mockGetUsageCreditsByLogId).toHaveBeenCalledWith( - 'user-1', - expect.objectContaining({ source: ['copilot', 'workspace-chat'] }) - ) }) it('names the specific workflow for workflow-sourced rows', async () => { @@ -208,7 +204,9 @@ describe('GET /api/users/me/usage-logs/export', () => { expect(csv.split('\n')).toHaveLength(3) }) - it('apportions credits once over the whole filtered set, not per page', async () => { + it('apportions across pages so the printed rows reconcile to the printed total', async () => { + // 0.002 + 0.002 = 0.004 -> 0.8 credits -> 1 credit for the whole file. Rounding each + // row alone would print 0 and 0; the largest-remainder split prints 1 and 0. mockGetUserUsageLogs .mockResolvedValueOnce({ logs: [ @@ -224,16 +222,35 @@ describe('GET /api/users/me/usage-logs/export', () => { summary: { totalCost: 0, bySource: {} }, pagination: { hasMore: false }, }) - mockGetUsageCreditsByLogId.mockResolvedValue( - apportionCredits([ - { key: 'log-1', dollars: 0.002 }, - { key: 'log-2', dollars: 0.002 }, - ]) + + const csv = await (await GET(createMockRequest('GET'))).text() + const printed = csv + .split('\n') + .slice(1) + .map((line) => Number(line.split(',')[2])) + + expect(printed.reduce((sum, credits) => sum + credits, 0)).toBe( + Object.values( + apportionCredits([ + { key: 'log-1', dollars: 0.002 }, + { key: 'log-2', dollars: 0.002 }, + ]) + ).reduce((sum, credits) => sum + credits, 0) ) + }) + + it('never issues the whole-filter apportionment read, so the safety cap actually bounds it', async () => { + // The cap stops the paging loop; a second unbounded read beside it would make that + // cap meaningless, and with `period=all` it is a full lifetime scan. + mockGetUserUsageLogs.mockResolvedValueOnce({ + logs: [{ id: 'log-1', createdAt: '2026-07-01T00:00:00.000Z', source: 'copilot', cost: 0.5 }], + summary: { totalCost: 0, bySource: {} }, + pagination: { hasMore: false }, + }) await GET(createMockRequest('GET')) - expect(mockGetUsageCreditsByLogId).toHaveBeenCalledTimes(1) + expect(mockGetUsageCreditsByLogId).not.toHaveBeenCalled() }) it('stops at exactly the safety cap without an extra wasted page fetch', async () => { diff --git a/apps/sim/app/api/users/me/usage-logs/export/route.ts b/apps/sim/app/api/users/me/usage-logs/export/route.ts index e9c20256455..97fc7e3bffd 100644 --- a/apps/sim/app/api/users/me/usage-logs/export/route.ts +++ b/apps/sim/app/api/users/me/usage-logs/export/route.ts @@ -3,7 +3,8 @@ import { type NextRequest, NextResponse } from 'next/server' import { exportUsageLogsContract } from '@/lib/api/contracts/user' import { parseRequest } from '@/lib/api/server' import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid' -import { getUsageCreditsByLogId, getUserUsageLogs } from '@/lib/billing/core/usage-log' +import { getUserUsageLogs } from '@/lib/billing/core/usage-log' +import { apportionCredits } from '@/lib/billing/credits/conversion' import { BILLING_USAGE_LOG_SOURCE_LABELS, toBillingUsageLogSource, @@ -70,7 +71,23 @@ export const GET = withRouteHandler(async (request: NextRequest) => { cursorCreatedAt = lastRow ? new Date(lastRow.createdAt) : undefined } - const creditsByLogId = await getUsageCreditsByLogId(auth.userId, filter) + /** + * Apportioned over the rows this file actually contains, not over a second + * unbounded read of the whole filter. + * + * `getUsageCreditsByLogId` exists for the paginated list, where a row must show the + * same value on every page and only the full set can guarantee that. An export has + * no pages: it already holds every row it will print. Re-reading the filter here + * made {@link EXPORT_SAFETY_CAP} illusory — the loop stopped at the cap and the very + * next statement loaded every matching row anyway, which with `period=all` is an + * unbounded lifetime scan. + * + * The values are unchanged in the ordinary case, because an untruncated export's row + * set *is* the whole filter. When it truncates they are strictly better: the printed + * rows now reconcile to the printed total instead of to one that includes rows the + * file does not contain. + */ + const creditsByLogId = apportionCredits(rows.map((log) => ({ key: log.id, dollars: log.cost }))) if (truncated) { logger.error('Usage log export hit the safety cap — investigate this account', { diff --git a/apps/sim/app/api/v1/admin/organizations/route.ts b/apps/sim/app/api/v1/admin/organizations/route.ts index ecc446207d3..9bb24ee5312 100644 --- a/apps/sim/app/api/v1/admin/organizations/route.ts +++ b/apps/sim/app/api/v1/admin/organizations/route.ts @@ -38,6 +38,7 @@ import { OrganizationSlugTakenError, } from '@/lib/billing/organizations/create-organization' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { attachOwnedWorkspacesToOrganization } from '@/lib/workspaces/organization-workspaces' import { withAdminAuth } from '@/app/api/v1/admin/middleware' import { adminInvalidJsonResponse, @@ -150,6 +151,25 @@ export const POST = withRouteHandler( slug, }) + /** + * Organization settings are reached only through a workspace the organization + * owns, so an organization that owns none leaves its own admin with no route + * to administer it. Attaching the owner's existing workspaces is what makes the + * organization reachable, and matches what `POST /api/organizations` already + * does — this path was the inconsistent one. + * + * An owner with no workspaces has nothing to attach and stays unreachable until + * one exists. Creating a workspace only closes that gap once the organization + * carries a usable Team/Enterprise plan; without one the creation policy still + * resolves to a personal workspace. + */ + const { attachedWorkspaceIds } = await attachOwnedWorkspacesToOrganization({ + ownerUserId: ownerId, + organizationId, + externalMemberPolicy: 'keep-external', + includeArchived: true, + }) + const [createdOrg] = await db .select(organizationColumns) .from(organization) @@ -161,6 +181,7 @@ export const POST = withRouteHandler( slug, ownerId, memberId, + attachedWorkspaceIds, }) recordAudit({ diff --git a/apps/sim/app/organization/[organizationId]/settings/[section]/page.tsx b/apps/sim/app/organization/[organizationId]/settings/[section]/page.tsx deleted file mode 100644 index ab2f2fce4ae..00000000000 --- a/apps/sim/app/organization/[organizationId]/settings/[section]/page.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import type { Metadata } from 'next' -import { notFound, redirect } from 'next/navigation' -import { - getOrganizationSettingsFeatures, - getSettingsSectionMeta, - isOrganizationSettingsSectionAvailable, - ORGANIZATION_SETTINGS_ITEMS, - ORGANIZATION_SETTINGS_PATH_ALIASES, - parseSettingsPathSection, -} from '@/components/settings/navigation' -import { OrganizationSettingsRenderer } from '@/components/settings/organization-settings-renderer' -import { SettingsUnavailable } from '@/components/settings/settings-unavailable' -import { getSession } from '@/lib/auth' -import { isOrganizationOnEnterprisePlan } from '@/lib/billing' -import { canOpenOrganizationSettingsSection } from '@/lib/organizations/settings-access' - -interface OrganizationSettingsSectionPageProps { - params: Promise<{ organizationId: string; section: string }> -} - -export async function generateMetadata({ - params, -}: OrganizationSettingsSectionPageProps): Promise { - const { section } = await params - const parsed = parseSettingsPathSection({ - path: section, - items: ORGANIZATION_SETTINGS_ITEMS, - defaultSection: null, - aliases: ORGANIZATION_SETTINGS_PATH_ALIASES, - }) - const meta = parsed ? getSettingsSectionMeta('organization', parsed) : null - return { title: meta ? `${meta.label} - Organization settings` : 'Organization settings' } -} - -export default async function OrganizationSettingsSectionPage({ - params, -}: OrganizationSettingsSectionPageProps) { - const session = await getSession() - if (!session?.user) redirect('/login') - - const { organizationId, section } = await params - const parsed = parseSettingsPathSection({ - path: section, - items: ORGANIZATION_SETTINGS_ITEMS, - defaultSection: null, - aliases: ORGANIZATION_SETTINGS_PATH_ALIASES, - }) - if (!parsed) notFound() - - const canOpen = await canOpenOrganizationSettingsSection(organizationId, session.user.id, parsed) - if (!canOpen) return - const hasEnterprisePlan = - parsed !== 'members' && - parsed !== 'billing' && - (await isOrganizationOnEnterprisePlan(organizationId)) - if ( - !isOrganizationSettingsSectionAvailable( - parsed, - getOrganizationSettingsFeatures(hasEnterprisePlan) - ) - ) { - return ( - - ) - } - - return -} diff --git a/apps/sim/app/organization/[organizationId]/settings/layout.tsx b/apps/sim/app/organization/[organizationId]/settings/layout.tsx deleted file mode 100644 index 12dce2f96e3..00000000000 --- a/apps/sim/app/organization/[organizationId]/settings/layout.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { redirect } from 'next/navigation' -import { SettingsUnavailable } from '@/components/settings/settings-unavailable' -import { StandaloneSettingsShell } from '@/components/settings/standalone-settings-shell' -import { getSession } from '@/lib/auth' -import { isOrganizationOnEnterprisePlan } from '@/lib/billing' -import { getOrganizationSettingsAccess } from '@/lib/organizations/settings-access' - -interface OrganizationSettingsLayoutProps { - children: React.ReactNode - params: Promise<{ organizationId: string }> -} - -export default async function OrganizationSettingsLayout({ - children, - params, -}: OrganizationSettingsLayoutProps) { - const session = await getSession() - if (!session?.user) redirect('/login') - - const { organizationId } = await params - const access = await getOrganizationSettingsAccess(organizationId, session.user.id) - if (!access.isMember) return - const hasEnterprisePlan = access.isAdmin && (await isOrganizationOnEnterprisePlan(organizationId)) - - return ( - - {children} - - ) -} diff --git a/apps/sim/app/organization/[organizationId]/settings/page.tsx b/apps/sim/app/organization/[organizationId]/settings/page.tsx deleted file mode 100644 index c0d5c917a48..00000000000 --- a/apps/sim/app/organization/[organizationId]/settings/page.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import { redirect } from 'next/navigation' -import { getOrganizationSettingsHref } from '@/components/settings/navigation' - -interface OrganizationSettingsPageProps { - params: Promise<{ organizationId: string }> -} - -export default async function OrganizationSettingsPage({ params }: OrganizationSettingsPageProps) { - const { organizationId } = await params - redirect(getOrganizationSettingsHref(organizationId, 'members')) -} diff --git a/apps/sim/app/organization/[organizationId]/settings/unavailable/page.tsx b/apps/sim/app/organization/[organizationId]/settings/unavailable/page.tsx deleted file mode 100644 index 9e4bd26f677..00000000000 --- a/apps/sim/app/organization/[organizationId]/settings/unavailable/page.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { SettingsUnavailable } from '@/components/settings/settings-unavailable' - -export default function OrganizationSettingsUnavailablePage() { - return -} diff --git a/apps/sim/app/workspace/[workspaceId]/logs/components/dashboard/components/index.ts b/apps/sim/app/workspace/[workspaceId]/logs/components/dashboard/components/index.ts index 7f3469b912e..85d51971767 100644 --- a/apps/sim/app/workspace/[workspaceId]/logs/components/dashboard/components/index.ts +++ b/apps/sim/app/workspace/[workspaceId]/logs/components/dashboard/components/index.ts @@ -1,3 +1,2 @@ -export { LineChart, type LineChartMultiSeries, type LineChartPoint } from './line-chart' export { StatusBar, type StatusBarSegment } from './status-bar' export { type WorkflowExecutionItem, WorkflowsList } from './workflows-list' diff --git a/apps/sim/app/workspace/[workspaceId]/logs/components/dashboard/components/line-chart/index.ts b/apps/sim/app/workspace/[workspaceId]/logs/components/dashboard/components/line-chart/index.ts deleted file mode 100644 index be4131ebb5a..00000000000 --- a/apps/sim/app/workspace/[workspaceId]/logs/components/dashboard/components/line-chart/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { LineChart, type LineChartMultiSeries, type LineChartPoint } from './line-chart' diff --git a/apps/sim/app/workspace/[workspaceId]/logs/components/dashboard/dashboard.tsx b/apps/sim/app/workspace/[workspaceId]/logs/components/dashboard/dashboard.tsx index 83a53d724c0..44b145c8b54 100644 --- a/apps/sim/app/workspace/[workspaceId]/logs/components/dashboard/dashboard.tsx +++ b/apps/sim/app/workspace/[workspaceId]/logs/components/dashboard/dashboard.tsx @@ -3,6 +3,7 @@ import { memo, useCallback, useMemo, useRef, useState } from 'react' import { Loader } from '@sim/emcn' import { useParams } from 'next/navigation' +import { LineChart } from '@/components/charts' import { DashboardSegmentsContext, type SegmentSelectionMode, @@ -11,7 +12,7 @@ import { useLogFilters } from '@/app/workspace/[workspaceId]/logs/hooks/use-log- import { formatLatency } from '@/app/workspace/[workspaceId]/logs/utils' import type { DashboardStatsResponse, WorkflowStats } from '@/hooks/queries/logs' import { useWorkflows } from '@/hooks/queries/workflows' -import { LineChart, WorkflowsList } from './components' +import { WorkflowsList } from './components' interface WorkflowExecution { workflowId: string diff --git a/apps/sim/app/workspace/[workspaceId]/logs/utils.ts b/apps/sim/app/workspace/[workspaceId]/logs/utils.ts index 4736430b5b2..b45d859aee4 100644 --- a/apps/sim/app/workspace/[workspaceId]/logs/utils.ts +++ b/apps/sim/app/workspace/[workspaceId]/logs/utils.ts @@ -1,7 +1,8 @@ import React from 'react' import { Badge } from '@sim/emcn' -import { formatDuration, formatRelativeTime } from '@sim/utils/formatting' +import { formatRelativeTime } from '@sim/utils/formatting' import { format } from 'date-fns' +import { formatChartLatency } from '@/components/charts/chart-format' import { getIntegrationMetadata } from '@/lib/logs/get-trigger-options' import { getBlock } from '@/blocks/registry' import { CORE_TRIGGER_TYPES } from '@/stores/logs/filters/types' @@ -199,13 +200,16 @@ export function parseDuration(log: LogWithDuration): number | null { } /** - * Format latency value for display in dashboard UI + * Format latency value for display in dashboard UI. + * + * Delegates so the axis ticks and the surrounding table can never disagree about + * what a duration reads as. + * * @param ms - Latency in milliseconds (number) * @returns Formatted latency string */ export function formatLatency(ms: number): string { - if (!Number.isFinite(ms) || ms <= 0) return '—' - return formatDuration(ms, { precision: 2 }) ?? '—' + return formatChartLatency(ms) } export const formatDate = (dateString: string) => { diff --git a/apps/sim/app/workspace/[workspaceId]/settings/[section]/page.test.tsx b/apps/sim/app/workspace/[workspaceId]/settings/[section]/page.test.tsx index 26e408c6af1..0bf8767b548 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/[section]/page.test.tsx +++ b/apps/sim/app/workspace/[workspaceId]/settings/[section]/page.test.tsx @@ -42,6 +42,18 @@ vi.mock('@/components/settings/navigation', () => ({ getOrganizationSettingsFeatures: vi.fn(() => ({})), isOrganizationSettingsSectionAvailable: mockIsOrganizationSettingsSectionAvailable, resolveWorkspaceNavigation: mockResolveWorkspaceNavigation, + /** Mirrors the registry-derived map; a section absent here gets no organization gate. */ + UNIFIED_TO_ORGANIZATION_SECTION: { + organization: 'members', + billing: 'billing', + 'access-control': 'access-control', + 'audit-logs': 'audit-logs', + sso: 'sso', + sessions: 'sessions', + 'data-retention': 'data-retention', + 'data-drains': 'data-drains', + whitelabeling: 'whitelabeling', + }, workspaceSectionUsesPermissionConfig: vi.fn((section: string) => ['secrets', 'api-keys', 'inbox', 'mcp', 'custom-tools'].includes(section) ), diff --git a/apps/sim/app/workspace/[workspaceId]/settings/[section]/page.tsx b/apps/sim/app/workspace/[workspaceId]/settings/[section]/page.tsx index 0cf8a89a781..ca0c7426125 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/[section]/page.tsx +++ b/apps/sim/app/workspace/[workspaceId]/settings/[section]/page.tsx @@ -5,8 +5,8 @@ import { notFound, redirect } from 'next/navigation' import { getOrganizationSettingsFeatures, isOrganizationSettingsSectionAvailable, - type OrganizationSettingsSection, resolveWorkspaceNavigation, + UNIFIED_TO_ORGANIZATION_SECTION, type WorkspaceSettingsSection, workspaceSectionUsesPermissionConfig, } from '@/components/settings/navigation' @@ -48,18 +48,6 @@ const WORKSPACE_SECTION_MAP: Partial> = { - organization: 'members', - billing: 'billing', - 'access-control': 'access-control', - 'audit-logs': 'audit-logs', - sso: 'sso', - sessions: 'sessions', - 'data-retention': 'data-retention', - 'data-drains': 'data-drains', - whitelabeling: 'whitelabeling', -} - /** * Settings availability varies across workspaces, so a preserved section may * need to land on the destination workspace's universally available page. @@ -163,7 +151,7 @@ export default async function WorkspaceSettingsSectionPage({ } } - const organizationSection = ORGANIZATION_SECTION_MAP[parsed] + const organizationSection = UNIFIED_TO_ORGANIZATION_SECTION[parsed] if (organizationSection) { if (!isBillingEnabled && (parsed === 'billing' || parsed === 'organization')) { redirectToGeneralSettings(workspaceId) @@ -173,7 +161,12 @@ export default async function WorkspaceSettingsSectionPage({ redirectToGeneralSettings(workspaceId) } } else { - if (!hostContext.viewer.isHostOrganizationAdmin) { + /** + * The roster is the one organization section a plain member may open, read-only + * (`resolveOrganizationSectionAccess` returns `'view'` for it below). Everything + * else acts on the organization and stays admin-only. + */ + if (organizationSection !== 'members' && !hostContext.viewer.isHostOrganizationAdmin) { redirectToGeneralSettings(workspaceId) } /** diff --git a/apps/sim/app/workspace/[workspaceId]/settings/[section]/settings.tsx b/apps/sim/app/workspace/[workspaceId]/settings/[section]/settings.tsx index edb01ca9214..b565424b422 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/[section]/settings.tsx +++ b/apps/sim/app/workspace/[workspaceId]/settings/[section]/settings.tsx @@ -104,6 +104,9 @@ const DataRetentionSettings = dynamic(() => const DataDrainsSettings = dynamic(() => import('@/ee/data-drains/components/data-drains-settings').then((m) => m.DataDrainsSettings) ) +const UsageMonitoring = dynamic(() => + import('@/ee/organization-usage/components/usage-monitoring').then((m) => m.UsageMonitoring) +) const Desktop = dynamic(() => import('@/app/workspace/[workspaceId]/settings/components/desktop/desktop').then((m) => m.Desktop) ) @@ -172,6 +175,9 @@ export function SettingsPage({ section }: SettingsPageProps) { {effectiveSection === 'audit-logs' && organizationId && ( )} + {effectiveSection === 'usage' && organizationId && ( + + )} {effectiveSection === 'apikeys' && } {isBillingEnabled && effectiveSection === 'billing' && ( 0 ? Math.min(segments, Math.round((used / total) * segments)) : used > 0 ? segments : 0 + const allowedSegments = total > 0 ? segments : 0 + + return ( +

-
- {Array.from({ length: pillCount }).map((_, i) => { - const isFilled = i < usedSeats - const isOverage = i >= totalSeats - return ( -
- ) - })} -
+

{isOverLimit diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/team-management/team-management.test.tsx b/apps/sim/app/workspace/[workspaceId]/settings/components/team-management/team-management.test.tsx index 94c191fb855..ffa5a03c2ff 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/components/team-management/team-management.test.tsx +++ b/apps/sim/app/workspace/[workspaceId]/settings/components/team-management/team-management.test.tsx @@ -94,7 +94,11 @@ describe('TeamManagement organization errors', () => { isLoading: false, }) - act(() => root.render()) + act(() => + root.render( + + ) + ) expect(container.textContent).toContain('Organization request failed') expect(container.textContent).not.toContain('no-organization-view') diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/team-management/team-management.tsx b/apps/sim/app/workspace/[workspaceId]/settings/components/team-management/team-management.tsx index b62f1e0b292..8efc23f7ba5 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/components/team-management/team-management.tsx +++ b/apps/sim/app/workspace/[workspaceId]/settings/components/team-management/team-management.tsx @@ -35,13 +35,14 @@ const logger = createLogger('TeamManagement') interface TeamManagementProps { organizationId: string - billingHref?: string + /** + * Required: organization billing is reached only through a workspace, so the + * caller — which knows the workspace — is the only thing that can build it. + */ + billingHref: string } -export function TeamManagement({ - organizationId, - billingHref = `/organization/${organizationId}/settings/billing`, -}: TeamManagementProps) { +export function TeamManagement({ organizationId, billingHref }: TeamManagementProps) { const { data: session } = useSession() const { isInvitationsDisabled } = usePermissionConfig() const [memberQuery, setMemberQuery] = useSettingsSearch() diff --git a/apps/sim/app/workspace/[workspaceId]/settings/navigation.test.ts b/apps/sim/app/workspace/[workspaceId]/settings/navigation.test.ts index b3c64e62c0d..f304c1e6574 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/navigation.test.ts +++ b/apps/sim/app/workspace/[workspaceId]/settings/navigation.test.ts @@ -31,6 +31,7 @@ describe('unified settings navigation', () => { { id: 'billing', label: 'Subscription', section: 'account' }, { id: 'teammates', label: 'Teammates', section: 'workspace' }, { id: 'organization', label: 'Members', section: 'organization' }, + { id: 'usage', label: 'Usage tracking', section: 'organization' }, { id: 'secrets', label: 'Secrets', section: 'workspace' }, { id: 'credential-groups', label: 'Credential groups', section: 'workspace' }, { id: 'custom-tools', label: 'Custom tools', section: 'workspace' }, @@ -82,6 +83,7 @@ describe('unified settings navigation', () => { ]) expect(idsForSection('organization')).toEqual([ 'organization', + 'usage', 'custom-blocks', 'forks', 'access-control', diff --git a/apps/sim/app/workspace/[workspaceId]/settings/usage/events/layout.tsx b/apps/sim/app/workspace/[workspaceId]/settings/usage/events/layout.tsx new file mode 100644 index 00000000000..5886a0c78b5 --- /dev/null +++ b/apps/sim/app/workspace/[workspaceId]/settings/usage/events/layout.tsx @@ -0,0 +1,18 @@ +import { + SettingsHeaderProvider, + SettingsHeaderShell, +} from '@/app/workspace/[workspaceId]/settings/components/settings-header/settings-header' + +/** + * Usage events is a static route outside `[section]`, so it inherits none of + * `SettingsSectionLayout`'s chrome. Its body renders through `SettingsPanel`, which + * only registers header config into `SettingsHeaderProvider` — without this shell the + * page would have no header bar, title, or scroll region. + */ +export default function UsageEventsLayout({ children }: { children: React.ReactNode }) { + return ( + + {children} + + ) +} diff --git a/apps/sim/app/workspace/[workspaceId]/settings/usage/events/loading.tsx b/apps/sim/app/workspace/[workspaceId]/settings/usage/events/loading.tsx new file mode 100644 index 00000000000..aa745a1024e --- /dev/null +++ b/apps/sim/app/workspace/[workspaceId]/settings/usage/events/loading.tsx @@ -0,0 +1,22 @@ +'use client' + +import { ArrowLeft } from '@sim/emcn/icons' +import { useParams, useRouter } from 'next/navigation' +import { SettingsPanel } from '@/app/workspace/[workspaceId]/settings/components/settings-panel' + +export default function UsageEventsLoading() { + const router = useRouter() + const { workspaceId } = useParams<{ workspaceId: string }>() + + return ( + router.push(`/workspace/${workspaceId}/settings/usage`), + }} + title='Usage events' + description='Every credit-consuming event behind your usage.' + /> + ) +} diff --git a/apps/sim/app/workspace/[workspaceId]/settings/usage/events/page.tsx b/apps/sim/app/workspace/[workspaceId]/settings/usage/events/page.tsx new file mode 100644 index 00000000000..36c0777ef07 --- /dev/null +++ b/apps/sim/app/workspace/[workspaceId]/settings/usage/events/page.tsx @@ -0,0 +1,47 @@ +import { Suspense } from 'react' +import type { Metadata } from 'next' +import { redirect } from 'next/navigation' +import { getSession } from '@/lib/auth' +import { isOrganizationFeatureEntitled } from '@/lib/billing/core/subscription' +import { isUsageMonitoringEnabled } from '@/lib/core/config/env-flags' +import { getWorkspaceHostContextForViewer } from '@/lib/workspaces/host-context' +import UsageEventsLoading from '@/app/workspace/[workspaceId]/settings/usage/events/loading' +import { UsageEventsView } from '@/ee/organization-usage/components/usage-events-view' + +export const metadata: Metadata = { + title: 'Usage events', +} + +interface UsageEventsPageProps { + params: Promise<{ workspaceId: string }> +} + +/** + * This route sits outside `[section]`, so it inherits none of that page's gates and + * has to repeat them: a host organization, an org-admin viewer, and the enterprise + * entitlement. The API refuses regardless; this keeps a deep link from rendering a + * shell the viewer can never fill. + */ +export default async function UsageEventsPage({ params }: UsageEventsPageProps) { + const session = await getSession() + if (!session?.user) redirect('/login') + + const { workspaceId } = await params + const hostContext = await getWorkspaceHostContextForViewer(workspaceId, session.user.id) + const organizationId = hostContext?.hostOrganizationId + if (!hostContext || !organizationId || !hostContext.viewer.isHostOrganizationAdmin) { + redirect(`/workspace/${workspaceId}/settings/general`) + } + if (!(await isOrganizationFeatureEntitled(organizationId, isUsageMonitoringEnabled))) { + redirect(`/workspace/${workspaceId}/settings/general`) + } + + return ( + }> + + + ) +} diff --git a/apps/sim/components/charts/bar-chart.tsx b/apps/sim/components/charts/bar-chart.tsx new file mode 100644 index 00000000000..a1156ce6545 --- /dev/null +++ b/apps/sim/components/charts/bar-chart.tsx @@ -0,0 +1,338 @@ +'use client' + +import { memo, useMemo, useRef, useState } from 'react' +import { cn } from '@sim/emcn' +import { generateShortId } from '@sim/utils/id' +import { + formatChartCompactNumber, + formatChartLatency, + formatChartTimestamp, +} from '@/components/charts/chart-format' +import { + CHART_DEFAULT_HEIGHT, + CHART_GRID_FRACTIONS, + CHART_PADDING, + CHART_TICK_FILL, + CHART_TICK_FONT_SIZE, + chartPlotBand, + formatTimeTick, + resolveSpanMs, + resolveTimeTickIndices, +} from '@/components/charts/chart-geometry' +import { + ChartTooltip, + ChartTooltipRow, + estimateTooltipWidth, + positionChartTooltip, +} from '@/components/charts/chart-tooltip' +import { + useChartWidth, + useIsDarkTheme, + useResolvedChartColors, +} from '@/components/charts/use-chart-theme' + +export interface BarChartPoint { + timestamp: string + value: number +} + +interface BarChartProps { + data: BarChartPoint[] + /** Pass `''` for the caller-owned-wrapper form, mirroring {@link LineChart}. */ + label: string + color: string + /** `''` | `'%'` | `'ms'` | `'latency'` | `'credits'` — drives tick and tooltip formatting. */ + unit?: string + height?: number + /** Bucket drawn at full opacity, e.g. the period in progress. */ + highlightIndex?: number +} + +/** + * Discrete time buckets as bars. + * + * The sibling of {@link LineChart}, and deliberately built from the same geometry, + * tooltip, and theme modules: a smoothed line implies a continuous signal between + * samples, which is wrong for a calendar bucket like a day's spend, but the two must + * still line up pixel-for-pixel when stacked in one card. + */ +function BarChartComponent({ + data, + label, + color, + unit, + height = CHART_DEFAULT_HEIGHT, + highlightIndex, +}: BarChartProps) { + const uniqueId = useRef(`bar-${generateShortId(7)}`).current + const [containerRef, containerWidth] = useChartWidth() + const width = containerWidth ?? 0 + const padding = CHART_PADDING + const chartWidth = width - padding.left - padding.right + const chartHeight = height - padding.top - padding.bottom + const { yMin, yMax } = chartPlotBand(height) + const isDark = useIsDarkTheme() + const [hoverIndex, setHoverIndex] = useState(null) + const [hoverPos, setHoverPos] = useState<{ x: number; y: number } | null>(null) + + const colorTokens = useMemo(() => ({ base: color }), [color]) + const resolvedColors = useResolvedChartColors(colorTokens) + const resolvedColor = resolvedColors.base || color + + const hasExternalWrapper = !label + + /** + * The track is read against its own background, so its opacity is per-theme + * rather than one shared value. `--border` is the platform's neutral track + * token — the same one the proportional row meters use — but it resolves to + * `#444` on dark and `#d8d8d8` on light, and a strength that reads as a column + * on near-black is a half-percent delta on white. Hover keeps the same ratio. + */ + const trackOpacity = isDark ? 0.12 : 0.3 + const trackHoverOpacity = isDark ? 0.22 : 0.5 + + const maxValue = useMemo(() => { + const peak = Math.max(...data.map((d) => d.value), 0) + return peak <= 0 ? 1 : peak * 1.1 + }, [data]) + + /** Slot geometry: every bucket owns an equal slice, with the bar centred in it. */ + const slot = data.length > 0 ? Math.max(1, chartWidth) / data.length : 0 + const barWidth = Math.max(1, Math.min(24, slot * 0.7)) + + const bars = useMemo( + () => + data.map((point, index) => { + const x = padding.left + slot * index + (slot - barWidth) / 2 + const rawY = padding.top + chartHeight - (point.value / maxValue) * chartHeight + const y = Math.max(yMin, Math.min(yMax, rawY)) + return { x, y, height: Math.max(0, height - padding.bottom - y), point } + }), + [data, slot, barWidth, maxValue, chartHeight, height, padding.left, padding.top, yMin, yMax] + ) + + const formatValue = (value?: number) => { + if (typeof value !== 'number' || !Number.isFinite(value)) return '—' + const suffix = (unit ?? '').toLowerCase() + if (suffix.includes('%')) return `${value.toFixed(1)}%` + if (suffix === 'latency') return formatChartLatency(value) + if (suffix.includes('ms')) return `${Math.round(value)}ms` + if (suffix === 'credits') return formatChartCompactNumber(value) + return `${Math.round(value)}${unit ?? ''}` + } + + if (containerWidth === null) { + return ( +

+ ) + } + + if (data.length === 0) { + return ( +
+

No data

+
+ ) + } + + const spanMs = resolveSpanMs(data) + const tickIndices = resolveTimeTickIndices(data.length, Math.max(1, chartWidth)) + + return ( +
+ {!hasExternalWrapper && ( +
+

{label}

+
+ )} +
+ { + if (bars.length === 0 || slot <= 0) return + const rect = (e.currentTarget as SVGSVGElement).getBoundingClientRect() + const x = e.clientX - rect.left + // Bars own a slot, so the hovered bucket is which slot the cursor is in — + // not the nearest sample, which is how a line chart resolves it. + const index = Math.max( + 0, + Math.min(data.length - 1, Math.floor((x - padding.left) / slot)) + ) + setHoverIndex(index) + setHoverPos({ x, y: e.clientY - rect.top }) + }} + onMouseLeave={() => { + setHoverIndex(null) + setHoverPos(null) + }} + > + + + + + + + + + + {CHART_GRID_FRACTIONS.map((fraction) => ( + + ))} + + {/* + A full-height track keeps an empty bucket visible and gives every slot + the same hover target, so a run of zero days reads as zero rather than + as missing data. + + Drawn outside the blend group below: the bars want `screen` on dark so + the gradient stays luminous, but a track composited that way is only + legible against a dark background, and on white it disappears. + */} + + {bars.map((bar, index) => ( + + ))} + + + + {bars.map( + (bar, index) => + bar.height > 0 && ( + + ) + )} + + + {tickIndices.map((index) => { + const timestamp = data[index]?.timestamp + if (!timestamp) return null + const date = new Date(timestamp) + return ( + + {Number.isNaN(date.getTime()) ? '' : formatTimeTick(date, spanMs)} + + ) + })} + + + {formatChartCompactNumber(maxValue)} + + + 0 + + + + + + {hoverIndex !== null && + bars[hoverIndex] && + (() => { + const bar = bars[hoverIndex] + const value = formatValue(bar.point.value) + const { left, top } = positionChartTooltip({ + anchorX: hoverPos?.x ?? bar.x, + anchorY: hoverPos?.y ?? bar.y, + width, + height, + tooltipMaxWidth: estimateTooltipWidth(value.length), + }) + return ( + + + + ) + })()} +
+
+ ) +} + +export const BarChart = memo(BarChartComponent) diff --git a/apps/sim/components/charts/chart-format.ts b/apps/sim/components/charts/chart-format.ts new file mode 100644 index 00000000000..c5b7151814b --- /dev/null +++ b/apps/sim/components/charts/chart-format.ts @@ -0,0 +1,31 @@ +import { formatDuration } from '@sim/utils/formatting' +import { format } from 'date-fns' + +/** + * Value and tick formatting shared by the chart family. + * + * These live here rather than in the logs feature's `utils.ts` because that module + * imports the block registry, and a chart that reached for it would drag the whole + * executable registry into every consumer's bundle. + */ + +/** Duration for an axis tick or tooltip. `—` for a missing or non-positive value. */ +export function formatChartLatency(ms: number): string { + if (!Number.isFinite(ms) || ms <= 0) return '—' + return formatDuration(ms, { precision: 2 }) ?? '—' +} + +/** The tooltip's header line: `MAR 4 3:05 PM`. Empty for an unparseable timestamp. */ +export function formatChartTimestamp(timestamp?: string): string { + if (!timestamp) return '' + const date = new Date(timestamp) + if (Number.isNaN(date.getTime())) return '' + return `${format(date, 'MMM d').toUpperCase()} ${format(date, 'h:mm a')}` +} + +/** Compact axis magnitude — `1.2k`, `3.4m`. */ +export function formatChartCompactNumber(value: number): string { + return new Intl.NumberFormat('en-US', { notation: 'compact', maximumFractionDigits: 1 }) + .format(value) + .toLowerCase() +} diff --git a/apps/sim/components/charts/chart-geometry.test.ts b/apps/sim/components/charts/chart-geometry.test.ts new file mode 100644 index 00000000000..82c2694abbe --- /dev/null +++ b/apps/sim/components/charts/chart-geometry.test.ts @@ -0,0 +1,80 @@ +/** + * @vitest-environment node + */ +import { describe, expect, it } from 'vitest' +import { + CHART_PADDING, + chartPlotBand, + formatTimeTick, + resolveSpanMs, + resolveTimeTickIndices, +} from '@/components/charts/chart-geometry' + +describe('resolveTimeTickIndices', () => { + it('budgets roughly one tick per 64px, clamped to 3..8', () => { + expect(resolveTimeTickIndices(100, 100).length).toBe(3) + expect(resolveTimeTickIndices(100, 320).length).toBe(5) + expect(resolveTimeTickIndices(100, 4000).length).toBe(8) + }) + + it('dedupes the collisions rounding produces on a short series', () => { + // 2 points across a wide chart wants 8 ticks but only has indices 0 and 1. + const indices = resolveTimeTickIndices(2, 4000) + expect(indices).toEqual([...new Set(indices)]) + expect(indices.every((index) => index >= 0 && index < 2)).toBe(true) + }) + + it('always spans the full series, first index to last', () => { + const indices = resolveTimeTickIndices(50, 640) + expect(indices[0]).toBe(0) + expect(indices[indices.length - 1]).toBe(49) + }) +}) + +describe('formatTimeTick', () => { + const date = new Date('2026-03-04T15:05:00.000Z') + + it('shows clock time within a day and a half', () => { + expect(formatTimeTick(date, 36 * 60 * 60 * 1000)).toMatch(/^\d{2}:\d{2}$/) + }) + + it('shows a calendar day within a quarter', () => { + expect(formatTimeTick(date, 40 * 24 * 60 * 60 * 1000)).toMatch(/^[A-Z][a-z]{2} \d{1,2}$/) + }) + + it('shows month and year beyond a quarter', () => { + expect(formatTimeTick(date, 400 * 24 * 60 * 60 * 1000)).toMatch(/^[A-Z][a-z]{2} \d{4}$/) + }) +}) + +describe('resolveSpanMs', () => { + it('measures first to last', () => { + expect( + resolveSpanMs([ + { timestamp: '2026-03-04T00:00:00.000Z' }, + { timestamp: '2026-03-05T00:00:00.000Z' }, + ]) + ).toBe(24 * 60 * 60 * 1000) + }) + + it('returns 0 for a degenerate or unparseable series rather than NaN', () => { + expect(resolveSpanMs([])).toBe(0) + expect(resolveSpanMs([{ timestamp: '2026-03-04T00:00:00.000Z' }])).toBe(0) + expect(resolveSpanMs([{ timestamp: 'nope' }, { timestamp: 'also nope' }])).toBe(0) + }) +}) + +describe('chartPlotBand', () => { + it('insets the band so strokes clear the axis rules', () => { + // The line and bar charts both clamp to this band, which is what keeps them + // aligned when stacked in the same card. + expect(chartPlotBand(166)).toEqual({ + yMin: CHART_PADDING.top + 3, + yMax: CHART_PADDING.top + (166 - CHART_PADDING.top - CHART_PADDING.bottom) - 3, + }) + }) + + it('tracks a caller-supplied height', () => { + expect(chartPlotBand(240).yMax).toBeGreaterThan(chartPlotBand(166).yMax) + }) +}) diff --git a/apps/sim/components/charts/chart-geometry.ts b/apps/sim/components/charts/chart-geometry.ts new file mode 100644 index 00000000000..e85a010d251 --- /dev/null +++ b/apps/sim/components/charts/chart-geometry.ts @@ -0,0 +1,67 @@ +/** + * Geometry shared by every chart in the family. + * + * Extracted so a sibling chart cannot drift: bar and line charts read the same + * padding, the same clamps, the same gridlines, and resolve their x ticks the same + * way, so two charts stacked in one card line up on the pixel. + * + * Pure — no React, no DOM — so a server module can read the constants. + */ + +export const CHART_PADDING = { top: 16, right: 28, bottom: 26, left: 26 } as const + +/** Matches the loader placeholders callers size themselves against. */ +export const CHART_DEFAULT_HEIGHT = 166 + +/** Below this the axis labels collide, so the chart scrolls rather than compresses. */ +export const CHART_MIN_WIDTH = 280 + +export const CHART_TICK_FILL = 'var(--text-tertiary)' +export const CHART_TICK_FONT_SIZE = '9' +export const CHART_GRID_FRACTIONS = [0.25, 0.5, 0.75] as const + +/** Vertical clamp for plotted geometry, keeping strokes off the axis rules. */ +export function chartPlotBand(height: number): { yMin: number; yMax: number } { + const chartHeight = height - CHART_PADDING.top - CHART_PADDING.bottom + return { yMin: CHART_PADDING.top + 3, yMax: CHART_PADDING.top + chartHeight - 3 } +} + +/** + * Evenly spaced point indices to label, budgeting ~64px per tick and deduping the + * collisions that rounding produces on short series. + */ +export function resolveTimeTickIndices(pointCount: number, usableWidth: number): number[] { + const approxLabelWidth = 64 + const desired = Math.min(8, Math.max(3, Math.floor(usableWidth / approxLabelWidth))) + const seen = new Set() + return Array.from({ length: desired }, (_, i) => + Math.round((i * (pointCount - 1)) / Math.max(1, desired - 1)) + ).filter((index) => { + if (seen.has(index)) return false + seen.add(index) + return true + }) +} + +/** + * Tick label whose precision follows the window: clock time within a day and a half, + * calendar day within a quarter, month beyond that. + */ +export function formatTimeTick(date: Date, spanMs: number): string { + if (spanMs <= 36 * 60 * 60 * 1000) { + return date.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', hour12: false }) + } + if (spanMs <= 90 * 24 * 60 * 60 * 1000) { + return date.toLocaleString('en-US', { month: 'short', day: 'numeric' }) + } + return date.toLocaleString('en-US', { month: 'short', year: 'numeric' }) +} + +/** Milliseconds between the first and last timestamp, or 0 for a degenerate series. */ +export function resolveSpanMs(points: ReadonlyArray<{ timestamp: string }>): number { + if (points.length < 2) return 0 + const first = new Date(points[0].timestamp).getTime() + const last = new Date(points[points.length - 1].timestamp).getTime() + if (Number.isNaN(first) || Number.isNaN(last)) return 0 + return Math.abs(last - first) +} diff --git a/apps/sim/components/charts/chart-tooltip.tsx b/apps/sim/components/charts/chart-tooltip.tsx new file mode 100644 index 00000000000..6a49032394a --- /dev/null +++ b/apps/sim/components/charts/chart-tooltip.tsx @@ -0,0 +1,89 @@ +'use client' + +import type { ReactNode } from 'react' +import { CHART_PADDING } from '@/components/charts/chart-geometry' + +/** + * The chart family's hover surface. Defined once so a sibling chart cannot ship a + * tooltip that looks almost the same — this class string was previously duplicated + * between the line chart and the status bar. + */ +export const CHART_TOOLTIP_CLASSES = + 'pointer-events-none absolute rounded-lg border border-[var(--border-1)] bg-[var(--surface-1)] px-2 py-1.5 text-xs shadow-lg' + +interface PositionChartTooltipArgs { + anchorX: number + anchorY: number + width: number + height: number + tooltipMaxWidth: number +} + +/** + * Places the tooltip beside the cursor, preferring the right and flipping left when + * it would overflow, then clamping into the plot band so it never escapes the card. + */ +export function positionChartTooltip({ + anchorX, + anchorY, + width, + height, + tooltipMaxWidth, +}: PositionChartTooltipArgs): { left: number; top: number } { + const margin = 10 + const rightEdge = width - CHART_PADDING.right + const preferRight = anchorX + margin + tooltipMaxWidth <= rightEdge + const left = preferRight + ? Math.max(CHART_PADDING.left, Math.min(anchorX + margin, rightEdge - tooltipMaxWidth)) + : Math.max( + CHART_PADDING.left, + Math.min(anchorX - margin - tooltipMaxWidth, rightEdge - tooltipMaxWidth) + ) + const top = Math.min( + Math.max(anchorY - 26, CHART_PADDING.top), + height - CHART_PADDING.bottom - 18 + ) + return { left, top } +} + +/** Width estimate for the longest `label value` row, used by {@link positionChartTooltip}. */ +export function estimateTooltipWidth(longestRowLength: number): number { + return Math.min(220, Math.max(80, 7 * longestRowLength + 24)) +} + +interface ChartTooltipProps { + left: number + top: number + /** Header line; omitted when the timestamp could not be formatted. */ + date?: string + children: ReactNode +} + +export function ChartTooltip({ left, top, date, children }: ChartTooltipProps) { + return ( +
+ {date &&
{date}
} + {children} +
+ ) +} + +interface ChartTooltipRowProps { + color: string + label?: string + value: string +} + +export function ChartTooltipRow({ color, label, value }: ChartTooltipRowProps) { + return ( +
+
+ ) +} diff --git a/apps/sim/components/charts/index.ts b/apps/sim/components/charts/index.ts new file mode 100644 index 00000000000..138241f0436 --- /dev/null +++ b/apps/sim/components/charts/index.ts @@ -0,0 +1,12 @@ +export { BarChart, type BarChartPoint } from '@/components/charts/bar-chart' +export { + formatChartCompactNumber, + formatChartLatency, + formatChartTimestamp, +} from '@/components/charts/chart-format' +export { CHART_DEFAULT_HEIGHT } from '@/components/charts/chart-geometry' +export { + LineChart, + type LineChartMultiSeries, + type LineChartPoint, +} from '@/components/charts/line-chart' diff --git a/apps/sim/app/workspace/[workspaceId]/logs/components/dashboard/components/line-chart/line-chart.tsx b/apps/sim/components/charts/line-chart.tsx similarity index 75% rename from apps/sim/app/workspace/[workspaceId]/logs/components/dashboard/components/line-chart/line-chart.tsx rename to apps/sim/components/charts/line-chart.tsx index 9717ab254e7..db22e5f2353 100644 --- a/apps/sim/app/workspace/[workspaceId]/logs/components/dashboard/components/line-chart/line-chart.tsx +++ b/apps/sim/components/charts/line-chart.tsx @@ -1,7 +1,35 @@ -import { memo, useEffect, useMemo, useRef, useState } from 'react' +'use client' + +import { memo, useMemo, useRef, useState } from 'react' import { Button, cn } from '@sim/emcn' import { generateShortId } from '@sim/utils/id' -import { formatDate, formatLatency } from '@/app/workspace/[workspaceId]/logs/utils' +import { + formatChartCompactNumber, + formatChartLatency, + formatChartTimestamp, +} from '@/components/charts/chart-format' +import { + CHART_DEFAULT_HEIGHT, + CHART_GRID_FRACTIONS, + CHART_PADDING, + CHART_TICK_FILL, + CHART_TICK_FONT_SIZE, + chartPlotBand, + formatTimeTick, + resolveSpanMs, + resolveTimeTickIndices, +} from '@/components/charts/chart-geometry' +import { + ChartTooltip, + ChartTooltipRow, + estimateTooltipWidth, + positionChartTooltip, +} from '@/components/charts/chart-tooltip' +import { + useChartWidth, + useIsDarkTheme, + useResolvedChartColors, +} from '@/components/charts/use-chart-theme' export interface LineChartPoint { timestamp: string @@ -16,83 +44,45 @@ export interface LineChartMultiSeries { dashed?: boolean } +interface LineChartProps { + data: LineChartPoint[] + /** Pass `''` for the caller-owned-wrapper form: no card chrome, title, or legend. */ + label: string + color: string + unit?: string + series?: LineChartMultiSeries[] + height?: number +} + function LineChartComponent({ data, label, color, unit, series, -}: { - data: LineChartPoint[] - label: string - color: string - unit?: string - series?: LineChartMultiSeries[] -}) { - const containerRef = useRef(null) + height = CHART_DEFAULT_HEIGHT, +}: LineChartProps) { const uniqueId = useRef(`chart-${generateShortId(7)}`).current - const [containerWidth, setContainerWidth] = useState(null) + const [containerRef, containerWidth] = useChartWidth() const width = containerWidth ?? 0 - const height = 166 - const padding = { top: 16, right: 28, bottom: 26, left: 26 } - useEffect(() => { - if (!containerRef.current) return - const element = containerRef.current - const ro = new ResizeObserver((entries) => { - const entry = entries[0] - if (entry?.contentRect && entry.contentRect.width > 0) { - const w = Math.max(280, Math.floor(entry.contentRect.width)) - setContainerWidth(w) - } - }) - ro.observe(element) - const rect = element.getBoundingClientRect() - if (rect?.width && rect.width > 0) setContainerWidth(Math.max(280, Math.floor(rect.width))) - return () => ro.disconnect() - }, []) + const padding = CHART_PADDING const chartWidth = width - padding.left - padding.right const chartHeight = height - padding.top - padding.bottom const [hoverIndex, setHoverIndex] = useState(null) - const [isDark, setIsDark] = useState(true) + const isDark = useIsDarkTheme() const [hoverSeriesId, setHoverSeriesId] = useState(null) const [activeSeriesId, setActiveSeriesId] = useState(null) const [hoverPos, setHoverPos] = useState<{ x: number; y: number } | null>(null) - const [resolvedColors, setResolvedColors] = useState>({}) - - useEffect(() => { - if (typeof window === 'undefined') return - const el = document.documentElement - const update = () => setIsDark(el.classList.contains('dark')) - update() - const observer = new MutationObserver(update) - observer.observe(el, { attributes: true, attributeFilter: ['class'] }) - return () => observer.disconnect() - }, []) - - useEffect(() => { - if (typeof window === 'undefined') return - - const resolveColor = (c: string): string => { - if (!c.startsWith('var(')) return c - - const tempEl = document.createElement('div') - tempEl.style.color = c - document.body.appendChild(tempEl) - const computed = window.getComputedStyle(tempEl).color - document.body.removeChild(tempEl) - return computed - } - - const colorMap: Record = { base: resolveColor(color) } - const allSeriesToResolve = Array.isArray(series) && series.length > 0 ? series : [] - for (const s of allSeriesToResolve) { + const colorTokens = useMemo(() => { + const tokens: Record = { base: color } + for (const s of series ?? []) { const id = s.id || s.label || '' - if (id) colorMap[id] = resolveColor(s.color) + if (id) tokens[id] = s.color } - - setResolvedColors(colorMap) + return tokens }, [color, series]) + const resolvedColors = useResolvedChartColors(colorTokens) const hasExternalWrapper = !label || label === '' @@ -135,8 +125,7 @@ function LineChartComponent({ } }, [allSeries, unit]) - const yMin = padding.top + 3 - const yMax = padding.top + chartHeight - 3 + const { yMin, yMax } = chartPlotBand(height) const scaledPoints = useMemo( () => @@ -202,26 +191,8 @@ function LineChartComponent({ return d }, [scaledPoints, yMin, yMax]) - const getCompactDateLabel = (timestamp?: string) => { - if (!timestamp) return '' - try { - const f = formatDate(timestamp) - return `${f.compactDate} ${f.compactTime}` - } catch (e) { - const d = new Date(timestamp) - if (Number.isNaN(d.getTime())) return '' - return d.toLocaleString('en-US', { - month: 'short', - day: 'numeric', - hour: '2-digit', - minute: '2-digit', - hour12: false, - }) - } - } - const currentHoverDate = - hoverIndex !== null && data[hoverIndex] ? getCompactDateLabel(data[hoverIndex].timestamp) : '' + hoverIndex !== null && data[hoverIndex] ? formatChartTimestamp(data[hoverIndex].timestamp) : '' if (containerWidth === null) { return ( @@ -371,7 +342,7 @@ function LineChartComponent({ strokeWidth='1' /> - {[0.25, 0.5, 0.75].map((p) => ( + {CHART_GRID_FRACTIONS.map((p) => ( { if (data.length < 2) return null const usableW = Math.max(1, chartWidth) - const firstTs = new Date(data[0].timestamp) - const lastTs = new Date(data[data.length - 1].timestamp) - const spanMs = Math.abs(lastTs.getTime() - firstTs.getTime()) - - const approxLabelWidth = 64 - const desired = Math.min(8, Math.max(3, Math.floor(usableW / approxLabelWidth))) - const rawIdx = Array.from({ length: desired }, (_, i) => - Math.round((i * (data.length - 1)) / Math.max(1, desired - 1)) - ) - const seen = new Set() - const idx = rawIdx.filter((i) => { - if (seen.has(i)) return false - seen.add(i) - return true - }) - - const formatTick = (d: Date) => { - if (spanMs <= 36 * 60 * 60 * 1000) { - return d.toLocaleTimeString('en-US', { - hour: '2-digit', - minute: '2-digit', - hour12: false, - }) - } - if (spanMs <= 90 * 24 * 60 * 60 * 1000) { - return d.toLocaleString('en-US', { month: 'short', day: 'numeric' }) - } - return d.toLocaleString('en-US', { month: 'short', year: 'numeric' }) - } + const spanMs = resolveSpanMs(data) + const idx = resolveTimeTickIndices(data.length, usableW) return idx.map((i) => { const x = padding.left + (i / (data.length - 1 || 1)) * usableW const tsSource = data[i]?.timestamp if (!tsSource) return null const ts = new Date(tsSource) - const labelStr = Number.isNaN(ts.getTime()) ? '' : formatTick(ts) + const labelStr = Number.isNaN(ts.getTime()) ? '' : formatTimeTick(ts, spanMs) return ( {labelStr} @@ -577,16 +521,8 @@ function LineChartComponent({ const showInTicks = unitSuffix === '%' const isLatency = unitSuffix.toLowerCase() === 'latency' const fmtCompact = (v: number) => { - if (isLatency) { - if (v === 0) return '0' - return formatLatency(v) - } - return new Intl.NumberFormat('en-US', { - notation: 'compact', - maximumFractionDigits: 1, - }) - .format(v) - .toLowerCase() + if (isLatency) return v === 0 ? '0' : formatChartLatency(v) + return formatChartCompactNumber(v) } return ( <> @@ -594,8 +530,8 @@ function LineChartComponent({ x={padding.left - 8} y={padding.top} textAnchor='end' - fontSize='9' - fill='var(--text-tertiary)' + fontSize={CHART_TICK_FONT_SIZE} + fill={CHART_TICK_FILL} > {fmtCompact(maxValue)} {showInTicks && !isLatency ? unit : ''} @@ -604,8 +540,8 @@ function LineChartComponent({ x={padding.left - 8} y={height - padding.bottom} textAnchor='end' - fontSize='9' - fill='var(--text-tertiary)' + fontSize={CHART_TICK_FONT_SIZE} + fill={CHART_TICK_FILL} > {fmtCompact(minValue)} {showInTicks && !isLatency ? unit : ''} @@ -640,7 +576,7 @@ function LineChartComponent({ if (typeof v !== 'number' || !Number.isFinite(v)) return '—' const u = unit || '' if (u.includes('%')) return `${v.toFixed(1)}%` - if (u.toLowerCase() === 'latency') return formatLatency(v) + if (u.toLowerCase() === 'latency') return formatChartLatency(v) if (u.toLowerCase().includes('ms')) return `${Math.round(v)}ms` if (u.toLowerCase().includes('exec')) return `${Math.round(v)}` return `${Math.round(v)}${u}` @@ -654,31 +590,15 @@ function LineChartComponent({ const len = `${labelStr} ${valueStr}`.length return Math.max(m, len) }, 0) - const tooltipMaxW = Math.min(220, Math.max(80, 7 * longest + 24)) - const anchorX = hoverPos?.x ?? pt.x - const margin = 10 - const preferRight = anchorX + margin + tooltipMaxW <= width - padding.right - const left = preferRight - ? Math.max( - padding.left, - Math.min(anchorX + margin, width - padding.right - tooltipMaxW) - ) - : Math.max( - padding.left, - Math.min(anchorX - margin - tooltipMaxW, width - padding.right - tooltipMaxW) - ) - const anchorY = hoverPos?.y ?? pt.y - const top = Math.min(Math.max(anchorY - 26, padding.top), height - padding.bottom - 18) + const { left, top } = positionChartTooltip({ + anchorX: hoverPos?.x ?? pt.x, + anchorY: hoverPos?.y ?? pt.y, + width, + height, + tooltipMaxWidth: estimateTooltipWidth(longest), + }) return ( -
- {currentHoverDate && ( -
- {currentHoverDate} -
- )} + {toDisplay.map((s) => { const seriesIndex = allSeries.findIndex((x) => x.id === s.id) const val = allSeries[seriesIndex]?.data?.[hoverIndex]?.value @@ -686,19 +606,15 @@ function LineChartComponent({ const showLabel = seriesLabel && seriesLabel !== 'base' && seriesLabel.trim() !== '' return ( -
- - {showLabel && ( - {seriesLabel} - )} - {fmt(val)} -
+ ) })} -
+ ) })()}
diff --git a/apps/sim/components/charts/use-chart-theme.ts b/apps/sim/components/charts/use-chart-theme.ts new file mode 100644 index 00000000000..30bcd9fd588 --- /dev/null +++ b/apps/sim/components/charts/use-chart-theme.ts @@ -0,0 +1,84 @@ +'use client' + +import { type RefObject, useEffect, useRef, useState } from 'react' +import { CHART_MIN_WIDTH } from '@/components/charts/chart-geometry' + +/** + * Whether the document is in dark mode, tracked by observing the class the theme + * toggle writes. Charts need this as a *value* rather than a CSS class because SVG + * stroke opacity and blend mode are set per element, not by a selector. + */ +export function useIsDarkTheme(): boolean { + const [isDark, setIsDark] = useState(true) + + useEffect(() => { + if (typeof window === 'undefined') return + const element = document.documentElement + const update = () => setIsDark(element.classList.contains('dark')) + update() + const observer = new MutationObserver(update) + observer.observe(element, { attributes: true, attributeFilter: ['class'] }) + return () => observer.disconnect() + }, []) + + return isDark +} + +/** + * Resolves `var(--token)` colors to concrete `rgb()` strings. + * + * SVG `stroke` and gradient `stopColor` do not accept a CSS variable that is defined + * on an ancestor, so the value has to be computed once and passed literally. Callers + * still declare colors as tokens; this is the one place that materializes them. + */ +export function useResolvedChartColors(colors: Record): Record { + const [resolved, setResolved] = useState>({}) + const serialized = JSON.stringify(colors) + + useEffect(() => { + if (typeof window === 'undefined') return + + const resolveColor = (value: string): string => { + if (!value.startsWith('var(')) return value + const probe = document.createElement('div') + probe.style.color = value + document.body.appendChild(probe) + const computed = window.getComputedStyle(probe).color + probe.remove() + return computed + } + + const next: Record = {} + for (const [key, value] of Object.entries(JSON.parse(serialized) as Record)) { + next[key] = resolveColor(value) + } + setResolved(next) + }, [serialized]) + + return resolved +} + +/** + * Observed container width, floored at {@link CHART_MIN_WIDTH}. `null` until the + * first measurement, which callers render as an empty box of the right height so the + * chart does not reflow the page when it appears. + */ +export function useChartWidth(): [RefObject, number | null] { + const containerRef = useRef(null) + const [width, setWidth] = useState(null) + + useEffect(() => { + const element = containerRef.current + if (!element) return + const observer = new ResizeObserver((entries) => { + const measured = entries[0]?.contentRect?.width + if (measured && measured > 0) setWidth(Math.max(CHART_MIN_WIDTH, Math.floor(measured))) + }) + observer.observe(element) + const rect = element.getBoundingClientRect() + if (rect?.width > 0) setWidth(Math.max(CHART_MIN_WIDTH, Math.floor(rect.width))) + return () => observer.disconnect() + }, []) + + return [containerRef, width] +} diff --git a/apps/sim/components/emails/render-notifications.test.ts b/apps/sim/components/emails/render-notifications.test.ts index fb5286cfc08..49e6f08a881 100644 --- a/apps/sim/components/emails/render-notifications.test.ts +++ b/apps/sim/components/emails/render-notifications.test.ts @@ -73,7 +73,7 @@ describe('renderUsageLimitReachedEmail', () => { scope: 'organization', currentUsage: 500, limit: 500, - ctaLink: 'https://sim.ai/organization/org_1/settings/billing', + ctaLink: 'https://sim.ai/workspace/ws_1/settings/billing', }) expect(html).toContain('Raise Organization Limit') diff --git a/apps/sim/components/settings/navigation.test.ts b/apps/sim/components/settings/navigation.test.ts index 8420aba40bb..df3a5f0d5cd 100644 --- a/apps/sim/components/settings/navigation.test.ts +++ b/apps/sim/components/settings/navigation.test.ts @@ -11,17 +11,15 @@ import { buildUnifiedSettingsNavigation, canMutateWorkspaceSettingsSection, getAccountSettingsHref, - getOrganizationSettingsHref, getWorkspaceSettingsHref, isOrganizationSettingsSectionAvailable, ORGANIZATION_PLANE_UNIFIED_SECTIONS, - ORGANIZATION_SETTINGS_ITEMS, - ORGANIZATION_SETTINGS_PATH_ALIASES, parseSettingsPathSection, resolveOrganizationSectionAccess, resolveWorkspaceNavigation, SELFHOST_SETTINGS_ITEMS, SETTINGS_SECTION_REGISTRY, + UNIFIED_TO_ORGANIZATION_SECTION, WORKSPACE_SETTINGS_ITEMS, WORKSPACE_SETTINGS_PATH_ALIASES, } from '@/components/settings/navigation' @@ -55,6 +53,7 @@ describe('settings navigation boundaries', () => { 'billing', 'teammates', 'organization', + 'usage', 'secrets', 'credential-groups', 'custom-tools', @@ -83,17 +82,6 @@ describe('settings navigation boundaries', () => { 'mothership', ]) expect(SELFHOST_SETTINGS_ITEMS.map(({ id }) => id)).toEqual(['general', 'billing', 'chat-keys']) - expect(ORGANIZATION_SETTINGS_ITEMS.map(({ id }) => id)).toEqual([ - 'members', - 'billing', - 'access-control', - 'audit-logs', - 'sso', - 'sessions', - 'data-retention', - 'data-drains', - 'whitelabeling', - ]) expect(WORKSPACE_SETTINGS_ITEMS.map(({ id }) => id)).toEqual([ 'teammates', 'secrets', @@ -199,9 +187,6 @@ describe('settings navigation boundaries', () => { const accountIds = SETTINGS_SECTION_REGISTRY.flatMap(({ planes }) => planes?.account ? [planes.account.id] : [] ) - const organizationIds = SETTINGS_SECTION_REGISTRY.flatMap(({ planes }) => - planes?.organization ? [planes.organization.id] : [] - ) const selfHostIds = SETTINGS_SECTION_REGISTRY.flatMap(({ planes }) => planes?.selfhost ? [planes.selfhost.id] : [] ) @@ -211,7 +196,6 @@ describe('settings navigation boundaries', () => { expect(new Set(unifiedIds).size).toBe(unifiedIds.length) expect(new Set(accountIds).size).toBe(accountIds.length) - expect(new Set(organizationIds).size).toBe(organizationIds.length) expect(new Set(selfHostIds).size).toBe(selfHostIds.length) expect(new Set(workspaceIds).size).toBe(workspaceIds.length) expect([...unifiedIds].sort()).toEqual( @@ -220,9 +204,6 @@ describe('settings navigation boundaries', () => { .sort() ) expect([...accountIds].sort()).toEqual(ACCOUNT_SETTINGS_ITEMS.map(({ id }) => id).sort()) - expect([...organizationIds].sort()).toEqual( - ORGANIZATION_SETTINGS_ITEMS.map(({ id }) => id).sort() - ) expect([...selfHostIds].sort()).toEqual(SELFHOST_SETTINGS_ITEMS.map(({ id }) => id).sort()) expect([...workspaceIds].sort()).toEqual(WORKSPACE_SETTINGS_ITEMS.map(({ id }) => id).sort()) }) @@ -237,27 +218,37 @@ describe('settings navigation boundaries', () => { 'organization', 'sessions', 'sso', + 'usage', 'whitelabeling', ]) }) - it('shares labels, icons, and docs links across projections', () => { - const unifiedSso = buildUnifiedSettingsNavigation().find(({ id }) => id === 'sso') - const organizationSso = ORGANIZATION_SETTINGS_ITEMS.find(({ id }) => id === 'sso') - - expect(organizationSso?.label).toBe(unifiedSso?.label) - expect(organizationSso?.icon).toBe(unifiedSso?.icon) - expect(organizationSso?.docsLink).toBe(unifiedSso?.docsLink) + it('maps every organization-scoped unified section to its organization counterpart', () => { + // The section page reads this map to decide whether to apply the organization + // gate at all, so a section missing from it is not "ungated by omission" — it + // is a section any workspace member could open. + expect(UNIFIED_TO_ORGANIZATION_SECTION).toEqual({ + organization: 'members', + billing: 'billing', + 'access-control': 'access-control', + 'audit-logs': 'audit-logs', + sso: 'sso', + sessions: 'sessions', + 'data-retention': 'data-retention', + 'data-drains': 'data-drains', + whitelabeling: 'whitelabeling', + usage: 'usage', + }) + expect(Object.keys(UNIFIED_TO_ORGANIZATION_SECTION).sort()).toEqual( + [...ORGANIZATION_PLANE_UNIFIED_SECTIONS].sort() + ) }) - it('uses scope-specific labels consistently across settings surfaces', () => { - const organizationMembers = ORGANIZATION_SETTINGS_ITEMS.find(({ id }) => id === 'members') + it('labels the members section consistently', () => { const unifiedOrganization = buildUnifiedSettingsNavigation().find( ({ id }) => id === 'organization' ) - expect(organizationMembers?.label).toBe('Members') - expect(organizationMembers?.description).toBe('Manage organization members, roles, and seats.') expect(unifiedOrganization?.label).toBe('Members') }) @@ -293,9 +284,6 @@ describe('settings navigation boundaries', () => { it('builds canonical settings hrefs across all three planes', () => { expect(getAccountSettingsHref('general')).toBe('/account/settings/general') - expect(getOrganizationSettingsHref('organization-a', 'members')).toBe( - '/organization/organization-a/settings/members' - ) expect(getWorkspaceSettingsHref('workspace-a', 'teammates')).toBe( '/workspace/workspace-a/settings/teammates' ) @@ -328,20 +316,6 @@ describe('settings navigation boundaries', () => { expect(parseAccountPath('/account/settings', 'general')).toBe('general') }) - it('parses canonical, aliased, and invalid organization settings paths', () => { - const parseOrganizationPath = (path: string) => - parseSettingsPathSection({ - path, - items: ORGANIZATION_SETTINGS_ITEMS, - defaultSection: null, - aliases: ORGANIZATION_SETTINGS_PATH_ALIASES, - }) - - expect(parseOrganizationPath('sso')).toBe('sso') - expect(parseOrganizationPath('/organization/org-a/settings/organization')).toBe('members') - expect(parseOrganizationPath('/organization/org-a/settings/not-a-section')).toBeNull() - }) - it('parses canonical, aliased, and invalid workspace settings paths', () => { const parseWorkspacePath = (path: string) => parseSettingsPathSection({ @@ -359,7 +333,6 @@ describe('settings navigation boundaries', () => { it('keeps API keys split between account and workspace settings', () => { expect(ACCOUNT_SETTINGS_ITEMS.some(({ id }) => id === 'api-keys')).toBe(true) expect(WORKSPACE_SETTINGS_ITEMS.some(({ id }) => id === 'api-keys')).toBe(true) - expect(ORGANIZATION_SETTINGS_ITEMS.some(({ id }) => String(id) === 'api-keys')).toBe(false) }) it('requires target-organization membership and admin authority', () => { diff --git a/apps/sim/components/settings/navigation.ts b/apps/sim/components/settings/navigation.ts index 64704f531d5..70b7f24b35f 100644 --- a/apps/sim/components/settings/navigation.ts +++ b/apps/sim/components/settings/navigation.ts @@ -1,5 +1,6 @@ import type { ComponentType } from 'react' import { + ChartColumn, ClipboardList, Clock, Credit, @@ -40,10 +41,11 @@ import { isSandboxesEnabled, isSessionPoliciesEnabled, isSsoEnabled, + isUsageMonitoringEnabled, isWhitelabelingEnabled, } from '@/lib/core/config/env-flags' -export type SettingsPlane = 'account' | 'organization' | 'selfhost' | 'workspace' +export type SettingsPlane = 'account' | 'selfhost' | 'workspace' export type AccountSettingsSection = 'general' | 'billing' | 'api-keys' | 'admin' | 'mothership' @@ -56,6 +58,7 @@ export type SelfHostSettingsSection = 'general' | 'billing' | 'chat-keys' export type OrganizationSettingsSection = | 'members' | 'billing' + | 'usage' | 'access-control' | 'audit-logs' | 'sso' @@ -86,8 +89,6 @@ export type SettingsSection = | SelfHostSettingsSection | WorkspaceSettingsSection -export type OrganizationSettingsRouteSection = OrganizationSettingsSection | 'unavailable' - export interface SettingsNavigationItem
{ id: Section label: string @@ -112,6 +113,7 @@ export type UnifiedSettingsSection = | 'billing' | 'teammates' | 'organization' + | 'usage' | 'sso' | 'whitelabeling' | 'forks' @@ -164,6 +166,16 @@ export interface UnifiedSettingsNavigationItem { hideForEnterprise?: boolean externalUrl?: string docsLink?: string + /** + * The organization-scoped counterpart of this section. Declaring it marks the + * section as acting on the host organization rather than the workspace, which + * routes it through the organization gate (host organization present, org-admin + * viewer, plan entitlement) in both the sidebar and the section page. + * + * This is the single source for {@link ORGANIZATION_PLANE_UNIFIED_SECTIONS} and + * {@link UNIFIED_TO_ORGANIZATION_SECTION}, so the two cannot drift apart. + */ + organizationSection?: OrganizationSettingsSection } interface UnifiedSettingsProjection @@ -173,7 +185,6 @@ interface UnifiedSettingsProjection interface SettingsPlaneSectionMap { account: AccountSettingsSection - organization: OrganizationSettingsSection selfhost: SelfHostSettingsSection workspace: WorkspaceSettingsSection } @@ -220,6 +231,7 @@ const SETTINGS_SELF_HOSTED_OVERRIDES = { sandboxes: isSandboxesEnabled, sessionPolicies: isSessionPoliciesEnabled, sso: isSsoEnabled, + usageMonitoring: isUsageMonitoringEnabled, whitelabeling: isWhitelabelingEnabled, } as const @@ -249,17 +261,6 @@ export function getSelfHostSettingsHref( return withSettingsSearchParams(`/selfhost/settings/${section}`, searchParams) } -export function getOrganizationSettingsHref( - organizationId: string, - section: OrganizationSettingsRouteSection, - searchParams?: SettingsHrefSearchParams -): string { - return withSettingsSearchParams( - `/organization/${organizationId}/settings/${section}`, - searchParams - ) -} - export function getWorkspaceSettingsHref( workspaceId: string, section: WorkspaceSettingsSection, @@ -272,12 +273,6 @@ export const ACCOUNT_SETTINGS_PATH_ALIASES = { apikeys: 'api-keys', } as const satisfies Readonly> -export const ORGANIZATION_SETTINGS_PATH_ALIASES = { - organization: 'members', - // Verified domains moved into the SSO page; keep old links working. - domains: 'sso', -} as const satisfies Readonly> - export const WORKSPACE_SETTINGS_PATH_ALIASES = { apikeys: 'api-keys', } as const satisfies Readonly> @@ -341,7 +336,6 @@ export const SETTINGS_PLANE_CHROME: Record< { label: string; showWordmark: boolean } > = { account: { label: 'Account', showWordmark: false }, - organization: { label: 'Organization', showWordmark: false }, selfhost: { label: 'Self-host', showWordmark: true }, } @@ -350,12 +344,6 @@ export const SELFHOST_SETTINGS_GROUPS = [ { key: 'developer', title: 'Developer' }, ] as const -export const ORGANIZATION_SETTINGS_GROUPS = [ - { key: 'organization', title: 'Organization' }, - { key: 'security', title: 'Security' }, - { key: 'enterprise', title: 'Enterprise' }, -] as const - export const SETTINGS_SECTION_REGISTRY: readonly SettingsSectionRegistryEntry[] = [ { label: 'General', @@ -412,13 +400,11 @@ export const SETTINGS_SECTION_REGISTRY: readonly SettingsSectionRegistryEntry[] id: 'access-control', description: 'Manage permission groups across your organization.', group: 'organization', - order: 3, + order: 4, requiresHosted: true, requiresEnterprise: true, selfHostedOverride: SETTINGS_SELF_HOSTED_OVERRIDES.accessControl, - }, - planes: { - organization: { id: 'access-control', group: 'security', order: 2 }, + organizationSection: 'access-control', }, }, { @@ -429,13 +415,11 @@ export const SETTINGS_SECTION_REGISTRY: readonly SettingsSectionRegistryEntry[] id: 'audit-logs', description: 'Review activity and changes across your organization.', group: 'organization', - order: 4, + order: 5, requiresHosted: true, requiresEnterprise: true, selfHostedOverride: SETTINGS_SELF_HOSTED_OVERRIDES.auditLogs, - }, - planes: { - organization: { id: 'audit-logs', group: 'security', order: 3 }, + organizationSection: 'audit-logs', }, }, { @@ -446,7 +430,7 @@ export const SETTINGS_SECTION_REGISTRY: readonly SettingsSectionRegistryEntry[] id: 'forks', description: 'Fork this workspace and sync changes with its parent.', group: 'organization', - order: 2, + order: 3, }, planes: { workspace: { id: 'forks', group: 'enterprise', order: 10 }, @@ -461,6 +445,7 @@ export const SETTINGS_SECTION_REGISTRY: readonly SettingsSectionRegistryEntry[] group: 'account', order: 1, hideWhenBillingDisabled: true, + organizationSection: 'billing', }, planes: { account: { @@ -475,12 +460,6 @@ export const SETTINGS_SECTION_REGISTRY: readonly SettingsSectionRegistryEntry[] group: 'account', order: 1, }, - organization: { - id: 'billing', - description: 'Manage the organization plan, usage, and invoices.', - group: 'organization', - order: 1, - }, }, }, { @@ -507,14 +486,29 @@ export const SETTINGS_SECTION_REGISTRY: readonly SettingsSectionRegistryEntry[] hideWhenBillingDisabled: true, requiresHosted: true, requiresTeam: true, + /** + * A plain member sees the roster read-only — `resolveOrganizationSectionAccess` + * grants them `'view'` on this one section, and `TeamManagement` renders + * without management controls. Every other organization section stays + * admin-only. + */ + allowNonOrgAdmin: true, + organizationSection: 'members', }, - planes: { - organization: { - id: 'members', - description: 'Manage organization members, roles, and seats.', - group: 'organization', - order: 0, - }, + }, + { + label: 'Usage tracking', + icon: ChartColumn, + unified: { + id: 'usage', + description: 'Monitor credit usage across your organization.', + group: 'organization', + order: 1, + hideWhenBillingDisabled: true, + requiresHosted: true, + requiresEnterprise: true, + selfHostedOverride: SETTINGS_SELF_HOSTED_OVERRIDES.usageMonitoring, + organizationSection: 'usage', }, }, { @@ -704,13 +698,11 @@ export const SETTINGS_SECTION_REGISTRY: readonly SettingsSectionRegistryEntry[] id: 'sso', description: 'Configure single sign-on for your organization.', group: 'organization', - order: 6, + order: 7, requiresHosted: true, requiresEnterprise: true, selfHostedOverride: SETTINGS_SELF_HOSTED_OVERRIDES.sso, - }, - planes: { - organization: { id: 'sso', group: 'security', order: 4 }, + organizationSection: 'sso', }, }, { @@ -721,13 +713,11 @@ export const SETTINGS_SECTION_REGISTRY: readonly SettingsSectionRegistryEntry[] id: 'sessions', description: 'Limit session lifetimes and sign out members org-wide.', group: 'organization', - order: 7, + order: 8, requiresHosted: true, requiresEnterprise: true, selfHostedOverride: SETTINGS_SELF_HOSTED_OVERRIDES.sessionPolicies, - }, - planes: { - organization: { id: 'sessions', group: 'security', order: 5 }, + organizationSection: 'sessions', }, }, { @@ -739,13 +729,11 @@ export const SETTINGS_SECTION_REGISTRY: readonly SettingsSectionRegistryEntry[] description: 'Control data retention windows and PII redaction. Workspaces without an override inherit the organization defaults.', group: 'organization', - order: 8, + order: 9, requiresHosted: true, requiresEnterprise: true, selfHostedOverride: SETTINGS_SELF_HOSTED_OVERRIDES.dataRetention, - }, - planes: { - organization: { id: 'data-retention', group: 'enterprise', order: 6 }, + organizationSection: 'data-retention', }, }, { @@ -756,13 +744,11 @@ export const SETTINGS_SECTION_REGISTRY: readonly SettingsSectionRegistryEntry[] id: 'data-drains', description: 'Stream your logs and events to external destinations.', group: 'organization', - order: 9, + order: 10, requiresHosted: true, requiresEnterprise: true, selfHostedOverride: SETTINGS_SELF_HOSTED_OVERRIDES.dataDrains, - }, - planes: { - organization: { id: 'data-drains', group: 'enterprise', order: 7 }, + organizationSection: 'data-drains', }, }, { @@ -773,13 +759,11 @@ export const SETTINGS_SECTION_REGISTRY: readonly SettingsSectionRegistryEntry[] id: 'whitelabeling', description: 'Customize your workspace branding and appearance.', group: 'organization', - order: 5, + order: 6, requiresHosted: true, requiresEnterprise: true, selfHostedOverride: SETTINGS_SELF_HOSTED_OVERRIDES.whitelabeling, - }, - planes: { - organization: { id: 'whitelabeling', group: 'enterprise', order: 8 }, + organizationSection: 'whitelabeling', }, }, { @@ -790,7 +774,7 @@ export const SETTINGS_SECTION_REGISTRY: readonly SettingsSectionRegistryEntry[] id: 'custom-blocks', description: 'Publish workflows as reusable blocks for your organization.', group: 'organization', - order: 1, + order: 2, requiresHosted: true, requiresEnterprise: true, allowNonOrgAdmin: true, @@ -878,9 +862,6 @@ function buildPlaneSettingsItems( export const ACCOUNT_SETTINGS_ITEMS: SettingsNavigationItem[] = buildPlaneSettingsItems('account') -export const ORGANIZATION_SETTINGS_ITEMS: SettingsNavigationItem[] = - buildPlaneSettingsItems('organization') - export const SELFHOST_SETTINGS_ITEMS: SettingsNavigationItem[] = buildPlaneSettingsItems('selfhost') @@ -895,7 +876,26 @@ export const WORKSPACE_SETTINGS_ITEMS: SettingsNavigationItem = new Set( SETTINGS_SECTION_REGISTRY.flatMap((entry) => - entry.planes?.organization && entry.unified ? [entry.unified.id] : [] + entry.unified?.organizationSection ? [entry.unified.id] : [] + ) +) + +/** + * Unified section id to the organization-scoped section it acts on, for the gates + * that take an {@link OrganizationSettingsSection} (`canOpenOrganizationSettingsSection`, + * `isOrganizationSettingsSectionAvailable`). + * + * Derived from the registry rather than hand-listed: a section added to one and + * forgotten in the other used to mean the page applied *no* organization gate at + * all, since an unmapped section reads as "not organization-scoped". + */ +export const UNIFIED_TO_ORGANIZATION_SECTION: Readonly< + Partial> +> = Object.fromEntries( + SETTINGS_SECTION_REGISTRY.flatMap((entry) => + entry.unified?.organizationSection + ? [[entry.unified.id, entry.unified.organizationSection] as const] + : [] ) ) @@ -938,6 +938,7 @@ export function getOrganizationSettingsFeatures( sessions: SETTINGS_SELF_HOSTED_OVERRIDES.sessionPolicies, 'data-retention': SETTINGS_SELF_HOSTED_OVERRIDES.dataRetention, 'data-drains': SETTINGS_SELF_HOSTED_OVERRIDES.dataDrains, + usage: SETTINGS_SELF_HOSTED_OVERRIDES.usageMonitoring, whitelabeling: SETTINGS_SELF_HOSTED_OVERRIDES.whitelabeling, }, } @@ -1097,11 +1098,9 @@ export function getSettingsSectionMeta( const catalog = plane === 'account' ? ACCOUNT_SETTINGS_ITEMS - : plane === 'organization' - ? ORGANIZATION_SETTINGS_ITEMS - : plane === 'selfhost' - ? SELFHOST_SETTINGS_ITEMS - : WORKSPACE_SETTINGS_ITEMS + : plane === 'selfhost' + ? SELFHOST_SETTINGS_ITEMS + : WORKSPACE_SETTINGS_ITEMS const item = catalog.find((candidate) => candidate.id === section) return item ? { label: item.label, description: item.description, docsLink: item.docsLink } : null } diff --git a/apps/sim/components/settings/organization-settings-renderer.tsx b/apps/sim/components/settings/organization-settings-renderer.tsx deleted file mode 100644 index 66ae7a1efc3..00000000000 --- a/apps/sim/components/settings/organization-settings-renderer.tsx +++ /dev/null @@ -1,79 +0,0 @@ -'use client' - -import { useEffect } from 'react' -import dynamic from 'next/dynamic' -import { usePostHog } from 'posthog-js/react' -import type { OrganizationSettingsSection } from '@/components/settings/navigation' -import { captureEvent } from '@/lib/posthog/client' - -const TeamManagement = dynamic(() => - import('@/app/workspace/[workspaceId]/settings/components/team-management/team-management').then( - (module) => module.TeamManagement - ) -) -const Billing = dynamic(() => - import('@/app/workspace/[workspaceId]/settings/components/billing/billing').then( - (module) => module.Billing - ) -) -const AccessControl = dynamic(() => - import('@/ee/access-control/components/access-control').then((module) => module.AccessControl) -) -const AuditLogs = dynamic(() => - import('@/ee/audit-logs/components/audit-logs').then((module) => module.AuditLogs) -) -const SSO = dynamic(() => import('@/ee/sso/components/sso-settings').then((module) => module.SSO)) -const SessionPolicySettings = dynamic(() => - import('@/ee/session-policy/components/session-policy-settings').then( - (module) => module.SessionPolicySettings - ) -) -const DataRetentionSettings = dynamic(() => - import('@/ee/data-retention/components/data-retention-settings').then( - (module) => module.DataRetentionSettings - ) -) -const DataDrainsSettings = dynamic(() => - import('@/ee/data-drains/components/data-drains-settings').then( - (module) => module.DataDrainsSettings - ) -) -const WhitelabelingSettings = dynamic( - () => - import('@/ee/whitelabeling/components/whitelabeling-settings').then( - (module) => module.WhitelabelingSettings - ), - { ssr: false } -) - -interface OrganizationSettingsRendererProps { - organizationId: string - section: OrganizationSettingsSection -} - -export function OrganizationSettingsRenderer({ - organizationId, - section, -}: OrganizationSettingsRendererProps) { - const posthog = usePostHog() - - useEffect(() => { - captureEvent(posthog, 'settings_tab_viewed', { plane: 'organization', section }) - }, [posthog, section]) - - if (section === 'members') return - if (section === 'billing') return - if (section === 'access-control') { - return - } - if (section === 'audit-logs') return - if (section === 'sso') return - if (section === 'sessions') { - return - } - if (section === 'data-retention') { - return - } - if (section === 'data-drains') return - return -} diff --git a/apps/sim/components/settings/settings-header-shell.test.tsx b/apps/sim/components/settings/settings-header-shell.test.tsx index e2d588159ff..ecf53c2706a 100644 --- a/apps/sim/components/settings/settings-header-shell.test.tsx +++ b/apps/sim/components/settings/settings-header-shell.test.tsx @@ -181,8 +181,8 @@ describe('SettingsHeaderShell static meta', () => { }) it('lets a body claim the header with nothing in it, suppressing the meta entirely', () => { - // How SettingsUnavailable opts out: it renders its own centred heading, so the routed - // section's catalog title must not caption it. + // How a surface that draws its own centred heading opts out: the routed section's + // catalog title must not also caption it. function OwnHeadingBody() { useSettingsHeader(EMPTY_HEADER) return
diff --git a/apps/sim/components/settings/settings-header.tsx b/apps/sim/components/settings/settings-header.tsx index 7c1b3ae4c69..27788ef7293 100644 --- a/apps/sim/components/settings/settings-header.tsx +++ b/apps/sim/components/settings/settings-header.tsx @@ -274,7 +274,7 @@ export function SettingsHeaderShell({ meta, children }: SettingsHeaderShellProps * field-by-field: a body that registers an explicit `title` and no `description` is * deliberately suppressing the meta description, so the two must never be merged — and a * body that registers an empty config is deliberately asking for no heading at all, which - * is how a surface that renders its own (`SettingsUnavailable`) opts out. + * is how a surface that renders its own heading opts out. */ const config: SettingsHeaderConfig = registered ?? meta ?? EMPTY_CONFIG const { title, description, docsLink, back, actions, search, scrollContainerRef } = config diff --git a/apps/sim/components/settings/settings-unavailable.tsx b/apps/sim/components/settings/settings-unavailable.tsx deleted file mode 100644 index 806ab078c3f..00000000000 --- a/apps/sim/components/settings/settings-unavailable.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { ChipLink, cn } from '@sim/emcn' - -interface SettingsUnavailableProps { - title?: string - description?: string - embedded?: boolean -} - -export function SettingsUnavailable({ - title = 'Settings unavailable', - description = 'You do not have access to manage this organization. Contact an organization owner or admin for help.', - embedded = false, -}: SettingsUnavailableProps) { - return ( -
-
-

{title}

-

{description}

- Back to workspaces -
-
- ) -} diff --git a/apps/sim/components/settings/standalone-settings-shell.test.ts b/apps/sim/components/settings/standalone-settings-shell.test.ts index 720bcdfff56..49d462e8f76 100644 --- a/apps/sim/components/settings/standalone-settings-shell.test.ts +++ b/apps/sim/components/settings/standalone-settings-shell.test.ts @@ -5,8 +5,6 @@ import { describe, expect, it } from 'vitest' import { ACCOUNT_SETTINGS_ITEMS, ACCOUNT_SETTINGS_PATH_ALIASES, - ORGANIZATION_SETTINGS_ITEMS, - ORGANIZATION_SETTINGS_PATH_ALIASES, parseSettingsPathSection, SELFHOST_SETTINGS_ITEMS, } from '@/components/settings/navigation' @@ -23,17 +21,6 @@ describe('standalone settings section resolution', () => { ).toBe('billing') }) - it('resolves the organization section from its pathname', () => { - expect( - parseSettingsPathSection({ - path: '/organization/org-1/settings/audit-logs', - items: ORGANIZATION_SETTINGS_ITEMS, - defaultSection: 'members', - aliases: ORGANIZATION_SETTINGS_PATH_ALIASES, - }) - ).toBe('audit-logs') - }) - it('keeps Subscription active for the self-host billing route', () => { expect( parseSettingsPathSection({ diff --git a/apps/sim/components/settings/standalone-settings-shell.tsx b/apps/sim/components/settings/standalone-settings-shell.tsx index 2991fe68cfe..ac31197e7b6 100644 --- a/apps/sim/components/settings/standalone-settings-shell.tsx +++ b/apps/sim/components/settings/standalone-settings-shell.tsx @@ -7,15 +7,8 @@ import { ACCOUNT_SETTINGS_ITEMS, ACCOUNT_SETTINGS_PATH_ALIASES, getAccountSettingsHref, - getOrganizationSettingsFeatures, - getOrganizationSettingsHref, getSelfHostSettingsHref, - isOrganizationSettingsSectionAvailable, - ORGANIZATION_SETTINGS_GROUPS, - ORGANIZATION_SETTINGS_ITEMS, - ORGANIZATION_SETTINGS_PATH_ALIASES, parseSettingsPathSection, - resolveOrganizationSectionAccess, SELFHOST_SETTINGS_GROUPS, SELFHOST_SETTINGS_ITEMS, SETTINGS_PLANE_CHROME, @@ -40,40 +33,19 @@ interface SelfHostSettingsShellProps extends StandaloneSettingsShellBaseProps { plane: 'selfhost' } -interface OrganizationSettingsShellProps extends StandaloneSettingsShellBaseProps { - plane: 'organization' - organizationId: string - hasEnterprisePlan: boolean - isOrganizationAdmin: boolean -} - -type StandaloneSettingsShellProps = - | AccountSettingsShellProps - | OrganizationSettingsShellProps - | SelfHostSettingsShellProps +type StandaloneSettingsShellProps = AccountSettingsShellProps | SelfHostSettingsShellProps export function StandaloneSettingsShell(props: StandaloneSettingsShellProps) { const { children, plane } = props useSettingsBeforeUnload() const pathname = usePathname() - const hasEnterprisePlan = plane === 'organization' ? props.hasEnterprisePlan : false - const isOrganizationAdmin = plane === 'organization' ? props.isOrganizationAdmin : false const isSuperUser = plane === 'account' ? (props.isSuperUser ?? false) : false - const organizationFeatures = getOrganizationSettingsFeatures(hasEnterprisePlan) const accountItems = ACCOUNT_SETTINGS_ITEMS.filter((item) => { if (item.id === 'billing' && !isBillingEnabled) return false if ((item.id === 'admin' || item.id === 'mothership') && !isSuperUser) return false return true }) - const organizationItems = ORGANIZATION_SETTINGS_ITEMS.filter( - (item) => - resolveOrganizationSectionAccess({ - section: item.id, - isTargetOrganizationMember: true, - isTargetOrganizationAdmin: isOrganizationAdmin, - }) !== 'unavailable' && isOrganizationSettingsSectionAvailable(item.id, organizationFeatures) - ) const selfHostItems = SELFHOST_SETTINGS_ITEMS.filter((item) => { if (item.id === 'billing' && !isBillingEnabled) return false // Chat keys are issued by the managed service, so there are none to list on @@ -93,28 +65,9 @@ export function StandaloneSettingsShell(props: StandaloneSettingsShellProps) { defaultSection: 'general', aliases: ACCOUNT_SETTINGS_PATH_ALIASES, }) - const organizationSection = parseSettingsPathSection({ - path: pathname, - items: ORGANIZATION_SETTINGS_ITEMS, - defaultSection: 'members', - aliases: ORGANIZATION_SETTINGS_PATH_ALIASES, - }) - const activeSection = - plane === 'account' - ? accountSection - : plane === 'selfhost' - ? selfHostSection - : organizationSection + const activeSection = plane === 'account' ? accountSection : selfHostSection const sidebar = - plane === 'selfhost' ? ( - - ) : plane === 'account' ? ( + plane === 'account' ? ( ) : ( getOrganizationSettingsHref(props.organizationId, section)} - items={organizationItems} + groups={SELFHOST_SETTINGS_GROUPS} + hrefForSection={getSelfHostSettingsHref} + items={selfHostItems} /> ) diff --git a/apps/sim/ee/organization-usage/components/usage-consumers.tsx b/apps/sim/ee/organization-usage/components/usage-consumers.tsx new file mode 100644 index 00000000000..c071fdb1f56 --- /dev/null +++ b/apps/sim/ee/organization-usage/components/usage-consumers.tsx @@ -0,0 +1,193 @@ +'use client' + +import type { ComponentType } from 'react' +import { cn } from '@sim/emcn' +import { ChevronRight } from '@sim/emcn/icons' +import { formatChartCompactNumber } from '@/components/charts' +import { + AnthropicIcon, + AzureIcon, + CerebrasIcon, + DeepseekIcon, + GoogleIcon, + GroqIcon, + MistralIcon, + OllamaIcon, + OpenAIIcon, + OpenRouterIcon, + xAIIcon, +} from '@/components/icons' +import type { + OrganizationUsageBreakdown, + OrganizationUsageBreakdownRow, + UsageBreakdownDimension, +} from '@/lib/api/contracts/organization-usage' +import { + type RowAction, + RowActionsMenu, +} from '@/app/workspace/[workspaceId]/settings/components/row-actions-menu' +import { SettingsEmptyState } from '@/app/workspace/[workspaceId]/settings/components/settings-empty-state' +import { USAGE_TAB_EMPTY_COPY } from '@/ee/organization-usage/constants' + +/** + * Provider brand marks, keyed by the `providerId` the server resolves. + * + * Kept here rather than read from `providers/models.ts`: that module carries the + * whole model registry and would land in this settings chunk for two dozen glyphs. + */ +const PROVIDER_ICONS: Readonly>> = { + openai: OpenAIIcon, + anthropic: AnthropicIcon, + google: GoogleIcon, + 'azure-openai': AzureIcon, + deepseek: DeepseekIcon, + xai: xAIIcon, + groq: GroqIcon, + cerebras: CerebrasIcon, + ollama: OllamaIcon, + openrouter: OpenRouterIcon, + mistral: MistralIcon, +} + +interface UsageConsumerRowProps { + row: OrganizationUsageBreakdownRow + /** BYOK rows carry no cost, so tokens are the only usage they can show. */ + showTokensOnly: boolean + onSelect?: (row: OrganizationUsageBreakdownRow) => void + actions?: RowAction[] +} + +/** + * Width of each trailing affordance, so a list that carries one can reserve the + * same slot on its `Other` row and keep every figure in one column. + */ +const TRAILING_SLOT_CLASSES = { + /** `ChevronRight` at the platform icon size. */ + chevron: 'size-[14px]', + /** `RowActionsMenu`'s trigger: a 14px glyph in a `chipVariants()` pill. */ + menu: 'size-[30px]', +} as const + +/** + * A tabular row, not `SettingsResourceRow` — tabular columns are the sanctioned + * exception in `sim-settings-pages.md`, alongside billing invoices and credit usage. + */ +function UsageConsumerRow({ row, showTokensOnly, onSelect, actions }: UsageConsumerRowProps) { + const ProviderIcon = row.providerId ? PROVIDER_ICONS[row.providerId] : undefined + const Row = onSelect ? 'button' : 'div' + + return ( + onSelect(row), + 'aria-label': `Open ${row.label}`, + } + : {})} + className={cn( + 'flex w-full items-center gap-2.5 rounded-lg p-2 text-left', + onSelect && 'transition-colors hover:bg-[var(--surface-active)]' + )} + > + {ProviderIcon && ( + + )} + {row.label} +