Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
215 changes: 189 additions & 26 deletions app/middlewares/dreamkast_exporter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,17 @@ def initialize(app, options = {})
docstring: 'Count dreamkast talk difficulties',
labels: [:conference_id, :talk_difficulty_name]
),
Prometheus::Client::Gauge.new(
:dreamkast_select_talks,
docstring: 'Select dreamkast talks',
labels: [:talk_id, :conference_id, :title, :talk_difficulty_name]
),
Prometheus::Client::Gauge.new(
:dreamkast_select_proposal_items,
docstring: 'Select dreamkast proposal items',
labels: [:talk_id, :conference_id, :proposal_items_label, :proposal_items_params]
),
# カテゴリ別メトリクスで代替されたため無効化
# Prometheus::Client::Gauge.new(
# :dreamkast_select_talks,
# docstring: 'Select dreamkast talks',
# labels: [:talk_id, :conference_id, :title, :talk_difficulty_name]
# ),
# Prometheus::Client::Gauge.new(
# :dreamkast_select_proposal_items,
# docstring: 'Select dreamkast proposal items',
# labels: [:talk_id, :conference_id, :proposal_items_label, :proposal_items_params]
# ),
Prometheus::Client::Gauge.new(
:dreamkast_stats_of_registrants_offline,
docstring: 'Stats of Registrants(Offline)',
Expand All @@ -61,6 +62,36 @@ def initialize(app, options = {})
:dreamkast_talk_difficulties_by_category_count,
docstring: 'Count talks by category and difficulty',
labels: [:conference_id, :target_conference, :talk_difficulty_name]
),
Prometheus::Client::Gauge.new(
:dreamkast_assumed_visitors_by_category_count,
docstring: 'Count assumed visitors by category',
labels: [:conference_id, :target_conference, :assumed_visitor_name]
),
Prometheus::Client::Gauge.new(
:dreamkast_execution_phases_by_category_count,
docstring: 'Count execution phases by category',
labels: [:conference_id, :target_conference, :execution_phase_name]
),
Prometheus::Client::Gauge.new(
:dreamkast_publication_permissions_by_category_count,
docstring: 'Count publication permissions by category',
labels: [:conference_id, :target_conference, :publication_permission_name]
),
Prometheus::Client::Gauge.new(
:dreamkast_session_times_by_category_count,
docstring: 'Count session times by category',
labels: [:conference_id, :target_conference, :session_time_name]
),
Prometheus::Client::Gauge.new(
:dreamkast_languages_by_category_count,
docstring: 'Count languages by category',
labels: [:conference_id, :target_conference, :language_name]
),
Prometheus::Client::Gauge.new(
:dreamkast_proposals_by_category_count,
docstring: 'Count proposals by category',
labels: [:conference_id, :target_conference]
)
]
metrics.each do |metric|
Expand All @@ -73,6 +104,8 @@ def initialize(app, options = {})
end

def respond_with(format)
@category_talk_ids_map = nil
@proposal_item_config_params_map = nil
@registry.metrics.each do |metrics|
send(metrics.name, metrics)
end
Expand Down Expand Up @@ -137,23 +170,24 @@ def dreamkast_talk_difficulties_count(metrics)
end
end

def dreamkast_select_talks(metrics)
Talk.preload(:talk_difficulty).all.each do |talk|
metrics.set(
talk.id,
labels: { talk_id: talk.id, conference_id: talk.conference_id, title: talk.title, talk_difficulty_name: talk.talk_difficulty&.name }
)
end
end
# カテゴリ別メトリクスで代替されたため無効化
# def dreamkast_select_talks(metrics)
# Talk.preload(:talk_difficulty).all.each do |talk|
# metrics.set(
# talk.id,
# labels: { talk_id: talk.id, conference_id: talk.conference_id, title: talk.title, talk_difficulty_name: talk.talk_difficulty&.name }
# )
# end
# end

