Skip to content

Commit d29b9e5

Browse files
CopilotkarpikplCopilot
authored
feat: Full GHEC support — org dropdown + org-level teams in Teams tab (#351)
* Initial plan * feat: Full GHEC support — org dropdown + org-level teams in Teams tab - Add server/api/enterprise-orgs.ts endpoint that detects Full GHEC via GraphQL and returns enterprise organizations - Update Options.getTeamsApiUrl() to use /orgs/:org/teams when githubOrg is set in enterprise scope (Full GHEC org teams) - Update Options.getTeamMembersApiUrl() to use /orgs/:org/teams/:team/members when githubOrg is set in enterprise scope (avoids enterprise API versioning) - Update server/api/teams.ts to skip enterprise API version header when using org-based teams URL - Update server/api/seats.ts fetchAllTeamMembers() to skip enterprise API version header when githubOrg is set - Update TeamsComponent.vue to: - Detect Full GHEC at mount time via /api/enterprise-orgs - Show org dropdown above teams dropdown for Full GHEC enterprises - Filter teams by selected org (or show enterprise teams when none selected) - Navigate to /orgs/:org when org link is clicked - Use org context for team membership lookup and team detail URLs - Add unit tests for new getTeamsApiUrl and getTeamMembersApiUrl behavior Agent-Logs-Url: https://github.com/github-copilot-resources/copilot-metrics-viewer/sessions/dca47d83-74e0-44dc-9f4e-ed497ad27e12 Co-authored-by: karpikpl <3539908+karpikpl@users.noreply.github.com> * fix: address code review - paginate org listing and add watcher error handling Agent-Logs-Url: https://github.com/github-copilot-resources/copilot-metrics-viewer/sessions/dca47d83-74e0-44dc-9f4e-ed497ad27e12 Co-authored-by: karpikpl <3539908+karpikpl@users.noreply.github.com> * test: add Teams tab tests (enterprise-orgs endpoint, teams mock mode, GHEC logic) Agent-Logs-Url: https://github.com/github-copilot-resources/copilot-metrics-viewer/sessions/c8691e31-4d41-4a74-8990-74915f9837d5 Co-authored-by: karpikpl <3539908+karpikpl@users.noreply.github.com> * fix: don't auto-fill githubOrg from config for enterprise scope in teams endpoint When scope=enterprise, githubOrg is an explicit Full GHEC org override and should not be automatically populated from NUXT_PUBLIC_GITHUB_ORG config. This prevented enterprise-level teams from being fetched when the app was configured with a default org (e.g. cody-test-org) in the environment. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * chore: bump version to 3.5.0 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: show org dropdown for all enterprise scopes; move View Org icon inside field - Org autocomplete now always renders for enterprise scope (disabled/empty when isFullGhec=false) so the layout is consistent regardless of whether the enterprise has organizations - Replaced external 'VIEW ORG' button with a compact icon button (mdi-open-in-new) inside the autocomplete's append-inner slot, shown only when an org is selected Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: remove View Org navigation from org dropdown in enterprise context The enterprise token doesn't have org-level API access, so navigating to /orgs/:org from enterprise context always results in 404. The org dropdown is purely a team filter — no org navigation needed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: restore org navigation link as compact icon inside dropdown The link to /orgs/:org is valid — 404 was a token permissions issue (enterprise-only token lacks org-level API access), not a routing bug. Users with a PAT that has org-level scopes can switch to org view. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: align Deep Dive/Clear All buttons with teams combobox align-start + mt-1 keeps buttons vertically level with the input field instead of centering against the full component height (label + input + hint). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: use githubEnt (not githubOrg) as metrics identifier in enterprise scope When enterprise scope + org selected (Full GHEC), identifier was resolving to githubOrg (e.g. 'cody-test-org') causing fetchRawUserDayRecords to fetch the wrong data source. Enterprise metrics are always keyed by githubEnt; githubOrg is only used for team membership lookups via getTeamMembersApiUrl. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: add spacing between teams combobox and Deep Dive/Clear All buttons Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: increase gap between Deep Dive and Clear All buttons Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: single team header uses same compact card as comparison mode Replaces the full-width header card with the same v-col/v-card structure used in comparison mode (cols=12 sm=6 md=4 lg=3), showing active users, acceptance rate, interactions and date range — consistent layout regardless of how many teams are selected. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: align team summary cards with charts below Use fluid + px-4 + dense row on cards container to match the charts container layout — eliminates the left-edge offset caused by v-container centering and v-row negative gutters. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat: add full-width toggle option and larger icons to Teams tab chart controls - Add 3rd 'full width' (mdi-fullscreen) option matching MetricsViewer - Switch to icon= prop (no hardcoded size=18) for consistent icon sizing - Update chartColumns type to include 'full' Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: add margin between Deep Dive/Comparison chip and Clear All button Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: restore original icon style for chart toggle buttons (v-icon size=18) Use <v-icon size="18"> inside v-btn instead of icon= prop to preserve original button proportions, keeping the same style as the 2-button version but with a 3rd fullscreen option added. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: align MetricsViewer chart toggle buttons to use v-icon size=18 style Matches TeamsComponent toggle style for consistency across components. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat: add full-width toggle to all viewer components with consistent icon style All chart toggles now have 3 options (single/two/full) using v-icon size=18 for consistency with MetricsViewer and TeamsComponent. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: karpikpl <3539908+karpikpl@users.noreply.github.com> Co-authored-by: Piotr Karpala <karpik.pl@gmail.com> Co-authored-by: Piotr Karpala <piotrkarpala@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent e441bdb commit d29b9e5

18 files changed

Lines changed: 630 additions & 69 deletions

app/components/AgentActivityViewer.vue

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,9 @@
6666
<v-container :fluid="chartColumns === 'full'" :class="['elevation-2', chartColumns === 'full' ? 'px-0' : 'px-4']">
6767
<div class="d-flex justify-end mb-2">
6868
<v-btn-toggle v-model="chartColumns" density="compact" variant="outlined" mandatory>
69-
<v-btn value="1" size="small" icon="mdi-view-agenda" title="Single column" />
70-
<v-btn value="2" size="small" icon="mdi-view-grid" title="Two columns" />
71-
<v-btn value="full" size="small" icon="mdi-fullscreen" title="Full width" />
69+
<v-btn value="1" size="small" title="Single column"><v-icon size="18">mdi-view-agenda</v-icon></v-btn>
70+
<v-btn value="2" size="small" title="Two columns"><v-icon size="18">mdi-view-grid</v-icon></v-btn>
71+
<v-btn value="full" size="small" title="Full width"><v-icon size="18">mdi-fullscreen</v-icon></v-btn>
7272
</v-btn-toggle>
7373
</div>
7474

app/components/AgentModeViewer.vue

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,9 +109,9 @@
109109
<v-container :fluid="chartColumns === 'full'" :class="['elevation-2', chartColumns === 'full' ? 'px-0' : 'px-4']">
110110
<div class="d-flex justify-end mb-2">
111111
<v-btn-toggle v-model="chartColumns" density="compact" variant="outlined" mandatory>
112-
<v-btn value="1" size="small" icon="mdi-view-agenda" title="Single column" />
113-
<v-btn value="2" size="small" icon="mdi-view-grid" title="Two columns" />
114-
<v-btn value="full" size="small" icon="mdi-fullscreen" title="Full width" />
112+
<v-btn value="1" size="small" title="Single column"><v-icon size="18">mdi-view-agenda</v-icon></v-btn>
113+
<v-btn value="2" size="small" title="Two columns"><v-icon size="18">mdi-view-grid</v-icon></v-btn>
114+
<v-btn value="full" size="small" title="Full width"><v-icon size="18">mdi-fullscreen</v-icon></v-btn>
115115
</v-btn-toggle>
116116
</div>
117117

app/components/BreakdownComponent.vue

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,9 @@
5252
<v-container :fluid="chartColumns === 'full'" :class="[chartColumns === 'full' ? 'px-0' : 'px-4']">
5353
<div class="d-flex justify-end mb-2">
5454
<v-btn-toggle v-model="chartColumns" density="compact" variant="outlined" mandatory>
55-
<v-btn value="1" size="small" icon="mdi-view-agenda" title="Single column" />
56-
<v-btn value="2" size="small" icon="mdi-view-grid" title="Two columns" />
57-
<v-btn value="full" size="small" icon="mdi-fullscreen" title="Full width" />
55+
<v-btn value="1" size="small" title="Single column"><v-icon size="18">mdi-view-agenda</v-icon></v-btn>
56+
<v-btn value="2" size="small" title="Two columns"><v-icon size="18">mdi-view-grid</v-icon></v-btn>
57+
<v-btn value="full" size="small" title="Full width"><v-icon size="18">mdi-fullscreen</v-icon></v-btn>
5858
</v-btn-toggle>
5959
</div>
6060
<!-- CLI summary card (editors tab only) -->

app/components/CopilotChatViewer.vue

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,9 @@
9090
<v-container :fluid="chartColumns === 'full'" :class="['elevation-2', chartColumns === 'full' ? 'px-0' : 'px-4']">
9191
<div class="d-flex justify-end mb-2">
9292
<v-btn-toggle v-model="chartColumns" density="compact" variant="outlined" mandatory>
93-
<v-btn value="1" size="small" icon="mdi-view-agenda" title="Single column" />
94-
<v-btn value="2" size="small" icon="mdi-view-grid" title="Two columns" />
95-
<v-btn value="full" size="small" icon="mdi-fullscreen" title="Full width" />
93+
<v-btn value="1" size="small" title="Single column"><v-icon size="18">mdi-view-agenda</v-icon></v-btn>
94+
<v-btn value="2" size="small" title="Two columns"><v-icon size="18">mdi-view-grid</v-icon></v-btn>
95+
<v-btn value="full" size="small" title="Full width"><v-icon size="18">mdi-fullscreen</v-icon></v-btn>
9696
</v-btn-toggle>
9797
</div>
9898
<v-row>

app/components/MetricsViewer.vue

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -179,9 +179,9 @@
179179
<!-- Chart layout toggle -->
180180
<div class="d-flex justify-end mb-2">
181181
<v-btn-toggle v-model="chartColumns" density="compact" variant="outlined" mandatory>
182-
<v-btn value="1" size="small" icon="mdi-view-agenda" title="Single column" />
183-
<v-btn value="2" size="small" icon="mdi-view-grid" title="Two columns" />
184-
<v-btn value="full" size="small" icon="mdi-fullscreen" title="Full width" />
182+
<v-btn value="1" size="small" title="Single column"><v-icon size="18">mdi-view-agenda</v-icon></v-btn>
183+
<v-btn value="2" size="small" title="Two columns"><v-icon size="18">mdi-view-grid</v-icon></v-btn>
184+
<v-btn value="full" size="small" title="Full width"><v-icon size="18">mdi-fullscreen</v-icon></v-btn>
185185
</v-btn-toggle>
186186
</div>
187187

app/components/PullRequestViewer.vue

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,9 +107,9 @@
107107
<v-container :fluid="chartColumns === 'full'" :class="['elevation-2', chartColumns === 'full' ? 'px-0' : 'px-4']">
108108
<div class="d-flex justify-end mb-2">
109109
<v-btn-toggle v-model="chartColumns" density="compact" variant="outlined" mandatory>
110-
<v-btn value="1" size="small" icon="mdi-view-agenda" title="Single column" />
111-
<v-btn value="2" size="small" icon="mdi-view-grid" title="Two columns" />
112-
<v-btn value="full" size="small" icon="mdi-fullscreen" title="Full width" />
110+
<v-btn value="1" size="small" title="Single column"><v-icon size="18">mdi-view-agenda</v-icon></v-btn>
111+
<v-btn value="2" size="small" title="Two columns"><v-icon size="18">mdi-view-grid</v-icon></v-btn>
112+
<v-btn value="full" size="small" title="Full width"><v-icon size="18">mdi-fullscreen</v-icon></v-btn>
113113
</v-btn-toggle>
114114
</div>
115115
<v-row>

app/components/SeatsAnalysisViewer.vue

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,9 +107,9 @@
107107
<v-container :fluid="chartColumns === 'full'" :class="['elevation-2', chartColumns === 'full' ? 'px-0' : 'px-4']">
108108
<div class="d-flex justify-end mb-2">
109109
<v-btn-toggle v-model="chartColumns" density="compact" variant="outlined" mandatory>
110-
<v-btn value="1" size="small" icon="mdi-view-agenda" title="Single column" />
111-
<v-btn value="2" size="small" icon="mdi-view-grid" title="Two columns" />
112-
<v-btn value="full" size="small" icon="mdi-fullscreen" title="Full width" />
110+
<v-btn value="1" size="small" title="Single column"><v-icon size="18">mdi-view-agenda</v-icon></v-btn>
111+
<v-btn value="2" size="small" title="Two columns"><v-icon size="18">mdi-view-grid</v-icon></v-btn>
112+
<v-btn value="full" size="small" title="Full width"><v-icon size="18">mdi-fullscreen</v-icon></v-btn>
113113
</v-btn-toggle>
114114
</div>
115115
<v-row v-if="seatsHistory.length > 0" class="mb-4">

app/components/TeamsComponent.vue

Lines changed: 155 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,49 @@
3838

3939
<!-- Team selector -->
4040
<v-card variant="outlined" class="mx-4 mb-2 pa-3" density="compact">
41-
<div class="d-flex align-center gap-2">
41+
<!-- Organization dropdown (enterprise scope) -->
42+
<div v-if="scopeType === 'enterprise'" class="mb-2">
43+
<v-autocomplete
44+
v-model="selectedOrg"
45+
:items="availableOrgs"
46+
item-value="login"
47+
item-title="name"
48+
label="Filter by organization (optional)"
49+
clearable
50+
variant="outlined"
51+
density="compact"
52+
:theme="isDark ? 'dark' : 'light'"
53+
:menu-props="{
54+
contentClass: 'orgs-select-menu',
55+
maxHeight: 360,
56+
scrim: false,
57+
offset: 8
58+
}"
59+
:hint="isFullGhec ? 'Select an org to browse its teams, or leave blank for enterprise-level teams' : 'No organizations found for this enterprise'"
60+
persistent-hint
61+
:disabled="!isFullGhec"
62+
prepend-inner-icon="mdi-domain"
63+
>
64+
<template #item="{ props, item }">
65+
<v-list-item v-bind="props" :title="item.raw.name" :subtitle="item.raw.login" />
66+
</template>
67+
<template v-if="selectedOrg" #append-inner>
68+
<v-tooltip text="Switch to org view" location="top">
69+
<template #activator="{ props: tooltipProps }">
70+
<v-btn
71+
v-bind="tooltipProps"
72+
:to="`/orgs/${selectedOrg}`"
73+
icon="mdi-open-in-new"
74+
variant="text"
75+
size="small"
76+
density="compact"
77+
/>
78+
</template>
79+
</v-tooltip>
80+
</template>
81+
</v-autocomplete>
82+
</div>
83+
<div class="d-flex align-start gap-2">
4284
<v-autocomplete
4385
v-model="selectedTeams"
4486
:items="availableTeams"
@@ -69,33 +111,52 @@
69111
<v-chip v-bind="props" class="select-chip" :text="item.raw.name" closable />
70112
</template>
71113
</v-autocomplete>
72-
<div class="d-flex align-center gap-2 flex-shrink-0">
114+
<div class="d-flex align-center gap-4 flex-shrink-0 mt-1 ml-2">
73115
<v-chip v-if="singleTeamMode" color="primary" size="small" prepend-icon="mdi-view-dashboard">Deep Dive</v-chip>
74116
<v-chip v-else-if="comparisonMode" color="secondary" size="small" prepend-icon="mdi-compare">Comparison</v-chip>
75-
<v-btn v-if="selectedTeams.length > 0" variant="outlined" size="small" @click="clearSelection">Clear All</v-btn>
117+
<v-btn v-if="selectedTeams.length > 0" variant="outlined" size="small" class="ml-2" @click="clearSelection">Clear All</v-btn>
76118
</div>
77119
</div>
78120
</v-card>
79121

