Commit eb39fdb4 authored by Mario de la Ossa's avatar Mario de la Ossa

Add more Issue usage metrics

Get a unique MAU count for different metrics
Tracks:
- issue created
- issue closed
- issue reopened
- label changed
- milestone changed
- iteration changed
- weight changed
parent 0e74f73d
......@@ -145,6 +145,7 @@ class Issue < ApplicationRecord
after_commit :expire_etag_cache, unless: :importing?
after_save :ensure_metrics, unless: :importing?
after_create_commit :record_create_action, unless: :importing?
attr_spammable :title, spam_title: true
attr_spammable :description, spam_description: true
......@@ -420,6 +421,10 @@ class Issue < ApplicationRecord
metrics.record!
end
def record_create_action
Gitlab::UsageDataCounters::IssueActivityUniqueCounter.track_issue_created_action(author: author)
end
# Returns `true` if the given User can read the current Issue.
#
# This method duplicates the same check of issue_policy.rb
......
......@@ -15,6 +15,7 @@ class ResourceLabelEvent < ResourceEvent
validate :exactly_one_issuable
after_save :expire_etag_cache
after_save :usage_metrics
after_destroy :expire_etag_cache
enum action: {
......@@ -113,6 +114,16 @@ class ResourceLabelEvent < ResourceEvent
def discussion_id_key
[self.class.name, created_at, user_id]
end
def for_issue?
issue_id.present?
end
def usage_metrics
return unless for_issue?
Gitlab::UsageDataCounters::IssueActivityUniqueCounter.track_issue_label_changed_action(author: user)
end
end
ResourceLabelEvent.prepend_if_ee('EE::ResourceLabelEvent')
......@@ -11,6 +11,8 @@ class ResourceStateEvent < ResourceEvent
# state is used for issue and merge request states.
enum state: Issue.available_states.merge(MergeRequest.available_states).merge(reopened: 5)
after_save :usage_metrics
def self.issuable_attrs
%i(issue merge_request).freeze
end
......@@ -18,6 +20,29 @@ class ResourceStateEvent < ResourceEvent
def issuable
issue || merge_request
end
def for_issue?
issue_id.present?
end
private
def usage_metrics
return unless for_issue?
case state
when 'closed'
issue_usage_counter.track_issue_closed_action(author: user)
when 'reopened'
issue_usage_counter.track_issue_reopened_action(author: user)
else
# no-op, nothing to do, not a state we're tracking
end
end
def issue_usage_counter
Gitlab::UsageDataCounters::IssueActivityUniqueCounter
end
end
ResourceStateEvent.prepend_if_ee('EE::ResourceStateEvent')
......@@ -13,6 +13,8 @@ class ResourceTimeboxEvent < ResourceEvent
remove: 2
}
after_save :usage_metrics
def self.issuable_attrs
%i(issue merge_request).freeze
end
......@@ -20,4 +22,17 @@ class ResourceTimeboxEvent < ResourceEvent
def issuable
issue || merge_request
end
private
def usage_metrics
case self
when ResourceMilestoneEvent
Gitlab::UsageDataCounters::IssueActivityUniqueCounter.track_issue_milestone_changed_action(author: user)
when ResourceIterationEvent
Gitlab::UsageDataCounters::IssueActivityUniqueCounter.track_issue_iteration_changed_action(author: user)
else
# no-op
end
end
end
# frozen_string_literal: true
class ResourceWeightEvent < ResourceEvent
include IssueResourceEvent
validates :issue, presence: true
include IssueResourceEvent
after_save :usage_metrics
private
def usage_metrics
Gitlab::UsageDataCounters::IssueActivityUniqueCounter.track_issue_weight_changed_action(author: user)
end
end
......@@ -10,6 +10,7 @@ RSpec.describe ResourceIterationEvent, type: :model do
it_behaves_like 'having unique enum values'
it_behaves_like 'timebox resource event validations'
it_behaves_like 'timebox resource event actions'
it_behaves_like 'timebox resource tracks issue metrics', :iteration
describe 'associations' do
it { is_expected.to belong_to(:iteration) }
......
......@@ -3,14 +3,26 @@
module Gitlab
module UsageDataCounters
module IssueActivityUniqueCounter
ISSUE_TITLE_CHANGED = 'g_project_management_issue_title_changed'
ISSUE_DESCRIPTION_CHANGED = 'g_project_management_issue_description_changed'
ISSUE_CATEGORY = 'issues_edit'
ISSUE_ASSIGNEE_CHANGED = 'g_project_management_issue_assignee_changed'
ISSUE_CREATED = 'g_project_management_issue_created'
ISSUE_CLOSED = 'g_project_management_issue_closed'
ISSUE_DESCRIPTION_CHANGED = 'g_project_management_issue_description_changed'
ISSUE_ITERATION_CHANGED = 'g_project_management_issue_iteration_changed'
ISSUE_LABEL_CHANGED = 'g_project_management_issue_label_changed'
ISSUE_MADE_CONFIDENTIAL = 'g_project_management_issue_made_confidential'
ISSUE_MADE_VISIBLE = 'g_project_management_issue_made_visible'
ISSUE_CATEGORY = 'issues_edit'
ISSUE_MILESTONE_CHANGED = 'g_project_management_issue_milestone_changed'
ISSUE_REOPENED = 'g_project_management_issue_reopened'
ISSUE_TITLE_CHANGED = 'g_project_management_issue_title_changed'
ISSUE_WEIGHT_CHANGED = 'g_project_management_issue_weight_changed'
class << self
def track_issue_created_action(author:, time: Time.zone.now)
track_unique_action(ISSUE_CREATED, author, time)
end
def track_issue_title_changed_action(author:, time: Time.zone.now)
track_unique_action(ISSUE_TITLE_CHANGED, author, time)
end
......@@ -31,6 +43,30 @@ module Gitlab
track_unique_action(ISSUE_MADE_VISIBLE, author, time)
end
def track_issue_closed_action(author:, time: Time.zone.now)
track_unique_action(ISSUE_CLOSED, author, time)
end
def track_issue_reopened_action(author:, time: Time.zone.now)
track_unique_action(ISSUE_REOPENED, author, time)
end
def track_issue_label_changed_action(author:, time: Time.zone.now)
track_unique_action(ISSUE_LABEL_CHANGED, author, time)
end
def track_issue_milestone_changed_action(author:, time: Time.zone.now)
track_unique_action(ISSUE_MILESTONE_CHANGED, author, time)
end
def track_issue_iteration_changed_action(author:, time: Time.zone.now)
track_unique_action(ISSUE_ITERATION_CHANGED, author, time)
end
def track_issue_weight_changed_action(author:, time: Time.zone.now)
track_unique_action(ISSUE_WEIGHT_CHANGED, author, time)
end
private
def track_unique_action(action, author, time)
......
......@@ -206,3 +206,31 @@
category: issues_edit
redis_slot: project_management
aggregation: daily
- name: g_project_management_issue_created
category: issues_edit
redis_slot: project_management
aggregation: daily
- name: g_project_management_issue_closed
category: issues_edit
redis_slot: project_management
aggregation: daily
- name: g_project_management_issue_reopened
category: issues_edit
redis_slot: project_management
aggregation: daily
- name: g_project_management_issue_label_changed
category: issues_edit
redis_slot: project_management
aggregation: daily
- name: g_project_management_issue_milestone_changed
category: issues_edit
redis_slot: project_management
aggregation: daily
- name: g_project_management_issue_iteration_changed
category: issues_edit
redis_slot: project_management
aggregation: daily
- name: g_project_management_issue_weight_changed
category: issues_edit
redis_slot: project_management
aggregation: daily
......@@ -92,6 +92,46 @@ RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_git
end
end
context 'for Issue created actions' do
it_behaves_like 'tracks and counts action' do
let(:action) { described_class::ISSUE_CREATED }
def track_action(params)
described_class.track_issue_created_action(params)
end
end
end
context 'for Issue closed actions' do
it_behaves_like 'tracks and counts action' do
let(:action) { described_class::ISSUE_CLOSED }
def track_action(params)
described_class.track_issue_closed_action(params)
end
end
end
context 'for Issue reopened actions' do
it_behaves_like 'tracks and counts action' do
let(:action) { described_class::ISSUE_REOPENED }
def track_action(params)
described_class.track_issue_reopened_action(params)
end
end
end
context 'for Issue label changed actions' do
it_behaves_like 'tracks and counts action' do
let(:action) { described_class::ISSUE_LABEL_CHANGED }
def track_action(params)
described_class.track_issue_label_changed_action(params)
end
end
end
it 'can return the count of actions per user deduplicated', :aggregate_failures do
described_class.track_issue_title_changed_action(author: user1)
described_class.track_issue_description_changed_action(author: user1)
......
......@@ -105,6 +105,14 @@ RSpec.describe Issue do
create(:issue)
end
end
describe '#record_create_action' do
it 'records the creation action after saving' do
expect(Gitlab::UsageDataCounters::IssueActivityUniqueCounter).to receive(:track_issue_created_action)
create(:issue)
end
end
end
describe '.with_alert_management_alerts' do
......
......@@ -50,26 +50,36 @@ RSpec.describe ResourceLabelEvent, type: :model do
end
end
describe '#expire_etag_cache' do
def expect_expiration(issue)
expect_next_instance_of(Gitlab::EtagCaching::Store) do |instance|
expect(instance).to receive(:touch)
.with("/#{issue.project.namespace.to_param}/#{issue.project.to_param}/noteable/issue/#{issue.id}/notes")
context 'callbacks' do
describe '#usage_metrics' do
it 'tracks changed labels' do
expect(Gitlab::UsageDataCounters::IssueActivityUniqueCounter).to receive(:track_issue_label_changed_action)
subject.save!
end
end
it 'expires resource note etag cache on event save' do
expect_expiration(subject.issuable)
describe '#expire_etag_cache' do
def expect_expiration(issue)
expect_next_instance_of(Gitlab::EtagCaching::Store) do |instance|
expect(instance).to receive(:touch)
.with("/#{issue.project.namespace.to_param}/#{issue.project.to_param}/noteable/issue/#{issue.id}/notes")
end
end
subject.save!
end
it 'expires resource note etag cache on event save' do
expect_expiration(subject.issuable)
it 'expires resource note etag cache on event destroy' do
subject.save!
subject.save!
end
it 'expires resource note etag cache on event destroy' do
subject.save!
expect_expiration(subject.issuable)
expect_expiration(subject.issuable)
subject.destroy!
subject.destroy!
end
end
end
......
......@@ -11,6 +11,7 @@ RSpec.describe ResourceMilestoneEvent, type: :model do
it_behaves_like 'timebox resource event validations'
it_behaves_like 'timebox resource event states'
it_behaves_like 'timebox resource event actions'
it_behaves_like 'timebox resource tracks issue metrics', :milestone
describe 'associations' do
it { is_expected.to belong_to(:milestone) }
......
......@@ -39,4 +39,20 @@ RSpec.describe ResourceStateEvent, type: :model do
end
end
end
context 'callbacks' do
describe '#usage_metrics' do
it 'tracks closed issues' do
expect(Gitlab::UsageDataCounters::IssueActivityUniqueCounter).to receive(:track_issue_closed_action)
create(described_class.name.underscore.to_sym, issue: issue, state: described_class.states[:closed])
end
it 'tracks reopened issues' do
expect(Gitlab::UsageDataCounters::IssueActivityUniqueCounter).to receive(:track_issue_reopened_action)
create(described_class.name.underscore.to_sym, issue: issue, state: described_class.states[:reopened])
end
end
end
end
......@@ -73,4 +73,14 @@ RSpec.describe ResourceWeightEvent, type: :model do
expect(event.discussion_id).to eq('73d167c478')
end
end
context 'callbacks' do
describe '#usage_metrics' do
it 'tracks changed weights' do
expect(Gitlab::UsageDataCounters::IssueActivityUniqueCounter).to receive(:track_issue_weight_changed_action).with(author: user1)
create(:resource_weight_event, issue: issue1, user: user1)
end
end
end
end
......@@ -73,3 +73,13 @@ RSpec.shared_examples 'timebox resource event actions' do
end
end
end
RSpec.shared_examples 'timebox resource tracks issue metrics' do |type|
describe '#usage_metrics' do
it 'tracks usage' do
expect(Gitlab::UsageDataCounters::IssueActivityUniqueCounter).to receive(:"track_issue_#{type}_changed_action")
create(described_class.name.underscore.to_sym, issue: create(:issue))
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