diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml index 84a5c339f43eb68cfb3bb0dcb3061966ec60fb99..20536f69bf1efc4e3bb822d2fcce623bf12ad737 100644 --- a/app/workers/all_queues.yml +++ b/app/workers/all_queues.yml @@ -3270,6 +3270,15 @@ :weight: 2 :idempotent: false :tags: [] +- :name: issuable_create_reminder + :worker_name: Issuable::CreateReminderWorker + :feature_category: :code_review_workflow + :has_external_dependencies: false + :urgency: :low + :resource_boundary: :unknown + :weight: 1 + :idempotent: true + :tags: [] - :name: issuable_export_csv :worker_name: IssuableExportCsvWorker :feature_category: :team_planning diff --git a/app/workers/issuable/create_reminder_worker.rb b/app/workers/issuable/create_reminder_worker.rb new file mode 100644 index 0000000000000000000000000000000000000000..2fbb59584cadb9f19c2a3209e487b843cb9bd78a --- /dev/null +++ b/app/workers/issuable/create_reminder_worker.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module Issuable + class CreateReminderWorker + include ApplicationWorker + + data_consistency :delayed + + idempotent! + feature_category :code_review_workflow + + def perform(target_id, target_type, user_id) + # Create a notification for the user against the target + # + current_user = User.find(user_id) + return unless current_user + + target = target_type.constantize.find(target_id) + return unless target + + TodoService.new.mark_todo(target, current_user) + end + end +end diff --git a/config/feature_flags/experiment/remind_me_quick_action.yml b/config/feature_flags/experiment/remind_me_quick_action.yml new file mode 100644 index 0000000000000000000000000000000000000000..790f3028b240ce36075049d654a561734d73859c --- /dev/null +++ b/config/feature_flags/experiment/remind_me_quick_action.yml @@ -0,0 +1,9 @@ +--- +name: remind_me_quick_action +feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/20490 +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/149938 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/450706 +milestone: '17.0' +group: group::code review +type: experiment +default_enabled: false diff --git a/config/metrics/counts_28d/20210216184803_quickactions_total_unique_counts_monthly.yml b/config/metrics/counts_28d/20210216184803_quickactions_total_unique_counts_monthly.yml index c070154d2ae1f09a086f3d1b9f671837fb5763c2..1e339465e20bfeb3e414d606c815dedd18a780c9 100644 --- a/config/metrics/counts_28d/20210216184803_quickactions_total_unique_counts_monthly.yml +++ b/config/metrics/counts_28d/20210216184803_quickactions_total_unique_counts_monthly.yml @@ -83,6 +83,8 @@ events: unique: user.id - name: i_quickactions_relate unique: user.id +- name: i_quickactions_remind_me + unique: user.id - name: i_quickactions_remove_child_epic unique: user.id - name: i_quickactions_remove_due_date diff --git a/config/metrics/counts_28d/20210427103119_code_review_group_monthly_active_users.yml b/config/metrics/counts_28d/20210427103119_code_review_group_monthly_active_users.yml index 99c8c2deb180158a5b7ae69b29fe5dccdb0e42ab..b0551b086d6529754fb2322bac26adc73194f5d0 100644 --- a/config/metrics/counts_28d/20210427103119_code_review_group_monthly_active_users.yml +++ b/config/metrics/counts_28d/20210427103119_code_review_group_monthly_active_users.yml @@ -267,3 +267,5 @@ events: unique: user.id - name: i_code_review_saved_replies_use_in_other unique: user.id +- name: i_quickactions_remind_me + unique: user.id diff --git a/config/metrics/counts_28d/20240418165317_i_quickactions_remind_me_monthly.yml b/config/metrics/counts_28d/20240418165317_i_quickactions_remind_me_monthly.yml new file mode 100644 index 0000000000000000000000000000000000000000..7e2dfa935a52cf9c87aabd40299bc4cc5518ff8e --- /dev/null +++ b/config/metrics/counts_28d/20240418165317_i_quickactions_remind_me_monthly.yml @@ -0,0 +1,25 @@ +--- +key_path: redis_hll_counters.code_review.i_quickactions_remind_me_monthly +description: Count of WAU using the `/remind_me` quick action +product_section: dev +product_stage: create +product_group: code_review +value_type: number +status: active +milestone: "17.0" +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/149938 +time_frame: 28d +data_source: redis_hll +data_category: optional +instrumentation_class: RedisHLLMetric +performance_indicator_type: [] +options: + events: + - i_quickactions_remind_me +distribution: +- ce +- ee +tier: +- free +- premium +- ultimate diff --git a/config/metrics/counts_7d/20210216184801_quickactions_total_unique_counts_weekly.yml b/config/metrics/counts_7d/20210216184801_quickactions_total_unique_counts_weekly.yml index a7a8a9b7975fcd36d77ea2215891722b7c8b86de..eb7a22fdbdc5020b9956d3f3dd42a7cdfee30aac 100644 --- a/config/metrics/counts_7d/20210216184801_quickactions_total_unique_counts_weekly.yml +++ b/config/metrics/counts_7d/20210216184801_quickactions_total_unique_counts_weekly.yml @@ -83,6 +83,8 @@ events: unique: user.id - name: i_quickactions_relate unique: user.id +- name: i_quickactions_remind_me + unique: user.id - name: i_quickactions_remove_child_epic unique: user.id - name: i_quickactions_remove_due_date diff --git a/config/metrics/counts_7d/20240418165311_i_quickactions_remind_me_weekly.yml b/config/metrics/counts_7d/20240418165311_i_quickactions_remind_me_weekly.yml new file mode 100644 index 0000000000000000000000000000000000000000..10ef057c8291c0b4a5563523e997be14686fffb8 --- /dev/null +++ b/config/metrics/counts_7d/20240418165311_i_quickactions_remind_me_weekly.yml @@ -0,0 +1,25 @@ +--- +key_path: redis_hll_counters.code_review.i_quickactions_remind_me_weekly +description: Count of WAU using the `/remind_me` quick action +product_section: dev +product_stage: create +product_group: code_review +value_type: number +status: active +milestone: "17.0" +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/149938 +time_frame: 7d +data_source: redis_hll +data_category: optional +instrumentation_class: RedisHLLMetric +performance_indicator_type: [] +options: + events: + - i_quickactions_remind_me +distribution: +- ce +- ee +tier: +- free +- premium +- ultimate diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml index 18dd4431ad783bcb541cef966ced3508ce119741..fd57568444fca2a1a5c367148c4f927b49c9e079 100644 --- a/config/sidekiq_queues.yml +++ b/config/sidekiq_queues.yml @@ -419,6 +419,8 @@ - 1 - - invalid_gpg_signature_update - 2 +- - issuable_create_reminder + - 1 - - issuable_export_csv - 1 - - issuable_label_links_destroy diff --git a/lib/gitlab/quick_actions/issue_and_merge_request_actions.rb b/lib/gitlab/quick_actions/issue_and_merge_request_actions.rb index 10ccba081332eac36560c25f7b24479425ed6604..90027e091b2871e6d4b3a7b1df2167e8ff2dac3a 100644 --- a/lib/gitlab/quick_actions/issue_and_merge_request_actions.rb +++ b/lib/gitlab/quick_actions/issue_and_merge_request_actions.rb @@ -238,6 +238,37 @@ module IssueAndMergeRequestActions end end + ######################################################################## + # + # /remind_me + # + types Issue, MergeRequest + desc do + _('Set to-do reminder') + end + explanation do + _('Creates a reminder to-do item after the specified time period.') + end + params '<1w 3d 2h 14m>' + parse_params do |raw_delay| + ChronicDuration.parse(raw_delay) + end + condition do + Feature.enabled?(:remind_me_quick_action, quick_action_target.project) + end + command :remind_me do |parsed_delay| + # Schedule a CreateReminderWorker for the specified delay + # + ::Issuable::CreateReminderWorker.perform_in( + parsed_delay, + quick_action_target.id, + quick_action_target.class.to_s, + current_user.id + ) + + @execution_message[:remind_me] = _('Reminder set.') + end + ######################################################################## # # /remove_estimate, /remove_time_estimate diff --git a/lib/gitlab/usage_data_counters/hll_redis_legacy_events.yml b/lib/gitlab/usage_data_counters/hll_redis_legacy_events.yml index 1dff24ecf58e1e7965da60539f11282e99209ada..247add5a1c234420f57afff4f07ba4f6a03f33e2 100644 --- a/lib/gitlab/usage_data_counters/hll_redis_legacy_events.yml +++ b/lib/gitlab/usage_data_counters/hll_redis_legacy_events.yml @@ -276,6 +276,7 @@ - i_quickactions_rebase - i_quickactions_relabel - i_quickactions_relate +- i_quickactions_remind_me - i_quickactions_remove_child_epic - i_quickactions_remove_contacts - i_quickactions_remove_due_date diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 3896d4b92ce754c761e19633e38ba87653fc6a30..7d714103c713258abdc7aeeb3c9f545b0e35d10c 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -15502,6 +15502,9 @@ msgstr "" msgid "Creates a branch and a merge request to resolve this issue." msgstr "" +msgid "Creates a reminder to-do item after the specified time period." +msgstr "" + msgid "Creates a summary of all comments" msgstr "" @@ -42451,6 +42454,9 @@ msgstr "" msgid "Remind later" msgstr "" +msgid "Reminder set." +msgstr "" + msgid "Remote" msgstr "" @@ -48101,6 +48107,9 @@ msgstr "" msgid "Set to auto-merge" msgstr "" +msgid "Set to-do reminder" +msgstr "" + msgid "Set up" msgstr "" diff --git a/spec/lib/gitlab/usage_data_counters/code_review_events_spec.rb b/spec/lib/gitlab/usage_data_counters/code_review_events_spec.rb index f7082531401fe87bd1ee640404da8aa9dc8afe87..61e78c1c27f6dac164c0fb9a6175c8a4a15d7de7 100644 --- a/spec/lib/gitlab/usage_data_counters/code_review_events_spec.rb +++ b/spec/lib/gitlab/usage_data_counters/code_review_events_spec.rb @@ -6,7 +6,18 @@ # NOTE: ONLY user related metrics to be added to the aggregates - otherwise add it to the exception list RSpec.describe 'Code review events' do it 'the aggregated metrics contain all the code review metrics' do - mr_related_events = %w[i_code_review_create_mr i_code_review_mr_diffs i_code_review_mr_with_invalid_approvers i_code_review_mr_single_file_diffs i_code_review_total_suggestions_applied i_code_review_total_suggestions_added i_code_review_create_note_in_ipynb_diff i_code_review_create_note_in_ipynb_diff_mr i_code_review_create_note_in_ipynb_diff_commit i_code_review_merge_request_widget_license_compliance_warning] + mr_related_events = %w[ + i_code_review_create_mr + i_code_review_mr_diffs + i_code_review_mr_with_invalid_approvers + i_code_review_mr_single_file_diffs + i_code_review_total_suggestions_applied + i_code_review_total_suggestions_added + i_code_review_create_note_in_ipynb_diff + i_code_review_create_note_in_ipynb_diff_mr + i_code_review_create_note_in_ipynb_diff_commit + i_code_review_merge_request_widget_license_compliance_warning + ] all_code_review_events = Gitlab::Usage::MetricDefinition.all.flat_map do |definition| next [] unless definition.attributes[:key_path].include?('.code_review.') && diff --git a/spec/services/notes/quick_actions_service_spec.rb b/spec/services/notes/quick_actions_service_spec.rb index dcdfdf0439d33cbcd34e68633c540e294f980735..27f9385490a7d0c333079ae8c4dde172c6217446 100644 --- a/spec/services/notes/quick_actions_service_spec.rb +++ b/spec/services/notes/quick_actions_service_spec.rb @@ -347,6 +347,38 @@ end end + describe '/remind_me' do + let(:issue) { create(:issue, project: project, milestone: milestone) } + let(:note_text) { '/remind_me 1d' } + let(:note) { create(:note_on_issue, noteable: issue, project: project, note: note_text) } + + context 'on an issue' do + it 'leaves the note empty' do + expect(execute(note)).to be_empty + end + + it 'attempts to set a reminder' do + expect(Issuable::CreateReminderWorker).to receive(:perform_in) + + execute(note) + end + end + + context 'on a merge request' do + let(:note_mr) { create(:note_on_merge_request, project: project, note: note_text) } + + it 'leaves the note empty' do + expect(execute(note_mr)).to be_empty + end + + it 'attempts to set a reminder' do + expect(Issuable::CreateReminderWorker).to receive(:perform_in) + + execute(note) + end + end + end + describe '/add_child' do let_it_be(:noteable) { create(:work_item, :objective, project: project) } let_it_be(:child) { create(:work_item, :objective, project: project) } diff --git a/spec/workers/issuable/create_reminder_worker_spec.rb b/spec/workers/issuable/create_reminder_worker_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..cfe5e80756d5b58885e48d700e32177783328a8b --- /dev/null +++ b/spec/workers/issuable/create_reminder_worker_spec.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe Issuable::CreateReminderWorker, feature_category: :code_review_workflow do + let_it_be(:user) { create(:user) } + let_it_be(:merge_request) { create(:merge_request, author: user) } + let(:job_args) { [merge_request.id, "MergeRequest", user.id] } + let(:service) { double } + + subject(:worker_perform) { described_class.new.perform(*job_args) } + + include_examples "an idempotent worker" + + describe "#perform" do + context "when the user does not exist" do + before do + allow(User).to receive(:find).at_least(:once).with(user.id).and_return(nil) + end + + it "does not call TodoService#mark_todo" do + expect(TodoService).not_to receive(:new) + + worker_perform + end + end + + context "when the merge request does not exist" do + before do + allow(MergeRequest).to receive(:find).at_least(:once).with(merge_request.id).and_return(nil) + end + + it "does not call TodoService#mark_todo" do + expect(TodoService).not_to receive(:new) + + worker_perform + end + end + + context "when MR and user exist" do + it "creates a new instance of TodoService" do + expect(TodoService).to receive(:new).at_least(:once).and_call_original + + worker_perform + end + + it "calls TodoService#mark_todo" do + expect(TodoService).to receive(:new).at_least(:once).and_return(service) + expect(service).to receive(:mark_todo).at_least(:once) + + worker_perform + end + end + end +end