Record usage on snippet usage
Generalize wiki page counter for other page types to extend to.
This commit is contained in:
		
							parent
							
								
									8505049e1f
								
							
						
					
					
						commit
						4a6f959ab8
					
				| 
						 | 
				
			
			@ -23,6 +23,7 @@ class CreateSnippetService < BaseService
 | 
			
		|||
 | 
			
		||||
    if snippet.save
 | 
			
		||||
      UserAgentDetailService.new(snippet, @request).create
 | 
			
		||||
      Gitlab::UsageDataCounters::SnippetCounter.count(:create)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    snippet
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,5 +9,9 @@ module Notes
 | 
			
		|||
        note.noteable.diffs.clear_cache
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def increment_usage_counter(note)
 | 
			
		||||
      Gitlab::UsageDataCounters::NoteCounter.count(:create, note.noteable_type)
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -41,6 +41,7 @@ module Notes
 | 
			
		|||
        todo_service.new_note(note, current_user)
 | 
			
		||||
        clear_noteable_diffs_cache(note)
 | 
			
		||||
        Suggestions::CreateService.new(note).execute
 | 
			
		||||
        increment_usage_counter(note)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      if quick_actions_service.commands_executed_count.to_i > 0
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -25,6 +25,8 @@ class UpdateSnippetService < BaseService
 | 
			
		|||
    snippet.assign_attributes(params)
 | 
			
		||||
    spam_check(snippet, current_user)
 | 
			
		||||
 | 
			
		||||
    snippet.save
 | 
			
		||||
    snippet.save.tap do |succeeded|
 | 
			
		||||
      Gitlab::UsageDataCounters::SnippetCounter.count(:update) if succeeded
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,5 @@
 | 
			
		|||
---
 | 
			
		||||
title: Count snippet creation, update and comment events
 | 
			
		||||
merge_request: 30930
 | 
			
		||||
author:
 | 
			
		||||
type: added
 | 
			
		||||
| 
						 | 
				
			
			@ -140,6 +140,8 @@ module Gitlab
 | 
			
		|||
        [
 | 
			
		||||
         Gitlab::UsageDataCounters::WikiPageCounter,
 | 
			
		||||
         Gitlab::UsageDataCounters::WebIdeCounter,
 | 
			
		||||
         Gitlab::UsageDataCounters::NoteCounter,
 | 
			
		||||
         Gitlab::UsageDataCounters::SnippetCounter,
 | 
			
		||||
         Gitlab::UsageDataCounters::SearchCounter
 | 
			
		||||
        ]
 | 
			
		||||
      end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,39 @@
 | 
			
		|||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
module Gitlab::UsageDataCounters
 | 
			
		||||
  class BaseCounter
 | 
			
		||||
    extend RedisCounter
 | 
			
		||||
 | 
			
		||||
    UnknownEvent = Class.new(StandardError)
 | 
			
		||||
 | 
			
		||||
    class << self
 | 
			
		||||
      def redis_key(event)
 | 
			
		||||
        Gitlab::Sentry.track_exception(UnknownEvent, extra: { event: event }) unless known_events.include?(event.to_s)
 | 
			
		||||
 | 
			
		||||
        "USAGE_#{prefix}_#{event}".upcase
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def count(event)
 | 
			
		||||
        increment(redis_key event)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def read(event)
 | 
			
		||||
        total_count(redis_key event)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def totals
 | 
			
		||||
        known_events.map { |e| ["#{prefix}_#{e}".to_sym, read(e)] }.to_h
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      private
 | 
			
		||||
 | 
			
		||||
      def known_events
 | 
			
		||||
        self::KNOWN_EVENTS
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def prefix
 | 
			
		||||
        self::PREFIX
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,39 @@
 | 
			
		|||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
module Gitlab::UsageDataCounters
 | 
			
		||||
  class NoteCounter < BaseCounter
 | 
			
		||||
    KNOWN_EVENTS = %w[create].freeze
 | 
			
		||||
    PREFIX = 'note'
 | 
			
		||||
    COUNTABLE_TYPES = %w[Snippet].freeze
 | 
			
		||||
 | 
			
		||||
    class << self
 | 
			
		||||
      def redis_key(event, noteable_type)
 | 
			
		||||
        "#{super(event)}_#{noteable_type}".upcase
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def count(event, noteable_type)
 | 
			
		||||
        return unless countable?(noteable_type)
 | 
			
		||||
 | 
			
		||||
        increment(redis_key(event, noteable_type))
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def read(event, noteable_type)
 | 
			
		||||
        return 0 unless countable?(noteable_type)
 | 
			
		||||
 | 
			
		||||
        total_count(redis_key(event, noteable_type))
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def totals
 | 
			
		||||
        {
 | 
			
		||||
          snippet_comment: read(:create, 'Snippet')
 | 
			
		||||
        }
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      private
 | 
			
		||||
 | 
			
		||||
      def countable?(noteable_type)
 | 
			
		||||
        COUNTABLE_TYPES.include?(noteable_type.to_s)
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,8 @@
 | 
			
		|||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
module Gitlab::UsageDataCounters
 | 
			
		||||
  class SnippetCounter < BaseCounter
 | 
			
		||||
    KNOWN_EVENTS = %w[create update].freeze
 | 
			
		||||
    PREFIX = 'snippet'
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			@ -1,32 +1,8 @@
 | 
			
		|||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
module Gitlab::UsageDataCounters
 | 
			
		||||
  class WikiPageCounter
 | 
			
		||||
    extend RedisCounter
 | 
			
		||||
 | 
			
		||||
    KNOWN_EVENTS = %w[create update delete].map(&:freeze).freeze
 | 
			
		||||
 | 
			
		||||
    UnknownEvent = Class.new(StandardError)
 | 
			
		||||
 | 
			
		||||
    class << self
 | 
			
		||||
      # Each event gets a unique Redis key
 | 
			
		||||
      def redis_key(event)
 | 
			
		||||
        raise UnknownEvent, event unless KNOWN_EVENTS.include?(event.to_s)
 | 
			
		||||
 | 
			
		||||
        "USAGE_WIKI_PAGES_#{event}".upcase
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def count(event)
 | 
			
		||||
        increment(redis_key event)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def read(event)
 | 
			
		||||
        total_count(redis_key event)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def totals
 | 
			
		||||
        KNOWN_EVENTS.map { |e| ["wiki_pages_#{e}".to_sym, read(e)] }.to_h
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  class WikiPageCounter < BaseCounter
 | 
			
		||||
    KNOWN_EVENTS = %w[create update delete].freeze
 | 
			
		||||
    PREFIX = 'wiki_pages'
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,78 @@
 | 
			
		|||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require 'spec_helper'
 | 
			
		||||
 | 
			
		||||
describe Gitlab::UsageDataCounters::NoteCounter, :clean_gitlab_redis_shared_state do
 | 
			
		||||
  shared_examples 'a note usage counter' do |event, noteable_type|
 | 
			
		||||
    describe ".count(#{event})" do
 | 
			
		||||
      it "increments the Note #{event} counter by 1" do
 | 
			
		||||
        expect do
 | 
			
		||||
          described_class.count(event, noteable_type)
 | 
			
		||||
        end.to change { described_class.read(event, noteable_type) }.by 1
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    describe ".read(#{event})" do
 | 
			
		||||
      event_count = 5
 | 
			
		||||
 | 
			
		||||
      it "returns the total number of #{event} events" do
 | 
			
		||||
        event_count.times do
 | 
			
		||||
          described_class.count(event, noteable_type)
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        expect(described_class.read(event, noteable_type)).to eq(event_count)
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  it_behaves_like 'a note usage counter', :create, 'Snippet'
 | 
			
		||||
 | 
			
		||||
  describe '.totals' do
 | 
			
		||||
    let(:combinations) do
 | 
			
		||||
      [
 | 
			
		||||
        [:create, 'Snippet', 3]
 | 
			
		||||
      ]
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    let(:expected_totals) do
 | 
			
		||||
      { snippet_comment: 3 }
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    before do
 | 
			
		||||
      combinations.each do |event, noteable_type, n|
 | 
			
		||||
        n.times do
 | 
			
		||||
          described_class.count(event, noteable_type)
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    it 'can report all totals' do
 | 
			
		||||
      expect(described_class.totals).to include(expected_totals)
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  describe 'unknown events or noteable_type' do
 | 
			
		||||
    using RSpec::Parameterized::TableSyntax
 | 
			
		||||
 | 
			
		||||
    let(:unknown_event_error) { Gitlab::UsageDataCounters::BaseCounter::UnknownEvent }
 | 
			
		||||
 | 
			
		||||
    where(:event, :noteable_type, :expected_count, :should_raise) do
 | 
			
		||||
      :create | 'Snippet' | 1 | false
 | 
			
		||||
      :wibble | 'Snippet' | 0 | true
 | 
			
		||||
      :create | 'Issue'   | 0 | false
 | 
			
		||||
      :wibble | 'Issue'   | 0 | false
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    with_them do
 | 
			
		||||
      it "handles event" do
 | 
			
		||||
        if should_raise
 | 
			
		||||
          expect { described_class.count(event, noteable_type) }.to raise_error(unknown_event_error)
 | 
			
		||||
        else
 | 
			
		||||
          described_class.count(event, noteable_type)
 | 
			
		||||
 | 
			
		||||
          expect(described_class.read(event, noteable_type)).to eq(expected_count)
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,12 @@
 | 
			
		|||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require 'spec_helper'
 | 
			
		||||
 | 
			
		||||
describe Gitlab::UsageDataCounters::SnippetCounter do
 | 
			
		||||
  it_behaves_like 'a redis usage counter', 'Snippet', :create
 | 
			
		||||
  it_behaves_like 'a redis usage counter', 'Snippet', :update
 | 
			
		||||
 | 
			
		||||
  it_behaves_like 'a redis usage counter with totals', :snippet,
 | 
			
		||||
    create: 3,
 | 
			
		||||
    update: 2
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			@ -62,6 +62,9 @@ describe Gitlab::UsageData do
 | 
			
		|||
      ))
 | 
			
		||||
 | 
			
		||||
      expect(subject).to include(
 | 
			
		||||
        snippet_create: a_kind_of(Integer),
 | 
			
		||||
        snippet_update: a_kind_of(Integer),
 | 
			
		||||
        snippet_comment: a_kind_of(Integer),
 | 
			
		||||
        wiki_pages_create: a_kind_of(Integer),
 | 
			
		||||
        wiki_pages_update: a_kind_of(Integer),
 | 
			
		||||
        wiki_pages_delete: a_kind_of(Integer),
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -36,6 +36,22 @@ describe CreateSnippetService do
 | 
			
		|||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  describe 'usage counter' do
 | 
			
		||||
    let(:counter) { Gitlab::UsageDataCounters::SnippetCounter }
 | 
			
		||||
 | 
			
		||||
    it 'increments count' do
 | 
			
		||||
      expect do
 | 
			
		||||
        create_snippet(nil, @admin, @opts)
 | 
			
		||||
      end.to change { counter.read(:create) }.by 1
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    it 'does not increment count if create fails' do
 | 
			
		||||
      expect do
 | 
			
		||||
        create_snippet(nil, @admin, {})
 | 
			
		||||
      end.not_to change { counter.read(:create) }
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def create_snippet(project, user, opts)
 | 
			
		||||
    CreateSnippetService.new(project, user, opts).execute
 | 
			
		||||
  end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -365,5 +365,43 @@ describe Notes::CreateService do
 | 
			
		|||
            .and change { existing_note.updated_at }
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    describe "usage counter" do
 | 
			
		||||
      let(:counter) { Gitlab::UsageDataCounters::NoteCounter }
 | 
			
		||||
 | 
			
		||||
      context 'snippet note' do
 | 
			
		||||
        let(:snippet) { create(:project_snippet, project: project) }
 | 
			
		||||
        let(:opts) { { note: 'reply', noteable_type: 'Snippet', noteable_id: snippet.id, project: project } }
 | 
			
		||||
 | 
			
		||||
        it 'increments usage counter' do
 | 
			
		||||
          expect do
 | 
			
		||||
            note = described_class.new(project, user, opts).execute
 | 
			
		||||
 | 
			
		||||
            expect(note).to be_valid
 | 
			
		||||
          end.to change { counter.read(:create, opts[:noteable_type]) }.by 1
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        it 'does not increment usage counter when creation fails' do
 | 
			
		||||
          expect do
 | 
			
		||||
            note = described_class.new(project, user, { note: '' }).execute
 | 
			
		||||
 | 
			
		||||
            expect(note).to be_invalid
 | 
			
		||||
          end.not_to change { counter.read(:create, opts[:noteable_type]) }
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      context 'issue note' do
 | 
			
		||||
        let(:issue) { create(:issue, project: project) }
 | 
			
		||||
        let(:opts) { { note: 'reply', noteable_type: 'Issue', noteable_id: issue.id, project: project } }
 | 
			
		||||
 | 
			
		||||
        it 'does not increment usage counter' do
 | 
			
		||||
          expect do
 | 
			
		||||
            note = described_class.new(project, user, opts).execute
 | 
			
		||||
 | 
			
		||||
            expect(note).to be_valid
 | 
			
		||||
          end.not_to change { counter.read(:create, opts[:noteable_type]) }
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -40,6 +40,23 @@ describe UpdateSnippetService do
 | 
			
		|||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  describe 'usage counter' do
 | 
			
		||||
    let(:counter) { Gitlab::UsageDataCounters::SnippetCounter }
 | 
			
		||||
    let(:snippet) { create_snippet(nil, @user, @opts) }
 | 
			
		||||
 | 
			
		||||
    it 'increments count' do
 | 
			
		||||
      expect do
 | 
			
		||||
        update_snippet(nil, @admin, snippet, @opts)
 | 
			
		||||
      end.to change { counter.read(:update) }.by 1
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    it 'does not increment count if create fails' do
 | 
			
		||||
      expect do
 | 
			
		||||
        update_snippet(nil, @admin, snippet, { title: '' })
 | 
			
		||||
      end.not_to change { counter.read(:update) }
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def create_snippet(project, user, opts)
 | 
			
		||||
    CreateSnippetService.new(project, user, opts).execute
 | 
			
		||||
  end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue