Commit 5433dadb authored by Sean McGivern's avatar Sean McGivern

Merge branch '219956-instrument-last-git-write-operation-per-user' into 'master'

Resolve "Instrument last Git write operation per user"

See merge request gitlab-org/gitlab!35580
parents df1b576d 5219f392
...@@ -119,6 +119,8 @@ class EventCreateService ...@@ -119,6 +119,8 @@ class EventCreateService
event.update_columns(updated_at: time_stamp, created_at: time_stamp) event.update_columns(updated_at: time_stamp, created_at: time_stamp)
end end
Gitlab::UsageDataCounters::TrackUniqueActions.track_action(event_action: action, event_target: wiki_page_meta.class, author_id: author.id)
event event
end end
...@@ -163,7 +165,13 @@ class EventCreateService ...@@ -163,7 +165,13 @@ class EventCreateService
.merge(action: action, target_id: record.id, target_type: record.class.name) .merge(action: action, target_id: record.id, target_type: record.class.name)
end end
Event.insert_all(attribute_sets, returning: %w[id]) result = Event.insert_all(attribute_sets, returning: %w[id])
pairs.each do |record, status|
Gitlab::UsageDataCounters::TrackUniqueActions.track_action(event_action: status, event_target: record.class, author_id: current_user.id)
end
result
end end
def create_push_event(service_class, project, current_user, push_data) def create_push_event(service_class, project, current_user, push_data)
...@@ -178,6 +186,8 @@ class EventCreateService ...@@ -178,6 +186,8 @@ class EventCreateService
new_event new_event
end end
Gitlab::UsageDataCounters::TrackUniqueActions.track_action(event_action: :pushed, event_target: Project, author_id: current_user.id)
Users::LastPushEventService.new(current_user) Users::LastPushEventService.new(current_user)
.cache_last_push_event(event) .cache_last_push_event(event)
......
---
title: Track the number of unique users who push, change wikis and change design managerment
merge_request:
author:
type: other
...@@ -658,6 +658,9 @@ appear to be associated to any of the services running, since they all appear to ...@@ -658,6 +658,9 @@ appear to be associated to any of the services running, since they all appear to
| `remote_mirrors` | `usage_activity_by_stage` | `create` | | CE+EE | | | `remote_mirrors` | `usage_activity_by_stage` | `create` | | CE+EE | |
| `snippets` | `usage_activity_by_stage` | `create` | | CE+EE | | | `snippets` | `usage_activity_by_stage` | `create` | | CE+EE | |
| `merge_requests_users` | `usage_activity_by_stage_monthly` | `create` | | CE+EE | Unique count of users who used a merge request | | `merge_requests_users` | `usage_activity_by_stage_monthly` | `create` | | CE+EE | Unique count of users who used a merge request |
| `action_monthly_active_users_project_repo` | `usage_activity_by_stage_monthly` | `create` | | CE+EE | Unique count of users who pushed to a project repo |
| `action_monthly_active_users_design_management` | `usage_activity_by_stage_monthly` | `create` | | CE+EE | Unique count of users who interacted with the design system management |
| `action_monthly_active_users_wiki_repo` | `usage_activity_by_stage_monthly` | `create` | | CE+EE | Unique count of users who created or updated a wiki repo |
| `projects_enforcing_code_owner_approval` | `usage_activity_by_stage` | `create` | | EE | | | `projects_enforcing_code_owner_approval` | `usage_activity_by_stage` | `create` | | EE | |
| `merge_requests_with_optional_codeowners` | `usage_activity_by_stage` | `create` | | EE | | | `merge_requests_with_optional_codeowners` | `usage_activity_by_stage` | `create` | | EE | |
| `merge_requests_with_required_codeowners` | `usage_activity_by_stage` | `create` | | EE | | | `merge_requests_with_required_codeowners` | `usage_activity_by_stage` | `create` | | EE | |
......
...@@ -490,7 +490,10 @@ module Gitlab ...@@ -490,7 +490,10 @@ module Gitlab
remote_mirrors: distinct_count(::Project.with_remote_mirrors.where(time_period), :creator_id), remote_mirrors: distinct_count(::Project.with_remote_mirrors.where(time_period), :creator_id),
snippets: distinct_count(::Snippet.where(time_period), :author_id) snippets: distinct_count(::Snippet.where(time_period), :author_id)
}.tap do |h| }.tap do |h|
h[:merge_requests_users] = merge_requests_users(time_period) if time_period.present? if time_period.present?
h[:merge_requests_users] = merge_requests_users(time_period)
h.merge!(action_monthly_active_users(time_period))
end
end end
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
...@@ -582,6 +585,42 @@ module Gitlab ...@@ -582,6 +585,42 @@ module Gitlab
{ analytics_unique_visits: results } { analytics_unique_visits: results }
end end
def action_monthly_active_users(time_period)
return {} unless Feature.enabled?(Gitlab::UsageDataCounters::TrackUniqueActions::FEATURE_FLAG)
counter = Gitlab::UsageDataCounters::TrackUniqueActions
project_count = redis_usage_data do
counter.count_unique_events(
event_action: Gitlab::UsageDataCounters::TrackUniqueActions::PUSH_ACTION,
date_from: time_period[:created_at].first,
date_to: time_period[:created_at].last
)
end
design_count = redis_usage_data do
counter.count_unique_events(
event_action: Gitlab::UsageDataCounters::TrackUniqueActions::DESIGN_ACTION,
date_from: time_period[:created_at].first,
date_to: time_period[:created_at].last
)
end
wiki_count = redis_usage_data do
counter.count_unique_events(
event_action: Gitlab::UsageDataCounters::TrackUniqueActions::WIKI_ACTION,
date_from: time_period[:created_at].first,
date_to: time_period[:created_at].last
)
end
{
action_monthly_active_users_project_repo: project_count,
action_monthly_active_users_design_management: design_count,
action_monthly_active_users_wiki_repo: wiki_count
}
end
private private
def unique_visit_service def unique_visit_service
......
# frozen_string_literal: true
module Gitlab
module UsageDataCounters
module TrackUniqueActions
KEY_EXPIRY_LENGTH = 29.days
FEATURE_FLAG = :track_unique_actions
WIKI_ACTION = :wiki_action
DESIGN_ACTION = :design_action
PUSH_ACTION = :project_action
ACTION_TRANSFORMATIONS = HashWithIndifferentAccess.new({
wiki: {
created: WIKI_ACTION,
updated: WIKI_ACTION,
destroyed: WIKI_ACTION
},
design: {
created: DESIGN_ACTION,
updated: DESIGN_ACTION,
destroyed: DESIGN_ACTION
},
project: {
pushed: PUSH_ACTION
}
}).freeze
class << self
def track_action(event_action:, event_target:, author_id:, time: Time.zone.now)
return unless Gitlab::CurrentSettings.usage_ping_enabled
return unless Feature.enabled?(FEATURE_FLAG)
return unless valid_target?(event_target)
return unless valid_action?(event_action)
transformed_target = transform_target(event_target)
transformed_action = transform_action(event_action, transformed_target)
add_event(transformed_action, author_id, time)
end
def count_unique_events(event_action:, date_from:, date_to:)
keys = (date_from.to_date..date_to.to_date).map { |date| key(event_action, date) }
Gitlab::Redis::SharedState.with do |redis|
redis.pfcount(*keys)
end
end
private
def transform_action(event_action, event_target)
ACTION_TRANSFORMATIONS.dig(event_target, event_action) || event_action
end
def transform_target(event_target)
Event::TARGET_TYPES.key(event_target)
end
def valid_target?(target)
Event::TARGET_TYPES.value?(target)
end
def valid_action?(action)
Event.actions.key?(action)
end
def key(event_action, date)
year_day = date.strftime('%G-%j')
"#{year_day}-{#{event_action}}"
end
def add_event(event_action, author_id, date)
target_key = key(event_action, date)
Gitlab::Redis::SharedState.with do |redis|
redis.multi do |multi|
multi.pfadd(target_key, author_id)
multi.expire(target_key, KEY_EXPIRY_LENGTH)
end
end
end
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::UsageDataCounters::TrackUniqueActions, :clean_gitlab_redis_shared_state do
subject(:track_unique_events) { described_class }
let(:time) { Time.zone.now }
def track_action(params)
track_unique_events.track_action(params)
end
def count_unique_events(params)
track_unique_events.count_unique_events(params)
end
context 'tracking an event' do
context 'when tracking successfully' do
context 'when the feature flag and the application setting is enabled' do
context 'when the target and the action is valid' do
before do
stub_feature_flags(described_class::FEATURE_FLAG => true)
stub_application_setting(usage_ping_enabled: true)
end
it 'tracks and counts the events as expected' do
project = Event::TARGET_TYPES[:project]
design = Event::TARGET_TYPES[:design]
wiki = Event::TARGET_TYPES[:wiki]
expect(track_action(event_action: :pushed, event_target: project, author_id: 1)).to be_truthy
expect(track_action(event_action: :pushed, event_target: project, author_id: 1)).to be_truthy
expect(track_action(event_action: :pushed, event_target: project, author_id: 2)).to be_truthy
expect(track_action(event_action: :pushed, event_target: project, author_id: 3)).to be_truthy
expect(track_action(event_action: :pushed, event_target: project, author_id: 4, time: time - 3.days)).to be_truthy
expect(track_action(event_action: :created, event_target: project, author_id: 5, time: time - 3.days)).to be_truthy
expect(track_action(event_action: :destroyed, event_target: design, author_id: 3)).to be_truthy
expect(track_action(event_action: :created, event_target: design, author_id: 4)).to be_truthy
expect(track_action(event_action: :updated, event_target: design, author_id: 5)).to be_truthy
expect(track_action(event_action: :pushed, event_target: design, author_id: 6)).to be_truthy
expect(track_action(event_action: :destroyed, event_target: wiki, author_id: 5)).to be_truthy
expect(track_action(event_action: :created, event_target: wiki, author_id: 3)).to be_truthy
expect(track_action(event_action: :updated, event_target: wiki, author_id: 4)).to be_truthy
expect(track_action(event_action: :pushed, event_target: wiki, author_id: 6)).to be_truthy
expect(count_unique_events(event_action: described_class::PUSH_ACTION, date_from: time, date_to: Date.today)).to eq(3)
expect(count_unique_events(event_action: described_class::PUSH_ACTION, date_from: time - 5.days, date_to: Date.tomorrow)).to eq(4)
expect(count_unique_events(event_action: described_class::DESIGN_ACTION, date_from: time - 5.days, date_to: Date.today)).to eq(3)
expect(count_unique_events(event_action: described_class::WIKI_ACTION, date_from: time - 5.days, date_to: Date.today)).to eq(3)
expect(count_unique_events(event_action: described_class::PUSH_ACTION, date_from: time - 5.days, date_to: time - 2.days)).to eq(1)
end
end
end
end
context 'when tracking unsuccessfully' do
using RSpec::Parameterized::TableSyntax
where(:feature_flag, :application_setting, :target, :action) do
true | true | Project | :invalid_action
false | true | Project | :pushed
true | false | Project | :pushed
true | true | :invalid_target | :pushed
end
with_them do
before do
stub_application_setting(usage_ping_enabled: application_setting)
stub_feature_flags(described_class::FEATURE_FLAG => feature_flag)
end
it 'returns the expected values' do
expect(track_action(event_action: action, event_target: target, author_id: 2)).to be_nil
expect(count_unique_events(event_action: described_class::PUSH_ACTION, date_from: time, date_to: Date.today)).to eq(0)
end
end
end
end
end
...@@ -919,6 +919,53 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do ...@@ -919,6 +919,53 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
end end
end end
describe '#action_monthly_active_users', :clean_gitlab_redis_shared_state do
let(:time_period) { { created_at: 2.days.ago..time } }
let(:time) { Time.zone.now }
before do
stub_feature_flags(Gitlab::UsageDataCounters::TrackUniqueActions::FEATURE_FLAG => feature_flag)
end
context 'when the feature flag is enabled' do
let(:feature_flag) { true }
before do
counter = Gitlab::UsageDataCounters::TrackUniqueActions
project = Event::TARGET_TYPES[:project]
wiki = Event::TARGET_TYPES[:wiki]
design = Event::TARGET_TYPES[:design]
counter.track_action(event_action: :pushed, event_target: project, author_id: 1)
counter.track_action(event_action: :pushed, event_target: project, author_id: 1)
counter.track_action(event_action: :pushed, event_target: project, author_id: 2)
counter.track_action(event_action: :pushed, event_target: project, author_id: 3)
counter.track_action(event_action: :pushed, event_target: project, author_id: 4, time: time - 3.days)
counter.track_action(event_action: :created, event_target: project, author_id: 5, time: time - 3.days)
counter.track_action(event_action: :created, event_target: wiki, author_id: 3)
counter.track_action(event_action: :created, event_target: design, author_id: 3)
end
it 'returns the distinct count of user actions within the specified time period' do
expect(described_class.action_monthly_active_users(time_period)).to eq(
{
action_monthly_active_users_design_management: 1,
action_monthly_active_users_project_repo: 3,
action_monthly_active_users_wiki_repo: 1
}
)
end
end
context 'when the feature flag is disabled' do
let(:feature_flag) { false }
it 'returns an empty hash' do
expect(described_class.action_monthly_active_users(time_period)).to eq({})
end
end
end
describe '.analytics_unique_visits_data' do describe '.analytics_unique_visits_data' do
subject { described_class.analytics_unique_visits_data } subject { described_class.analytics_unique_visits_data }
......
...@@ -166,7 +166,7 @@ RSpec.describe EventCreateService do ...@@ -166,7 +166,7 @@ RSpec.describe EventCreateService do
end end
end end
describe '#wiki_event' do describe '#wiki_event', :clean_gitlab_redis_shared_state do
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
let_it_be(:wiki_page) { create(:wiki_page) } let_it_be(:wiki_page) { create(:wiki_page) }
let_it_be(:meta) { create(:wiki_page_meta, :for_wiki_page, wiki_page: wiki_page) } let_it_be(:meta) { create(:wiki_page_meta, :for_wiki_page, wiki_page: wiki_page) }
...@@ -186,6 +186,16 @@ RSpec.describe EventCreateService do ...@@ -186,6 +186,16 @@ RSpec.describe EventCreateService do
) )
end end
it 'records the event in the event counter' do
stub_feature_flags(Gitlab::UsageDataCounters::TrackUniqueActions::FEATURE_FLAG => true)
counter_class = Gitlab::UsageDataCounters::TrackUniqueActions
tracking_params = { event_action: counter_class::WIKI_ACTION, date_from: Date.yesterday, date_to: Date.today }
expect { event }
.to change { counter_class.count_unique_events(tracking_params) }
.from(0).to(1)
end
it 'is idempotent', :aggregate_failures do it 'is idempotent', :aggregate_failures do
expect { event }.to change(Event, :count).by(1) expect { event }.to change(Event, :count).by(1)
duplicate = nil duplicate = nil
...@@ -224,6 +234,16 @@ RSpec.describe EventCreateService do ...@@ -224,6 +234,16 @@ RSpec.describe EventCreateService do
subject { service.push(project, user, push_data) } subject { service.push(project, user, push_data) }
it_behaves_like 'service for creating a push event', PushEventPayloadService it_behaves_like 'service for creating a push event', PushEventPayloadService
it 'records the event in the event counter' do
stub_feature_flags(Gitlab::UsageDataCounters::TrackUniqueActions::FEATURE_FLAG => true)
counter_class = Gitlab::UsageDataCounters::TrackUniqueActions
tracking_params = { event_action: counter_class::PUSH_ACTION, date_from: Date.yesterday, date_to: Date.today }
expect { subject }
.to change { counter_class.count_unique_events(tracking_params) }
.from(0).to(1)
end
end end
describe '#bulk_push', :clean_gitlab_redis_shared_state do describe '#bulk_push', :clean_gitlab_redis_shared_state do
...@@ -238,6 +258,16 @@ RSpec.describe EventCreateService do ...@@ -238,6 +258,16 @@ RSpec.describe EventCreateService do
subject { service.bulk_push(project, user, push_data) } subject { service.bulk_push(project, user, push_data) }
it_behaves_like 'service for creating a push event', BulkPushEventPayloadService it_behaves_like 'service for creating a push event', BulkPushEventPayloadService
it 'records the event in the event counter' do
stub_feature_flags(Gitlab::UsageDataCounters::TrackUniqueActions::FEATURE_FLAG => true)
counter_class = Gitlab::UsageDataCounters::TrackUniqueActions
tracking_params = { event_action: counter_class::PUSH_ACTION, date_from: Date.yesterday, date_to: Date.today }
expect { subject }
.to change { counter_class.count_unique_events(tracking_params) }
.from(0).to(1)
end
end end
describe 'Project' do describe 'Project' do
...@@ -256,7 +286,7 @@ RSpec.describe EventCreateService do ...@@ -256,7 +286,7 @@ RSpec.describe EventCreateService do
end end
end end
describe 'design events' do describe 'design events', :clean_gitlab_redis_shared_state do
let_it_be(:design) { create(:design, project: project) } let_it_be(:design) { create(:design, project: project) }
let_it_be(:author) { user } let_it_be(:author) { user }
...@@ -297,6 +327,16 @@ RSpec.describe EventCreateService do ...@@ -297,6 +327,16 @@ RSpec.describe EventCreateService do
end end
it_behaves_like 'feature flag gated multiple event creation' it_behaves_like 'feature flag gated multiple event creation'
it 'records the event in the event counter' do
stub_feature_flags(Gitlab::UsageDataCounters::TrackUniqueActions::FEATURE_FLAG => true)
counter_class = Gitlab::UsageDataCounters::TrackUniqueActions
tracking_params = { event_action: counter_class::DESIGN_ACTION, date_from: Date.yesterday, date_to: Date.today }
expect { result }
.to change { counter_class.count_unique_events(tracking_params) }
.from(0).to(1)
end
end end
describe '#destroy_designs' do describe '#destroy_designs' do
...@@ -317,6 +357,16 @@ RSpec.describe EventCreateService do ...@@ -317,6 +357,16 @@ RSpec.describe EventCreateService do
end end
it_behaves_like 'feature flag gated multiple event creation' it_behaves_like 'feature flag gated multiple event creation'
it 'records the event in the event counter' do
stub_feature_flags(Gitlab::UsageDataCounters::TrackUniqueActions::FEATURE_FLAG => true)
counter_class = Gitlab::UsageDataCounters::TrackUniqueActions
tracking_params = { event_action: counter_class::DESIGN_ACTION, date_from: Date.yesterday, date_to: Date.today }
expect { result }
.to change { counter_class.count_unique_events(tracking_params) }
.from(0).to(1)
end
end end
end end
end end
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