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
event.update_columns(updated_at: time_stamp, created_at: time_stamp)
end
Gitlab::UsageDataCounters::TrackUniqueActions.track_action(event_action: action, event_target: wiki_page_meta.class, author_id: author.id)
event
end
......@@ -163,7 +165,13 @@ class EventCreateService
.merge(action: action, target_id: record.id, target_type: record.class.name)
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
def create_push_event(service_class, project, current_user, push_data)
......@@ -178,6 +186,8 @@ class EventCreateService
new_event
end
Gitlab::UsageDataCounters::TrackUniqueActions.track_action(event_action: :pushed, event_target: Project, author_id: current_user.id)
Users::LastPushEventService.new(current_user)
.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
| `remote_mirrors` | `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 |
| `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 | |
| `merge_requests_with_optional_codeowners` | `usage_activity_by_stage` | `create` | | EE | |
| `merge_requests_with_required_codeowners` | `usage_activity_by_stage` | `create` | | EE | |
......
......@@ -490,7 +490,10 @@ module Gitlab
remote_mirrors: distinct_count(::Project.with_remote_mirrors.where(time_period), :creator_id),
snippets: distinct_count(::Snippet.where(time_period), :author_id)
}.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
# rubocop: enable CodeReuse/ActiveRecord
......@@ -582,6 +585,42 @@ module Gitlab
{ analytics_unique_visits: results }
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
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
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
subject { described_class.analytics_unique_visits_data }
......
......@@ -166,7 +166,7 @@ RSpec.describe EventCreateService do
end
end
describe '#wiki_event' do
describe '#wiki_event', :clean_gitlab_redis_shared_state do
let_it_be(:user) { create(:user) }
let_it_be(:wiki_page) { create(: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
)
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
expect { event }.to change(Event, :count).by(1)
duplicate = nil
......@@ -224,6 +234,16 @@ RSpec.describe EventCreateService do
subject { service.push(project, user, push_data) }
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
describe '#bulk_push', :clean_gitlab_redis_shared_state do
......@@ -238,6 +258,16 @@ RSpec.describe EventCreateService do
subject { service.bulk_push(project, user, push_data) }
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
describe 'Project' do
......@@ -256,7 +286,7 @@ RSpec.describe EventCreateService do
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(:author) { user }
......@@ -297,6 +327,16 @@ RSpec.describe EventCreateService do
end
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
describe '#destroy_designs' do
......@@ -317,6 +357,16 @@ RSpec.describe EventCreateService do
end
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
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