def dreamkast_select_proposal_items(metrics)
ProposalItem.all.each do |proposal_items|
metrics.set(
proposal_items.talk_id,
labels: { talk_id: proposal_items.talk_id, conference_id: proposal_items.conference_id, proposal_items_label: proposal_items.label, proposal_items_params: proposal_items.params }
)
end
end
# def dreamkast_select_proposal_items(metrics)
# ProposalItem.all.each do |proposal_items|
# metrics.set(
# proposal_items.talk_id,
# labels: { talk_id: proposal_items.talk_id, conference_id: proposal_items.conference_id, proposal_items_label: proposal_items.label, proposal_items_params: proposal_items.params }
# )
# end
# end

def dreamkast_stats_of_registrants_offline(metrics)
StatsOfRegistrant.all.each do |stats|
Expand Down Expand Up @@ -195,4 +229,133 @@ def dreamkast_talk_difficulties_by_category_count(metrics)
)
end
end

def dreamkast_assumed_visitors_by_category_count(metrics)
config_map = proposal_item_config_params_map

# カテゴリ -> assumed_visitorラベルのマッピング
category_to_av_label = {
'cnd_category' => 'cnd_assumed_visitor',
'pek_category' => 'pek_assumed_visitor',
'srek_category' => 'srek_assumed_visitor'
}

category_talk_ids_map.each do |category, talk_ids|
av_label = category_to_av_label[category]
next unless av_label

items = ProposalItem.where(talk_id: talk_ids, label: av_label, conference_id: CONFERENCE_ID)
counts = Hash.new(0)

# CheckBox: params は配列
items.each do |item|
Array(item.params).compact.each do |pid|
name = config_map[pid.to_i]
counts[name] += 1 if name
end
end

counts.each do |name, count|
metrics.set(
count,
labels: {
conference_id: CONFERENCE_ID,
target_conference: category,
assumed_visitor_name: name
}
)
end
end
end

def dreamkast_execution_phases_by_category_count(metrics)
count_by_category(metrics, label: 'execution_phase', value_label_name: 'execution_phase_name', is_checkbox: true)
end

def dreamkast_publication_permissions_by_category_count(metrics)
count_by_category(metrics, label: 'whether_it_can_be_published', value_label_name: 'publication_permission_name', is_checkbox: false)
end

def dreamkast_session_times_by_category_count(metrics)
count_by_category(metrics, label: 'session_time', value_label_name: 'session_time_name', is_checkbox: false)
end

def dreamkast_languages_by_category_count(metrics)
count_by_category(metrics, label: 'language', value_label_name: 'language_name', is_checkbox: false)
end

def dreamkast_proposals_by_category_count(metrics)
category_talk_ids_map.each do |category, talk_ids|
metrics.set(
talk_ids.size,
labels: {
conference_id: CONFERENCE_ID,
target_conference: category
}
)
end
end

# カテゴリ別talk_idマッピング(リクエスト内キャッシュ)
# 結果: { "cnd_category" => [1, 2], "pek_category" => [3], "srek_category" => [4] }
def category_talk_ids_map
@category_talk_ids_map ||= ProposalItem
.where(label: %w[cnd_category pek_category srek_category], conference_id: CONFERENCE_ID)
.pluck(:label, :talk_id)
.group_by(&:first)
.transform_values { |pairs| pairs.map(&:last).uniq }
end

# ProposalItemConfig ID -> params(名称) マッピング(リクエスト内キャッシュ)
def proposal_item_config_params_map
@proposal_item_config_params_map ||= ProposalItemConfig
.where(conference_id: CONFERENCE_ID)
.pluck(:id, :params)
.to_h
end

# 共通label(execution_phase等)のカテゴリ別集計
# 全カテゴリのtalk_idをまとめて1クエリで取得し、カテゴリごとに振り分けてカウント
# is_checkbox: true の場合、params を配列として展開してカウント
def count_by_category(metrics, label:, value_label_name:, is_checkbox:)
config_map = proposal_item_config_params_map
cat_map = category_talk_ids_map

