Commit 9455a8b5 authored by Vasilii Iakliushin's avatar Vasilii Iakliushin Committed by Mark Chao

Add `increment_counter` to Usage Ping API

Contributes to https://gitlab.com/gitlab-org/gitlab/-/issues/233994

**Problem**

There is no API endpoint that can process ordinary Redis counter
events.

**Solution**

Add `increment_counter` endpoint to accept ordinary Redis events.
parent 8aa3973a
---
title: Add `increment_counter` to Usage Ping API
merge_request: 47309
author:
type: added
---
name: usage_data_static_site_editor_commits
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/47309
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/284082
milestone: '13.6'
type: development
group: group::static_site_editor
default_enabled: false
---
name: usage_data_static_site_editor_merge_requests
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/47309
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/284083
milestone: '13.6'
type: development
group: group::static_site_editor
default_enabled: false
......@@ -536,6 +536,15 @@ module API
)
end
def increment_counter(event_name)
feature_name = "usage_data_#{event_name}"
return unless Feature.enabled?(feature_name)
Gitlab::UsageDataCounters.count(event_name)
rescue => error
Gitlab::AppLogger.warn("Redis tracking event failed for event: #{event_name}, message: #{error.message}")
end
# @param event_name [String] the event name
# @param values [Array|String] the values counted
def increment_unique_values(event_name, values)
......
......@@ -20,6 +20,18 @@ module API
requires :event, type: String, desc: 'The event name that should be tracked'
end
post 'increment_counter' do
event_name = params[:event]
increment_counter(event_name)
status :ok
end
params do
requires :event, type: String, desc: 'The event name that should be tracked'
end
post 'increment_unique_users' do
event_name = params[:event]
......
......@@ -296,20 +296,7 @@ module Gitlab
# @return [Array<#totals>] An array of objects that respond to `#totals`
def usage_data_counters
[
Gitlab::UsageDataCounters::WikiPageCounter,
Gitlab::UsageDataCounters::WebIdeCounter,
Gitlab::UsageDataCounters::NoteCounter,
Gitlab::UsageDataCounters::SnippetCounter,
Gitlab::UsageDataCounters::SearchCounter,
Gitlab::UsageDataCounters::CycleAnalyticsCounter,
Gitlab::UsageDataCounters::ProductivityAnalyticsCounter,
Gitlab::UsageDataCounters::SourceCodeCounter,
Gitlab::UsageDataCounters::MergeRequestCounter,
Gitlab::UsageDataCounters::DesignsCounter,
Gitlab::UsageDataCounters::KubernetesAgentCounter,
Gitlab::UsageDataCounters::StaticSiteEditorCounter
]
Gitlab::UsageDataCounters.counters
end
def components_usage_data
......
# frozen_string_literal: true
module Gitlab
module UsageDataCounters
COUNTERS = [
WikiPageCounter,
WebIdeCounter,
NoteCounter,
SnippetCounter,
SearchCounter,
CycleAnalyticsCounter,
ProductivityAnalyticsCounter,
SourceCodeCounter,
MergeRequestCounter,
DesignsCounter,
KubernetesAgentCounter,
StaticSiteEditorCounter
].freeze
UsageDataCounterError = Class.new(StandardError)
UnknownEvent = Class.new(UsageDataCounterError)
class << self
def counters
self::COUNTERS
end
def count(event_name)
counters.each do |counter|
event = counter.fetch_supported_event(event_name)
return counter.count(event) if event
end
raise UnknownEvent, "Cannot find counter for event #{event_name}"
end
end
end
end
......@@ -29,6 +29,12 @@ module Gitlab::UsageDataCounters
known_events.map { |event| [counter_key(event), -1] }.to_h
end
def fetch_supported_event(event_name)
return if prefix.present? && !event_name.start_with?(prefix)
known_events.find { |event| counter_key(event) == event_name.to_sym }
end
private
def require_known_event(event)
......
......@@ -4,6 +4,7 @@ module Gitlab
module UsageDataCounters
class SearchCounter < BaseCounter
KNOWN_EVENTS = %w[all_searches navbar_searches].freeze
PREFIX = nil
class << self
def redis_key(event)
......
......@@ -20,6 +20,7 @@ UsageData/LargeTable:
- :Gitlab::Runtime
- :Gitaly::Server
- :Gitlab::UsageData
- :Gitlab::UsageDataCounters
- :License
- :Rails
- :Time
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::UsageDataCounters::BaseCounter do
describe '.fetch_supported_event' do
subject { described_class.fetch_supported_event(event_name) }
let(:event_name) { 'generic_event' }
let(:prefix) { 'generic' }
let(:known_events) { %w[event another_event] }
before do
allow(described_class).to receive(:prefix) { prefix }
allow(described_class).to receive(:known_events) { known_events }
end
it 'returns the matching event' do
is_expected.to eq 'event'
end
context 'when event is unknown' do
let(:event_name) { 'generic_unknown_event' }
it { is_expected.to be_nil }
end
context 'when prefix does not match the event name' do
let(:prefix) { 'special' }
it { is_expected.to be_nil }
end
end
end
......@@ -20,4 +20,12 @@ RSpec.describe Gitlab::UsageDataCounters::SearchCounter, :clean_gitlab_redis_sha
context 'navbar_searches counter' do
it_behaves_like 'usage counter with totals', :navbar_searches
end
describe '.fetch_supported_event' do
subject { described_class.fetch_supported_event(event_name) }
let(:event_name) { 'all_searches' }
it { is_expected.to eq 'all_searches' }
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::UsageDataCounters do
describe '.usage_data_counters' do
subject { described_class.counters }
it { is_expected.to all(respond_to :totals) }
it { is_expected.to all(respond_to :fallback_totals) }
end
describe '.count' do
subject { described_class.count(event_name) }
let(:event_name) { 'static_site_editor_views' }
it 'increases a view counter' do
expect(Gitlab::UsageDataCounters::StaticSiteEditorCounter).to receive(:count).with('views')
subject
end
context 'when event_name is not defined' do
let(:event_name) { 'unknown' }
it 'raises an exception' do
expect { subject }.to raise_error(Gitlab::UsageDataCounters::UnknownEvent)
end
end
end
end
......@@ -5,6 +5,87 @@ require 'spec_helper'
RSpec.describe API::UsageData do
let_it_be(:user) { create(:user) }
describe 'POST /usage_data/increment_counter' do
let(:endpoint) { '/usage_data/increment_counter' }
let(:known_event) { "#{known_event_prefix}_#{known_event_postfix}" }
let(:known_event_prefix) { "static_site_editor" }
let(:known_event_postfix) { 'commits' }
let(:unknown_event) { 'unknown' }
context 'without CSRF token' do
it 'returns forbidden' do
stub_feature_flags(usage_data_api: true)
allow(Gitlab::RequestForgeryProtection).to receive(:verified?).and_return(false)
post api(endpoint, user), params: { event: known_event }
expect(response).to have_gitlab_http_status(:forbidden)
end
end
context 'usage_data_api feature not enabled' do
it 'returns not_found' do
stub_feature_flags(usage_data_api: false)
post api(endpoint, user), params: { event: known_event }
expect(response).to have_gitlab_http_status(:not_found)
end
end
context 'without authentication' do
it 'returns 401 response' do
post api(endpoint), params: { event: known_event }
expect(response).to have_gitlab_http_status(:unauthorized)
end
end
context 'with authentication' do
before do
stub_feature_flags(usage_data_api: true)
stub_feature_flags("usage_data_#{known_event}" => true)
stub_application_setting(usage_ping_enabled: true)
allow(Gitlab::RequestForgeryProtection).to receive(:verified?).and_return(true)
end
context 'when event is missing from params' do
it 'returns bad request' do
post api(endpoint, user), params: {}
expect(response).to have_gitlab_http_status(:bad_request)
end
end
%w[merge_requests commits].each do |postfix|
context 'with correct params' do
let(:known_event_postfix) { postfix }
it 'returns status ok' do
expect(Gitlab::UsageDataCounters::BaseCounter).to receive(:count).with(known_event_postfix)
post api(endpoint, user), params: { event: known_event }
expect(response).to have_gitlab_http_status(:ok)
end
end
end
context 'with unknown event' do
before do
skip_feature_flags_yaml_validation
end
it 'returns status ok' do
expect(Gitlab::UsageDataCounters::BaseCounter).not_to receive(:count)
post api(endpoint, user), params: { event: unknown_event }
expect(response).to have_gitlab_http_status(:ok)
end
end
end
end
describe 'POST /usage_data/increment_unique_users' do
let(:endpoint) { '/usage_data/increment_unique_users' }
let(:known_event) { 'g_compliance_dashboard' }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment