mirror of
https://gitee.com/wanwujie/sub2api
synced 2026-04-03 06:52:13 +08:00
feat: implement last 24 hours date range preset and update filters in UsageView
This commit is contained in:
@@ -139,17 +139,6 @@
|
|||||||
<Select v-model="filters.group_id" :options="groupOptions" searchable @change="emitChange" />
|
<Select v-model="filters.group_id" :options="groupOptions" searchable @change="emitChange" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Date Range Filter -->
|
|
||||||
<div class="w-full sm:w-auto [&_.date-picker-trigger]:w-full">
|
|
||||||
<label class="input-label">{{ t('usage.timeRange') }}</label>
|
|
||||||
<DateRangePicker
|
|
||||||
:start-date="startDate"
|
|
||||||
:end-date="endDate"
|
|
||||||
@update:startDate="updateStartDate"
|
|
||||||
@update:endDate="updateEndDate"
|
|
||||||
@change="emitChange"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Right: actions -->
|
<!-- Right: actions -->
|
||||||
@@ -177,7 +166,6 @@ import { ref, onMounted, onUnmounted, toRef, watch } from 'vue'
|
|||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { adminAPI } from '@/api/admin'
|
import { adminAPI } from '@/api/admin'
|
||||||
import Select, { type SelectOption } from '@/components/common/Select.vue'
|
import Select, { type SelectOption } from '@/components/common/Select.vue'
|
||||||
import DateRangePicker from '@/components/common/DateRangePicker.vue'
|
|
||||||
import type { SimpleApiKey, SimpleUser } from '@/api/admin/usage'
|
import type { SimpleApiKey, SimpleUser } from '@/api/admin/usage'
|
||||||
|
|
||||||
type ModelValue = Record<string, any>
|
type ModelValue = Record<string, any>
|
||||||
@@ -195,8 +183,6 @@ const props = withDefaults(defineProps<Props>(), {
|
|||||||
})
|
})
|
||||||
const emit = defineEmits([
|
const emit = defineEmits([
|
||||||
'update:modelValue',
|
'update:modelValue',
|
||||||
'update:startDate',
|
|
||||||
'update:endDate',
|
|
||||||
'change',
|
'change',
|
||||||
'refresh',
|
'refresh',
|
||||||
'reset',
|
'reset',
|
||||||
@@ -248,16 +234,6 @@ const billingTypeOptions = ref<SelectOption[]>([
|
|||||||
|
|
||||||
const emitChange = () => emit('change')
|
const emitChange = () => emit('change')
|
||||||
|
|
||||||
const updateStartDate = (value: string) => {
|
|
||||||
emit('update:startDate', value)
|
|
||||||
filters.value.start_date = value
|
|
||||||
}
|
|
||||||
|
|
||||||
const updateEndDate = (value: string) => {
|
|
||||||
emit('update:endDate', value)
|
|
||||||
filters.value.end_date = value
|
|
||||||
}
|
|
||||||
|
|
||||||
const debounceUserSearch = () => {
|
const debounceUserSearch = () => {
|
||||||
if (userSearchTimeout) clearTimeout(userSearchTimeout)
|
if (userSearchTimeout) clearTimeout(userSearchTimeout)
|
||||||
userSearchTimeout = setTimeout(async () => {
|
userSearchTimeout = setTimeout(async () => {
|
||||||
@@ -441,7 +417,11 @@ onMounted(async () => {
|
|||||||
groupOptions.value.push(...gs.items.map((g: any) => ({ value: g.id, label: g.name })))
|
groupOptions.value.push(...gs.items.map((g: any) => ({ value: g.id, label: g.name })))
|
||||||
|
|
||||||
const uniqueModels = new Set<string>()
|
const uniqueModels = new Set<string>()
|
||||||
ms.models?.forEach((s: any) => s.model && uniqueModels.add(s.model))
|
ms.models?.forEach((s: any) => {
|
||||||
|
if (s.model) {
|
||||||
|
uniqueModels.add(s.model)
|
||||||
|
}
|
||||||
|
})
|
||||||
modelOptions.value.push(
|
modelOptions.value.push(
|
||||||
...Array.from(uniqueModels)
|
...Array.from(uniqueModels)
|
||||||
.sort()
|
.sort()
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ const isOpen = ref(false)
|
|||||||
const containerRef = ref<HTMLElement | null>(null)
|
const containerRef = ref<HTMLElement | null>(null)
|
||||||
const localStartDate = ref(props.startDate)
|
const localStartDate = ref(props.startDate)
|
||||||
const localEndDate = ref(props.endDate)
|
const localEndDate = ref(props.endDate)
|
||||||
const activePreset = ref<string | null>('7days')
|
const activePreset = ref<string | null>('last24Hours')
|
||||||
|
|
||||||
const today = computed(() => {
|
const today = computed(() => {
|
||||||
// Use local timezone to avoid UTC timezone issues
|
// Use local timezone to avoid UTC timezone issues
|
||||||
@@ -152,6 +152,18 @@ const presets: DatePreset[] = [
|
|||||||
return { start: yesterday, end: yesterday }
|
return { start: yesterday, end: yesterday }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
labelKey: 'dates.last24Hours',
|
||||||
|
value: 'last24Hours',
|
||||||
|
getRange: () => {
|
||||||
|
const end = new Date()
|
||||||
|
const start = new Date(end.getTime() - 24 * 60 * 60 * 1000)
|
||||||
|
return {
|
||||||
|
start: formatDateToString(start),
|
||||||
|
end: formatDateToString(end)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
labelKey: 'dates.last7Days',
|
labelKey: 'dates.last7Days',
|
||||||
value: '7days',
|
value: '7days',
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
import { describe, expect, it, vi } from 'vitest'
|
||||||
|
import { mount } from '@vue/test-utils'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
import DateRangePicker from '../DateRangePicker.vue'
|
||||||
|
|
||||||
|
const messages: Record<string, string> = {
|
||||||
|
'dates.today': 'Today',
|
||||||
|
'dates.yesterday': 'Yesterday',
|
||||||
|
'dates.last24Hours': 'Last 24 Hours',
|
||||||
|
'dates.last7Days': 'Last 7 Days',
|
||||||
|
'dates.last14Days': 'Last 14 Days',
|
||||||
|
'dates.last30Days': 'Last 30 Days',
|
||||||
|
'dates.thisMonth': 'This Month',
|
||||||
|
'dates.lastMonth': 'Last Month',
|
||||||
|
'dates.startDate': 'Start Date',
|
||||||
|
'dates.endDate': 'End Date',
|
||||||
|
'dates.apply': 'Apply',
|
||||||
|
'dates.selectDateRange': 'Select date range'
|
||||||
|
}
|
||||||
|
|
||||||
|
vi.mock('vue-i18n', () => ({
|
||||||
|
useI18n: () => ({
|
||||||
|
t: (key: string) => messages[key] ?? key,
|
||||||
|
locale: ref('en')
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
|
||||||
|
const formatLocalDate = (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}`
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('DateRangePicker', () => {
|
||||||
|
it('uses last 24 hours as the default recognized preset', () => {
|
||||||
|
const now = new Date()
|
||||||
|
const yesterday = new Date(now.getTime() - 24 * 60 * 60 * 1000)
|
||||||
|
|
||||||
|
const wrapper = mount(DateRangePicker, {
|
||||||
|
props: {
|
||||||
|
startDate: formatLocalDate(yesterday),
|
||||||
|
endDate: formatLocalDate(now)
|
||||||
|
},
|
||||||
|
global: {
|
||||||
|
stubs: {
|
||||||
|
Icon: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(wrapper.text()).toContain('Last 24 Hours')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('emits range updates with last24Hours preset when applied', async () => {
|
||||||
|
const now = new Date()
|
||||||
|
const today = formatLocalDate(now)
|
||||||
|
|
||||||
|
const wrapper = mount(DateRangePicker, {
|
||||||
|
props: {
|
||||||
|
startDate: today,
|
||||||
|
endDate: today
|
||||||
|
},
|
||||||
|
global: {
|
||||||
|
stubs: {
|
||||||
|
Icon: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
await wrapper.find('.date-picker-trigger').trigger('click')
|
||||||
|
const presetButton = wrapper.findAll('.date-picker-preset').find((node) =>
|
||||||
|
node.text().includes('Last 24 Hours')
|
||||||
|
)
|
||||||
|
expect(presetButton).toBeDefined()
|
||||||
|
|
||||||
|
await presetButton!.trigger('click')
|
||||||
|
await wrapper.find('.date-picker-apply').trigger('click')
|
||||||
|
|
||||||
|
const nowAfterClick = new Date()
|
||||||
|
const yesterdayAfterClick = new Date(nowAfterClick.getTime() - 24 * 60 * 60 * 1000)
|
||||||
|
const expectedStart = formatLocalDate(yesterdayAfterClick)
|
||||||
|
const expectedEnd = formatLocalDate(nowAfterClick)
|
||||||
|
|
||||||
|
expect(wrapper.emitted('update:startDate')?.[0]).toEqual([expectedStart])
|
||||||
|
expect(wrapper.emitted('update:endDate')?.[0]).toEqual([expectedEnd])
|
||||||
|
expect(wrapper.emitted('change')?.[0]).toEqual([
|
||||||
|
{
|
||||||
|
startDate: expectedStart,
|
||||||
|
endDate: expectedEnd,
|
||||||
|
preset: 'last24Hours'
|
||||||
|
}
|
||||||
|
])
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -920,6 +920,7 @@ export default {
|
|||||||
lastWeek: 'Last Week',
|
lastWeek: 'Last Week',
|
||||||
thisMonth: 'This Month',
|
thisMonth: 'This Month',
|
||||||
lastMonth: 'Last Month',
|
lastMonth: 'Last Month',
|
||||||
|
last24Hours: 'Last 24 Hours',
|
||||||
last7Days: 'Last 7 Days',
|
last7Days: 'Last 7 Days',
|
||||||
last14Days: 'Last 14 Days',
|
last14Days: 'Last 14 Days',
|
||||||
last30Days: 'Last 30 Days',
|
last30Days: 'Last 30 Days',
|
||||||
|
|||||||
@@ -925,6 +925,7 @@ export default {
|
|||||||
lastWeek: '上周',
|
lastWeek: '上周',
|
||||||
thisMonth: '本月',
|
thisMonth: '本月',
|
||||||
lastMonth: '上月',
|
lastMonth: '上月',
|
||||||
|
last24Hours: '近24小时',
|
||||||
last7Days: '近 7 天',
|
last7Days: '近 7 天',
|
||||||
last14Days: '近 14 天',
|
last14Days: '近 14 天',
|
||||||
last30Days: '近 30 天',
|
last30Days: '近 30 天',
|
||||||
|
|||||||
@@ -5,10 +5,20 @@
|
|||||||
<!-- Charts Section -->
|
<!-- Charts Section -->
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<div class="card p-4">
|
<div class="card p-4">
|
||||||
<div class="flex items-center gap-4">
|
<div class="flex flex-wrap items-center gap-4">
|
||||||
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">{{ t('admin.dashboard.granularity') }}:</span>
|
<div class="flex items-center gap-2">
|
||||||
<div class="w-28">
|
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">{{ t('admin.dashboard.timeRange') }}:</span>
|
||||||
<Select v-model="granularity" :options="granularityOptions" @change="loadChartData" />
|
<DateRangePicker
|
||||||
|
v-model:start-date="startDate"
|
||||||
|
v-model:end-date="endDate"
|
||||||
|
@change="onDateRangeChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="ml-auto flex items-center gap-2">
|
||||||
|
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">{{ t('admin.dashboard.granularity') }}:</span>
|
||||||
|
<div class="w-28">
|
||||||
|
<Select v-model="granularity" :options="granularityOptions" @change="loadChartData" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -41,7 +51,7 @@
|
|||||||
<TokenUsageTrend :trend-data="trendData" :loading="chartsLoading" />
|
<TokenUsageTrend :trend-data="trendData" :loading="chartsLoading" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<UsageFilters v-model="filters" v-model:startDate="startDate" v-model:endDate="endDate" :exporting="exporting" @change="applyFilters" @refresh="refreshData" @reset="resetFilters" @cleanup="openCleanupDialog" @export="exportToExcel">
|
<UsageFilters v-model="filters" :start-date="startDate" :end-date="endDate" :exporting="exporting" @change="applyFilters" @refresh="refreshData" @reset="resetFilters" @cleanup="openCleanupDialog" @export="exportToExcel">
|
||||||
<template #after-reset>
|
<template #after-reset>
|
||||||
<div class="relative" ref="columnDropdownRef">
|
<div class="relative" ref="columnDropdownRef">
|
||||||
<button
|
<button
|
||||||
@@ -106,7 +116,7 @@ import { useRoute } from 'vue-router'
|
|||||||
import { useAppStore } from '@/stores/app'; import { adminAPI } from '@/api/admin'; import { adminUsageAPI } from '@/api/admin/usage'
|
import { useAppStore } from '@/stores/app'; import { adminAPI } from '@/api/admin'; import { adminUsageAPI } from '@/api/admin/usage'
|
||||||
import { formatReasoningEffort } from '@/utils/format'
|
import { formatReasoningEffort } from '@/utils/format'
|
||||||
import { resolveUsageRequestType, requestTypeToLegacyStream } from '@/utils/usageRequestType'
|
import { resolveUsageRequestType, requestTypeToLegacyStream } from '@/utils/usageRequestType'
|
||||||
import AppLayout from '@/components/layout/AppLayout.vue'; import Pagination from '@/components/common/Pagination.vue'; import Select from '@/components/common/Select.vue'
|
import AppLayout from '@/components/layout/AppLayout.vue'; import Pagination from '@/components/common/Pagination.vue'; import Select from '@/components/common/Select.vue'; import DateRangePicker from '@/components/common/DateRangePicker.vue'
|
||||||
import UsageStatsCards from '@/components/admin/usage/UsageStatsCards.vue'; import UsageFilters from '@/components/admin/usage/UsageFilters.vue'
|
import UsageStatsCards from '@/components/admin/usage/UsageStatsCards.vue'; import UsageFilters from '@/components/admin/usage/UsageFilters.vue'
|
||||||
import UsageTable from '@/components/admin/usage/UsageTable.vue'; import UsageExportProgress from '@/components/admin/usage/UsageExportProgress.vue'
|
import UsageTable from '@/components/admin/usage/UsageTable.vue'; import UsageExportProgress from '@/components/admin/usage/UsageExportProgress.vue'
|
||||||
import UsageCleanupDialog from '@/components/admin/usage/UsageCleanupDialog.vue'
|
import UsageCleanupDialog from '@/components/admin/usage/UsageCleanupDialog.vue'
|
||||||
@@ -158,9 +168,22 @@ const formatLD = (d: Date) => {
|
|||||||
const day = String(d.getDate()).padStart(2, '0')
|
const day = String(d.getDate()).padStart(2, '0')
|
||||||
return `${year}-${month}-${day}`
|
return `${year}-${month}-${day}`
|
||||||
}
|
}
|
||||||
const getTodayLocalDate = () => formatLD(new Date())
|
const getLast24HoursRangeDates = (): { start: string; end: string } => {
|
||||||
const getGranularityForRange = (start: string, end: string): 'day' | 'hour' => start === end ? 'hour' : 'day'
|
const end = new Date()
|
||||||
const startDate = ref(getTodayLocalDate()); const endDate = ref(getTodayLocalDate())
|
const start = new Date(end.getTime() - 24 * 60 * 60 * 1000)
|
||||||
|
return {
|
||||||
|
start: formatLD(start),
|
||||||
|
end: formatLD(end)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const getGranularityForRange = (start: string, end: string): 'day' | 'hour' => {
|
||||||
|
const startTime = new Date(`${start}T00:00:00`).getTime()
|
||||||
|
const endTime = new Date(`${end}T00:00:00`).getTime()
|
||||||
|
const daysDiff = Math.ceil((endTime - startTime) / (1000 * 60 * 60 * 24))
|
||||||
|
return daysDiff <= 1 ? 'hour' : 'day'
|
||||||
|
}
|
||||||
|
const defaultRange = getLast24HoursRangeDates()
|
||||||
|
const startDate = ref(defaultRange.start); const endDate = ref(defaultRange.end)
|
||||||
const filters = ref<AdminUsageQueryParams>({ user_id: undefined, model: undefined, group_id: undefined, request_type: undefined, billing_type: null, start_date: startDate.value, end_date: endDate.value })
|
const filters = ref<AdminUsageQueryParams>({ user_id: undefined, model: undefined, group_id: undefined, request_type: undefined, billing_type: null, start_date: startDate.value, end_date: endDate.value })
|
||||||
const pagination = reactive({ page: 1, page_size: 20, total: 0 })
|
const pagination = reactive({ page: 1, page_size: 20, total: 0 })
|
||||||
|
|
||||||
@@ -197,6 +220,18 @@ const applyRouteQueryFilters = () => {
|
|||||||
granularity.value = getGranularityForRange(startDate.value, endDate.value)
|
granularity.value = getGranularityForRange(startDate.value, endDate.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const onDateRangeChange = (range: { startDate: string; endDate: string; preset: string | null }) => {
|
||||||
|
startDate.value = range.startDate
|
||||||
|
endDate.value = range.endDate
|
||||||
|
filters.value = {
|
||||||
|
...filters.value,
|
||||||
|
start_date: range.startDate,
|
||||||
|
end_date: range.endDate
|
||||||
|
}
|
||||||
|
granularity.value = getGranularityForRange(range.startDate, range.endDate)
|
||||||
|
applyFilters()
|
||||||
|
}
|
||||||
|
|
||||||
const loadLogs = async () => {
|
const loadLogs = async () => {
|
||||||
abortController?.abort(); const c = new AbortController(); abortController = c; loading.value = true
|
abortController?.abort(); const c = new AbortController(); abortController = c; loading.value = true
|
||||||
try {
|
try {
|
||||||
@@ -260,7 +295,14 @@ const loadChartData = async () => {
|
|||||||
}
|
}
|
||||||
const applyFilters = () => { pagination.page = 1; loadLogs(); loadStats(); loadChartData() }
|
const applyFilters = () => { pagination.page = 1; loadLogs(); loadStats(); loadChartData() }
|
||||||
const refreshData = () => { loadLogs(); loadStats(); loadChartData() }
|
const refreshData = () => { loadLogs(); loadStats(); loadChartData() }
|
||||||
const resetFilters = () => { startDate.value = getTodayLocalDate(); endDate.value = getTodayLocalDate(); filters.value = { start_date: startDate.value, end_date: endDate.value, request_type: undefined, billing_type: null }; granularity.value = getGranularityForRange(startDate.value, endDate.value); applyFilters() }
|
const resetFilters = () => {
|
||||||
|
const range = getLast24HoursRangeDates()
|
||||||
|
startDate.value = range.start
|
||||||
|
endDate.value = range.end
|
||||||
|
filters.value = { start_date: startDate.value, end_date: endDate.value, request_type: undefined, billing_type: null }
|
||||||
|
granularity.value = getGranularityForRange(startDate.value, endDate.value)
|
||||||
|
applyFilters()
|
||||||
|
}
|
||||||
const handlePageChange = (p: number) => { pagination.page = p; loadLogs() }
|
const handlePageChange = (p: number) => { pagination.page = p; loadLogs() }
|
||||||
const handlePageSizeChange = (s: number) => { pagination.page_size = s; pagination.page = 1; loadLogs() }
|
const handlePageSizeChange = (s: number) => { pagination.page_size = s; pagination.page = 1; loadLogs() }
|
||||||
const cancelExport = () => exportAbortController?.abort()
|
const cancelExport = () => exportAbortController?.abort()
|
||||||
|
|||||||
@@ -19,11 +19,19 @@ const { list, getStats, getSnapshotV2, getById } = vi.hoisted(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const messages: Record<string, string> = {
|
const messages: Record<string, string> = {
|
||||||
|
'admin.dashboard.timeRange': 'Time Range',
|
||||||
'admin.dashboard.day': 'Day',
|
'admin.dashboard.day': 'Day',
|
||||||
'admin.dashboard.hour': 'Hour',
|
'admin.dashboard.hour': 'Hour',
|
||||||
'admin.usage.failedToLoadUser': 'Failed to load user',
|
'admin.usage.failedToLoadUser': 'Failed to load user',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const formatLocalDate = (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}`
|
||||||
|
}
|
||||||
|
|
||||||
vi.mock('@/api/admin', () => ({
|
vi.mock('@/api/admin', () => ({
|
||||||
adminAPI: {
|
adminAPI: {
|
||||||
usage: {
|
usage: {
|
||||||
@@ -68,6 +76,12 @@ vi.mock('vue-i18n', async () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
vi.mock('vue-router', () => ({
|
||||||
|
useRoute: () => ({
|
||||||
|
query: {}
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
|
||||||
const AppLayoutStub = { template: '<div><slot /></div>' }
|
const AppLayoutStub = { template: '<div><slot /></div>' }
|
||||||
const UsageFiltersStub = { template: '<div><slot name="after-reset" /></div>' }
|
const UsageFiltersStub = { template: '<div><slot name="after-reset" /></div>' }
|
||||||
const ModelDistributionChartStub = {
|
const ModelDistributionChartStub = {
|
||||||
@@ -138,6 +152,7 @@ describe('admin UsageView distribution metric toggles', () => {
|
|||||||
UserBalanceHistoryModal: true,
|
UserBalanceHistoryModal: true,
|
||||||
Pagination: true,
|
Pagination: true,
|
||||||
Select: true,
|
Select: true,
|
||||||
|
DateRangePicker: true,
|
||||||
Icon: true,
|
Icon: true,
|
||||||
TokenUsageTrend: true,
|
TokenUsageTrend: true,
|
||||||
ModelDistributionChart: ModelDistributionChartStub,
|
ModelDistributionChart: ModelDistributionChartStub,
|
||||||
@@ -150,6 +165,13 @@ describe('admin UsageView distribution metric toggles', () => {
|
|||||||
await flushPromises()
|
await flushPromises()
|
||||||
|
|
||||||
expect(getSnapshotV2).toHaveBeenCalledTimes(1)
|
expect(getSnapshotV2).toHaveBeenCalledTimes(1)
|
||||||
|
const now = new Date()
|
||||||
|
const yesterday = new Date(now.getTime() - 24 * 60 * 60 * 1000)
|
||||||
|
expect(getSnapshotV2).toHaveBeenCalledWith(expect.objectContaining({
|
||||||
|
start_date: formatLocalDate(yesterday),
|
||||||
|
end_date: formatLocalDate(now),
|
||||||
|
granularity: 'hour'
|
||||||
|
}))
|
||||||
|
|
||||||
const modelChart = wrapper.find('[data-test="model-chart"]')
|
const modelChart = wrapper.find('[data-test="model-chart"]')
|
||||||
const groupChart = wrapper.find('[data-test="group-chart"]')
|
const groupChart = wrapper.find('[data-test="group-chart"]')
|
||||||
|
|||||||
Reference in New Issue
Block a user