# talk_id -> 所属カテゴリ一覧のマッピング
talk_id_to_categories = {}
cat_map.each do |category, talk_ids|
talk_ids.each { |tid| (talk_id_to_categories[tid] ||= []) << category }
end

# 全カテゴリのtalk_idをまとめて1クエリで取得
all_talk_ids = cat_map.values.flatten.uniq
items = ProposalItem.where(talk_id: all_talk_ids, label:, conference_id: CONFERENCE_ID)

# カテゴリ x 名称でカウント
counts = Hash.new { |h, k| h[k] = Hash.new(0) }
items.each do |item|
param_ids = is_checkbox ? Array(item.params) : [item.params]
param_ids.compact.each do |pid|
name = config_map[pid.to_i]
next unless name

talk_id_to_categories[item.talk_id]&.each do |category|
counts[category][name] += 1
end
end
end

counts.each do |category, name_counts|
name_counts.each do |name, count|
metrics.set(
count,
labels: {
conference_id: CONFERENCE_ID,
target_conference: category,
value_label_name.to_sym => name
}
)
end
end
end
end
87 changes: 87 additions & 0 deletions spec/requests/exporter_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,44 @@
create(:proposal_item, talk: cnk_talk3, conference: cnk, label: 'pek_category', params: '257')
# SREK category proposal item
create(:proposal_item, talk: cnk_talk4, conference: cnk, label: 'srek_category', params: '269')

# ProposalItemConfig(名称解決用)
create(:proposal_item_config, id: 251, conference: cnk, type: 'ProposalItemConfigCheckBox',
label: 'cnd_assumed_visitor', params: 'architect - システム設計')
create(:proposal_item_config, id: 252, conference: cnk, type: 'ProposalItemConfigCheckBox',
label: 'cnd_assumed_visitor', params: 'developer - システム開発')
create(:proposal_item_config, id: 263, conference: cnk, type: 'ProposalItemConfigCheckBox',
label: 'pek_assumed_visitor', params: 'Platform Engineer')
create(:proposal_item_config, id: 221, conference: cnk, type: 'ProposalItemConfigCheckBox',
label: 'execution_phase', params: 'Production(本番環境)')
create(:proposal_item_config, id: 229, conference: cnk, type: 'ProposalItemConfigRadioButton',
label: 'language', params: 'JA')
create(:proposal_item_config, id: 230, conference: cnk, type: 'ProposalItemConfigRadioButton',
label: 'language', params: 'EN')
create(:proposal_item_config, id: 227, conference: cnk, type: 'ProposalItemConfigRadioButton',
label: 'session_time', params: '_40min (full session)')
create(:proposal_item_config, id: 223, conference: cnk, type: 'ProposalItemConfigRadioButton',
label: 'whether_it_can_be_published', params: 'All okay - スライド・動画両方ともに公開可')

# assumed_visitor (CheckBox - 配列)
create(:proposal_item, talk: cnk_talk1, conference: cnk, label: 'cnd_assumed_visitor', params: ['251', '252'])
create(:proposal_item, talk: cnk_talk2, conference: cnk, label: 'cnd_assumed_visitor', params: ['251'])
create(:proposal_item, talk: cnk_talk3, conference: cnk, label: 'pek_assumed_visitor', params: ['263'])

# execution_phase (CheckBox - 配列)
create(:proposal_item, talk: cnk_talk1, conference: cnk, label: 'execution_phase', params: ['221'])
create(:proposal_item, talk: cnk_talk3, conference: cnk, label: 'execution_phase', params: ['221'])

# language (RadioButton - 文字列)
create(:proposal_item, talk: cnk_talk1, conference: cnk, label: 'language', params: '229')
create(:proposal_item, talk: cnk_talk2, conference: cnk, label: 'language', params: '229')
create(:proposal_item, talk: cnk_talk3, conference: cnk, label: 'language', params: '230')