80122
<!-- ═══════════════════════════════════════════════ SINGLE TEAM DEEP DIVE -->
81123
<div v-if="singleTeamMode">
82-
<!-- Team header -->
83-
<v-card variant="outlined" class="mx-4 mb-1 pa-3" density="compact">
84-
<div class="d-flex align-center gap-2 text-body-2">
85-
<v-icon color="primary">mdi-account-group</v-icon>
86-
<div style="flex: 1;">
87-
<div class="font-weight-bold text-body-1">{{ singleTeamName }}</div>
88-
<div class="text-medium-emphasis">{{ dateRangeDesc }}</div>
89-
</div>
90-
<v-btn
91-
variant="outlined"
92-
size="small"
93-
color="error"
94-
prepend-icon="mdi-close"
95-
@click="selectedTeams = []"
96-
>Deselect</v-btn>
97-
</div>
98-
</v-card>
124+
<!-- Team header — same compact card as comparison mode -->
125+
<v-container fluid class="px-4 pb-0">
126+
<v-row dense>
127+
<v-col v-for="card in comparisonSummaryCards" :key="card.teamName" cols="12" sm="6" md="4" lg="3">
128+
<v-card elevation="3" class="pa-3" :style="`border-left: 4px solid ${card.color.border}`">
129+
<div class="d-flex align-center gap-2 mb-1">
130+
<v-icon size="18" :color="card.color.border">mdi-account-group</v-icon>
131+
<span class="text-subtitle-2 font-weight-medium">{{ card.teamName }}</span>
132+
<v-spacer />
133+
<v-btn
134+
variant="text"
135+
size="x-small"
136+
icon
137+
title="Deselect team"
138+
@click="selectedTeams = []"
139+
>
140+
<v-icon size="14">mdi-close</v-icon>
141+
</v-btn>
142+
</div>
143+
<div class="d-flex justify-space-between text-caption text-medium-emphasis">
144+
<span>Active Users</span>
145+
<span class="font-weight-medium">{{ card.activeUsers }}</span>
146+
</div>
147+
<div class="d-flex justify-space-between text-caption text-medium-emphasis">
148+
<span>Acceptance Rate</span>
149+
<span class="font-weight-medium">{{ card.acceptanceRate }}%</span>
150+
</div>
151+
<div class="d-flex justify-space-between text-caption text-medium-emphasis">
152+
<span>Interactions</span>
153+
<span class="font-weight-medium">{{ card.totalInteractions.toLocaleString() }}</span>
154+
</div>
155+
<div class="text-caption text-medium-emphasis mt-1">{{ dateRangeDesc }}</div>
156+
</v-card>
157+
</v-col>
158+
</v-row>
159+
</v-container>
99160

