diff --git a/app/assets/global.css b/app/assets/global.css index fc9e55aa..c8d6547c 100644 --- a/app/assets/global.css +++ b/app/assets/global.css @@ -75,12 +75,21 @@ pointer-events: none !important; } - /* Fix button hover styles - light gray background */ - .v-btn:hover { - background-color: var(--app-accent-weak) !important; + /* Fix button hover styles - light gray background (exclude primary outlined buttons) */ + .v-btn:hover:not(.v-btn--variant-outlined) { + background-color: var(--app-accent-weak) !important; } - .v-btn:hover .v-btn__overlay { + .v-btn:hover:not(.v-btn--variant-outlined) .v-btn__overlay { + display: none !important; + } + + /* Export button hover - use proper primary color with transparency */ + .v-btn--variant-outlined.v-btn--color-primary:hover { + background-color: rgba(var(--v-theme-primary), 0.08) !important; + } + + .v-btn--variant-outlined.v-btn--color-primary:hover .v-btn__overlay { display: none !important; } @@ -197,4 +206,38 @@ /* Pagination buttons hover state unify with buttons */ .v-data-table .v-data-table-footer .v-btn:hover { background-color: var(--app-accent-weak) !important; + } + + /* Export dropdown menus - ensure readable styling consistent with pagination dropdowns */ + .v-menu .v-list { + background: var(--v-theme-surface, #fff) !important; + border-radius: 8px; + box-shadow: 0 6px 24px rgba(56, 79, 184, 0.15); + } + + .v-menu .v-list .v-list-item { + background: var(--v-theme-surface, #fff) !important; + color: var(--v-theme-on-surface, #000) !important; + min-height: 40px; + } + + .v-menu .v-list .v-list-item .v-list-item-title { + color: var(--v-theme-on-surface, #000) !important; + } + + .v-menu .v-list .v-list-item .v-icon { + color: var(--v-theme-on-surface, #000) !important; + } + + .v-menu .v-list .v-list-item:hover { + background: var(--app-accent-weak) !important; + } + + .v-menu .v-list .v-list-item.v-list-item--active { + background: var(--app-accent-weak) !important; + } + + /* Remove dark overlay layer inside export dropdown list items */ + .v-menu .v-list .v-list-item .v-list-item__overlay { + background: transparent !important; } \ No newline at end of file diff --git a/app/components/AgentModeViewer.vue b/app/components/AgentModeViewer.vue index ee02c03f..62ceaddf 100644 --- a/app/components/AgentModeViewer.vue +++ b/app/components/AgentModeViewer.vue @@ -167,6 +167,34 @@ v-if="stats.agentModeChartData.labels.length" :data="stats.agentModeChartData" IDE Code Completions Models ({{ stats.ideCodeCompletionModels.length }}) +
+

IDE Code Completion Models

+ + + + + Export as CSV + + + Export as JSON + + + Copy to Clipboard + + + +
@@ -179,6 +207,34 @@ v-if="stats.agentModeChartData.labels.length" :data="stats.agentModeChartData" IDE Chat Models ({{ stats.ideChatModels.length }}) +
+

IDE Chat Models

+ + + + + Export as CSV + + + Export as JSON + + + Copy to Clipboard + + + +
@@ -191,6 +247,34 @@ v-if="stats.agentModeChartData.labels.length" :data="stats.agentModeChartData" GitHub.com Chat Models ({{ stats.dotcomChatModels.length }}) +
+

GitHub.com Chat Models

+ + + + + Export as CSV + + + Export as JSON + + + Copy to Clipboard + + + +
@@ -203,6 +287,34 @@ v-if="stats.agentModeChartData.labels.length" :data="stats.agentModeChartData" GitHub.com PR Summary Models ({{ stats.dotcomPRModels.length }}) +
+

GitHub.com PR Summary Models

+ + + + + Export as CSV + + + Export as JSON + + + Copy to Clipboard + + + +
@@ -239,6 +351,7 @@ import type { CopilotMetrics } from '@/model/Copilot_Metrics'; import { Options } from '@/model/Options'; import { useRoute } from 'vue-router'; import { Line as LineChart, Bar as BarChart } from 'vue-chartjs'; +import { exportToCSV, exportToJSON, copyTableToClipboard, formatFilename } from '@/utils/exportUtils'; import { Chart as ChartJS, CategoryScale, @@ -498,6 +611,163 @@ export default defineComponent({ } }; + // Export functions for each table + const exportIdeCodeCompletionCSV = () => { + const exportData = stats.value.ideCodeCompletionModels.map((model, index) => ({ + 'S.No': index + 1, + 'Model Name': model.name, + 'Editor': model.editor || '', + 'Type': model.model_type, + 'Total Users with Activity': model.total_engaged_users + })); + const filename = formatFilename('ide_code_completion_models', 'csv'); + exportToCSV(exportData, filename); + }; + + const exportIdeCodeCompletionJSON = () => { + const exportData = stats.value.ideCodeCompletionModels.map((model, index) => ({ + serialNumber: index + 1, + modelName: model.name, + editor: model.editor || '', + type: model.model_type, + totalUsersWithActivity: model.total_engaged_users + })); + const filename = formatFilename('ide_code_completion_models', 'json'); + exportToJSON(exportData, filename); + }; + + + const copyIdeCodeCompletionToClipboard = async () => { + const exportData = stats.value.ideCodeCompletionModels.map((model, index) => ({ + 'S.No': index + 1, + 'Model Name': model.name, + 'Editor': model.editor || '', + 'Type': model.model_type, + 'Total Users with Activity': model.total_engaged_users + })); + await copyTableToClipboard(exportData); + }; + + const exportIdeChatCSV = () => { + const exportData = stats.value.ideChatModels.map((model, index) => ({ + 'S.No': index + 1, + 'Model Name': model.name, + 'Editor': model.editor || '', + 'Type': model.model_type, + 'Total Users with Activity': model.total_engaged_users, + 'Total Chats': model.total_chats || 0, + 'Insertions': model.total_chat_insertion_events || 0, + 'Copy Events': model.total_chat_copy_events || 0 + })); + const filename = formatFilename('ide_chat_models', 'csv'); + exportToCSV(exportData, filename); + }; + + const exportIdeChatJSON = () => { + const exportData = stats.value.ideChatModels.map((model, index) => ({ + serialNumber: index + 1, + modelName: model.name, + editor: model.editor || '', + type: model.model_type, + totalUsersWithActivity: model.total_engaged_users, + totalChats: model.total_chats || 0, + insertions: model.total_chat_insertion_events || 0, + copyEvents: model.total_chat_copy_events || 0 + })); + const filename = formatFilename('ide_chat_models', 'json'); + exportToJSON(exportData, filename); + }; + + + const copyIdeChatToClipboard = async () => { + const exportData = stats.value.ideChatModels.map((model, index) => ({ + 'S.No': index + 1, + 'Model Name': model.name, + 'Editor': model.editor || '', + 'Type': model.model_type, + 'Total Users with Activity': model.total_engaged_users, + 'Total Chats': model.total_chats || 0, + 'Insertions': model.total_chat_insertion_events || 0, + 'Copy Events': model.total_chat_copy_events || 0 + })); + await copyTableToClipboard(exportData); + }; + + const exportDotcomChatCSV = () => { + const exportData = stats.value.dotcomChatModels.map((model, index) => ({ + 'S.No': index + 1, + 'Model Name': model.name, + 'Type': model.model_type, + 'Total Users with Activity': model.total_engaged_users, + 'Total Chats': model.total_chats || 0 + })); + const filename = formatFilename('github_com_chat_models', 'csv'); + exportToCSV(exportData, filename); + }; + + const exportDotcomChatJSON = () => { + const exportData = stats.value.dotcomChatModels.map((model, index) => ({ + serialNumber: index + 1, + modelName: model.name, + type: model.model_type, + totalUsersWithActivity: model.total_engaged_users, + totalChats: model.total_chats || 0 + })); + const filename = formatFilename('github_com_chat_models', 'json'); + exportToJSON(exportData, filename); + }; + + + const copyDotcomChatToClipboard = async () => { + const exportData = stats.value.dotcomChatModels.map((model, index) => ({ + 'S.No': index + 1, + 'Model Name': model.name, + 'Type': model.model_type, + 'Total Users with Activity': model.total_engaged_users, + 'Total Chats': model.total_chats || 0 + })); + await copyTableToClipboard(exportData); + }; + + const exportDotcomPRCSV = () => { + const exportData = stats.value.dotcomPRModels.map((model, index) => ({ + 'S.No': index + 1, + 'Model Name': model.name, + 'Repository': model.repository || '', + 'Type': model.model_type, + 'Total Users with Activity': model.total_engaged_users, + 'PR Summaries': model.total_pr_summaries_created || 0 + })); + const filename = formatFilename('github_com_pr_summary_models', 'csv'); + exportToCSV(exportData, filename); + }; + + const exportDotcomPRJSON = () => { + const exportData = stats.value.dotcomPRModels.map((model, index) => ({ + serialNumber: index + 1, + modelName: model.name, + repository: model.repository || '', + type: model.model_type, + totalUsersWithActivity: model.total_engaged_users, + prSummaries: model.total_pr_summaries_created || 0 + })); + const filename = formatFilename('github_com_pr_summary_models', 'json'); + exportToJSON(exportData, filename); + }; + + + const copyDotcomPRToClipboard = async () => { + const exportData = stats.value.dotcomPRModels.map((model, index) => ({ + 'S.No': index + 1, + 'Model Name': model.name, + 'Repository': model.repository || '', + 'Type': model.model_type, + 'Total Users with Activity': model.total_engaged_users, + 'PR Summaries': model.total_pr_summaries_created || 0 + })); + await copyTableToClipboard(exportData); + }; + return { stats, loading, @@ -507,7 +777,19 @@ export default defineComponent({ dotcomChatHeaders, dotcomPRHeaders, chartOptions, - barChartOptions + barChartOptions, + exportIdeCodeCompletionCSV, + exportIdeCodeCompletionJSON, + copyIdeCodeCompletionToClipboard, + exportIdeChatCSV, + exportIdeChatJSON, + copyIdeChatToClipboard, + exportDotcomChatCSV, + exportDotcomChatJSON, + copyDotcomChatToClipboard, + exportDotcomPRCSV, + exportDotcomPRJSON, + copyDotcomPRToClipboard }; } }); diff --git a/app/components/BreakdownComponent.vue b/app/components/BreakdownComponent.vue index 0889a043..7fd4e851 100644 --- a/app/components/BreakdownComponent.vue +++ b/app/components/BreakdownComponent.vue @@ -57,8 +57,27 @@
-

{{ breakdownDisplayNamePlural }} Breakdown

-
+
+

{{ breakdownDisplayNamePlural }} Breakdown

+ + + + + Export as CSV + + + Export as JSON + + + Copy to Clipboard + + + +