Skip to content

Commit 9ddd360

Browse files
CopilotkarpikplCopilot
authored
Implement Agent Mode Statistics display with models used by users (#226)
* Initial plan * Initial exploration and setup complete Co-authored-by: karpikpl <[email protected]> * Implement Agent Mode Statistics display with models used by users Co-authored-by: karpikpl <[email protected]> * Rename agent mode to GitHub.com and update terminology Co-authored-by: karpikpl <[email protected]> * Merge with main and move GitHub.com calculations to backend Co-authored-by: karpikpl <[email protected]> * model info * Update e2e-tests/pages/GitHubTab.ts Co-authored-by: Copilot <[email protected]> * Update app/components/AgentModeViewer.vue Co-authored-by: Copilot <[email protected]> * Update server/api/github-stats.ts Co-authored-by: Copilot <[email protected]> * Update e2e-tests/pages/GitHubTab.ts Co-authored-by: Copilot <[email protected]> * code cleanup after copilot made a mess --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: karpikpl <[email protected]> Co-authored-by: Piotr Karpala <[email protected]> Co-authored-by: Piotr Karpala <[email protected]> Co-authored-by: Copilot <[email protected]>
1 parent 3474b84 commit 9ddd360

File tree

12 files changed

+1376
-146
lines changed

12 files changed

+1376
-146
lines changed

app/assets/global.css

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,20 +22,57 @@
2222
margin-bottom: 25px;
2323
}
2424

