このコミットが含まれているのは:
2026-10-26 21:53:09 +09:00
コミット 1154babe45
22個のファイルの変更、537行の追加、145行の削除
+98 -9
ファイルの表示
@@ -10,6 +10,7 @@ class CountsTableExport
raise ArgumentError if (params.keys - CountsTableQuery::KEYS - EXTRA).any?
raise ArgumentError unless params.values.all? { _1.is_a?(String) }
params = CountsDefaults.for(params).merge(params)
unless params.key?('children')
params = params.merge('children' => CountsExportProjection.default_children(
params.fetch('resource', 'posts')).join(','))
@@ -63,7 +64,8 @@ class CountsTableExport
total = @summary ? nil : page[:count]
progress&.call(processed, total)
unless started
metadata = page.except(:versions, :cells, :axis_labels, :column_values).merge(
metadata = page.except(:versions, :cells, :axis_labels, :column_values,
:row_headers, :column_headers, :pivot_totals).merge(
resource:, filters: @params.slice(*CountsTableQuery::KEYS),
fields: @query.population.definition.metadata,
columns: @columns, column_labels: @headers,
@@ -71,12 +73,35 @@ class CountsTableExport
sort: @params.fetch('sort', ''), child_sort: @params['child_sort'])
output << (format == 'json' ? "{\"metadata\":#{ metadata.to_json },\"data\":[" : "\uFEFF")
if format == 'csv'
headers = @summary ? summary_headers : @headers
output << CSV.generate_line(headers.map { safe(_1) }, row_sep: "\r\n")
headings = @summary ? pivot_headers(page) : [@headers]
headings.each do |headers|
output << CSV.generate_line(headers.map { safe(_1) }, row_sep: "\r\n")
end
end
started = true
end
(page[:cells] || page[:versions]).each do |row|
if @summary
pivot_rows(page).each do |header|
records = page.fetch(:column_headers).map { pivot_cell(header, _1, page) }
if format == 'csv'
labels = pivot_row_labels(header, page)
values = labels + records.map { _1[:count] }
output << CSV.generate_line(values.map { safe(_1) }, row_sep: "\r\n")
processed += records.length
progress&.call(processed, nil)
else
records.each do |record|
output << ',' unless first
output << record.to_json
first = false
processed += 1
progress&.call(processed, nil)
end
end
end
next
end
page[:versions].each do |row|
record = project(row)
if format == 'json'
output << ',' unless first
@@ -106,12 +131,76 @@ class CountsTableExport
private
def summary_headers
return @headers if @headers.any?
def pivot_rows(page)
page.fetch(:row_headers).reject do |header|
header[:kind] == 'grand_total' && @query.page * @query.limit < page[:group_count]
end
end
labels = @labels
@query.axes.all.map { labels.fetch(_1['field'], _1['field']) } +
[labels.fetch('count', 'count')]
def total_label(header)
@labels.fetch(header[:kind] == 'grand_total' ? 'grandTotal' : 'subtotal', '')
end
def pivot_row_labels(header, page)
values = header[:values].each_with_index.map { |value, index|
summary_value(value, index, page)
}
values << total_label(header) unless header[:kind] == 'data'
leading = [@query.axes.rows.length, @query.axes.columns.any? ? 1 : 0].max
Array.new(leading) { values[_1] || '' }
end
def pivot_headers(page)
row_count = @query.axes.rows.length
cols = @query.axes.columns
leading = [row_count, cols.any? ? 1 : 0].max
columns = page.fetch(:column_headers)
Array.new([cols.length, 1].max) do |level|
prefix = Array.new(leading) { level.zero? ? @headers[_1].to_s : '' }
prefix = [''] if row_count.zero? && cols.any?
prefix + columns.each_with_index.map do |header, index|
values = header[:values]
if cols.empty?
header[:kind] == 'data' ? @labels.fetch('count', '') : total_label(header)
elsif level == values.length && header[:kind] != 'data'
total_label(header)
elsif level < values.length
previous = index.positive? ? columns[index - 1][:values] : nil
if level < cols.length - 1 && previous && previous.first(level + 1) == values.first(level + 1)
''
else
summary_value(values[level], row_count + level, page)
end
else
''
end
end
end
end
def pivot_cell(row, column, page)
@pivot_lookup ||= {}
# Discard the previous page's lookup; memory remains bounded by one page and its rollups.
unless @pivot_page.equal?(page)
@pivot_page = page
@pivot_lookup = page.fetch(:cells).to_h do |cell|
values = cell[:values]
[[values.first(@query.axes.rows.length), values.drop(@query.axes.rows.length)], cell[:count]]
end
page.fetch(:pivot_totals).each { @pivot_lookup[[_1[:row], _1[:column]]] = _1[:count] }
end
kinds = [row[:kind], column[:kind]]
kind = if kinds.include?('grand_total')
'grand_total'
elsif kinds.include?('subtotal')
'subtotal'
else
'data'
end
{ row: row[:values], column: column[:values], kind:,
row_kind: row[:kind], column_kind: column[:kind],
row_depth: row[:values].length, column_depth: column[:values].length,
count: @pivot_lookup.fetch([row[:values], column[:values]], 0) }
end
def summary_value(value, index, page)