100161
<!-- KPI Tiles -->
101162
<div class="tiles-container">
@@ -120,8 +181,9 @@
120181
<v-container :fluid="chartColumns === 'full'" :class="['elevation-2 mt-1 mb-2', chartColumns === 'full' ? 'px-0' : 'px-4']">
121182
<div class="d-flex justify-end mb-3">
122183
<v-btn-toggle v-model="chartColumns" density="compact" variant="outlined" mandatory>
123-
<v-btn value="1" size="small"><v-icon size="18">mdi-view-stream</v-icon></v-btn>
124-
<v-btn value="2" size="small"><v-icon size="18">mdi-view-grid</v-icon></v-btn>
184+
<v-btn value="1" size="small" title="Single column"><v-icon size="18">mdi-view-agenda</v-icon></v-btn>
185+
<v-btn value="2" size="small" title="Two columns"><v-icon size="18">mdi-view-grid</v-icon></v-btn>
186+
<v-btn value="full" size="small" title="Full width"><v-icon size="18">mdi-fullscreen</v-icon></v-btn>
125187
</v-btn-toggle>
126188
</div>
127189

@@ -281,8 +343,8 @@
281343
<!-- ═══════════════════════════════════════════════ COMPARISON MODE (2+ teams) -->
282344
<div v-else-if="comparisonMode">
283345
<!-- Per-team summary cards -->
284-
<v-container>
285-
<v-row>
346+
<v-container fluid class="px-4 pb-0">
347+
<v-row dense>
286348
<v-col v-for="card in comparisonSummaryCards" :key="card.teamName" cols="12" sm="6" md="4" lg="3">
287349
<v-card elevation="3" class="pa-3" :style="`border-left: 4px solid ${card.color.border}`">
288350
<div class="d-flex align-center gap-2 mb-1">
@@ -320,8 +382,9 @@
320382
<v-container :fluid="chartColumns === 'full'" :class="['elevation-2 mt-1 mb-2', chartColumns === 'full' ? 'px-0' : 'px-4']">
321383
<div class="d-flex justify-end mb-3">
322384
<v-btn-toggle v-model="chartColumns" density="compact" variant="outlined" mandatory>
323-
<v-btn value="1" size="small"><v-icon size="18">mdi-view-stream</v-icon></v-btn>
324-
<v-btn value="2" size="small"><v-icon size="18">mdi-view-grid</v-icon></v-btn>
385+
<v-btn value="1" size="small" title="Single column"><v-icon size="18">mdi-view-agenda</v-icon></v-btn>
386+
<v-btn value="2" size="small" title="Two columns"><v-icon size="18">mdi-view-grid</v-icon></v-btn>
387+
<v-btn value="full" size="small" title="Full width"><v-icon size="18">mdi-fullscreen</v-icon></v-btn>
325388
</v-btn-toggle>
326389
</div>
327390