25-
/* Fix tab hover styles - light blue background */
26-
.v-tab:hover {
27-
background-color: rgba(63, 81, 181, 0.1) !important;
25+
/* Fix tab hover styles - dark blue overlay using pseudo-element */
26+
.v-tab {
27+
position: relative !important;
2828
}
2929

30-
.v-tab:hover .v-tab__overlay {
31-
display: none !important;
30+
.v-tab:hover::before {
31+
content: '' !important;
32+
position: absolute !important;
33+
top: 0 !important;
34+
left: 0 !important;
35+
width: 100% !important;
36+
height: 100% !important;
37+
background-color: rgba(44, 32, 157, 0.557) !important;
38+
pointer-events: none !important;
39+
z-index: 1 !important;
40+
}
41+
42+
/* Ensure tab content is above overlay */
43+
.v-tab .v-tab__content {
44+
position: relative !important;
45+
z-index: 2 !important;
46+
}
47+
48+
/* Fix expansion panel title overlay on hover - gray overlay */
49+
.v-expansion-panel-title:hover .v-expansion-panel-title__overlay {
50+
display: block !important;
51+
background-color: rgba(0, 0, 0, 0.04) !important;
52+
opacity: 1 !important;
53+
position: absolute !important;
54+
top: 0 !important;
55+
left: 0 !important;
56+
width: 100% !important;
57+
height: 100% !important;
58+
pointer-events: none !important;
3259
}
3360

3461
/* Fix button hover styles - light gray background */
3562
.v-btn:hover {
36-
background-color: rgba(0, 0, 0, 0.04) !important;
63+
background-color: rgba(63, 81, 181, 0.1) !important;
3764
}
3865

3966
.v-btn:hover .v-btn__overlay {
4067
display: none !important;
68+
}
69+
70+
/* Override Vuetify's default overlay behavior for tabs */
71+
.v-tab.v-btn--variant-text .v-tab__overlay {
72+
background: transparent !important;
73+
}
74+
75+
/* Override Vuetify's default overlay behavior for variant-text buttons in tabs */
76+
.v-tab .v-btn--variant-text .v-btn__overlay {
77+
background: transparent !important;
4178
}

app/components/AgentModeViewer.vue

Lines changed: 533 additions & 0 deletions
Large diffs are not rendered by default.

app/components/DateRangeSelector.vue

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -106,13 +106,14 @@ const dateRangeText = computed(() => {
106106
const from = parseDate(fromDate.value)
107107
const to = parseDate(toDate.value)
108108
const diffDays = Math.ceil((to.getTime() - from.getTime()) / (1000 * 60 * 60 * 24)) + 1
109-
109+
const withoutHolidays = excludeHolidays.value ? ' (excluding holidays/weekends)' : ''
110+
110111
if (diffDays === 1) {
111-
return `For ${from.toLocaleDateString()}`
112+
return `For ${from.toLocaleDateString()}${withoutHolidays}`
112113
} else if (diffDays <= 28 && isLast28Days()) {
113-
return 'Over the last 28 days'
114+
return `Over the last 28 days ${withoutHolidays}`
114115
} else {
115-
return `From ${from.toLocaleDateString()} to ${to.toLocaleDateString()} (${diffDays} days)`
116+
return `From ${from.toLocaleDateString()} to ${to.toLocaleDateString()} (${diffDays} days)${withoutHolidays}`
116117
}
117118
})
118119

app/components/MainComponent.vue

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,9 @@
3535
</v-toolbar>
3636

3737
<!-- Date Range Selector - Hidden for seats tab -->
38-
<DateRangeSelector
39-
v-show="tab !== 'seat analysis' && !signInRequired" :loading="isLoading"
38+
<DateRangeSelector
39+
v-show="tab !== 'seat analysis' && !signInRequired"
40+
:loading="isLoading"
4041
@date-range-changed="handleDateRangeChange" />
4142

4243
<!-- Organization info for seats tab -->
@@ -79,6 +80,7 @@ v-if="item === 'editors'" :metrics="metrics" :breakdown-key="'editor'"
7980
<CopilotChatViewer
8081
v-if="item === 'copilot chat'" :metrics="metrics"
8182
:date-range-description="dateRangeDescription" />
83+
<AgentModeViewer v-if="item === 'github.com'" :original-metrics="originalMetrics" :date-range="dateRange" :date-range-description="dateRangeDescription" />
8284
<SeatsAnalysisViewer v-if="item === 'seat analysis'" :seats="seats" />
8385
<ApiResponse
8486
v-if="item === 'api response'" :metrics="metrics" :original-metrics="originalMetrics"
@@ -107,6 +109,7 @@ import BreakdownComponent from './BreakdownComponent.vue'
107109
import CopilotChatViewer from './CopilotChatViewer.vue'
108110
import SeatsAnalysisViewer from './SeatsAnalysisViewer.vue'
109111
import ApiResponse from './ApiResponse.vue'
112+
import AgentModeViewer from './AgentModeViewer.vue'
110113
import DateRangeSelector from './DateRangeSelector.vue'
111114
import { Options } from '@/model/Options';
112115
import { useRoute } from 'vue-router';
@@ -119,6 +122,7 @@ export default defineNuxtComponent({
119122
CopilotChatViewer,
120123
SeatsAnalysisViewer,
121124
ApiResponse,
125+
AgentModeViewer,
122126
DateRangeSelector
123127
},
124128
methods: {
@@ -211,7 +215,7 @@ export default defineNuxtComponent({
211215
212216
data() {
213217
return {
214-
tabItems: ['languages', 'editors', 'copilot chat', 'seat analysis', 'api response'],
218+
tabItems: ['languages', 'editors', 'copilot chat', 'github.com', 'seat analysis', 'api response'],
215219
tab: null,
216220
dateRangeDescription: 'Over the last 28 days',
217221
isLoading: false,

app/types/metricsApiResponse.d.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,4 @@ import type { Metrics } from "@/model/Metrics";
44
interface MetricsApiResponse {
55
metrics: Metrics[]; // Replace `any` with the actual type of metrics
66
usage: CopilotMetrics[]; // Replace `any` with the actual type of usage
7-
valid_until: number; // Timestamp indicating when the data is valid until
87
}

e2e-tests/github-tab.spec.ts

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import { test } from '@playwright/test';
2+
import { GitHubTab } from './pages/GitHubTab';
3+
import { DashboardPage } from './pages/DashboardPage';
4+
5+
6+
test.describe('AgentModeViewer Component', () => {
7+
8+
let dashboard: DashboardPage;
9+
10+
test.beforeAll(async ({ browser }) => {
11+
const page = await browser.newPage();
12+
await page.goto('/orgs/octo-demo-org?mock=true');
13+
14+
dashboard = new DashboardPage(page);
15+
16+
// wait for the data
17+
await dashboard.expectMetricLabelsVisible();
18+
});
19+
20+
test.afterAll(async () => {
21+
await dashboard?.close();
22+
});
23+
24+
25+
test('should display loading state initially', async () => {
26+
// Check if the github.com container is present
27+
const githubTab = await dashboard.gotoGitHubTab();
28+
await githubTab.expectContainerVisible();
29+
});
30+
31+
test('should display Copilot Statistics title', async () => {
32+
// Wait for the component to load and display the title
33+
const githubTab = await dashboard.gotoGitHubTab();
34+
await githubTab.expectStatisticsTitleVisible();
35+
});
36+
37+
test('should display overview cards', async () => {
38+
// Check if all four overview cards are present
39+
const githubTab = await dashboard.gotoGitHubTab();
40+
await githubTab.expectOverviewCardsVisible();
41+
});
42+
43+
test('should display chart sections', async () => {
44+
// Check if chart sections are present
45+
const githubTab = await dashboard.gotoGitHubTab();
46+
await githubTab.expectChartSectionsVisible();
47+
});
48+
49+
test('should display models section', async () => {
50+
// Check if models section is present
51+
const githubTab = await dashboard.gotoGitHubTab();
52+
await githubTab.expectModelsSectionVisible();
53+
});
54+
55+
test('should handle chart rendering without performance issues', async () => {
56+
// Measure page performance and check charts
57+
const githubTab = await dashboard.gotoGitHubTab();
58+
const renderTime = await githubTab.expectRenderTimeUnderLimit(5000);
59+
const chartCount = await githubTab.expectChartContainersPresent();
60+
61+
console.log(`Render time: ${renderTime}ms, Chart containers: ${chartCount}`);
62+
});
63+
64+
test('should display tooltips on hover', async () => {
65+
// Test tooltip functionality
66+
const githubTab = await dashboard.gotoGitHubTab();
67+
await githubTab.expectTooltipInteraction();
68+
});
69+
70+
test('should handle expansion panels correctly', async () => {
71+
// Look for expansion panels and interact with them
72+
const githubTab = await dashboard.gotoGitHubTab();
73+
await githubTab.clickFirstExpansionPanel();
74+
});
75+
76+
test('should not show requestAnimationFrame performance warnings', async () => {
77+
// Monitor console for performance warnings
78+
const githubTab = await dashboard.gotoGitHubTab();
79+
await githubTab.monitorPerformanceWarnings();
80+
});
81+
82+
test('should be responsive on different screen sizes', async () => {
83+
// Test responsive design across different viewports
84+
const githubTab = await dashboard.gotoGitHubTab();
85+
await githubTab.expectResponsiveDesign();
86+
});
87+
88+
test('should maintain chart aspect ratio', async () => {
89+
// Check that chart containers have the correct styling
90+
const githubTab = await dashboard.gotoGitHubTab();
91+
await githubTab.validateChartContainerStyles();
92+
});
93+
});

e2e-tests/pages/DashboardPage.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { EditorsTab } from './EditorsTab';
44
import { SeatAnalysisTab } from './SeatAnalysisTab';
55
import { ApiResponseTab } from './ApiResponseTab';
66
import { CopilotChatTab } from './CopilotChatTab';
7+
import { GitHubTab } from './GitHubTab';
78

89
export class DashboardPage {
910
readonly page: Page;
@@ -23,6 +24,7 @@ export class DashboardPage {
2324
readonly seatAnalysisTabLink: Locator;
2425
readonly apiResponseTabLink: Locator;
2526
readonly copilotChatTabLink: Locator;
27+
readonly githubTabLink: Locator;
2628

2729
constructor(page: Page) {
2830
this.page = page;
@@ -32,12 +34,13 @@ export class DashboardPage {
3234
this.totalLinesSuggestedLabel = page.getByRole('heading', { name: 'Total Lines Suggested | Total' })
3335
this.totalLinesSuggestedValue = page.locator('.v-card-item').filter({ has: page.getByText('Total Lines of code Suggested') }).locator('.text-h4')
3436
this.toolbarTitle = page.locator(".toolbar-title")
35-
37+
3638
this.languagesTabLink = page.getByRole('tab', { name: 'languages' })
3739
this.editorsTabLink = page.getByRole('tab', { name: 'editors' })
3840
this.seatAnalysisTabLink = page.getByRole('tab', { name: 'seat analysis' })
3941
this.apiResponseTabLink = page.getByRole('tab', { name: 'api response' })
4042
this.copilotChatTabLink = page.getByRole('tab', { name: 'copilot chat' })
43+
this.githubTabLink = page.getByRole('tab', { name: 'github.com' })
4144

4245
this.teamTabLink = page.getByRole('tab', { name: 'team' })
4346
this.orgTabLink = page.getByRole('tab', { name: 'organization' })
@@ -98,6 +101,11 @@ export class DashboardPage {
98101
return new CopilotChatTab(this.page);
99102
}
100103

104+
async gotoGitHubTab() {
105+
await this.githubTabLink.click();
106+
return new GitHubTab(this.page);
107+
}
108+
101109
async close() {
102110
await this.page.close();
103111
}

0 commit comments

Comments
 (0)