Commit 008dbea2 authored by Mayra Cabrera's avatar Mayra Cabrera

Merge branch '233933-poc-inicident-management-metrics' into 'master'

Add incident management analytics events

See merge request gitlab-org/gitlab!40475
parents 4a78ec9f e71ac60c
......@@ -20,6 +20,8 @@ module Mutations
alert = authorized_find!(project_path: args[:project_path], iid: args[:iid])
result = set_assignees(alert, args[:assignee_usernames], args[:operation_mode])
track_usage_event(:incident_management_alert_assigned, current_user)
prepare_response(result)
end
......
......@@ -11,6 +11,8 @@ module Mutations
alert = authorized_find!(project_path: args[:project_path], iid: args[:iid])
result = ::AlertManagement::Alerts::Todo::CreateService.new(alert, current_user).execute
track_usage_event(:incident_management_alert_todo, current_user)
prepare_response(result)
end
......
......@@ -3,6 +3,7 @@
module Mutations
module AlertManagement
class Base < BaseMutation
include Gitlab::Utils::UsageData
include ResolvesProject
argument :project_path, GraphQL::ID_TYPE,
......
......@@ -9,6 +9,8 @@ module Mutations
alert = authorized_find!(project_path: args[:project_path], iid: args[:iid])
result = create_alert_issue(alert, current_user)
track_usage_event(:incident_management_incident_created, current_user)
prepare_response(alert, result)
end
......
......@@ -13,6 +13,8 @@ module Mutations
alert = authorized_find!(project_path: args[:project_path], iid: args[:iid])
result = update_status(alert, args[:status])
track_usage_event(:incident_management_alert_status_changed, current_user)
prepare_response(result)
end
......
# frozen_string_literal: true
module IncidentManagement
module UsageData
include Gitlab::Utils::UsageData
def track_incident_action(current_user, target, action)
return unless target.incident?
track_usage_event(:"incident_management_#{action}", current_user)
end
# No-op as optionally overridden in implementing classes.
# For use to provide checks before calling #track_incident_action.
def track_event
end
end
end
......@@ -2,6 +2,8 @@
module IssuableLinks
class CreateService < BaseService
include IncidentManagement::UsageData
attr_reader :issuable, :current_user, :params
def initialize(issuable, user, params)
......@@ -27,6 +29,8 @@ module IssuableLinks
return error(@errors.join('. '), 422)
end
track_event
success
end
......
......@@ -2,6 +2,8 @@
module IssuableLinks
class DestroyService < BaseService
include IncidentManagement::UsageData
attr_reader :link, :current_user
def initialize(link, user)
......@@ -14,6 +16,7 @@ module IssuableLinks
remove_relation
create_notes
track_event
success(message: 'Relation was removed')
end
......
......@@ -36,6 +36,10 @@ module IssueLinks
def set_link_type(_link)
# EE only
end
def track_event
track_incident_action(current_user, issuable, :incident_relate)
end
end
end
......
......@@ -20,5 +20,9 @@ module IssueLinks
SystemNoteService.unrelate_issue(source, target, current_user)
SystemNoteService.unrelate_issue(target, source, current_user)
end
def track_event
track_incident_action(current_user, target, :incident_unrelate)
end
end
end
......@@ -2,6 +2,8 @@
module Issues
class BaseService < ::IssuableBaseService
include IncidentManagement::UsageData
def hook_data(issue, action, old_associations: {})
hook_data = issue.to_hook_data(current_user, old_associations: old_associations)
hook_data[:object_attributes][:action] = action
......
......@@ -37,6 +37,7 @@ module Issues
execute_hooks(issue, 'close')
invalidate_cache_counts(issue, users: issue.assignees)
issue.update_project_counter_caches
track_incident_action(current_user, issue, :incident_closed)
store_first_mentioned_in_commit_at(issue, closed_via) if closed_via.is_a?(MergeRequest)
......
......@@ -30,6 +30,7 @@ module Issues
user_agent_detail_service.create
resolve_discussions_with_issue(issuable)
delete_milestone_total_issue_counter_cache(issuable.milestone)
track_incident_action(current_user, issuable, :incident_created)
super
end
......
......@@ -13,6 +13,7 @@ module Issues
invalidate_cache_counts(issue, users: issue.assignees)
issue.update_project_counter_caches
delete_milestone_closed_issue_counter_cache(issue.milestone)
track_incident_action(current_user, issue, :incident_reopened)
end
issue
......
......@@ -44,12 +44,14 @@ module Issues
create_assignee_note(issue, old_assignees)
notification_service.async.reassigned_issue(issue, current_user, old_assignees)
todo_service.reassigned_assignable(issue, current_user, old_assignees)
track_incident_action(current_user, issue, :incident_assigned)
end
if issue.previous_changes.include?('confidential')
# don't enqueue immediately to prevent todos removal in case of a mistake
TodosDestroyer::ConfidentialIssueWorker.perform_in(Todo::WAIT_FOR_DELETE, issue.id) if issue.confidential?
create_confidentiality_note(issue)
track_usage_event(:incident_management_incident_change_confidential, current_user)
end
added_labels = issue.labels - old_labels
......
......@@ -60,6 +60,7 @@ module Issues
if @issue.persisted?
# Save the meeting directly since we only want to update one meeting, not all
zoom_meeting.save
track_incident_action(current_user, issue, :incident_zoom_meeting)
success(message: _('Zoom meeting added'))
else
success(message: _('Zoom meeting added'), payload: { zoom_meetings: [zoom_meeting] })
......
......@@ -2,6 +2,8 @@
module Notes
class CreateService < ::Notes::BaseService
include IncidentManagement::UsageData
def execute
note = Notes::BuildService.new(project, current_user, params.except(:merge_request_diff_head_sha)).execute
......@@ -62,6 +64,7 @@ module Notes
clear_noteable_diffs_cache(note)
Suggestions::CreateService.new(note).execute
increment_usage_counter(note)
track_event(note, current_user)
if Feature.enabled?(:notes_create_service_tracking, project)
Gitlab::Tracking.event('Notes::CreateService', 'execute', tracking_data_for(note))
......@@ -104,5 +107,11 @@ module Notes
value: note.id
}
end
def track_event(note, user)
return unless note.noteable.is_a?(Issue) && note.noteable.incident?
track_usage_event(:incident_management_incident_comment, user)
end
end
end
......@@ -8,6 +8,7 @@
# TodoService.new.new_issue(issue, current_user)
#
class TodoService
include Gitlab::Utils::UsageData
# When create an issue we should:
#
# * create a todo for assignee if issue is assigned
......@@ -217,6 +218,9 @@ class TodoService
Array(users).map do |user|
next if pending_todos(user, attributes).exists?
issue_type = attributes.delete(:issue_type)
track_todo_creation(user, issue_type)
todo = Todo.create(attributes.merge(user_id: user.id))
user.update_todos_count_cache
todo
......@@ -299,6 +303,8 @@ class TodoService
if target.is_a?(Commit)
attributes.merge!(target_id: nil, commit_id: target.id)
elsif target.is_a?(Issue)
attributes[:issue_type] = target.issue_type
end
attributes
......@@ -346,6 +352,12 @@ class TodoService
def pending_todos(user, criteria = {})
PendingTodosFinder.new(user, criteria).execute
end
def track_todo_creation(user, issue_type)
return unless issue_type == 'incident'
track_usage_event(:incident_management_incident_todo, user)
end
end
TodoService.prepend_if_ee('EE::TodoService')
---
title: Add incident management analytics events
merge_request: 40475
author:
type: added
......@@ -14,6 +14,7 @@ module StatusPage
# * StatusPage::PublishListService
class PublishService
include Gitlab::Utils::StrongMemoize
include Gitlab::Utils::UsageData
def initialize(user:, project:, issue_id:)
@user = user
......@@ -28,6 +29,8 @@ module StatusPage
response = process_details
return response if response.error?
track_event
process_list
end
......@@ -36,12 +39,14 @@ module StatusPage
attr_reader :user, :project, :issue_id
def process_details
unpublish_details? ? unpublish_details : publish_details
should_unpublish? ? unpublish_details : publish_details
end
def unpublish_details?
def should_unpublish?
strong_memoize(:should_unpublish) do
issue.confidential? || !issue.status_page_published_incident
end
end
def process_list
PublishListService.new(project: project).execute(issues)
......@@ -90,5 +95,9 @@ module StatusPage
def error(message)
ServiceResponse.error(message: message)
end
def track_event
track_usage_event(:incident_management_incident_published, user) unless should_unpublish?
end
end
end
......@@ -25,12 +25,18 @@ RSpec.describe StatusPage::PublishService do
describe 'publish details' do
context 'when upload succeeds' do
it 'uploads incident details and list' do
before do
expect_to_publish_details(error?: false, success?: true)
expect_to_publish_list(error?: false, success?: true)
end
it 'uploads incident details and list' do
expect(result).to be_success
end
it_behaves_like 'an incident management tracked event', :incident_management_incident_published do
let(:current_user) { user }
end
end
context 'when upload fails' do
......@@ -52,6 +58,8 @@ RSpec.describe StatusPage::PublishService do
expect(result).to be_success
end
it_behaves_like 'does not track incident management event', :incident_management_incident_published
end
context 'when unpublish service responses with error' do
......
......@@ -116,3 +116,60 @@
- name: merge_request_action
category: source_code
aggregation: daily
# Incident management
- name: incident_management_alert_status_changed
redis_slot: incident_management
category: incident_management
aggregation: weekly
- name: incident_management_alert_assigned
redis_slot: incident_management
category: incident_management
aggregation: weekly
- name: incident_management_alert_todo
redis_slot: incident_management
category: incident_management
aggregation: weekly
- name: incident_management_incident_created
redis_slot: incident_management
category: incident_management
aggregation: weekly
- name: incident_management_incident_reopened
redis_slot: incident_management
category: incident_management
aggregation: weekly
- name: incident_management_incident_closed
redis_slot: incident_management
category: incident_management
aggregation: weekly
- name: incident_management_incident_assigned
redis_slot: incident_management
category: incident_management
aggregation: weekly
- name: incident_management_incident_todo
redis_slot: incident_management
category: incident_management
aggregation: weekly
- name: incident_management_incident_comment
redis_slot: incident_management
category: incident_management
aggregation: weekly
- name: incident_management_incident_zoom_meeting
redis_slot: incident_management
category: incident_management
aggregation: weekly
- name: incident_management_incident_published
redis_slot: incident_management
category: incident_management
aggregation: weekly
- name: incident_management_incident_relate
redis_slot: incident_management
category: incident_management
aggregation: weekly
- name: incident_management_incident_unrelate
redis_slot: incident_management
category: incident_management
aggregation: weekly
- name: incident_management_incident_change_confidential
redis_slot: incident_management
category: incident_management
aggregation: weekly
......@@ -96,6 +96,13 @@ module Gitlab
yield.merge(key => Time.current)
end
def track_usage_event(metric_name, target)
return unless Feature.enabled?(:"usage_data_#{metric_name}", default_enabled: true)
return unless Gitlab::CurrentSettings.usage_ping_enabled?
Gitlab::UsageDataCounters::HLLRedisCounter.track_event(target.id, metric_name.to_s)
end
private
def redis_usage_counter
......
......@@ -55,6 +55,7 @@ RSpec.describe Mutations::AlertManagement::Alerts::SetAssignees do
context 'when operation mode is not specified' do
it_behaves_like 'successful resolution'
it_behaves_like 'an incident management tracked event', :incident_management_alert_assigned
end
context 'when user does not have permission to update alerts' do
......
......@@ -16,6 +16,8 @@ RSpec.describe Mutations::AlertManagement::Alerts::Todo::Create do
describe '#resolve' do
subject(:resolve) { mutation.resolve(args) }
it_behaves_like 'an incident management tracked event', :incident_management_alert_todo
context 'when user does not have permissions' do
let(:current_user) { nil }
......
......@@ -26,6 +26,8 @@ RSpec.describe Mutations::AlertManagement::CreateAlertIssue do
errors: []
)
end
it_behaves_like 'an incident management tracked event', :incident_management_incident_created
end
context 'when CreateAlertIssue responds with an error' do
......
......@@ -30,6 +30,10 @@ RSpec.describe Mutations::AlertManagement::UpdateAlertStatus do
)
end
it_behaves_like 'an incident management tracked event', :incident_management_alert_status_changed do
let(:user) { current_user }
end
context 'error occurs when updating' do
it 'returns the alert with errors' do
# Stub an error on the alert
......
......@@ -20,7 +20,7 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
describe '.categories' do
it 'gets all unique category names' do
expect(described_class.categories).to contain_exactly('analytics', 'compliance', 'ide_edit', 'search', 'source_code')
expect(described_class.categories).to contain_exactly('analytics', 'compliance', 'ide_edit', 'search', 'source_code', 'incident_management')
end
end
......
......@@ -118,6 +118,14 @@ RSpec.describe IssueLinks::CreateService do
subject
end
context 'issue is an incident' do
let(:issue) { create(:incident, project: project) }
it_behaves_like 'an incident management tracked event', :incident_management_incident_relate do
let(:current_user) { user }
end
end
end
context 'when reference of any already related issue is present' do
......
......@@ -36,6 +36,14 @@ RSpec.describe IssueLinks::DestroyService do
it 'returns success message' do
is_expected.to eq(message: 'Relation was removed', status: :success)
end
context 'target is an incident' do
let(:issue_b) { create(:incident, project: project) }
it_behaves_like 'an incident management tracked event', :incident_management_incident_unrelate do
let(:current_user) { user }
end
end
end
context 'when failing to remove an issue link' do
......
......@@ -67,6 +67,15 @@ RSpec.describe Issues::CloseService do
service.execute(issue)
end
context 'issue is incident type' do
let(:issue) { create(:incident, project: project) }
let(:current_user) { user }
subject { service.execute(issue) }
it_behaves_like 'an incident management tracked event', :incident_management_incident_closed
end
end
describe '#close_issue' do
......
......@@ -49,6 +49,18 @@ RSpec.describe Issues::CreateService do
end
end
context 'issue is incident type' do
before do
opts[:issue_type] = 'incident'
end
let(:current_user) { user }
subject { issue }
it_behaves_like 'an incident management tracked event', :incident_management_incident_created
end
it 'refreshes the number of open issues', :use_clean_rails_memory_store_caching do
expect { issue }.to change { project.open_issues_count }.from(0).to(1)
end
......
......@@ -53,6 +53,15 @@ RSpec.describe Issues::ReopenService do
described_class.new(project, user).execute(issue)
end
context 'issue is incident type' do
let(:issue) { create(:incident, :closed, project: project) }
let(:current_user) { user }
subject { described_class.new(project, user).execute(issue) }
it_behaves_like 'an incident management tracked event', :incident_management_incident_reopened
end
context 'when issue is not confidential' do
it 'executes issue hooks' do
expect(project).to receive(:execute_hooks).with(an_instance_of(Hash), :issue_hooks)
......
......@@ -112,6 +112,18 @@ RSpec.describe Issues::UpdateService, :mailer do
update_issue(confidential: false)
end
context 'issue in incident type' do
before do
opts[:issue_type] = 'incident'
end
let(:current_user) { user }
subject { update_issue(confidential: true) }
it_behaves_like 'an incident management tracked event', :incident_management_incident_change_confidential
end
it 'updates open issue counter for assignees when issue is reassigned' do
update_issue(assignee_ids: [user2.id])
......@@ -461,6 +473,13 @@ RSpec.describe Issues::UpdateService, :mailer do
expect(Todo.where(attributes).count).to eq(1)
end
context 'issue is incident type' do
let(:issue) { create(:incident, project: project) }
let(:current_user) { user }
it_behaves_like 'an incident management tracked event', :incident_management_incident_assigned
end
end
context 'when the milestone is removed' do
......
......@@ -82,6 +82,13 @@ RSpec.describe Issues::ZoomLinkService do
include_examples 'can add meeting'
context 'issue is incident type' do
let(:issue) { create(:incident) }
let(:current_user) { user }
it_behaves_like 'an incident management tracked event', :incident_management_incident_zoom_meeting
end
context 'with insufficient issue update permissions' do
include_context 'insufficient issue update permissions'
include_examples 'cannot add meeting'
......
......@@ -57,6 +57,16 @@ RSpec.describe Notes::CreateService do
described_class.new(project, user, opts).execute
end
context 'issue is an incident' do
subject { described_class.new(project, user, opts).execute }
let(:issue) { create(:incident, project: project) }
it_behaves_like 'an incident management tracked event', :incident_management_incident_comment do
let(:current_user) { user }
end
end
end
context 'noteable highlight cache clearing' do
......
......@@ -194,6 +194,19 @@ RSpec.describe TodoService do
should_create_todo(user: john_doe, target: issue)
end
end
context 'issue is an incident' do
let(:issue) { create(:incident, project: project, assignees: [john_doe], author: author) }
subject do
service.new_issue(issue, author)
should_create_todo(user: john_doe, target: issue, action: Todo::ASSIGNED)
end
it_behaves_like 'an incident management tracked event', :incident_management_incident_todo do
let(:current_user) { john_doe}
end
end
end
describe '#update_issue' do
......
# frozen_string_literal: true
RSpec.shared_examples 'an incident management tracked event' do |event|
describe ".track_event", :clean_gitlab_redis_shared_state do
let(:counter) { Gitlab::UsageDataCounters::HLLRedisCounter }
let(:start_time) { 1.minute.ago }
let(:end_time) { 1.minute.from_now }
it "tracks the event using redis" do
# Allow other subsequent calls
allow(Gitlab::UsageDataCounters::HLLRedisCounter)
.to receive(:track_event)
expect(Gitlab::UsageDataCounters::HLLRedisCounter)
.to receive(:track_event)
.with(current_user.id, event.to_s)
.and_call_original
expect { subject }
.to change { counter.unique_events(event_names: event.to_s, start_date: start_time, end_date: end_time) }
.by 1
end
end
end
RSpec.shared_examples 'does not track incident management event' do |event|
it 'does not track the event', :clean_gitlab_redis_shared_state do
expect(Gitlab::UsageDataCounters::HLLRedisCounter)
.not_to receive(:track_event)
.with(anything, event.to_s)
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