Commit 872192f0 authored by Stan Hu's avatar Stan Hu

Merge branch '229918-more-issue-data' into 'master'

Add more Issue usage metrics

See merge request gitlab-org/gitlab!43228
parents 529fb7f7 eb39fdb4
...@@ -148,6 +148,7 @@ class Issue < ApplicationRecord ...@@ -148,6 +148,7 @@ class Issue < ApplicationRecord
after_commit :expire_etag_cache, unless: :importing? after_commit :expire_etag_cache, unless: :importing?
after_save :ensure_metrics, unless: :importing? after_save :ensure_metrics, unless: :importing?
after_create_commit :record_create_action, unless: :importing?
attr_spammable :title, spam_title: true attr_spammable :title, spam_title: true
attr_spammable :description, spam_description: true attr_spammable :description, spam_description: true
...@@ -429,6 +430,10 @@ class Issue < ApplicationRecord ...@@ -429,6 +430,10 @@ class Issue < ApplicationRecord
metrics.record! metrics.record!
end 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. # Returns `true` if the given User can read the current Issue.
# #
# This method duplicates the same check of issue_policy.rb # This method duplicates the same check of issue_policy.rb
......
...@@ -15,6 +15,7 @@ class ResourceLabelEvent < ResourceEvent ...@@ -15,6 +15,7 @@ class ResourceLabelEvent < ResourceEvent
validate :exactly_one_issuable validate :exactly_one_issuable
after_save :expire_etag_cache after_save :expire_etag_cache
after_save :usage_metrics
after_destroy :expire_etag_cache after_destroy :expire_etag_cache
enum action: { enum action: {
...@@ -113,6 +114,16 @@ class ResourceLabelEvent < ResourceEvent ...@@ -113,6 +114,16 @@ class ResourceLabelEvent < ResourceEvent
def discussion_id_key def discussion_id_key
[self.class.name, created_at, user_id] [self.class.name, created_at, user_id]
end 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 end
ResourceLabelEvent.prepend_if_ee('EE::ResourceLabelEvent') ResourceLabelEvent.prepend_if_ee('EE::ResourceLabelEvent')
...@@ -11,6 +11,8 @@ class ResourceStateEvent < ResourceEvent ...@@ -11,6 +11,8 @@ class ResourceStateEvent < ResourceEvent
# state is used for issue and merge request states. # state is used for issue and merge request states.
enum state: Issue.available_states.merge(MergeRequest.available_states).merge(reopened: 5) enum state: Issue.available_states.merge(MergeRequest.available_states).merge(reopened: 5)
after_save :usage_metrics
def self.issuable_attrs def self.issuable_attrs
%i(issue merge_request).freeze %i(issue merge_request).freeze
end end
...@@ -18,6 +20,29 @@ class ResourceStateEvent < ResourceEvent ...@@ -18,6 +20,29 @@ class ResourceStateEvent < ResourceEvent
def issuable def issuable
issue || merge_request issue || merge_request
end 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 end
ResourceStateEvent.prepend_if_ee('EE::ResourceStateEvent') ResourceStateEvent.prepend_if_ee('EE::ResourceStateEvent')
...@@ -13,6 +13,8 @@ class ResourceTimeboxEvent < ResourceEvent ...@@ -13,6 +13,8 @@ class ResourceTimeboxEvent < ResourceEvent
remove: 2 remove: 2
} }
after_save :usage_metrics
def self.issuable_attrs def self.issuable_attrs
%i(issue merge_request).freeze %i(issue merge_request).freeze
end end
...@@ -20,4 +22,17 @@ class ResourceTimeboxEvent < ResourceEvent ...@@ -20,4 +22,17 @@ class ResourceTimeboxEvent < ResourceEvent
def issuable def issuable
issue || merge_request issue || merge_request
end 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 end
# frozen_string_literal: true # frozen_string_literal: true
class ResourceWeightEvent < ResourceEvent class ResourceWeightEvent < ResourceEvent
include IssueResourceEvent
validates :issue, presence: true 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 end
...@@ -10,6 +10,7 @@ RSpec.describe ResourceIterationEvent, type: :model do ...@@ -10,6 +10,7 @@ RSpec.describe ResourceIterationEvent, type: :model do
it_behaves_like 'having unique enum values' it_behaves_like 'having unique enum values'
it_behaves_like 'timebox resource event validations' it_behaves_like 'timebox resource event validations'
it_behaves_like 'timebox resource event actions' it_behaves_like 'timebox resource event actions'
it_behaves_like 'timebox resource tracks issue metrics', :iteration
describe 'associations' do describe 'associations' do
it { is_expected.to belong_to(:iteration) } it { is_expected.to belong_to(:iteration) }
......
...@@ -3,14 +3,26 @@ ...@@ -3,14 +3,26 @@
module Gitlab module Gitlab
module UsageDataCounters module UsageDataCounters
module IssueActivityUniqueCounter module IssueActivityUniqueCounter
ISSUE_TITLE_CHANGED = 'g_project_management_issue_title_changed' ISSUE_CATEGORY = 'issues_edit'
ISSUE_DESCRIPTION_CHANGED = 'g_project_management_issue_description_changed'
ISSUE_ASSIGNEE_CHANGED = 'g_project_management_issue_assignee_changed' 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_CONFIDENTIAL = 'g_project_management_issue_made_confidential'
ISSUE_MADE_VISIBLE = 'g_project_management_issue_made_visible' 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 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) def track_issue_title_changed_action(author:, time: Time.zone.now)
track_unique_action(ISSUE_TITLE_CHANGED, author, time) track_unique_action(ISSUE_TITLE_CHANGED, author, time)
end end
...@@ -31,6 +43,30 @@ module Gitlab ...@@ -31,6 +43,30 @@ module Gitlab
track_unique_action(ISSUE_MADE_VISIBLE, author, time) track_unique_action(ISSUE_MADE_VISIBLE, author, time)
end 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 private
def track_unique_action(action, author, time) def track_unique_action(action, author, time)
......
...@@ -206,3 +206,31 @@ ...@@ -206,3 +206,31 @@
category: issues_edit category: issues_edit
redis_slot: project_management redis_slot: project_management
aggregation: daily 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 ...@@ -92,6 +92,46 @@ RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_git
end end
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 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_title_changed_action(author: user1)
described_class.track_issue_description_changed_action(author: user1) described_class.track_issue_description_changed_action(author: user1)
......
...@@ -105,6 +105,14 @@ RSpec.describe Issue do ...@@ -105,6 +105,14 @@ RSpec.describe Issue do
create(:issue, project: reusable_project) create(:issue, project: reusable_project)
end end
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 end
describe '.with_alert_management_alerts' do describe '.with_alert_management_alerts' do
......
...@@ -50,26 +50,36 @@ RSpec.describe ResourceLabelEvent, type: :model do ...@@ -50,26 +50,36 @@ RSpec.describe ResourceLabelEvent, type: :model do
end end
end end
describe '#expire_etag_cache' do context 'callbacks' do
def expect_expiration(issue) describe '#usage_metrics' do
expect_next_instance_of(Gitlab::EtagCaching::Store) do |instance| it 'tracks changed labels' do
expect(instance).to receive(:touch) expect(Gitlab::UsageDataCounters::IssueActivityUniqueCounter).to receive(:track_issue_label_changed_action)
.with("/#{issue.project.namespace.to_param}/#{issue.project.to_param}/noteable/issue/#{issue.id}/notes")
subject.save!
end end
end end
it 'expires resource note etag cache on event save' do describe '#expire_etag_cache' do
expect_expiration(subject.issuable) 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! it 'expires resource note etag cache on event save' do
end 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
end end
......
...@@ -11,6 +11,7 @@ RSpec.describe ResourceMilestoneEvent, type: :model do ...@@ -11,6 +11,7 @@ RSpec.describe ResourceMilestoneEvent, type: :model do
it_behaves_like 'timebox resource event validations' it_behaves_like 'timebox resource event validations'
it_behaves_like 'timebox resource event states' it_behaves_like 'timebox resource event states'
it_behaves_like 'timebox resource event actions' it_behaves_like 'timebox resource event actions'
it_behaves_like 'timebox resource tracks issue metrics', :milestone
describe 'associations' do describe 'associations' do
it { is_expected.to belong_to(:milestone) } it { is_expected.to belong_to(:milestone) }
......
...@@ -39,4 +39,20 @@ RSpec.describe ResourceStateEvent, type: :model do ...@@ -39,4 +39,20 @@ RSpec.describe ResourceStateEvent, type: :model do
end end
end 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 end
...@@ -73,4 +73,14 @@ RSpec.describe ResourceWeightEvent, type: :model do ...@@ -73,4 +73,14 @@ RSpec.describe ResourceWeightEvent, type: :model do
expect(event.discussion_id).to eq('73d167c478') expect(event.discussion_id).to eq('73d167c478')
end end
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 end
...@@ -73,3 +73,13 @@ RSpec.shared_examples 'timebox resource event actions' do ...@@ -73,3 +73,13 @@ RSpec.shared_examples 'timebox resource event actions' do
end end
end 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