@@ -475,6 +538,11 @@ interface Team {
475538
description?: string
476539
}
477540
541+
interface EnterpriseOrg {
542+
login: string
543+
name: string
544+
}
545+
478546
interface PerTeamData {
479547
slug: string
480548
metrics: Metrics[]
@@ -512,7 +580,12 @@ export default defineComponent({
512580
513581
const availableTeams = ref<Team[]>([])
514582
const selectedTeams = ref<string[]>([])
515-
const chartColumns = ref<'1' | '2'>('2')
583+
const chartColumns = ref<'1' | '2' | 'full'>('2')
584+
585+
// ── Full GHEC org support ─────────────────────────────────────────────────
586+
const isFullGhec = ref(false)
587+
const availableOrgs = ref<EnterpriseOrg[]>([])
588+
const selectedOrg = ref<string | null>(null)
516589
517590
// Core per-team data (reactive so computed values update)
518591
const perTeamData = ref<PerTeamData[]>([])
@@ -574,9 +647,14 @@ export default defineComponent({
574647
575648
const getTeamDetailUrl = (teamSlug: string) => {
576649
const config = useRuntimeConfig()
577-
return config.public.scope === 'enterprise'
578-
? `/enterprises/${config.public.githubEnt}/teams/${teamSlug}`
579-
: `/orgs/${config.public.githubOrg}/teams/${teamSlug}`
650+
// For enterprise scope with a selected org, navigate to the org-level team page
651+
if (scopeType.value === 'enterprise') {
652+
if (selectedOrg.value) {
653+
return `/orgs/${selectedOrg.value}/teams/${teamSlug}`
654+
}
655+
return `/enterprises/${config.public.githubEnt}/teams/${teamSlug}`
656+
}
657+
return `/orgs/${config.public.githubOrg}/teams/${teamSlug}`
580658
}
581659
582660
// ── Language/editor aggregation helpers ───────────────────────────────────
@@ -859,6 +937,10 @@ export default defineComponent({
859937
const route = useRoute()
860938
const options = Options.fromRoute(route, props.dateRange.since, props.dateRange.until)
861939
options.githubTeam = slug
940+
// Pass org override for Full GHEC org-level team membership lookup
941+
if (selectedOrg.value && scopeType.value === 'enterprise') {
942+
options.githubOrg = selectedOrg.value
943+
}
862944
const params = options.toParams()
863945
const result = await $fetch<UserTotals[]>('/api/user-metrics', { params })
864946
singleTeamUserMetrics.value = Array.isArray(result) ? result : []
@@ -941,9 +1023,28 @@ export default defineComponent({
9411023
})
9421024
9431025
// ── Data loading ──────────────────────────────────────────────────────────
944-
const loadTeams = async () => {
1026+
const loadEnterpriseOrgs = async () => {
1027+
const route = useRoute()
1028+
const options = Options.fromRoute(route, props.dateRange.since, props.dateRange.until)
1029+
if (scopeType.value !== 'enterprise') return
1030+
const params = options.toParams()
1031+
try {
1032+
const result = await $fetch<{ isFullGhec: boolean; orgs: EnterpriseOrg[] }>('/api/enterprise-orgs', { params })
1033+
isFullGhec.value = result.isFullGhec
1034+
availableOrgs.value = result.orgs
1035+
} catch {
1036+
isFullGhec.value = false
1037+
availableOrgs.value = []
1038+
}
1039+
}
1040+
1041+
const loadTeams = async (orgOverride?: string) => {
9451042
const route = useRoute()
9461043
const options = Options.fromRoute(route, props.dateRange.since, props.dateRange.until)
1044+
// When an org is selected in enterprise context, list that org's teams
1045+
if (orgOverride) {
1046+
options.githubOrg = orgOverride
1047+
}
9471048
const params = options.toParams()
9481049
const teams = await $fetch<Team[]>('/api/teams', { params })
9491050
availableTeams.value = teams
@@ -953,6 +1054,10 @@ export default defineComponent({
9531054
const route = useRoute()
9541055
const options = Options.fromRoute(route, props.dateRange.since, props.dateRange.until)
9551056
options.githubTeam = teamSlug
1057+
// Pass org override for Full GHEC org-level team membership lookup
1058+
if (selectedOrg.value && scopeType.value === 'enterprise') {
1059+
options.githubOrg = selectedOrg.value
1060+
}
9561061
const params = options.toParams()
9571062
const response = await $fetch<MetricsApiResponse>('/api/metrics', { params })
9581063
return {
@@ -1066,10 +1171,24 @@ export default defineComponent({
10661171
}
10671172
}
10681173
1069-
onMounted(async () => { await loadTeams() })
1174+
onMounted(async () => {
1175+
// For enterprise scope, detect Full GHEC and load orgs before teams
1176+
await loadEnterpriseOrgs()
1177+
await loadTeams()
1178+
})
10701179
10711180
watch(selectedTeams, async () => { await updateChartData() })
10721181
1182+
watch(selectedOrg, async (org) => {
1183+
// When org selection changes, clear team selection and reload teams for that org
1184+
selectedTeams.value = []
1185+
try {
1186+
await loadTeams(org || undefined)
1187+
} catch {
1188+
availableTeams.value = []
1189+
}
1190+
})
1191+
10731192
watch(() => props.dateRange, async () => { await updateChartData() }, { deep: true })
10741193
10751194
return {
@@ -1079,6 +1198,10 @@ export default defineComponent({
10791198
isLoading,
10801199
chartColumns,
10811200
perTeamData,
1201+
// Full GHEC org support
1202+
isFullGhec,
1203+
availableOrgs,
1204+
selectedOrg,
10821205
// modes
10831206
singleTeamMode,
10841207
comparisonMode,

0 commit comments

Comments
 (0)