@byline/admin 4.15.0 → 4.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/abilities.js +2 -0
- package/dist/fields/relation/relation-field.js +3 -2
- package/dist/fields/relation/relation-many-field.js +3 -2
- package/dist/fields/relation/relation-picker.d.ts +2 -2
- package/dist/fields/relation/relation-summary.d.ts +2 -2
- package/dist/forms/document-actions.js +30 -25
- package/dist/forms/form-renderer.d.ts +17 -3
- package/dist/forms/form-renderer.js +18 -9
- package/dist/forms/tree-placement-widget.js +3 -2
- package/dist/forms/use-form-layout.d.ts +3 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/modules/analytics/abilities.d.ts +14 -0
- package/dist/modules/analytics/abilities.js +21 -0
- package/dist/modules/analytics/components/dashboard.d.ts +20 -0
- package/dist/modules/analytics/components/dashboard.js +308 -0
- package/dist/modules/analytics/components/dashboard.module.js +29 -0
- package/dist/modules/analytics/components/dashboard.test.node.d.ts +8 -0
- package/dist/modules/analytics/components/dashboard_module.css +268 -0
- package/dist/modules/analytics/components/timeseries.d.ts +59 -0
- package/dist/modules/analytics/components/timeseries.js +253 -0
- package/dist/modules/analytics/components/timeseries.module.js +17 -0
- package/dist/modules/analytics/components/timeseries_module.css +126 -0
- package/dist/modules/analytics/index.d.ts +9 -0
- package/dist/modules/analytics/index.js +1 -0
- package/dist/modules/analytics/types.d.ts +20 -0
- package/dist/modules/analytics/types.js +1 -0
- package/package.json +21 -11
- package/src/abilities.ts +2 -0
- package/src/fields/relation/relation-field.tsx +5 -3
- package/src/fields/relation/relation-many-field.tsx +5 -3
- package/src/fields/relation/relation-picker.tsx +2 -2
- package/src/fields/relation/relation-summary.tsx +2 -2
- package/src/forms/document-actions.test.tsx +157 -0
- package/src/forms/document-actions.tsx +93 -74
- package/src/forms/form-renderer-capabilities.test.tsx +155 -0
- package/src/forms/form-renderer-submit.test.tsx +194 -0
- package/src/forms/form-renderer.tsx +59 -25
- package/src/forms/tree-placement-widget.tsx +3 -2
- package/src/forms/use-form-layout.ts +3 -3
- package/src/index.ts +1 -0
- package/src/modules/analytics/abilities.ts +33 -0
- package/src/modules/analytics/components/dashboard.module.css +330 -0
- package/src/modules/analytics/components/dashboard.test.node.ts +193 -0
- package/src/modules/analytics/components/dashboard.tsx +371 -0
- package/src/modules/analytics/components/timeseries.module.css +151 -0
- package/src/modules/analytics/components/timeseries.tsx +332 -0
- package/src/modules/analytics/index.ts +14 -0
- package/src/modules/analytics/types.ts +31 -0
- package/dist/forms/__probe-nested.test.node.d.ts +0 -1
- package/dist/forms/__probe-two.test.node.d.ts +0 -1
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* This Source Code is subject to the terms of the Mozilla Public
|
|
5
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
6
|
+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
7
|
+
*
|
|
8
|
+
* Copyright (c) Infonomic Company Limited
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The analytics timeseries — one column per explicit UTC reporting bucket.
|
|
13
|
+
*
|
|
14
|
+
* A column rather than a continuous line is a claim about the data, not a
|
|
15
|
+
* style: the rows behind this chart are sums of daily aggregates and visitor
|
|
16
|
+
* hashes rotate at UTC midnight, so nothing is measured between two points.
|
|
17
|
+
* The dashboard selects the granularity and passes it into this renderer;
|
|
18
|
+
* the chart never guesses from the number of rows.
|
|
19
|
+
*/
|
|
20
|
+
import type React from 'react'
|
|
21
|
+
import { useMemo, useRef, useState } from 'react'
|
|
22
|
+
|
|
23
|
+
import type { AnalyticsSummaryDay } from '@byline/analytics'
|
|
24
|
+
import type { AnalyticsDashboardPeriod } from '@byline/analytics/config'
|
|
25
|
+
import { useTranslation } from '@byline/i18n/react'
|
|
26
|
+
import cx from 'clsx'
|
|
27
|
+
|
|
28
|
+
import styles from './timeseries.module.css'
|
|
29
|
+
|
|
30
|
+
/** Drawn in a fixed user-space box and stretched to the container width. */
|
|
31
|
+
const VIEWBOX_WIDTH = 900
|
|
32
|
+
const VIEWBOX_HEIGHT = 180
|
|
33
|
+
const COLUMN_GAP_RATIO = 0.38
|
|
34
|
+
const MAX_COLUMN_WIDTH = 26
|
|
35
|
+
|
|
36
|
+
export type AnalyticsChartGranularity = 'day' | 'seven-day' | 'month'
|
|
37
|
+
|
|
38
|
+
export interface AnalyticsChartBucket {
|
|
39
|
+
from: string
|
|
40
|
+
to: string
|
|
41
|
+
granularity: AnalyticsChartGranularity
|
|
42
|
+
/** Actual number of daily rows, including partial first or last buckets. */
|
|
43
|
+
dayCount: number
|
|
44
|
+
views: number
|
|
45
|
+
/** Sum of the bucket's daily-unique visitor values. */
|
|
46
|
+
visitors: number
|
|
47
|
+
downloads: number
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface AnalyticsColumn {
|
|
51
|
+
index: number
|
|
52
|
+
/** Full-height mark: that bucket's page views. */
|
|
53
|
+
x: number
|
|
54
|
+
width: number
|
|
55
|
+
y: number
|
|
56
|
+
height: number
|
|
57
|
+
/** Inset mark: summed daily uniques, always no greater than views. */
|
|
58
|
+
insetX: number
|
|
59
|
+
insetWidth: number
|
|
60
|
+
insetY: number
|
|
61
|
+
insetHeight: number
|
|
62
|
+
/** Full-height transparent target, so narrow columns stay easy to hit. */
|
|
63
|
+
hitX: number
|
|
64
|
+
hitWidth: number
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Resolve chart density outside the renderer so the chosen width is explicit. */
|
|
68
|
+
export function resolveAnalyticsChartGranularity(
|
|
69
|
+
period: AnalyticsDashboardPeriod,
|
|
70
|
+
dayCount: number
|
|
71
|
+
): AnalyticsChartGranularity {
|
|
72
|
+
if (typeof period === 'number' || (period === 'all' && dayCount <= 90)) return 'day'
|
|
73
|
+
if (period === 'ytd' || dayCount <= 732) return 'seven-day'
|
|
74
|
+
return 'month'
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Combine complete daily query rows using the caller-selected granularity. */
|
|
78
|
+
export function bucketAnalyticsTimeseries(
|
|
79
|
+
days: readonly AnalyticsSummaryDay[],
|
|
80
|
+
granularity: AnalyticsChartGranularity
|
|
81
|
+
): readonly AnalyticsChartBucket[] {
|
|
82
|
+
if (granularity === 'day') {
|
|
83
|
+
return days.map((day) => ({
|
|
84
|
+
from: day.day,
|
|
85
|
+
to: day.day,
|
|
86
|
+
granularity,
|
|
87
|
+
dayCount: 1,
|
|
88
|
+
views: day.views,
|
|
89
|
+
visitors: day.visitors,
|
|
90
|
+
downloads: day.downloads,
|
|
91
|
+
}))
|
|
92
|
+
}
|
|
93
|
+
if (granularity === 'seven-day') return bucketBySevenDays(days)
|
|
94
|
+
return bucketByUtcMonth(days)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Project reporting buckets into column geometry. */
|
|
98
|
+
export function buildAnalyticsColumns(
|
|
99
|
+
buckets: readonly Pick<AnalyticsChartBucket, 'views' | 'visitors'>[]
|
|
100
|
+
): readonly AnalyticsColumn[] {
|
|
101
|
+
if (buckets.length === 0) return []
|
|
102
|
+
const ceiling = Math.max(1, ...buckets.map((bucket) => bucket.views))
|
|
103
|
+
const step = VIEWBOX_WIDTH / buckets.length
|
|
104
|
+
const width = Math.max(2, Math.min(step * (1 - COLUMN_GAP_RATIO), MAX_COLUMN_WIDTH))
|
|
105
|
+
|
|
106
|
+
return buckets.map((bucket, index) => {
|
|
107
|
+
const centre = index * step + step / 2
|
|
108
|
+
const height = (bucket.views / ceiling) * VIEWBOX_HEIGHT
|
|
109
|
+
const insetHeight = (bucket.visitors / ceiling) * VIEWBOX_HEIGHT
|
|
110
|
+
const insetWidth = Math.max(1, width / 2)
|
|
111
|
+
return {
|
|
112
|
+
index,
|
|
113
|
+
x: centre - width / 2,
|
|
114
|
+
width,
|
|
115
|
+
y: VIEWBOX_HEIGHT - height,
|
|
116
|
+
height,
|
|
117
|
+
insetX: centre - insetWidth / 2,
|
|
118
|
+
insetWidth,
|
|
119
|
+
insetY: VIEWBOX_HEIGHT - insetHeight,
|
|
120
|
+
insetHeight,
|
|
121
|
+
hitX: index * step,
|
|
122
|
+
hitWidth: step,
|
|
123
|
+
}
|
|
124
|
+
})
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export interface AnalyticsTimeseriesProps {
|
|
128
|
+
days: readonly AnalyticsSummaryDay[]
|
|
129
|
+
granularity: AnalyticsChartGranularity
|
|
130
|
+
locale: string
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function AnalyticsTimeseries({
|
|
134
|
+
days,
|
|
135
|
+
granularity,
|
|
136
|
+
locale,
|
|
137
|
+
}: AnalyticsTimeseriesProps): React.JSX.Element {
|
|
138
|
+
const { t } = useTranslation('byline-admin')
|
|
139
|
+
const [hovered, setHovered] = useState<number | null>(null)
|
|
140
|
+
const plot = useRef<SVGSVGElement | null>(null)
|
|
141
|
+
|
|
142
|
+
const buckets = useMemo(() => bucketAnalyticsTimeseries(days, granularity), [days, granularity])
|
|
143
|
+
const columns = useMemo(() => buildAnalyticsColumns(buckets), [buckets])
|
|
144
|
+
const numbers = useMemo(() => new Intl.NumberFormat(locale), [locale])
|
|
145
|
+
// Stored days are UTC calendar days. Formatting in the viewer's zone would
|
|
146
|
+
// shift the labels by up to one day.
|
|
147
|
+
const dayLabel = useMemo(
|
|
148
|
+
() => new Intl.DateTimeFormat(locale, { month: 'short', day: 'numeric', timeZone: 'UTC' }),
|
|
149
|
+
[locale]
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
const formatDay = (day: string) => dayLabel.format(new Date(`${day}T00:00:00.000Z`))
|
|
153
|
+
const formatBucket = (bucket: AnalyticsChartBucket) =>
|
|
154
|
+
bucket.dayCount === 1
|
|
155
|
+
? formatDay(bucket.from)
|
|
156
|
+
: `${formatDay(bucket.from)} – ${formatDay(bucket.to)}`
|
|
157
|
+
const visitorLabel = (bucket: AnalyticsChartBucket) =>
|
|
158
|
+
bucket.granularity === 'day'
|
|
159
|
+
? t('analytics.stats.dailyUniques')
|
|
160
|
+
: t('analytics.stats.summedDailyUniques')
|
|
161
|
+
const active = hovered == null ? undefined : buckets[hovered]
|
|
162
|
+
const readBucket = (bucket: AnalyticsChartBucket) =>
|
|
163
|
+
[
|
|
164
|
+
formatBucket(bucket),
|
|
165
|
+
`${t('analytics.stats.views')} ${numbers.format(bucket.views)}`,
|
|
166
|
+
`${visitorLabel(bucket)} ${numbers.format(bucket.visitors)}`,
|
|
167
|
+
`${t('analytics.stats.downloads')} ${numbers.format(bucket.downloads)}`,
|
|
168
|
+
].join(', ')
|
|
169
|
+
|
|
170
|
+
// One handler on the plot rather than one per column keeps every reporting
|
|
171
|
+
// range to a single pointer listener and one keyboard tab stop.
|
|
172
|
+
const trackPointer = (event: React.PointerEvent<HTMLDivElement>) => {
|
|
173
|
+
if (buckets.length === 0 || plot.current == null) return
|
|
174
|
+
const bounds = plot.current.getBoundingClientRect()
|
|
175
|
+
if (bounds.width === 0) return
|
|
176
|
+
const ratio = (event.clientX - bounds.left) / bounds.width
|
|
177
|
+
setHovered(Math.min(buckets.length - 1, Math.max(0, Math.floor(ratio * buckets.length))))
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const trackKey = (event: React.KeyboardEvent<HTMLDivElement>) => {
|
|
181
|
+
if (buckets.length === 0) return
|
|
182
|
+
const step = event.key === 'ArrowRight' ? 1 : event.key === 'ArrowLeft' ? -1 : 0
|
|
183
|
+
if (step === 0 && event.key !== 'Home' && event.key !== 'End' && event.key !== 'Escape') return
|
|
184
|
+
event.preventDefault()
|
|
185
|
+
if (event.key === 'Escape') return setHovered(null)
|
|
186
|
+
if (event.key === 'Home') return setHovered(0)
|
|
187
|
+
if (event.key === 'End') return setHovered(buckets.length - 1)
|
|
188
|
+
setHovered((current) => {
|
|
189
|
+
const next = (current ?? (step > 0 ? -1 : buckets.length)) + step
|
|
190
|
+
return Math.min(buckets.length - 1, Math.max(0, next))
|
|
191
|
+
})
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return (
|
|
195
|
+
<div
|
|
196
|
+
className={cx('byline-analytics-timeseries', styles.root)}
|
|
197
|
+
role="slider"
|
|
198
|
+
tabIndex={0}
|
|
199
|
+
aria-label={t('analytics.chart.views')}
|
|
200
|
+
aria-valuemin={0}
|
|
201
|
+
aria-valuemax={Math.max(0, buckets.length - 1)}
|
|
202
|
+
aria-valuenow={hovered ?? 0}
|
|
203
|
+
aria-valuetext={active == null ? t('analytics.chart.hint') : readBucket(active)}
|
|
204
|
+
onPointerMove={trackPointer}
|
|
205
|
+
onPointerLeave={() => setHovered(null)}
|
|
206
|
+
onKeyDown={trackKey}
|
|
207
|
+
onBlur={() => setHovered(null)}
|
|
208
|
+
>
|
|
209
|
+
<svg
|
|
210
|
+
ref={plot}
|
|
211
|
+
className={cx('byline-analytics-chart', styles.chart)}
|
|
212
|
+
viewBox={`0 0 ${VIEWBOX_WIDTH} ${VIEWBOX_HEIGHT}`}
|
|
213
|
+
preserveAspectRatio="none"
|
|
214
|
+
aria-hidden="true"
|
|
215
|
+
>
|
|
216
|
+
<title>{t('analytics.chart.views')}</title>
|
|
217
|
+
{[0.5, 1].map((fraction) => (
|
|
218
|
+
<line
|
|
219
|
+
key={fraction}
|
|
220
|
+
className={styles.gridline}
|
|
221
|
+
x1={0}
|
|
222
|
+
x2={VIEWBOX_WIDTH}
|
|
223
|
+
y1={VIEWBOX_HEIGHT - VIEWBOX_HEIGHT * fraction}
|
|
224
|
+
y2={VIEWBOX_HEIGHT - VIEWBOX_HEIGHT * fraction}
|
|
225
|
+
vectorEffect="non-scaling-stroke"
|
|
226
|
+
/>
|
|
227
|
+
))}
|
|
228
|
+
{columns.map((column) => (
|
|
229
|
+
<g
|
|
230
|
+
key={buckets[column.index]?.from}
|
|
231
|
+
className={cx(styles.column, hovered === column.index && styles.columnActive)}
|
|
232
|
+
>
|
|
233
|
+
<rect
|
|
234
|
+
className={styles.views}
|
|
235
|
+
x={column.x}
|
|
236
|
+
width={column.width}
|
|
237
|
+
y={column.y}
|
|
238
|
+
height={column.height}
|
|
239
|
+
/>
|
|
240
|
+
<rect
|
|
241
|
+
className={styles.visitors}
|
|
242
|
+
x={column.insetX}
|
|
243
|
+
width={column.insetWidth}
|
|
244
|
+
y={column.insetY}
|
|
245
|
+
height={column.insetHeight}
|
|
246
|
+
/>
|
|
247
|
+
</g>
|
|
248
|
+
))}
|
|
249
|
+
</svg>
|
|
250
|
+
|
|
251
|
+
{/* HTML axis labels remain undistorted while the SVG stretches. */}
|
|
252
|
+
<div className={cx('byline-analytics-axis', styles.axis)} aria-hidden="true">
|
|
253
|
+
<span>{buckets.length > 0 && buckets[0] != null ? formatDay(buckets[0].from) : ''}</span>
|
|
254
|
+
<span>
|
|
255
|
+
{buckets.length > 0 && buckets[buckets.length - 1] != null
|
|
256
|
+
? formatDay(buckets[buckets.length - 1]?.to ?? '')
|
|
257
|
+
: ''}
|
|
258
|
+
</span>
|
|
259
|
+
</div>
|
|
260
|
+
|
|
261
|
+
<p className={cx('byline-analytics-readout', styles.readout)} aria-live="polite">
|
|
262
|
+
{active == null ? (
|
|
263
|
+
t('analytics.chart.hint')
|
|
264
|
+
) : (
|
|
265
|
+
<>
|
|
266
|
+
<strong>{formatBucket(active)}</strong>
|
|
267
|
+
<span className={styles.swatchViews} />
|
|
268
|
+
{t('analytics.stats.views')} {numbers.format(active.views)}
|
|
269
|
+
<span className={styles.swatchVisitors} />
|
|
270
|
+
{visitorLabel(active)} {numbers.format(active.visitors)}
|
|
271
|
+
<span className={styles.swatchDownloads} />
|
|
272
|
+
{t('analytics.stats.downloads')} {numbers.format(active.downloads)}
|
|
273
|
+
</>
|
|
274
|
+
)}
|
|
275
|
+
</p>
|
|
276
|
+
</div>
|
|
277
|
+
)
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function bucketBySevenDays(days: readonly AnalyticsSummaryDay[]): AnalyticsChartBucket[] {
|
|
281
|
+
const buckets: AnalyticsChartBucket[] = []
|
|
282
|
+
for (let index = 0; index < days.length; index += 7) {
|
|
283
|
+
const rows = days.slice(index, index + 7)
|
|
284
|
+
const first = rows[0]
|
|
285
|
+
const last = rows[rows.length - 1]
|
|
286
|
+
if (first != null && last != null) {
|
|
287
|
+
buckets.push(combineRows(first.day, last.day, 'seven-day', rows))
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return buckets
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function bucketByUtcMonth(days: readonly AnalyticsSummaryDay[]): AnalyticsChartBucket[] {
|
|
294
|
+
const buckets: AnalyticsChartBucket[] = []
|
|
295
|
+
let rows: AnalyticsSummaryDay[] = []
|
|
296
|
+
let month: string | undefined
|
|
297
|
+
|
|
298
|
+
const flush = () => {
|
|
299
|
+
const first = rows[0]
|
|
300
|
+
const last = rows[rows.length - 1]
|
|
301
|
+
if (first != null && last != null) {
|
|
302
|
+
buckets.push(combineRows(first.day, last.day, 'month', rows))
|
|
303
|
+
}
|
|
304
|
+
rows = []
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
for (const day of days) {
|
|
308
|
+
const nextMonth = day.day.slice(0, 7)
|
|
309
|
+
if (month != null && nextMonth !== month) flush()
|
|
310
|
+
month = nextMonth
|
|
311
|
+
rows.push(day)
|
|
312
|
+
}
|
|
313
|
+
flush()
|
|
314
|
+
return buckets
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function combineRows(
|
|
318
|
+
from: string,
|
|
319
|
+
to: string,
|
|
320
|
+
granularity: Exclude<AnalyticsChartGranularity, 'day'>,
|
|
321
|
+
rows: readonly AnalyticsSummaryDay[]
|
|
322
|
+
): AnalyticsChartBucket {
|
|
323
|
+
let views = 0
|
|
324
|
+
let visitors = 0
|
|
325
|
+
let downloads = 0
|
|
326
|
+
for (const row of rows) {
|
|
327
|
+
views += row.views
|
|
328
|
+
visitors += row.visitors
|
|
329
|
+
downloads += row.downloads
|
|
330
|
+
}
|
|
331
|
+
return { from, to, granularity, dayCount: rows.length, views, visitors, downloads }
|
|
332
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This Source Code is subject to the terms of the Mozilla Public
|
|
3
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
5
|
+
*
|
|
6
|
+
* Copyright (c) Infonomic Company Limited
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export {
|
|
10
|
+
ANALYTICS_ABILITIES,
|
|
11
|
+
type AnalyticsAbilityKey,
|
|
12
|
+
registerAnalyticsAbilities,
|
|
13
|
+
} from './abilities.js'
|
|
14
|
+
export type { AnalyticsDashboardData, AnalyticsDashboardPeriod } from './types.js'
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This Source Code is subject to the terms of the Mozilla Public
|
|
3
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
5
|
+
*
|
|
6
|
+
* Copyright (c) Infonomic Company Limited
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type {
|
|
10
|
+
AnalyticsCountryTotal,
|
|
11
|
+
AnalyticsDateRange,
|
|
12
|
+
AnalyticsPathTotal,
|
|
13
|
+
AnalyticsRankedTotals,
|
|
14
|
+
AnalyticsReferrerTotal,
|
|
15
|
+
AnalyticsReportCoverage,
|
|
16
|
+
AnalyticsSummary,
|
|
17
|
+
} from '@byline/analytics'
|
|
18
|
+
import type { AnalyticsDashboardPeriod as PortableAnalyticsDashboardPeriod } from '@byline/analytics/config'
|
|
19
|
+
|
|
20
|
+
export type AnalyticsDashboardPeriod = PortableAnalyticsDashboardPeriod
|
|
21
|
+
|
|
22
|
+
export interface AnalyticsDashboardData {
|
|
23
|
+
range: AnalyticsDateRange
|
|
24
|
+
coverage: AnalyticsReportCoverage
|
|
25
|
+
summary: AnalyticsSummary
|
|
26
|
+
pages: AnalyticsRankedTotals<AnalyticsPathTotal>
|
|
27
|
+
downloads: AnalyticsRankedTotals<AnalyticsPathTotal>
|
|
28
|
+
referrers: AnalyticsRankedTotals<AnalyticsReferrerTotal>
|
|
29
|
+
/** Countries are unbounded by a top-N limit, so there is nothing to truncate. */
|
|
30
|
+
countries: AnalyticsCountryTotal[]
|
|
31
|
+
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|