このコミットが含まれているのは:
+53
-15
@@ -1,4 +1,4 @@
|
||||
import { apiGet } from '@/lib/api'
|
||||
import { apiGet, apiPost } from '@/lib/api'
|
||||
|
||||
import type { LocaleCode } from '@/lib/locales'
|
||||
import type { TagNameSnapshot } from '@/types'
|
||||
@@ -216,16 +216,42 @@ export const changeCountSort = (
|
||||
}
|
||||
|
||||
|
||||
export const selectedCountFields = (
|
||||
fields: CountField[],
|
||||
params: URLSearchParams,
|
||||
selectionKey: string,
|
||||
parent = '',
|
||||
): CountField[] => {
|
||||
const selected = params.has (selectionKey)
|
||||
? new Set (params.get (selectionKey)!.split (',')) : null
|
||||
return orderedCountFields (fields, params, parent)
|
||||
.filter (field => selected == null || selected.has (field.key))
|
||||
}
|
||||
|
||||
|
||||
export const normaliseCountsParams = (
|
||||
resource: CountsResource,
|
||||
source: URLSearchParams,
|
||||
): URLSearchParams => {
|
||||
const params = new URLSearchParams (source)
|
||||
params.delete ('event_type')
|
||||
if (!params.has ('mode'))
|
||||
params.set ('mode', source.has ('axis') ? 'summary' : 'state')
|
||||
if (!['month', 'user'].includes (params.get ('axis') ?? ''))
|
||||
params.set ('axis', 'month')
|
||||
const legacy = params.get ('mode')
|
||||
if (['state', 'trend', 'snapshot'].includes (legacy ?? ''))
|
||||
{
|
||||
params.set ('mode', legacy === 'snapshot' ? 'details' : 'summary')
|
||||
if (legacy !== 'snapshot') params.set ('axis', resource === 'posts' ? 'tags' : 'month')
|
||||
if (legacy === 'trend')
|
||||
{
|
||||
if (params.get ('before')) params.set ('at', params.get ('before')!)
|
||||
params.delete ('from')
|
||||
params.delete ('before')
|
||||
}
|
||||
}
|
||||
if (!params.has ('mode')) params.set ('mode', 'summary')
|
||||
const axes = (resource === 'posts' && params.get ('basis') !== 'history')
|
||||
? ['tags', 'month', 'user'] : ['month', 'user']
|
||||
if (!axes.includes (params.get ('axis') ?? '')) params.set ('axis', axes[0])
|
||||
params.delete ('grain')
|
||||
if (params.has ('columns'))
|
||||
{
|
||||
const selected = new Set (params.get ('columns')!.split (','))
|
||||
@@ -237,17 +263,14 @@ export const normaliseCountsParams = (
|
||||
|
||||
|
||||
export const isCountsState = (params: URLSearchParams): boolean =>
|
||||
['state', 'trend', 'snapshot'].includes (params.get ('mode') ?? 'state')
|
||||
params.get ('basis') !== 'history'
|
||||
|
||||
|
||||
export const countsRequestParams = (params: URLSearchParams): URLSearchParams => {
|
||||
const result = new URLSearchParams (params)
|
||||
const hidden = isCountsState (params)
|
||||
? ['axis', 'latest', 'user_id', 'resource_id']
|
||||
: ['at', 'grain', 'kind']
|
||||
if (isCountsState (params) && params.get ('mode') !== 'trend')
|
||||
hidden.push ('from', 'before')
|
||||
for (const key of ['columns', 'children', 'child_sort', 'nested_json', 'order', ...hidden])
|
||||
const hidden = isCountsState (params) ? ['latest'] : ['kind']
|
||||
for (const key of ['columns', 'summary_columns', 'children', 'child_sort',
|
||||
'nested_json', 'order', 'basis', 'grain', ...hidden])
|
||||
result.delete (key)
|
||||
return result
|
||||
}
|
||||
@@ -257,9 +280,24 @@ export const fetchCounts = (
|
||||
resource: CountsResource,
|
||||
params: URLSearchParams,
|
||||
locale: LocaleCode,
|
||||
): Promise<CountsResult> => apiGet (isCountsState (params) ? '/counts/state' : `/counts/${resource}`, {
|
||||
locale, params: { ...Object.fromEntries (countsRequestParams (params)),
|
||||
...(isCountsState (params) ? { resource } : { }) } })
|
||||
): Promise<CountsResult> => apiGet (
|
||||
isCountsState (params) ? '/counts/state' : `/counts/${resource}`, {
|
||||
locale, params: { ...Object.fromEntries (countsRequestParams (params)),
|
||||
...(isCountsState (params) ? { resource } : { }) } })
|
||||
|
||||
|
||||
export const exportCounts = (
|
||||
resource: CountsResource,
|
||||
params: URLSearchParams,
|
||||
locale: LocaleCode,
|
||||
format: 'json' | 'csv',
|
||||
columns: string[],
|
||||
headers: string[],
|
||||
labels: Record<string, string>,
|
||||
): Promise<Blob> => apiPost ('/counts/export', {
|
||||
...Object.fromEntries (params), resource, format,
|
||||
columns: columns.join (','), headers: JSON.stringify (headers),
|
||||
labels: JSON.stringify (labels) }, { locale, responseType: 'blob' })
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -212,3 +212,35 @@ export const countsImageURL = (value: string | null | undefined): string | null
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export const countsExportLabels = (
|
||||
resource: CountsResource,
|
||||
locale: LocaleCode,
|
||||
): Record<string, string> => {
|
||||
const aliases: Record<string, string> = { versionNo: 'version_no',
|
||||
languageCode: 'language', scriptCode: 'script', autoGenerated: 'auto_generated' }
|
||||
const labels: Record<string, string> = { }
|
||||
const visit = (fields: CountField[]) => {
|
||||
for (const field of fields)
|
||||
{
|
||||
labels[aliases[field.key] ?? field.key] = translateUIMessage (
|
||||
field.label, 'counts', locale).text
|
||||
if (field.children) visit (field.children)
|
||||
}
|
||||
}
|
||||
visit (fieldsForCounts (resource))
|
||||
for (const key of ['internal', 'external'])
|
||||
labels[`kind:${key}`] = translateUIMessage (`kinds.${key}`, 'counts', locale).text
|
||||
for (const key of ['deerjikist', 'meme', 'character', 'general', 'material', 'meta'])
|
||||
labels[`category:${key}`] = translateUIMessage (
|
||||
`common:categories.${key}`, 'counts', locale).text
|
||||
for (const key of ['true', 'false'])
|
||||
labels[`boolean:${key}`] = translateUIMessage (`boolean.${key}`, 'counts', locale).text
|
||||
for (const key of ['ja', 'ain'])
|
||||
labels[`language:${key}`] = translateUIMessage (
|
||||
`history:languageNames.${key}`, 'counts', locale).text
|
||||
labels.latest = translateUIMessage ('latestValue', 'counts', locale).text
|
||||
labels.notRegistered = translateUIMessage ('notRegistered', 'counts', locale).text
|
||||
return labels
|
||||
}
|
||||
|
||||
@@ -1,52 +1,94 @@
|
||||
import { isCountsState, selectedCountFields } from '@/lib/counts'
|
||||
import { translateUIMessage } from '@/lib/i18n'
|
||||
|
||||
import type { CountField, CountsStateRow } from '@/lib/counts'
|
||||
import type { CountField, CountsResource, CountsResult } from '@/lib/counts'
|
||||
import type { LocaleCode } from '@/lib/locales'
|
||||
|
||||
export type CountsSummaryRow = {
|
||||
key: string | number | null
|
||||
name: string | null
|
||||
count: number | null
|
||||
kind?: 'internal' | 'external'
|
||||
tagId?: number
|
||||
deprecated?: string | null
|
||||
at?: string }
|
||||
|
||||
export const countsStateFields = (trend: boolean): CountField[] => trend
|
||||
? [{ key: 'period', label: 'counts:state.period' },
|
||||
{ key: 'count', label: 'counts:state.postCount' }]
|
||||
: [{ key: 'kind', label: 'counts:columns.kind' },
|
||||
|
||||
export const countsSummaryFields = (
|
||||
resource: CountsResource,
|
||||
params: URLSearchParams,
|
||||
): CountField[] => (isCountsState (params) && params.get ('axis') === 'tags')
|
||||
? [{ key: 'kind', label: 'counts:columns.kind' },
|
||||
{ key: 'tag_id', label: 'counts:columns.resourceId' },
|
||||
{ key: 'name', label: 'history:tagName' },
|
||||
{ key: 'deprecated', label: 'counts:columns.deprecated' },
|
||||
{ key: 'count', label: 'counts:state.postCount' }]
|
||||
: [{ key: 'group', label: `counts:axes.${params.get ('axis') ?? 'month'}` },
|
||||
{ key: 'count', label: isCountsState (params)
|
||||
? `counts:state.totals.${resource}` : 'counts:metric' }]
|
||||
|
||||
|
||||
export const countsStateCell = (
|
||||
row: CountsStateRow,
|
||||
export const selectedCountsSummaryFields = (
|
||||
resource: CountsResource,
|
||||
params: URLSearchParams,
|
||||
): CountField[] => selectedCountFields (
|
||||
countsSummaryFields (resource, params), params, 'summary_columns', 'summary')
|
||||
|
||||
|
||||
export const countsSummaryRows = (data: CountsResult): CountsSummaryRow[] =>
|
||||
data.stateRows != null
|
||||
? data.stateRows.map (row => ({ ...row, key: `${row.kind}:${row.tagId}` }))
|
||||
: data.groups ?? []
|
||||
|
||||
|
||||
export const countsSummaryCell = (
|
||||
row: CountsSummaryRow,
|
||||
key: string,
|
||||
locale: LocaleCode,
|
||||
): string => {
|
||||
): { text: string; language?: string } => {
|
||||
switch (key)
|
||||
{
|
||||
case 'period': return row.period ?? ''
|
||||
case 'count': return row.count == null ? '—' : String (row.count)
|
||||
case 'tag_id': return String (row.tagId)
|
||||
case 'kind': return translateUIMessage (`kinds.${row.kind}`, 'counts', locale).text
|
||||
case 'name': return row.name ?? translateUIMessage ('notRegistered', 'counts', locale).text
|
||||
case 'group': return row.key == null
|
||||
? translateUIMessage ('history:bot', 'counts', locale)
|
||||
: { text: row.name ?? String (row.key) }
|
||||
case 'count': return { text: row.count == null ? '—' : String (row.count) }
|
||||
case 'tag_id': return { text: String (row.tagId) }
|
||||
case 'kind': return translateUIMessage (`kinds.${row.kind}`, 'counts', locale)
|
||||
case 'name': return row.name == null
|
||||
? translateUIMessage ('notRegistered', 'counts', locale) : { text: row.name }
|
||||
case 'deprecated': return row.deprecated
|
||||
? translateUIMessage ('common:states.deprecated', 'counts', locale).text : ''
|
||||
default: return ''
|
||||
? translateUIMessage ('common:states.deprecated', 'counts', locale) : { text: '' }
|
||||
default: return { text: '' }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export const countsStateDrill = (
|
||||
export const countsSummaryDrill = (
|
||||
resource: CountsResource,
|
||||
params: URLSearchParams,
|
||||
at: string,
|
||||
resource = 'posts',
|
||||
row?: CountsStateRow,
|
||||
row: CountsSummaryRow,
|
||||
): string => {
|
||||
const next = new URLSearchParams (params)
|
||||
for (const key of ['sort', 'page', 'tag', 'from', 'before']) next.delete (key)
|
||||
next.set ('mode', 'snapshot')
|
||||
next.set ('at', at)
|
||||
if (row != null)
|
||||
for (const key of ['sort', 'page']) next.delete (key)
|
||||
next.set ('mode', 'details')
|
||||
if (row.at) next.set ('at', row.at)
|
||||
if (row.tagId != null)
|
||||
{
|
||||
next.set ('kind', row.kind)
|
||||
next.set ('kind', row.kind!)
|
||||
next.set ('tag', String (row.tagId))
|
||||
}
|
||||
else if (params.get ('axis') === 'user') next.set ('user_id', String (row.key ?? 'none'))
|
||||
else
|
||||
{
|
||||
const first = `${row.key}-01T00:00:00Z`
|
||||
const date = new Date (first)
|
||||
date.setUTCMonth (date.getUTCMonth () + 1)
|
||||
const last = date.toISOString ()
|
||||
const from = next.get ('from')
|
||||
const before = next.get ('before')
|
||||
next.set ('from', (from != null && Date.parse (from) > Date.parse (first)) ? from : first)
|
||||
next.set ('before',
|
||||
(before != null && Date.parse (before) < Date.parse (last)) ? before : last)
|
||||
}
|
||||
return `/counts/${resource}?${next}`
|
||||
}
|
||||
|
||||
新しいイシューから参照
ユーザーをブロックする