Fetch organization info in the extension (#4751)

This commit is contained in:
Matt Rubens 2025-06-16 14:58:49 -04:00 committed by GitHub
parent 2e2f83be60
commit 322a15ee5d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 149 additions and 1 deletions

View file

@ -5,7 +5,7 @@ import axios from "axios"
import * as vscode from "vscode"
import { z } from "zod"
import type { CloudUserInfo } from "@roo-code/types"
import type { CloudUserInfo, CloudOrganizationMembership } from "@roo-code/types"
import { getClerkBaseUrl, getRooCodeApiUrl } from "./Config"
import { RefreshTimer } from "./RefreshTimer"
@ -420,9 +420,42 @@ export class AuthService extends EventEmitter<AuthServiceEvents> {
}
userInfo.picture = userData?.image_url
// Fetch organization memberships separately
try {
const orgMemberships = await this.clerkGetOrganizationMemberships()
if (orgMemberships && orgMemberships.length > 0) {
// Get the first (or active) organization membership
const primaryOrgMembership = orgMemberships[0]
const organization = primaryOrgMembership?.organization
if (organization) {
userInfo.organizationId = organization.id
userInfo.organizationName = organization.name
userInfo.organizationRole = primaryOrgMembership.role
}
}
} catch (error) {
this.log("[auth] Failed to fetch organization memberships:", error)
// Don't throw - organization info is optional
}
return userInfo
}
private async clerkGetOrganizationMemberships(): Promise<CloudOrganizationMembership[]> {
const response = await axios.get(`${getClerkBaseUrl()}/v1/me/organization_memberships`, {
headers: {
Authorization: `Bearer ${this.credentials!.clientToken}`,
"User-Agent": this.userAgent(),
},
})
// The response structure is: { response: [...] }
// Extract the organization memberships from the response.response array
return response.data?.response || []
}
private async clerkLogout(credentials: AuthCredentials): Promise<void> {
const formData = new URLSearchParams()
formData.append("_is_native", "1")

View file

@ -92,6 +92,24 @@ export class CloudService {
return this.authService!.getUserInfo()
}
public getOrganizationId(): string | null {
this.ensureInitialized()
const userInfo = this.authService!.getUserInfo()
return userInfo?.organizationId || null
}
public getOrganizationName(): string | null {
this.ensureInitialized()
const userInfo = this.authService!.getUserInfo()
return userInfo?.organizationName || null
}
public getOrganizationRole(): string | null {
this.ensureInitialized()
const userInfo = this.authService!.getUserInfo()
return userInfo?.organizationRole || null
}
public getAuthState(): string {
this.ensureInitialized()
return this.authService!.getState()

View file

@ -184,6 +184,72 @@ describe("CloudService", () => {
expect(mockAuthService.getUserInfo).toHaveBeenCalled()
})
it("should return organization ID from user info", () => {
const mockUserInfo = {
name: "Test User",
email: "test@example.com",
organizationId: "org_123",
organizationName: "Test Org",
organizationRole: "admin",
}
mockAuthService.getUserInfo.mockReturnValue(mockUserInfo)
const result = cloudService.getOrganizationId()
expect(mockAuthService.getUserInfo).toHaveBeenCalled()
expect(result).toBe("org_123")
})
it("should return null when no organization ID available", () => {
mockAuthService.getUserInfo.mockReturnValue(null)
const result = cloudService.getOrganizationId()
expect(result).toBe(null)
})
it("should return organization name from user info", () => {
const mockUserInfo = {
name: "Test User",
email: "test@example.com",
organizationId: "org_123",
organizationName: "Test Org",
organizationRole: "admin",
}
mockAuthService.getUserInfo.mockReturnValue(mockUserInfo)
const result = cloudService.getOrganizationName()
expect(mockAuthService.getUserInfo).toHaveBeenCalled()
expect(result).toBe("Test Org")
})
it("should return null when no organization name available", () => {
mockAuthService.getUserInfo.mockReturnValue(null)
const result = cloudService.getOrganizationName()
expect(result).toBe(null)
})
it("should return organization role from user info", () => {
const mockUserInfo = {
name: "Test User",
email: "test@example.com",
organizationId: "org_123",
organizationName: "Test Org",
organizationRole: "admin",
}
mockAuthService.getUserInfo.mockReturnValue(mockUserInfo)
const result = cloudService.getOrganizationRole()
expect(mockAuthService.getUserInfo).toHaveBeenCalled()
expect(result).toBe("admin")
})
it("should return null when no organization role available", () => {
mockAuthService.getUserInfo.mockReturnValue(null)
const result = cloudService.getOrganizationRole()
expect(result).toBe(null)
})
it("should delegate getAuthState to AuthService", () => {
const result = cloudService.getAuthState()
expect(mockAuthService.getState).toHaveBeenCalled()

View file

@ -10,6 +10,32 @@ export interface CloudUserInfo {
name?: string
email?: string
picture?: string
organizationId?: string
organizationName?: string
organizationRole?: string
}
/**
* CloudOrganization Types
*/
export interface CloudOrganization {
id: string
name: string
slug?: string
image_url?: string
has_image?: boolean
created_at?: number
updated_at?: number
}
export interface CloudOrganizationMembership {
id: string
organization: CloudOrganization
role: string
permissions?: string[]
created_at?: number
updated_at?: number
}
/**

View file

@ -44,6 +44,11 @@ export const AccountView = ({ userInfo, isAuthenticated, onDone }: AccountViewPr
<h2 className="text-lg font-medium text-vscode-foreground mb-1">
{userInfo?.name || t("account:unknownUser")}
</h2>
{userInfo?.organizationName && (
<p className="text-sm text-vscode-descriptionForeground mb-1">
{userInfo.organizationName}
</p>
)}
<p className="text-sm text-vscode-descriptionForeground">{userInfo?.email || ""}</p>
</div>
)}