Skip to content

feat: add account usage statistics view#2227

Open
specialpointcentral wants to merge 2 commits into
Wei-Shaw:mainfrom
specialpointcentral:feature/account-usage-stats
Open

feat: add account usage statistics view#2227
specialpointcentral wants to merge 2 commits into
Wei-Shaw:mainfrom
specialpointcentral:feature/account-usage-stats

Conversation

@specialpointcentral
Copy link
Copy Markdown

Summary

  • Add an admin account usage statistics page with filters, sortable account rows, pagination, and persisted auto-refresh settings.
  • Add account detail statistics with date-range usage summaries and user activity tables.
  • Add backend account stats and recent/range user endpoints, including account billing and user charge totals.

Notes

  • The recent active users table is shown before the date-range active users table.
  • Recent active users show current requests based on real-time user concurrency, while the date-range active users table omits the current requests column.
  • Auto-refresh updates the table and open detail dialog silently to avoid full-page loading flicker.

Tests

  • Not run after branch split; changes were previously validated with frontend typecheck and Docker build on the combined branch.

Copilot AI review requested due to automatic review settings May 6, 2026 11:36
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a new admin-facing “Account Statistics” page to the frontend and introduces backend support for listing account users (recent and date-range) to power account detail usage breakdowns.

Changes:

  • Added AccountStatsView with filters (date range/platform/group), sortable/paginated account rows, and an auto-refresh control with persisted settings.
  • Added an account detail modal showing usage summary plus “recent users” and “range users” tables (including current concurrency for recent users).
  • Added backend /admin/accounts/:id/recent-users endpoint and repository/service support for fetching recent and date-range account users.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
frontend/src/views/admin/AccountStatsView.vue New admin UI for account usage stats with auto-refresh and account detail modal.
frontend/src/router/index.ts Registers the new /admin/account-stats route.
frontend/src/i18n/locales/en.ts Adds nav + page translations for Account Stats (EN).
frontend/src/i18n/locales/zh.ts Adds nav + page translations for Account Stats (ZH).
frontend/src/components/layout/AppSidebar.vue Adds “Account Stats” entry to the admin sidebar.
frontend/src/api/admin/accounts.ts Adds getRecentUsers API + RecentAccountUser type.
backend/internal/service/account_usage_service.go Adds service/repo interfaces + RecentAccountUser DTO for account user stats.
backend/internal/server/routes/admin.go Registers the new admin account route /recent-users.
backend/internal/server/api_contract_test.go Updates repo stub to satisfy the expanded repository interface.
backend/internal/repository/usage_log_repo.go Implements SQL queries for recent users and time-range users per account.
backend/internal/handler/admin/account_handler.go Adds GetRecentUsers handler with optional date-range behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +448 to +473
// Load data
async function loadData(options?: { silent?: boolean }) {
if (!options?.silent) loading.value = true
try {
// Build filters
const filters: Record<string, string> = {}
if (platformFilter.value) filters.platform = String(platformFilter.value)
if (groupFilter.value) filters.group = String(groupFilter.value)

const result = await adminAPI.accounts.list(page.value, pageSize.value, filters)
accounts.value = result.items || []
totalAccounts.value = result.total || 0

// Load concurrency data
await loadConcurrency()

// Load stats for each account
await loadAccountStats()

applyStatsToAccounts()
} catch (err) {
console.error('Failed to load account stats:', err)
} finally {
if (!options?.silent) loading.value = false
}
}
Comment on lines +508 to +522
async function loadAccountStats() {
const stats: Record<number, any> = {}
const promises = accounts.value.map(async (account) => {
try {
const result = await adminAPI.usage.getStats({
account_id: account.id,
start_date: startDate.value,
end_date: endDate.value
})
stats[account.id] = result
} catch {
// Ignore individual failures
}
})
await Promise.all(promises)
Comment on lines +263 to +294
// State
const loading = ref(false)
const accounts = ref<any[]>([])
const totalAccounts = ref(0)
const page = ref(1)
const pageSize = ref(getPersistedPageSize())
const platformFilter = ref<string | number | boolean | null>('')
const groupFilter = ref<string | number | boolean | null>('')
const groups = ref<AdminGroup[]>([])
const accountStats = ref<Record<number, any>>({})

// Time range (default: last 24 hours via DateRangePicker)
const formatDateToString = (date: Date): string => {
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
return `${year}-${month}-${day}`
}
const now = new Date()
const yesterday = new Date(now.getTime() - 24 * 60 * 60 * 1000)
const startDate = ref(formatDateToString(yesterday))
const endDate = ref(formatDateToString(now))

// Detail modal
const showDetail = ref(false)
const selectedAccount = ref<any>(null)
const detailStats = ref<any>(null)
const rangeUsers = ref<RecentAccountUser[]>([])
const recentUsers = ref<RecentAccountUser[]>([])
const detailUsersLoading = ref(false)
const userConcurrencyByID = ref<Record<number, number>>({})

Comment on lines +645 to +657
/**
* Get recent users of an account (last 5 minutes)
* @param id - Account ID
* @returns List of recent users
*/
export async function getRecentUsers(id: number, params?: {
start_date?: string
end_date?: string
timezone?: string
}): Promise<{ users: RecentAccountUser[] }> {
const { data } = await apiClient.get<{ users: RecentAccountUser[] }>(`/admin/accounts/${id}/recent-users`, { params })
return data
}
Comment on lines +1670 to +1711
// GetRecentUsers handles getting recent users of an account
// GET /api/v1/admin/accounts/:id/recent-users
func (h *AccountHandler) GetRecentUsers(c *gin.Context) {
accountID, err := strconv.ParseInt(c.Param("id"), 10, 64)
if err != nil {
response.BadRequest(c, "Invalid account ID")
return
}

var users []service.RecentAccountUser
startDateStr := strings.TrimSpace(c.Query("start_date"))
endDateStr := strings.TrimSpace(c.Query("end_date"))
if startDateStr != "" || endDateStr != "" {
if startDateStr == "" || endDateStr == "" {
response.BadRequest(c, "start_date and end_date are required together")
return
}
userTZ := c.Query("timezone")
startTime, parseErr := timezone.ParseInUserLocation("2006-01-02", startDateStr, userTZ)
if parseErr != nil {
response.BadRequest(c, "Invalid start_date format, use YYYY-MM-DD")
return
}
endTime, parseErr := timezone.ParseInUserLocation("2006-01-02", endDateStr, userTZ)
if parseErr != nil {
response.BadRequest(c, "Invalid end_date format, use YYYY-MM-DD")
return
}
users, err = h.accountUsageService.GetAccountUsersByTimeRange(c.Request.Context(), accountID, startTime, endTime.AddDate(0, 0, 1))
} else {
users, err = h.accountUsageService.GetRecentAccountUsers(c.Request.Context(), accountID, 5)
}
if err != nil {
response.ErrorFrom(c, err)
return
}
if users == nil {
users = []service.RecentAccountUser{}
}

response.Success(c, gin.H{"users": users})
}
@specialpointcentral specialpointcentral force-pushed the feature/account-usage-stats branch 2 times, most recently from 90c8ca3 to 6dcd32d Compare May 7, 2026 05:03
@specialpointcentral specialpointcentral force-pushed the feature/account-usage-stats branch 3 times, most recently from f4f6e9a to 45bd0bd Compare May 7, 2026 17:09
@specialpointcentral specialpointcentral force-pushed the feature/account-usage-stats branch from 45bd0bd to 7a9ba78 Compare May 7, 2026 17:37
@gebdalaoli-arch
Copy link
Copy Markdown

功能看起来不错

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants