diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-02-18 10:34:06 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-02-18 10:34:06 +0000 |
commit | 859a6fb938bb9ee2a317c46dfa4fcc1af49608f0 (patch) | |
tree | d7f2700abe6b4ffcb2dcfc80631b2d87d0609239 /spec/lib/gitlab/usage_data_counters | |
parent | 446d496a6d000c73a304be52587cd9bbc7493136 (diff) | |
download | gitlab-ce-859a6fb938bb9ee2a317c46dfa4fcc1af49608f0.tar.gz |
Add latest changes from gitlab-org/gitlab@13-9-stable-eev13.9.0-rc42
Diffstat (limited to 'spec/lib/gitlab/usage_data_counters')
6 files changed, 521 insertions, 175 deletions
diff --git a/spec/lib/gitlab/usage_data_counters/aggregated_metrics_spec.rb b/spec/lib/gitlab/usage_data_counters/aggregated_metrics_spec.rb index c0deb2aa00c..58f974fbe12 100644 --- a/spec/lib/gitlab/usage_data_counters/aggregated_metrics_spec.rb +++ b/spec/lib/gitlab/usage_data_counters/aggregated_metrics_spec.rb @@ -13,18 +13,32 @@ RSpec.describe 'aggregated metrics' do end end + RSpec::Matchers.define :has_known_source do + match do |aggregate| + Gitlab::Usage::Metrics::Aggregates::SOURCES.include?(aggregate[:source]) + end + + failure_message do |aggregate| + "Aggregate with name: `#{aggregate[:name]}` uses not allowed source `#{aggregate[:source]}`" + end + end + let_it_be(:known_events) do Gitlab::UsageDataCounters::HLLRedisCounter.known_events end - Gitlab::UsageDataCounters::HLLRedisCounter.aggregated_metrics.tap do |aggregated_metrics| + Gitlab::Usage::Metrics::Aggregates::Aggregate.new(Time.current).send(:aggregated_metrics).tap do |aggregated_metrics| it 'all events has unique name' do event_names = aggregated_metrics&.map { |event| event[:name] } expect(event_names).to eq(event_names&.uniq) end - aggregated_metrics&.each do |aggregate| + it 'all aggregated metrics has known source' do + expect(aggregated_metrics).to all has_known_source + end + + aggregated_metrics&.select { |agg| agg[:source] == Gitlab::Usage::Metrics::Aggregates::REDIS_SOURCE }&.each do |aggregate| context "for #{aggregate[:name]} aggregate of #{aggregate[:events].join(' ')}" do let_it_be(:events_records) { known_events.select { |event| aggregate[:events].include?(event[:name]) } } @@ -37,7 +51,7 @@ RSpec.describe 'aggregated metrics' do end it "uses allowed aggregation operators" do - expect(Gitlab::UsageDataCounters::HLLRedisCounter::ALLOWED_METRICS_AGGREGATIONS).to include aggregate[:operator] + expect(Gitlab::Usage::Metrics::Aggregates::ALLOWED_METRICS_AGGREGATIONS).to include aggregate[:operator] end it "uses events from the same Redis slot" do diff --git a/spec/lib/gitlab/usage_data_counters/ci_template_unique_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/ci_template_unique_counter_spec.rb index ba7bfe47bc9..b1d5d106082 100644 --- a/spec/lib/gitlab/usage_data_counters/ci_template_unique_counter_spec.rb +++ b/spec/lib/gitlab/usage_data_counters/ci_template_unique_counter_spec.rb @@ -3,28 +3,88 @@ require 'spec_helper' RSpec.describe Gitlab::UsageDataCounters::CiTemplateUniqueCounter do - let(:project_id) { 1 } - describe '.track_unique_project_event' do - described_class::TEMPLATE_TO_EVENT.keys.each do |template| - context "when given template #{template}" do - it_behaves_like 'tracking unique hll events', :usage_data_track_ci_templates_unique_projects do - subject(:request) { described_class.track_unique_project_event(project_id: project_id, template: template) } + using RSpec::Parameterized::TableSyntax + + where(:template, :config_source, :expected_event) do + # Implicit Auto DevOps usage + 'Auto-DevOps.gitlab-ci.yml' | :auto_devops_source | 'p_ci_templates_implicit_auto_devops' + 'Jobs/Build.gitlab-ci.yml' | :auto_devops_source | 'p_ci_templates_implicit_auto_devops_build' + 'Jobs/Deploy.gitlab-ci.yml' | :auto_devops_source | 'p_ci_templates_implicit_auto_devops_deploy' + 'Security/SAST.gitlab-ci.yml' | :auto_devops_source | 'p_ci_templates_implicit_security_sast' + 'Security/Secret-Detection.gitlab-ci.yml' | :auto_devops_source | 'p_ci_templates_implicit_security_secret_detection' + # Explicit include:template usage + '5-Minute-Production-App.gitlab-ci.yml' | :repository_source | 'p_ci_templates_5_min_production_app' + 'Auto-DevOps.gitlab-ci.yml' | :repository_source | 'p_ci_templates_auto_devops' + 'AWS/CF-Provision-and-Deploy-EC2.gitlab-ci.yml' | :repository_source | 'p_ci_templates_aws_cf_deploy_ec2' + 'AWS/Deploy-ECS.gitlab-ci.yml' | :repository_source | 'p_ci_templates_aws_deploy_ecs' + 'Jobs/Build.gitlab-ci.yml' | :repository_source | 'p_ci_templates_auto_devops_build' + 'Jobs/Deploy.gitlab-ci.yml' | :repository_source | 'p_ci_templates_auto_devops_deploy' + 'Jobs/Deploy.latest.gitlab-ci.yml' | :repository_source | 'p_ci_templates_auto_devops_deploy_latest' + 'Security/SAST.gitlab-ci.yml' | :repository_source | 'p_ci_templates_security_sast' + 'Security/Secret-Detection.gitlab-ci.yml' | :repository_source | 'p_ci_templates_security_secret_detection' + 'Terraform/Base.latest.gitlab-ci.yml' | :repository_source | 'p_ci_templates_terraform_base_latest' + end + + with_them do + it_behaves_like 'tracking unique hll events' do + subject(:request) { described_class.track_unique_project_event(project_id: project_id, template: template, config_source: config_source) } + + let(:project_id) { 1 } + let(:target_id) { expected_event } + let(:expected_type) { instance_of(Integer) } + end + end + + context 'known_events coverage tests' do + let(:project_id) { 1 } + let(:config_source) { :repository_source } - let(:target_id) { "p_ci_templates_#{described_class::TEMPLATE_TO_EVENT[template]}" } - let(:expected_type) { instance_of(Integer) } + # These tests help guard against missing "explicit" events in known_events/ci_templates.yml + context 'explicit include:template events' do + described_class::TEMPLATE_TO_EVENT.keys.each do |template| + it "does not raise error for #{template}" do + expect do + described_class.track_unique_project_event(project_id: project_id, template: template, config_source: config_source) + end.not_to raise_error + end + end + end + + # This test is to help guard against missing "implicit" events in known_events/ci_templates.yml + it 'does not raise error for any template in an implicit Auto DevOps pipeline' do + project = create(:project, :auto_devops) + pipeline = double(project: project) + command = double + result = Gitlab::Ci::YamlProcessor.new( + Gitlab::Ci::Pipeline::Chain::Config::Content::AutoDevops.new(pipeline, command).content, + project: project, + user: double, + sha: double + ).execute + + config_source = :auto_devops_source + + result.included_templates.each do |template| + expect do + described_class.track_unique_project_event(project_id: project.id, template: template, config_source: config_source) + end.not_to raise_error end end end - it 'does not track templates outside of TEMPLATE_TO_EVENT' do - expect(Gitlab::UsageDataCounters::HLLRedisCounter).not_to( - receive(:track_event) - ) + context 'templates outside of TEMPLATE_TO_EVENT' do + let(:project_id) { 1 } + let(:config_source) { :repository_source } + Dir.glob(File.join('lib', 'gitlab', 'ci', 'templates', '**'), base: Rails.root) do |template| next if described_class::TEMPLATE_TO_EVENT.key?(template) - described_class.track_unique_project_event(project_id: 1, template: template) + it "does not track #{template}" do + expect(Gitlab::UsageDataCounters::HLLRedisCounter).not_to(receive(:track_event)) + + described_class.track_unique_project_event(project_id: project_id, template: template, config_source: config_source) + end end end end diff --git a/spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb index b8eddc0ca7f..b4894ec049f 100644 --- a/spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb +++ b/spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb @@ -27,6 +27,7 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s 'deploy_token_packages', 'user_packages', 'compliance', + 'ecosystem', 'analytics', 'ide_edit', 'search', @@ -39,12 +40,16 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s 'snippets', 'code_review', 'terraform', - 'ci_templates' + 'ci_templates', + 'quickactions', + 'pipeline_authoring' ) end end describe 'known_events' do + let(:feature) { 'test_hll_redis_counter_ff_check' } + let(:weekly_event) { 'g_analytics_contribution' } let(:daily_event) { 'g_analytics_search' } let(:analytics_slot_event) { 'g_analytics_contribution' } @@ -64,7 +69,7 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s let(:known_events) do [ - { name: weekly_event, redis_slot: "analytics", category: analytics_category, expiry: 84, aggregation: "weekly" }, + { name: weekly_event, redis_slot: "analytics", category: analytics_category, expiry: 84, aggregation: "weekly", feature_flag: feature }, { name: daily_event, redis_slot: "analytics", category: analytics_category, expiry: 84, aggregation: "daily" }, { name: category_productivity_event, redis_slot: "analytics", category: productivity_category, aggregation: "weekly" }, { name: compliance_slot_event, redis_slot: "compliance", category: compliance_category, aggregation: "weekly" }, @@ -75,6 +80,8 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s end before do + skip_feature_flags_yaml_validation + skip_default_enabled_yaml_check allow(described_class).to receive(:known_events).and_return(known_events) end @@ -85,6 +92,32 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s end describe '.track_event' do + context 'with feature flag set' do + it 'tracks the event when feature enabled' do + stub_feature_flags(feature => true) + + expect(Gitlab::Redis::HLL).to receive(:add) + + described_class.track_event(weekly_event, values: 1) + end + + it 'does not track the event with feature flag disabled' do + stub_feature_flags(feature => false) + + expect(Gitlab::Redis::HLL).not_to receive(:add) + + described_class.track_event(weekly_event, values: 1) + end + end + + context 'with no feature flag set' do + it 'tracks the event' do + expect(Gitlab::Redis::HLL).to receive(:add) + + described_class.track_event(daily_event, values: 1) + end + end + context 'when usage_ping is disabled' do it 'does not track the event' do stub_application_setting(usage_ping_enabled: false) @@ -425,182 +458,59 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s end end - context 'aggregated_metrics_data' do + describe '.calculate_events_union' do + let(:time_range) { { start_date: 7.days.ago, end_date: DateTime.current } } let(:known_events) do [ { name: 'event1_slot', redis_slot: "slot", category: 'category1', aggregation: "weekly" }, { name: 'event2_slot', redis_slot: "slot", category: 'category2', aggregation: "weekly" }, { name: 'event3_slot', redis_slot: "slot", category: 'category3', aggregation: "weekly" }, - { name: 'event5_slot', redis_slot: "slot", category: 'category4', aggregation: "weekly" }, + { name: 'event5_slot', redis_slot: "slot", category: 'category4', aggregation: "daily" }, { name: 'event4', category: 'category2', aggregation: "weekly" } ].map(&:with_indifferent_access) end before do allow(described_class).to receive(:known_events).and_return(known_events) - end - - shared_examples 'aggregated_metrics_data' do - context 'no aggregated metrics is defined' do - it 'returns empty hash' do - allow(described_class).to receive(:aggregated_metrics).and_return([]) - - expect(aggregated_metrics_data).to eq({}) - end - end - - context 'there are aggregated metrics defined' do - before do - allow(described_class).to receive(:aggregated_metrics).and_return(aggregated_metrics) - end - - context 'with AND operator' do - let(:aggregated_metrics) do - [ - { name: 'gmau_1', events: %w[event1_slot event2_slot], operator: "AND" }, - { name: 'gmau_2', events: %w[event1_slot event2_slot event3_slot], operator: "AND" }, - { name: 'gmau_3', events: %w[event1_slot event2_slot event3_slot event5_slot], operator: "AND" }, - { name: 'gmau_4', events: %w[event4], operator: "AND" } - ].map(&:with_indifferent_access) - end - - it 'returns the number of unique events for all known events' do - results = { - 'gmau_1' => 3, - 'gmau_2' => 2, - 'gmau_3' => 1, - 'gmau_4' => 3 - } - - expect(aggregated_metrics_data).to eq(results) - end - end - - context 'with OR operator' do - let(:aggregated_metrics) do - [ - { name: 'gmau_1', events: %w[event3_slot event5_slot], operator: "OR" }, - { name: 'gmau_2', events: %w[event1_slot event2_slot event3_slot event5_slot], operator: "OR" }, - { name: 'gmau_3', events: %w[event4], operator: "OR" } - ].map(&:with_indifferent_access) - end - it 'returns the number of unique events for all known events' do - results = { - 'gmau_1' => 2, - 'gmau_2' => 3, - 'gmau_3' => 3 - } - - expect(aggregated_metrics_data).to eq(results) - end - end - - context 'hidden behind feature flag' do - let(:enabled_feature_flag) { 'test_ff_enabled' } - let(:disabled_feature_flag) { 'test_ff_disabled' } - let(:aggregated_metrics) do - [ - # represents stable aggregated metrics that has been fully released - { name: 'gmau_without_ff', events: %w[event3_slot event5_slot], operator: "OR" }, - # represents new aggregated metric that is under performance testing on gitlab.com - { name: 'gmau_enabled', events: %w[event4], operator: "AND", feature_flag: enabled_feature_flag }, - # represents aggregated metric that is under development and shouldn't be yet collected even on gitlab.com - { name: 'gmau_disabled', events: %w[event4], operator: "AND", feature_flag: disabled_feature_flag } - ].map(&:with_indifferent_access) - end - - it 'returns the number of unique events for all known events' do - skip_feature_flags_yaml_validation - stub_feature_flags(enabled_feature_flag => true, disabled_feature_flag => false) + described_class.track_event('event1_slot', values: entity1, time: 2.days.ago) + described_class.track_event('event1_slot', values: entity2, time: 2.days.ago) + described_class.track_event('event1_slot', values: entity3, time: 2.days.ago) + described_class.track_event('event2_slot', values: entity1, time: 2.days.ago) + described_class.track_event('event2_slot', values: entity2, time: 3.days.ago) + described_class.track_event('event2_slot', values: entity3, time: 3.days.ago) + described_class.track_event('event3_slot', values: entity1, time: 3.days.ago) + described_class.track_event('event3_slot', values: entity2, time: 3.days.ago) + described_class.track_event('event5_slot', values: entity2, time: 3.days.ago) + + # events out of time scope + described_class.track_event('event2_slot', values: entity4, time: 8.days.ago) - expect(aggregated_metrics_data).to eq('gmau_without_ff' => 2, 'gmau_enabled' => 3) - end - end - end + # events in different slots + described_class.track_event('event4', values: entity1, time: 2.days.ago) + described_class.track_event('event4', values: entity2, time: 2.days.ago) end - describe '.aggregated_metrics_weekly_data' do - subject(:aggregated_metrics_data) { described_class.aggregated_metrics_weekly_data } - - before do - described_class.track_event('event1_slot', values: entity1, time: 2.days.ago) - described_class.track_event('event1_slot', values: entity2, time: 2.days.ago) - described_class.track_event('event1_slot', values: entity3, time: 2.days.ago) - described_class.track_event('event2_slot', values: entity1, time: 2.days.ago) - described_class.track_event('event2_slot', values: entity2, time: 3.days.ago) - described_class.track_event('event2_slot', values: entity3, time: 3.days.ago) - described_class.track_event('event3_slot', values: entity1, time: 3.days.ago) - described_class.track_event('event3_slot', values: entity2, time: 3.days.ago) - described_class.track_event('event5_slot', values: entity2, time: 3.days.ago) - - # events out of time scope - described_class.track_event('event2_slot', values: entity3, time: 8.days.ago) - - # events in different slots - described_class.track_event('event4', values: entity1, time: 2.days.ago) - described_class.track_event('event4', values: entity2, time: 2.days.ago) - described_class.track_event('event4', values: entity4, time: 2.days.ago) - end - - it_behaves_like 'aggregated_metrics_data' + it 'calculates union of given events', :aggregate_failure do + expect(described_class.calculate_events_union(**time_range.merge(event_names: %w[event4]))).to eq 2 + expect(described_class.calculate_events_union(**time_range.merge(event_names: %w[event1_slot event2_slot event3_slot]))).to eq 3 end - describe '.aggregated_metrics_monthly_data' do - subject(:aggregated_metrics_data) { described_class.aggregated_metrics_monthly_data } - - it_behaves_like 'aggregated_metrics_data' do - before do - described_class.track_event('event1_slot', values: entity1, time: 2.days.ago) - described_class.track_event('event1_slot', values: entity2, time: 2.days.ago) - described_class.track_event('event1_slot', values: entity3, time: 2.days.ago) - described_class.track_event('event2_slot', values: entity1, time: 2.days.ago) - described_class.track_event('event2_slot', values: entity2, time: 3.days.ago) - described_class.track_event('event2_slot', values: entity3, time: 3.days.ago) - described_class.track_event('event3_slot', values: entity1, time: 3.days.ago) - described_class.track_event('event3_slot', values: entity2, time: 10.days.ago) - described_class.track_event('event5_slot', values: entity2, time: 4.weeks.ago.advance(days: 1)) - - # events out of time scope - described_class.track_event('event5_slot', values: entity1, time: 4.weeks.ago.advance(days: -1)) - - # events in different slots - described_class.track_event('event4', values: entity1, time: 2.days.ago) - described_class.track_event('event4', values: entity2, time: 2.days.ago) - described_class.track_event('event4', values: entity4, time: 2.days.ago) - end - end - - context 'Redis calls' do - let(:aggregated_metrics) do - [ - { name: 'gmau_3', events: %w[event1_slot event2_slot event3_slot event5_slot], operator: "AND" } - ].map(&:with_indifferent_access) - end - - let(:known_events) do - [ - { name: 'event1_slot', redis_slot: "slot", category: 'category1', aggregation: "weekly" }, - { name: 'event2_slot', redis_slot: "slot", category: 'category2', aggregation: "weekly" }, - { name: 'event3_slot', redis_slot: "slot", category: 'category3', aggregation: "weekly" }, - { name: 'event5_slot', redis_slot: "slot", category: 'category4', aggregation: "weekly" } - ].map(&:with_indifferent_access) - end - - it 'caches intermediate operations' do - allow(described_class).to receive(:known_events).and_return(known_events) - allow(described_class).to receive(:aggregated_metrics).and_return(aggregated_metrics) + it 'validates and raise exception if events has mismatched slot or aggregation', :aggregate_failure do + expect { described_class.calculate_events_union(**time_range.merge(event_names: %w[event1_slot event4])) }.to raise_error described_class::SlotMismatch + expect { described_class.calculate_events_union(**time_range.merge(event_names: %w[event5_slot event3_slot])) }.to raise_error described_class::AggregationMismatch + end + end - 4.downto(1) do |subset_size| - known_events.combination(subset_size).each do |events| - keys = described_class.send(:weekly_redis_keys, events: events, start_date: 4.weeks.ago.to_date, end_date: Date.current) - expect(Gitlab::Redis::HLL).to receive(:count).with(keys: keys).once.and_return(0) - end - end + describe '.weekly_time_range' do + it 'return hash with weekly time range boundaries' do + expect(described_class.weekly_time_range).to eq(start_date: 7.days.ago.to_date, end_date: Date.current) + end + end - subject - end - end + describe '.monthly_time_range' do + it 'return hash with monthly time range boundaries' do + expect(described_class.monthly_time_range).to eq(start_date: 4.weeks.ago.to_date, end_date: Date.current) end end end diff --git a/spec/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter_spec.rb index c7b208cfb31..a604de4a61f 100644 --- a/spec/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter_spec.rb +++ b/spec/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter_spec.rb @@ -73,6 +73,54 @@ RSpec.describe Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter, :cl end end + describe '.track_approve_mr_action' do + subject { described_class.track_approve_mr_action(user: user) } + + it_behaves_like 'a tracked merge request unique event' do + let(:action) { described_class::MR_APPROVE_ACTION } + end + end + + describe '.track_unapprove_mr_action' do + subject { described_class.track_unapprove_mr_action(user: user) } + + it_behaves_like 'a tracked merge request unique event' do + let(:action) { described_class::MR_UNAPPROVE_ACTION } + end + end + + describe '.track_resolve_thread_action' do + subject { described_class.track_resolve_thread_action(user: user) } + + it_behaves_like 'a tracked merge request unique event' do + let(:action) { described_class::MR_RESOLVE_THREAD_ACTION } + end + end + + describe '.track_unresolve_thread_action' do + subject { described_class.track_unresolve_thread_action(user: user) } + + it_behaves_like 'a tracked merge request unique event' do + let(:action) { described_class::MR_UNRESOLVE_THREAD_ACTION } + end + end + + describe '.track_title_edit_action' do + subject { described_class.track_title_edit_action(user: user) } + + it_behaves_like 'a tracked merge request unique event' do + let(:action) { described_class::MR_EDIT_MR_TITLE_ACTION } + end + end + + describe '.track_description_edit_action' do + subject { described_class.track_description_edit_action(user: user) } + + it_behaves_like 'a tracked merge request unique event' do + let(:action) { described_class::MR_EDIT_MR_DESC_ACTION } + end + end + describe '.track_create_comment_action' do subject { described_class.track_create_comment_action(note: note) } @@ -148,4 +196,92 @@ RSpec.describe Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter, :cl let(:action) { described_class::MR_PUBLISH_REVIEW_ACTION } end end + + describe '.track_add_suggestion_action' do + subject { described_class.track_add_suggestion_action(user: user) } + + it_behaves_like 'a tracked merge request unique event' do + let(:action) { described_class::MR_ADD_SUGGESTION_ACTION } + end + end + + describe '.track_apply_suggestion_action' do + subject { described_class.track_apply_suggestion_action(user: user) } + + it_behaves_like 'a tracked merge request unique event' do + let(:action) { described_class::MR_APPLY_SUGGESTION_ACTION } + end + end + + describe '.track_users_assigned_to_mr' do + subject { described_class.track_users_assigned_to_mr(users: [user]) } + + it_behaves_like 'a tracked merge request unique event' do + let(:action) { described_class::MR_ASSIGNED_USERS_ACTION } + end + end + + describe '.track_marked_as_draft_action' do + subject { described_class.track_marked_as_draft_action(user: user) } + + it_behaves_like 'a tracked merge request unique event' do + let(:action) { described_class::MR_MARKED_AS_DRAFT_ACTION } + end + end + + describe '.track_unmarked_as_draft_action' do + subject { described_class.track_unmarked_as_draft_action(user: user) } + + it_behaves_like 'a tracked merge request unique event' do + let(:action) { described_class::MR_UNMARKED_AS_DRAFT_ACTION } + end + end + + describe '.track_task_item_status_changed' do + subject { described_class.track_task_item_status_changed(user: user) } + + it_behaves_like 'a tracked merge request unique event' do + let(:action) { described_class::MR_TASK_ITEM_STATUS_CHANGED_ACTION } + end + end + + describe '.track_users_review_requested' do + subject { described_class.track_users_review_requested(users: [user]) } + + it_behaves_like 'a tracked merge request unique event' do + let(:action) { described_class::MR_REVIEW_REQUESTED_USERS_ACTION } + end + end + + describe '.track_approval_rule_added_action' do + subject { described_class.track_approval_rule_added_action(user: user) } + + it_behaves_like 'a tracked merge request unique event' do + let(:action) { described_class::MR_APPROVAL_RULE_ADDED_USERS_ACTION } + end + end + + describe '.track_approval_rule_edited_action' do + subject { described_class.track_approval_rule_edited_action(user: user) } + + it_behaves_like 'a tracked merge request unique event' do + let(:action) { described_class::MR_APPROVAL_RULE_EDITED_USERS_ACTION } + end + end + + describe '.track_approval_rule_deleted_action' do + subject { described_class.track_approval_rule_deleted_action(user: user) } + + it_behaves_like 'a tracked merge request unique event' do + let(:action) { described_class::MR_APPROVAL_RULE_DELETED_USERS_ACTION } + end + end + + describe '.track_mr_create_from_issue' do + subject { described_class.track_mr_create_from_issue(user: user) } + + it_behaves_like 'a tracked merge request unique event' do + let(:action) { described_class::MR_CREATE_FROM_ISSUE_ACTION } + end + end end diff --git a/spec/lib/gitlab/usage_data_counters/quick_action_activity_unique_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/quick_action_activity_unique_counter_spec.rb new file mode 100644 index 00000000000..d4c423f57fe --- /dev/null +++ b/spec/lib/gitlab/usage_data_counters/quick_action_activity_unique_counter_spec.rb @@ -0,0 +1,163 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::UsageDataCounters::QuickActionActivityUniqueCounter, :clean_gitlab_redis_shared_state do + let(:user) { build(:user, id: 1) } + let(:note) { build(:note, author: user) } + let(:args) { nil } + + shared_examples_for 'a tracked quick action unique event' do + specify do + expect { 3.times { subject } } + .to change { + Gitlab::UsageDataCounters::HLLRedisCounter.unique_events( + event_names: action, + start_date: 2.weeks.ago, + end_date: 2.weeks.from_now + ) + } + .by(1) + end + end + + subject { described_class.track_unique_action(quickaction_name, args: args, user: user) } + + describe '.track_unique_action' do + let(:quickaction_name) { 'approve' } + + it_behaves_like 'a tracked quick action unique event' do + let(:action) { 'i_quickactions_approve' } + end + end + + context 'tracking assigns' do + let(:quickaction_name) { 'assign' } + + context 'single assignee' do + let(:args) { '@one' } + + it_behaves_like 'a tracked quick action unique event' do + let(:action) { 'i_quickactions_assign_single' } + end + end + + context 'multiple assignees' do + let(:args) { '@one @two' } + + it_behaves_like 'a tracked quick action unique event' do + let(:action) { 'i_quickactions_assign_multiple' } + end + end + + context 'assigning "me"' do + let(:args) { 'me' } + + it_behaves_like 'a tracked quick action unique event' do + let(:action) { 'i_quickactions_assign_self' } + end + end + + context 'assigning a reviewer' do + let(:quickaction_name) { 'assign_reviewer' } + + it_behaves_like 'a tracked quick action unique event' do + let(:action) { 'i_quickactions_assign_reviewer' } + end + end + + context 'assigning a reviewer with request review alias' do + let(:quickaction_name) { 'request_review' } + + it_behaves_like 'a tracked quick action unique event' do + let(:action) { 'i_quickactions_assign_reviewer' } + end + end + end + + context 'tracking copy_metadata' do + let(:quickaction_name) { 'copy_metadata' } + + context 'for issues' do + let(:args) { '#123' } + + it_behaves_like 'a tracked quick action unique event' do + let(:action) { 'i_quickactions_copy_metadata_issue' } + end + end + + context 'for merge requests' do + let(:args) { '!123' } + + it_behaves_like 'a tracked quick action unique event' do + let(:action) { 'i_quickactions_copy_metadata_merge_request' } + end + end + end + + context 'tracking spend' do + let(:quickaction_name) { 'spend' } + + context 'adding time' do + let(:args) { '1d' } + + it_behaves_like 'a tracked quick action unique event' do + let(:action) { 'i_quickactions_spend_add' } + end + end + + context 'removing time' do + let(:args) { '-1d' } + + it_behaves_like 'a tracked quick action unique event' do + let(:action) { 'i_quickactions_spend_subtract' } + end + end + end + + context 'tracking unassign' do + let(:quickaction_name) { 'unassign' } + + context 'unassigning everyone' do + it_behaves_like 'a tracked quick action unique event' do + let(:action) { 'i_quickactions_unassign_all' } + end + end + + context 'unassigning specific users' do + let(:args) { '@hello' } + + it_behaves_like 'a tracked quick action unique event' do + let(:action) { 'i_quickactions_unassign_specific' } + end + end + end + + context 'tracking unlabel' do + context 'called as unlabel' do + let(:quickaction_name) { 'unlabel' } + + context 'removing all labels' do + it_behaves_like 'a tracked quick action unique event' do + let(:action) { 'i_quickactions_unlabel_all' } + end + end + + context 'removing specific labels' do + let(:args) { '~wow' } + + it_behaves_like 'a tracked quick action unique event' do + let(:action) { 'i_quickactions_unlabel_specific' } + end + end + end + + context 'called as remove_label' do + let(:quickaction_name) { 'remove_label' } + + it_behaves_like 'a tracked quick action unique event' do + let(:action) { 'i_quickactions_unlabel_all' } + end + end + end +end diff --git a/spec/lib/gitlab/usage_data_counters/vs_code_extenion_activity_unique_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/vs_code_extenion_activity_unique_counter_spec.rb new file mode 100644 index 00000000000..7593d51fe76 --- /dev/null +++ b/spec/lib/gitlab/usage_data_counters/vs_code_extenion_activity_unique_counter_spec.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.shared_examples 'a tracked vs code unique action' do |event| + before do + stub_application_setting(usage_ping_enabled: true) + end + + def count_unique(date_from:, date_to:) + Gitlab::UsageDataCounters::HLLRedisCounter.unique_events(event_names: action, start_date: date_from, end_date: date_to) + end + + it 'tracks when the user agent is from vs code' do + aggregate_failures do + user_agent = { user_agent: 'vs-code-gitlab-workflow/3.11.1 VSCode/1.52.1 Node.js/12.14.1 (darwin; x64)' } + + expect(track_action(user: user1, **user_agent)).to be_truthy + expect(track_action(user: user1, **user_agent)).to be_truthy + expect(track_action(user: user2, **user_agent)).to be_truthy + + expect(count_unique(date_from: time - 1.week, date_to: time + 1.week)).to eq(2) + end + end + + it 'does not track when the user agent is not from vs code' do + aggregate_failures do + user_agent = { user_agent: 'normal_user_agent' } + + expect(track_action(user: user1, **user_agent)).to be_falsey + expect(track_action(user: user1, **user_agent)).to be_falsey + expect(track_action(user: user2, **user_agent)).to be_falsey + + expect(count_unique(date_from: time - 1.week, date_to: time + 1.week)).to eq(0) + end + end + + it 'does not track if user agent is not present' do + expect(track_action(user: nil, user_agent: nil)).to be_nil + end + + it 'does not track if user is not present' do + user_agent = { user_agent: 'vs-code-gitlab-workflow/3.11.1 VSCode/1.52.1 Node.js/12.14.1 (darwin; x64)' } + + expect(track_action(user: nil, **user_agent)).to be_nil + end +end + +RSpec.describe Gitlab::UsageDataCounters::VSCodeExtensionActivityUniqueCounter, :clean_gitlab_redis_shared_state do + let(:user1) { build(:user, id: 1) } + let(:user2) { build(:user, id: 2) } + let(:time) { Time.current } + + context 'when tracking a vs code api request' do + it_behaves_like 'a tracked vs code unique action' do + let(:action) { described_class::VS_CODE_API_REQUEST_ACTION } + + def track_action(params) + described_class.track_api_request_when_trackable(**params) + end + end + end +end |