From dd47916940e13eab7dd82697e1475b84865b8ca9 Mon Sep 17 00:00:00 2001 From: Rajendra Kadam Date: Mon, 17 Oct 2022 05:10:20 +0000 Subject: [PATCH 1/2] Add slash command for incident declare Add and update specs for the new command Add feature flag to control the usage per user Add foundation code for modal logic --- .../incident_declare_slash_command.yml | 8 +++ .../slash_commands/global_slack_handler.rb | 22 +++++-- .../global_slack_handler_spec.rb | 24 ++++++- lib/gitlab/slash_commands/application_help.rb | 13 ++-- lib/gitlab/slash_commands/command.rb | 12 +++- .../incident_management/incident_command.rb | 17 +++++ .../incident_management/incident_new.rb | 33 ++++++++++ lib/gitlab/slash_commands/presenters/help.rb | 11 +++- .../incident_management/incident_new.rb | 15 +++++ .../slash_commands/application_help_spec.rb | 22 ++++++- .../lib/gitlab/slash_commands/command_spec.rb | 20 ++++++ .../incident_management/incident_new_spec.rb | 64 +++++++++++++++++++ .../incident_management/incident_new_spec.rb | 15 +++++ 13 files changed, 255 insertions(+), 21 deletions(-) create mode 100644 config/feature_flags/development/incident_declare_slash_command.yml create mode 100644 lib/gitlab/slash_commands/incident_management/incident_command.rb create mode 100644 lib/gitlab/slash_commands/incident_management/incident_new.rb create mode 100644 lib/gitlab/slash_commands/presenters/incident_management/incident_new.rb create mode 100644 spec/lib/gitlab/slash_commands/incident_management/incident_new_spec.rb create mode 100644 spec/lib/gitlab/slash_commands/presenters/incident_management/incident_new_spec.rb diff --git a/config/feature_flags/development/incident_declare_slash_command.yml b/config/feature_flags/development/incident_declare_slash_command.yml new file mode 100644 index 00000000000000..9a35dd3274ca31 --- /dev/null +++ b/config/feature_flags/development/incident_declare_slash_command.yml @@ -0,0 +1,8 @@ +--- +name: incident_declare_slash_command +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/101177 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/378072 +milestone: '15.6' +type: development +group: group::respond +default_enabled: false diff --git a/ee/app/services/slash_commands/global_slack_handler.rb b/ee/app/services/slash_commands/global_slack_handler.rb index 2b41df0a601ea4..8d56d21752326d 100644 --- a/ee/app/services/slash_commands/global_slack_handler.rb +++ b/ee/app/services/slash_commands/global_slack_handler.rb @@ -12,10 +12,6 @@ def initialize(params) def trigger return false unless valid_token? - if help_command? - return Gitlab::SlashCommands::ApplicationHelp.new(nil, params).execute - end - unless slack_integration = find_slack_integration error_message = 'GitLab error: project or alias not found' return Gitlab::SlashCommands::Presenters::Error.new(error_message).message @@ -26,6 +22,10 @@ def trigger chat_user = ChatNames::FindUserService.new(integration, params).execute + if help_command? + return Gitlab::SlashCommands::ApplicationHelp.new(nil, chat_user, params).execute + end + if chat_user&.user Gitlab::SlashCommands::Command.new(project, chat_user, params).execute else @@ -50,7 +50,11 @@ def help_command? # rubocop: disable CodeReuse/ActiveRecord def find_slack_integration - SlackIntegration.find_by(team_id: params[:team_id], alias: project_alias) + if project_alias.nil? + SlackIntegration.find_by(team_id: params[:team_id]) + else + SlackIntegration.find_by(team_id: params[:team_id], alias: project_alias) + end end # rubocop: enable CodeReuse/ActiveRecord @@ -58,8 +62,12 @@ def find_slack_integration # '/gitlab help' => [nil, 'help'] # '/gitlab group/project issue new some title' => ['group/project', 'issue new some title'] def parse_command_text(params) - fragments = params[:text].split(/\s/, 2) - fragments.size == 1 ? [nil, fragments.first] : fragments + if params[:text] == 'incident declare' + [nil, params[:text]] + else + fragments = params[:text].split(/\s/, 2) + fragments.size == 1 ? [nil, fragments.first] : fragments + end end end end diff --git a/ee/spec/services/slash_commands/global_slack_handler_spec.rb b/ee/spec/services/slash_commands/global_slack_handler_spec.rb index a2f1f6817d91eb..33d5fded755fca 100644 --- a/ee/spec/services/slash_commands/global_slack_handler_spec.rb +++ b/ee/spec/services/slash_commands/global_slack_handler_spec.rb @@ -33,6 +33,22 @@ def handler_with_valid_token(params) end context 'Valid token' do + context 'with incident declare command' do + it 'calls command handler with no project alias' do + expect_any_instance_of(Gitlab::SlashCommands::Command).to receive(:execute) + expect_any_instance_of(ChatNames::FindUserService).to receive(:execute).and_return(chat_name) + + enable_slack_application(project) + + slack_integration = create(:slack_integration, integration: project.gitlab_slack_application_integration) + + handler_with_valid_token( + text: "incident declare", + team_id: slack_integration.team_id + ).trigger + end + end + it 'calls command handler if project alias is valid' do expect_any_instance_of(Gitlab::SlashCommands::Command).to receive(:execute) expect_any_instance_of(ChatNames::FindUserService).to receive(:execute).and_return(chat_name) @@ -79,9 +95,15 @@ def handler_with_valid_token(params) it 'calls help presenter' do expect_any_instance_of(Gitlab::SlashCommands::ApplicationHelp).to receive(:execute) + expect_any_instance_of(ChatNames::FindUserService).to receive(:execute).and_return(chat_name) + + enable_slack_application(project) + + slack_integration = create(:slack_integration, integration: project.gitlab_slack_application_integration) handler_with_valid_token( - text: "help" + text: "help", + team_id: slack_integration.team_id ).trigger end end diff --git a/lib/gitlab/slash_commands/application_help.rb b/lib/gitlab/slash_commands/application_help.rb index 1a92346be1561e..bfdb65a816d601 100644 --- a/lib/gitlab/slash_commands/application_help.rb +++ b/lib/gitlab/slash_commands/application_help.rb @@ -3,14 +3,9 @@ module Gitlab module SlashCommands class ApplicationHelp < BaseCommand - def initialize(project, params) - @project = project - @params = params - end - def execute Gitlab::SlashCommands::Presenters::Help - .new(project, commands) + .new(project, commands, params) .present(trigger, params[:text]) end @@ -21,7 +16,11 @@ def trigger end def commands - Gitlab::SlashCommands::Command.commands + Gitlab::SlashCommands::Command.new( + project, + chat_name, + params + ).commands end end end diff --git a/lib/gitlab/slash_commands/command.rb b/lib/gitlab/slash_commands/command.rb index 239479f99d2e74..265eda46489ad9 100644 --- a/lib/gitlab/slash_commands/command.rb +++ b/lib/gitlab/slash_commands/command.rb @@ -3,8 +3,8 @@ module Gitlab module SlashCommands class Command < BaseCommand - def self.commands - [ + def commands + commands = [ Gitlab::SlashCommands::IssueShow, Gitlab::SlashCommands::IssueNew, Gitlab::SlashCommands::IssueSearch, @@ -14,6 +14,12 @@ def self.commands Gitlab::SlashCommands::Deploy, Gitlab::SlashCommands::Run ] + + if Feature.enabled?(:incident_declare_slash_command, current_user) + commands << Gitlab::SlashCommands::IncidentManagement::IncidentNew + end + + commands end def execute @@ -44,7 +50,7 @@ def match_command private def available_commands - self.class.commands.keep_if do |klass| + commands.keep_if do |klass| klass.available?(project) end end diff --git a/lib/gitlab/slash_commands/incident_management/incident_command.rb b/lib/gitlab/slash_commands/incident_management/incident_command.rb new file mode 100644 index 00000000000000..13d371151f913a --- /dev/null +++ b/lib/gitlab/slash_commands/incident_management/incident_command.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module Gitlab + module SlashCommands + module IncidentManagement + class IncidentCommand < BaseCommand + def self.available?(project) + true + end + + def collection + IssuesFinder.new(current_user, project_id: project.id, issue_types: :incident).execute + end + end + end + end +end diff --git a/lib/gitlab/slash_commands/incident_management/incident_new.rb b/lib/gitlab/slash_commands/incident_management/incident_new.rb new file mode 100644 index 00000000000000..ce148f888b803e --- /dev/null +++ b/lib/gitlab/slash_commands/incident_management/incident_new.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module Gitlab + module SlashCommands + module IncidentManagement + class IncidentNew < IncidentCommand + def self.help_message + 'incident declare' + end + + def self.allowed?(project, user) + Feature.enabled?(:incident_declare_slash_command, user) && can?(user, :create_incident, project) + end + + def self.match(text) + text == 'incident declare' + end + + def execute(_match) + response = ServiceResponse.success(message: 'It works!') + + presenter.present(response.message) + end + + private + + def presenter + Gitlab::SlashCommands::Presenters::IncidentManagement::IncidentNew.new + end + end + end + end +end diff --git a/lib/gitlab/slash_commands/presenters/help.rb b/lib/gitlab/slash_commands/presenters/help.rb index 71bc0dc0123cd1..61b36308d2090e 100644 --- a/lib/gitlab/slash_commands/presenters/help.rb +++ b/lib/gitlab/slash_commands/presenters/help.rb @@ -4,9 +4,10 @@ module Gitlab module SlashCommands module Presenters class Help < Presenters::Base - def initialize(project, commands) + def initialize(project, commands, params = {}) @project = project @commands = commands + @params = params end def present(trigger, text) @@ -66,7 +67,13 @@ def project_info def full_commands_message(trigger) list = @commands - .map { |command| "#{trigger} #{command.help_message}" } + .map do |command| + if command < Gitlab::SlashCommands::IncidentManagement::IncidentCommand + "#{@params[:command]} #{command.help_message}" + else + "#{trigger} #{command.help_message}" + end + end .join("\n") <<~MESSAGE diff --git a/lib/gitlab/slash_commands/presenters/incident_management/incident_new.rb b/lib/gitlab/slash_commands/presenters/incident_management/incident_new.rb new file mode 100644 index 00000000000000..5030c8282db65d --- /dev/null +++ b/lib/gitlab/slash_commands/presenters/incident_management/incident_new.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module Gitlab + module SlashCommands + module Presenters + module IncidentManagement + class IncidentNew < Presenters::Base + def present(message) + ephemeral_response(text: message) + end + end + end + end + end +end diff --git a/spec/lib/gitlab/slash_commands/application_help_spec.rb b/spec/lib/gitlab/slash_commands/application_help_spec.rb index b82121bf3a8534..b182c0e5cc6c7a 100644 --- a/spec/lib/gitlab/slash_commands/application_help_spec.rb +++ b/spec/lib/gitlab/slash_commands/application_help_spec.rb @@ -4,11 +4,13 @@ RSpec.describe Gitlab::SlashCommands::ApplicationHelp do let(:params) { { command: '/gitlab', text: 'help' } } + let_it_be(:user) { create(:user) } + let_it_be(:chat_user) { create(:chat_name, user: user) } let(:project) { build(:project) } describe '#execute' do subject do - described_class.new(project, params).execute + described_class.new(project, chat_user, params).execute end it 'displays the help section' do @@ -16,5 +18,23 @@ expect(subject[:text]).to include('Available commands') expect(subject[:text]).to include('/gitlab [project name or alias] issue show') end + + context 'with incident declare command' do + context 'when feature flag is enabled' do + it 'displays the declare command' do + expect(subject[:text]).to include('/gitlab incident declare') + end + end + + context 'when feature flag is disabled' do + before do + stub_feature_flags(incident_declare_slash_command: false) + end + + it 'does not displays the declare command' do + expect(subject[:text]).not_to include('/gitlab incident declare') + end + end + end end end diff --git a/spec/lib/gitlab/slash_commands/command_spec.rb b/spec/lib/gitlab/slash_commands/command_spec.rb index 069577b3846494..f4664bcfef99b7 100644 --- a/spec/lib/gitlab/slash_commands/command_spec.rb +++ b/spec/lib/gitlab/slash_commands/command_spec.rb @@ -122,5 +122,25 @@ it { is_expected.to eq(Gitlab::SlashCommands::IssueComment) } end + + context 'when incident declare is triggered' do + context 'IncidentNew is triggered' do + let(:params) { { text: 'incident declare' } } + + it { is_expected.to eq(Gitlab::SlashCommands::IncidentManagement::IncidentNew) } + end + + context 'when feature flag is disabled' do + before do + stub_feature_flags(incident_declare_slash_command: false) + end + + context 'IncidentNew is triggered' do + let(:params) { { text: 'incident declare' } } + + it { is_expected.not_to eq(Gitlab::SlashCommands::IncidentManagement::IncidentNew) } + end + end + end end end diff --git a/spec/lib/gitlab/slash_commands/incident_management/incident_new_spec.rb b/spec/lib/gitlab/slash_commands/incident_management/incident_new_spec.rb new file mode 100644 index 00000000000000..57301d134bca08 --- /dev/null +++ b/spec/lib/gitlab/slash_commands/incident_management/incident_new_spec.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::SlashCommands::IncidentManagement::IncidentNew do + let_it_be(:project) { create(:project) } + let_it_be(:user) { create(:user) } + let_it_be(:chat_name) { create(:chat_name, user: user) } + let_it_be(:regex_match) { described_class.match('declare') } + + subject do + described_class.new(project, chat_name) + end + + describe '#execute' do + context 'when invoked' do + it 'sends ephemeral response' do + response = subject.execute(regex_match) + + expect(response[:response_type]).to be(:ephemeral) + expect(response[:text]).to eq('It works!') + end + end + end + + describe '#allowed?' do + context 'when user has permissions' do + before do + project.add_developer(user) + end + + it 'returns true' do + # rubocop: disable RSpec/PredicateMatcher + expect(described_class.allowed?(project, user)).to be_truthy + # rubocop: enable RSpec/PredicateMatcher + end + end + + context 'when feature flag is disabled' do + before do + project.add_developer(user) + stub_feature_flags(incident_declare_slash_command: false) + end + + # rubocop: disable RSpec/PredicateMatcher + it 'returns false in allowed?' do + expect(described_class.allowed?(project, user)).to be_falsey + end + # rubocop: enable RSpec/PredicateMatcher + end + end + + describe '#collection' do + context 'when collection method id called' do + it 'calls IssuesFinder' do + expect_next_instance_of(IssuesFinder) do |finder| + expect(finder).to receive(:execute) + end + + subject.collection + end + end + end +end diff --git a/spec/lib/gitlab/slash_commands/presenters/incident_management/incident_new_spec.rb b/spec/lib/gitlab/slash_commands/presenters/incident_management/incident_new_spec.rb new file mode 100644 index 00000000000000..cbc584b931fbd3 --- /dev/null +++ b/spec/lib/gitlab/slash_commands/presenters/incident_management/incident_new_spec.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::SlashCommands::Presenters::IncidentManagement::IncidentNew do + subject { described_class.new } + + it 'returns the ephemeral message' do + message = subject.present('It works!') + + expect(message).to be_a(Hash) + expect(message[:text]).to eq('It works!') + expect(message[:response_type]).to be(:ephemeral) + end +end -- GitLab From b423d11f4b2c42f60edd7f729e1317de939069e1 Mon Sep 17 00:00:00 2001 From: Rajendra Kadam Date: Mon, 31 Oct 2022 10:12:17 +0530 Subject: [PATCH 2/2] Remove diable cop line --- .../incident_management/incident_new_spec.rb | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/spec/lib/gitlab/slash_commands/incident_management/incident_new_spec.rb b/spec/lib/gitlab/slash_commands/incident_management/incident_new_spec.rb index 57301d134bca08..ee54f29aec71a9 100644 --- a/spec/lib/gitlab/slash_commands/incident_management/incident_new_spec.rb +++ b/spec/lib/gitlab/slash_commands/incident_management/incident_new_spec.rb @@ -30,9 +30,7 @@ end it 'returns true' do - # rubocop: disable RSpec/PredicateMatcher - expect(described_class.allowed?(project, user)).to be_truthy - # rubocop: enable RSpec/PredicateMatcher + expect(described_class).to be_allowed(project, user) end end @@ -42,11 +40,9 @@ stub_feature_flags(incident_declare_slash_command: false) end - # rubocop: disable RSpec/PredicateMatcher it 'returns false in allowed?' do - expect(described_class.allowed?(project, user)).to be_falsey + expect(described_class).not_to be_allowed(project, user) end - # rubocop: enable RSpec/PredicateMatcher end end -- GitLab