# session_time (RadioButton)
create(:proposal_item, talk: cnk_talk1, conference: cnk, label: 'session_time', params: '227')

# whether_it_can_be_published (RadioButton)
create(:proposal_item, talk: cnk_talk1, conference: cnk, label: 'whether_it_can_be_published', params: '223')
end

it 'returns CNK talk difficulties count by target conference' do
Expand Down Expand Up @@ -96,6 +134,55 @@
expect(response.body).to(include('dreamkast_talk_difficulties_by_category_count{conference_id="15",target_conference="pek_category",talk_difficulty_name="中級者 - Intermediate"} 1.0'))
end
end

it 'returns assumed visitors count by category' do
get '/metrics'
expect(response).to(be_successful)
# CND: cnk_talk1(architect+developer), cnk_talk2(architect) -> architect=2, developer=1
expect(response.body).to(include('dreamkast_assumed_visitors_by_category_count{conference_id="15",target_conference="cnd_category",assumed_visitor_name="architect - システム設計"} 2.0'))
expect(response.body).to(include('dreamkast_assumed_visitors_by_category_count{conference_id="15",target_conference="cnd_category",assumed_visitor_name="developer - システム開発"} 1.0'))
# PEK: cnk_talk3(Platform Engineer) -> 1
expect(response.body).to(include('dreamkast_assumed_visitors_by_category_count{conference_id="15",target_conference="pek_category",assumed_visitor_name="Platform Engineer"} 1.0'))
end

it 'returns execution phases count by category' do
get '/metrics'
expect(response).to(be_successful)
# CND: cnk_talk1 -> Production=1, PEK: cnk_talk3 -> Production=1
expect(response.body).to(include('dreamkast_execution_phases_by_category_count{conference_id="15",target_conference="cnd_category",execution_phase_name="Production(本番環境)"} 1.0'))
expect(response.body).to(include('dreamkast_execution_phases_by_category_count{conference_id="15",target_conference="pek_category",execution_phase_name="Production(本番環境)"} 1.0'))
end

it 'returns languages count by category' do
get '/metrics'
expect(response).to(be_successful)
# CND: cnk_talk1(JA), cnk_talk2(JA) -> JA=2, PEK: cnk_talk3(EN) -> EN=1
expect(response.body).to(include('dreamkast_languages_by_category_count{conference_id="15",target_conference="cnd_category",language_name="JA"} 2.0'))
expect(response.body).to(include('dreamkast_languages_by_category_count{conference_id="15",target_conference="pek_category",language_name="EN"} 1.0'))
end

it 'returns session times count by category' do
get '/metrics'
expect(response).to(be_successful)
# CND: cnk_talk1 -> 40min=1
expect(response.body).to(include('dreamkast_session_times_by_category_count{conference_id="15",target_conference="cnd_category",session_time_name="_40min (full session)"} 1.0'))
end

it 'returns publication permissions count by category' do
get '/metrics'
expect(response).to(be_successful)
# CND: cnk_talk1 -> All okay=1
expect(response.body).to(include('dreamkast_publication_permissions_by_category_count{conference_id="15",target_conference="cnd_category",publication_permission_name="All okay - スライド・動画両方ともに公開可"} 1.0'))
end

it 'returns proposals count by category' do
get '/metrics'
expect(response).to(be_successful)
# CND: cnk_talk1 + cnk_talk2 = 2, PEK: cnk_talk3 = 1, SREK: cnk_talk4 = 1
expect(response.body).to(include('dreamkast_proposals_by_category_count{conference_id="15",target_conference="cnd_category"} 2.0'))
expect(response.body).to(include('dreamkast_proposals_by_category_count{conference_id="15",target_conference="pek_category"} 1.0'))
expect(response.body).to(include('dreamkast_proposals_by_category_count{conference_id="15",target_conference="srek_category"} 1.0'))
end
end
end
end
Loading