Commit 637d18c1 authored by Markus Koller's avatar Markus Koller

Move slash commands integrations to Integrations namespace

Part of https://gitlab.com/gitlab-org/gitlab/-/issues/201855

We're also renaming the base class to `BaseSlashCommands` to address
https://gitlab.com/gitlab-org/gitlab/-/issues/331181.
parent 40374ff3
......@@ -1149,6 +1149,7 @@ RSpec/AnyInstanceOf:
- 'spec/models/hooks/system_hook_spec.rb'
- 'spec/models/hooks/web_hook_spec.rb'
- 'spec/models/integrations/jira_spec.rb'
- 'spec/models/integrations/mattermost_slash_commands_spec.rb'
- 'spec/models/issue_spec.rb'
- 'spec/models/key_spec.rb'
- 'spec/models/member_spec.rb'
......@@ -1156,7 +1157,6 @@ RSpec/AnyInstanceOf:
- 'spec/models/merge_request_spec.rb'
- 'spec/models/note_spec.rb'
- 'spec/models/project_import_state_spec.rb'
- 'spec/models/project_services/mattermost_slash_commands_service_spec.rb'
- 'spec/models/project_spec.rb'
- 'spec/models/repository_spec.rb'
- 'spec/models/user_spec.rb'
......@@ -1289,8 +1289,8 @@ RSpec/AnyInstanceOf:
- 'spec/support/shared_examples/features/snippets_shared_examples.rb'
- 'spec/support/shared_examples/lib/gitlab/ci/ci_trace_shared_examples.rb'
- 'spec/support/shared_examples/models/atomic_internal_id_shared_examples.rb'
- 'spec/support/shared_examples/models/chat_slash_commands_shared_examples.rb'
- 'spec/support/shared_examples/models/diff_note_after_commit_shared_examples.rb'
- 'spec/support/shared_examples/models/integrations/base_slash_commands_shared_examples.rb'
- 'spec/support/shared_examples/models/mentionable_shared_examples.rb'
- 'spec/support/shared_examples/models/with_uploads_shared_examples.rb'
- 'spec/support/shared_examples/path_extraction_shared_examples.rb'
......@@ -1644,15 +1644,9 @@ Gitlab/NamespacedClass:
- 'app/models/project_pages_metadatum.rb'
- 'app/models/project_repository.rb'
- 'app/models/project_repository_storage_move.rb'
- 'app/models/project_services/alerts_service.rb'
- 'app/models/project_services/alerts_service_data.rb'
- 'app/models/project_services/chat_notification_service.rb'
- 'app/models/project_services/mattermost_slash_commands_service.rb'
- 'app/models/project_services/mock_monitoring_service.rb'
- 'app/models/project_services/monitoring_service.rb'
- 'app/models/project_services/prometheus_service.rb'
- 'app/models/project_services/slack_slash_commands_service.rb'
- 'app/models/project_services/slash_commands_service.rb'
- 'app/models/project_setting.rb'
- 'app/models/project_snippet.rb'
- 'app/models/project_statistics.rb'
......
# frozen_string_literal: true
# Base class for ChatOps integrations
# This class is not meant to be used directly, but only to inherrit from.
module Integrations
class BaseSlashCommands < Integration
default_value_for :category, 'chat'
prop_accessor :token
has_many :chat_names, foreign_key: :service_id, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
def valid_token?(token)
self.respond_to?(:token) &&
self.token.present? &&
ActiveSupport::SecurityUtils.secure_compare(token, self.token)
end
def self.supported_events
%w()
end
def can_test?
false
end
def fields
[
{ type: 'text', name: 'token', placeholder: 'XXxxXXxxXXxxXXxxXXxxXXxx' }
]
end
def trigger(params)
return unless valid_token?(params[:token])
chat_user = find_chat_user(params)
user = chat_user&.user
if user
unless user.can?(:use_slash_commands)
return Gitlab::SlashCommands::Presenters::Access.new.deactivated if user.deactivated?
return Gitlab::SlashCommands::Presenters::Access.new.access_denied(project)
end
Gitlab::SlashCommands::Command.new(project, chat_user, params).execute
else
url = authorize_chat_name_url(params)
Gitlab::SlashCommands::Presenters::Access.new(url).authorize
end
end
private
# rubocop: disable CodeReuse/ServiceClass
def find_chat_user(params)
ChatNames::FindUserService.new(self, params).execute
end
# rubocop: enable CodeReuse/ServiceClass
# rubocop: disable CodeReuse/ServiceClass
def authorize_chat_name_url(params)
ChatNames::AuthorizeUserService.new(self, params).execute
end
# rubocop: enable CodeReuse/ServiceClass
end
end
# frozen_string_literal: true
module Integrations
class MattermostSlashCommands < BaseSlashCommands
include Ci::TriggersHelper
prop_accessor :token
def can_test?
false
end
def title
'Mattermost slash commands'
end
def description
"Perform common tasks with slash commands."
end
def self.to_param
'mattermost_slash_commands'
end
def configure(user, params)
token = ::Mattermost::Command.new(user)
.create(command(params))
update(active: true, token: token) if token
rescue ::Mattermost::Error => e
[false, e.message]
end
def list_teams(current_user)
[::Mattermost::Team.new(current_user).all, nil]
rescue ::Mattermost::Error => e
[[], e.message]
end
def chat_responder
::Gitlab::Chat::Responder::Mattermost
end
private
def command(params)
pretty_project_name = project.full_name
params.merge(
auto_complete: true,
auto_complete_desc: "Perform common operations on: #{pretty_project_name}",
auto_complete_hint: '[help]',
description: "Perform common operations on: #{pretty_project_name}",
display_name: "GitLab / #{pretty_project_name}",
method: 'P',
username: 'GitLab')
end
end
end
# frozen_string_literal: true
module Integrations
class SlackSlashCommands < BaseSlashCommands
include Ci::TriggersHelper
def title
'Slack slash commands'
end
def description
"Perform common operations in Slack"
end
def self.to_param
'slack_slash_commands'
end
def trigger(params)
# Format messages to be Slack-compatible
super.tap do |result|
result[:text] = format(result[:text]) if result.is_a?(Hash)
end
end
def chat_responder
::Gitlab::Chat::Responder::Slack
end
private
def format(text)
::Slack::Messenger::Util::LinkFormatter.format(text) if text
end
end
end
......@@ -175,6 +175,7 @@ class Project < ApplicationRecord
has_one :jenkins_service, class_name: 'Integrations::Jenkins'
has_one :jira_service, class_name: 'Integrations::Jira'
has_one :mattermost_service, class_name: 'Integrations::Mattermost'
has_one :mattermost_slash_commands_service, class_name: 'Integrations::MattermostSlashCommands'
has_one :microsoft_teams_service, class_name: 'Integrations::MicrosoftTeams'
has_one :mock_ci_service, class_name: 'Integrations::MockCi'
has_one :packagist_service, class_name: 'Integrations::Packagist'
......@@ -183,12 +184,11 @@ class Project < ApplicationRecord
has_one :pushover_service, class_name: 'Integrations::Pushover'
has_one :redmine_service, class_name: 'Integrations::Redmine'
has_one :slack_service, class_name: 'Integrations::Slack'
has_one :slack_slash_commands_service, class_name: 'Integrations::SlackSlashCommands'
has_one :teamcity_service, class_name: 'Integrations::Teamcity'
has_one :unify_circuit_service, class_name: 'Integrations::UnifyCircuit'
has_one :webex_teams_service, class_name: 'Integrations::WebexTeams'
has_one :youtrack_service, class_name: 'Integrations::Youtrack'
has_one :mattermost_slash_commands_service
has_one :slack_slash_commands_service
has_one :prometheus_service, inverse_of: :project
has_one :mock_monitoring_service
......
# frozen_string_literal: true
class MattermostSlashCommandsService < SlashCommandsService
include Ci::TriggersHelper
prop_accessor :token
def can_test?
false
end
def title
'Mattermost slash commands'
end
def description
"Perform common tasks with slash commands."
end
def self.to_param
'mattermost_slash_commands'
end
def configure(user, params)
token = ::Mattermost::Command.new(user)
.create(command(params))
update(active: true, token: token) if token
rescue ::Mattermost::Error => e
[false, e.message]
end
def list_teams(current_user)
[::Mattermost::Team.new(current_user).all, nil]
rescue ::Mattermost::Error => e
[[], e.message]
end
def chat_responder
::Gitlab::Chat::Responder::Mattermost
end
private
def command(params)
pretty_project_name = project.full_name
params.merge(
auto_complete: true,
auto_complete_desc: "Perform common operations on: #{pretty_project_name}",
auto_complete_hint: '[help]',
description: "Perform common operations on: #{pretty_project_name}",
display_name: "GitLab / #{pretty_project_name}",
method: 'P',
username: 'GitLab')
end
end
# frozen_string_literal: true
class SlackSlashCommandsService < SlashCommandsService
include Ci::TriggersHelper
def title
'Slack slash commands'
end
def description
"Perform common operations in Slack"
end
def self.to_param
'slack_slash_commands'
end
def trigger(params)
# Format messages to be Slack-compatible
super.tap do |result|
result[:text] = format(result[:text]) if result.is_a?(Hash)
end
end
def chat_responder
::Gitlab::Chat::Responder::Slack
end
private
def format(text)
::Slack::Messenger::Util::LinkFormatter.format(text) if text
end
end
# frozen_string_literal: true
# Base class for Chat services
# This class is not meant to be used directly, but only to inherrit from.
class SlashCommandsService < Integration
default_value_for :category, 'chat'
prop_accessor :token
has_many :chat_names, foreign_key: :service_id, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
def valid_token?(token)
self.respond_to?(:token) &&
self.token.present? &&
ActiveSupport::SecurityUtils.secure_compare(token, self.token)
end
def self.supported_events
%w()
end
def can_test?
false
end
def fields
[
{ type: 'text', name: 'token', placeholder: 'XXxxXXxxXXxxXXxxXXxxXXxx' }
]
end
def trigger(params)
return unless valid_token?(params[:token])
chat_user = find_chat_user(params)
user = chat_user&.user
if user
unless user.can?(:use_slash_commands)
return Gitlab::SlashCommands::Presenters::Access.new.deactivated if user.deactivated?
return Gitlab::SlashCommands::Presenters::Access.new.access_denied(project)
end
Gitlab::SlashCommands::Command.new(project, chat_user, params).execute
else
url = authorize_chat_name_url(params)
Gitlab::SlashCommands::Presenters::Access.new(url).authorize
end
end
private
# rubocop: disable CodeReuse/ServiceClass
def find_chat_user(params)
ChatNames::FindUserService.new(self, params).execute
end
# rubocop: enable CodeReuse/ServiceClass
# rubocop: disable CodeReuse/ServiceClass
def authorize_chat_name_url(params)
ChatNames::AuthorizeUserService.new(self, params).execute
end
# rubocop: enable CodeReuse/ServiceClass
end
......@@ -31,21 +31,21 @@ RSpec.describe Projects::Settings::IntegrationsController do
let(:active_services) { assigns(:integrations).map(&:model_name) }
let(:disabled_services) { %w[Integrations::Github] }
it 'enables SlackSlashCommandsService and disables GitlabSlackApplication' do
it 'enables SlackSlashCommands and disables GitlabSlackApplication' do
get :show, params: { namespace_id: project.namespace, project_id: project }
expect(active_services).to include('SlackSlashCommandsService')
expect(active_services).to include('Integrations::SlackSlashCommands')
expect(active_services).not_to include('Integrations::GitlabSlackApplication')
end
it 'enables GitlabSlackApplication and disables SlackSlashCommandsService' do
it 'enables GitlabSlackApplication and disables SlackSlashCommands' do
stub_application_setting(slack_app_enabled: true)
allow(::Gitlab).to receive(:com?).and_return(true)
get :show, params: { namespace_id: project.namespace, project_id: project }
expect(active_services).to include('Integrations::GitlabSlackApplication')
expect(active_services).not_to include('SlackSlashCommandsService')
expect(active_services).not_to include('Integrations::SlackSlashCommands')
end
context 'without a license key' do
......
......@@ -7,12 +7,6 @@ FactoryBot.define do
type { 'GitlabSlackApplicationService' }
end
factory :slack_slash_commands_service do
project
active { true }
type { 'SlackSlashCommandsService' }
end
factory :github_service, class: 'Integrations::Github' do
project
type { 'GithubService' }
......
......@@ -794,6 +794,7 @@ module API
::Integrations::Jenkins,
::Integrations::Jira,
::Integrations::Mattermost,
::Integrations::MattermostSlashCommands,
::Integrations::MicrosoftTeams,
::Integrations::Packagist,
::Integrations::PipelinesEmail,
......@@ -801,10 +802,9 @@ module API
::Integrations::Pushover,
::Integrations::Redmine,
::Integrations::Slack,
::Integrations::SlackSlashCommands,
::Integrations::Teamcity,
::Integrations::Youtrack,
::MattermostSlashCommandsService,
::SlackSlashCommandsService,
::PrometheusService
]
end
......
......@@ -6,8 +6,8 @@ module Gitlab
NAMESPACED_INTEGRATIONS = Set.new(%w(
Asana Assembla Bamboo Bugzilla Buildkite Campfire Confluence CustomIssueTracker Datadog
Discord DroneCi EmailsOnPush Ewm ExternalWiki Flowdock HangoutsChat IssueTracker Irker
Jenkins Jira Mattermost MicrosoftTeams MockCi Packagist PipelinesEmail Pivotaltracker
Pushover Redmine Slack Teamcity UnifyCircuit Youtrack WebexTeams
Jenkins Jira Mattermost MattermostSlashCommands MicrosoftTeams MockCi Packagist PipelinesEmail Pivotaltracker
Pushover Redmine Slack SlackSlashCommands Teamcity UnifyCircuit Youtrack WebexTeams
)).freeze
def cast(value)
......
......@@ -13,7 +13,7 @@ RSpec.describe Projects::MattermostsController do
describe 'GET #new' do
before do
allow_next_instance_of(MattermostSlashCommandsService) do |instance|
allow_next_instance_of(Integrations::MattermostSlashCommands) do |instance|
allow(instance).to receive(:list_teams).and_return([])
end
end
......@@ -43,7 +43,7 @@ RSpec.describe Projects::MattermostsController do
context 'no request can be made to mattermost' do
it 'shows the error' do
allow_next_instance_of(MattermostSlashCommandsService) do |instance|
allow_next_instance_of(Integrations::MattermostSlashCommands) do |instance|
allow(instance).to receive(:configure).and_return([false, "error message"])
end
......
......@@ -167,6 +167,12 @@ FactoryBot.define do
type { 'SlackService' }
end
factory :slack_slash_commands_service, class: 'Integrations::SlackSlashCommands' do
project
active { true }
type { 'SlackSlashCommandsService' }
end
factory :pipelines_email_service, class: 'Integrations::PipelinesEmail' do
project
active { true }
......
......@@ -84,7 +84,7 @@ RSpec.describe 'Set up Mattermost slash commands', :js do
end
it 'shows an error alert with the error message if there is an error requesting teams' do
allow_any_instance_of(MattermostSlashCommandsService).to receive(:list_teams) { [[], 'test mattermost error message'] }
allow_any_instance_of(Integrations::MattermostSlashCommands).to receive(:list_teams) { [[], 'test mattermost error message'] }
click_link 'Add to Mattermost'
......@@ -113,7 +113,7 @@ RSpec.describe 'Set up Mattermost slash commands', :js do
def stub_teams(count: 0)
teams = create_teams(count)
allow_any_instance_of(MattermostSlashCommandsService).to receive(:list_teams) { [teams, nil] }
allow_any_instance_of(Integrations::MattermostSlashCommands).to receive(:list_teams) { [teams, nil] }
teams
end
......
......@@ -2,8 +2,8 @@
require 'spec_helper'
RSpec.describe MattermostSlashCommandsService do
it_behaves_like "chat slash commands service"
RSpec.describe Integrations::MattermostSlashCommands do
it_behaves_like Integrations::BaseSlashCommands
context 'Mattermost API' do
let(:project) { create(:project) }
......
......@@ -2,8 +2,8 @@
require 'spec_helper'
RSpec.describe SlackSlashCommandsService do
it_behaves_like "chat slash commands service"
RSpec.describe Integrations::SlackSlashCommands do
it_behaves_like Integrations::BaseSlashCommands
describe '#trigger' do
context 'when an auth url is generated' do
......
# frozen_string_literal: true
RSpec.shared_examples 'chat slash commands service' do
RSpec.shared_examples Integrations::BaseSlashCommands do
describe "Associations" do
it { is_expected.to respond_to :token }
it { is_expected.to have_many :chat_names }
......
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