Commit 9de15855 authored by Dmitry Gruzd's avatar Dmitry Gruzd

Merge branch '201855-rename-slash-commands-integrations' into 'master'

Move slash commands integrations to Integrations namespace

See merge request gitlab-org/gitlab!63015
parents de8cf309 637d18c1
...@@ -1149,6 +1149,7 @@ RSpec/AnyInstanceOf: ...@@ -1149,6 +1149,7 @@ RSpec/AnyInstanceOf:
- 'spec/models/hooks/system_hook_spec.rb' - 'spec/models/hooks/system_hook_spec.rb'
- 'spec/models/hooks/web_hook_spec.rb' - 'spec/models/hooks/web_hook_spec.rb'
- 'spec/models/integrations/jira_spec.rb' - 'spec/models/integrations/jira_spec.rb'
- 'spec/models/integrations/mattermost_slash_commands_spec.rb'
- 'spec/models/issue_spec.rb' - 'spec/models/issue_spec.rb'
- 'spec/models/key_spec.rb' - 'spec/models/key_spec.rb'
- 'spec/models/member_spec.rb' - 'spec/models/member_spec.rb'
...@@ -1156,7 +1157,6 @@ RSpec/AnyInstanceOf: ...@@ -1156,7 +1157,6 @@ RSpec/AnyInstanceOf:
- 'spec/models/merge_request_spec.rb' - 'spec/models/merge_request_spec.rb'
- 'spec/models/note_spec.rb' - 'spec/models/note_spec.rb'
- 'spec/models/project_import_state_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/project_spec.rb'
- 'spec/models/repository_spec.rb' - 'spec/models/repository_spec.rb'
- 'spec/models/user_spec.rb' - 'spec/models/user_spec.rb'
...@@ -1289,8 +1289,8 @@ RSpec/AnyInstanceOf: ...@@ -1289,8 +1289,8 @@ RSpec/AnyInstanceOf:
- 'spec/support/shared_examples/features/snippets_shared_examples.rb' - '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/lib/gitlab/ci/ci_trace_shared_examples.rb'
- 'spec/support/shared_examples/models/atomic_internal_id_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/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/mentionable_shared_examples.rb'
- 'spec/support/shared_examples/models/with_uploads_shared_examples.rb' - 'spec/support/shared_examples/models/with_uploads_shared_examples.rb'
- 'spec/support/shared_examples/path_extraction_shared_examples.rb' - 'spec/support/shared_examples/path_extraction_shared_examples.rb'
...@@ -1644,15 +1644,9 @@ Gitlab/NamespacedClass: ...@@ -1644,15 +1644,9 @@ Gitlab/NamespacedClass:
- 'app/models/project_pages_metadatum.rb' - 'app/models/project_pages_metadatum.rb'
- 'app/models/project_repository.rb' - 'app/models/project_repository.rb'
- 'app/models/project_repository_storage_move.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/mock_monitoring_service.rb'
- 'app/models/project_services/monitoring_service.rb' - 'app/models/project_services/monitoring_service.rb'
- 'app/models/project_services/prometheus_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_setting.rb'
- 'app/models/project_snippet.rb' - 'app/models/project_snippet.rb'
- 'app/models/project_statistics.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 ...@@ -175,6 +175,7 @@ class Project < ApplicationRecord
has_one :jenkins_service, class_name: 'Integrations::Jenkins' has_one :jenkins_service, class_name: 'Integrations::Jenkins'
has_one :jira_service, class_name: 'Integrations::Jira' has_one :jira_service, class_name: 'Integrations::Jira'
has_one :mattermost_service, class_name: 'Integrations::Mattermost' 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 :microsoft_teams_service, class_name: 'Integrations::MicrosoftTeams'
has_one :mock_ci_service, class_name: 'Integrations::MockCi' has_one :mock_ci_service, class_name: 'Integrations::MockCi'
has_one :packagist_service, class_name: 'Integrations::Packagist' has_one :packagist_service, class_name: 'Integrations::Packagist'
...@@ -183,12 +184,11 @@ class Project < ApplicationRecord ...@@ -183,12 +184,11 @@ class Project < ApplicationRecord
has_one :pushover_service, class_name: 'Integrations::Pushover' has_one :pushover_service, class_name: 'Integrations::Pushover'
has_one :redmine_service, class_name: 'Integrations::Redmine' has_one :redmine_service, class_name: 'Integrations::Redmine'
has_one :slack_service, class_name: 'Integrations::Slack' 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 :teamcity_service, class_name: 'Integrations::Teamcity'
has_one :unify_circuit_service, class_name: 'Integrations::UnifyCircuit' has_one :unify_circuit_service, class_name: 'Integrations::UnifyCircuit'
has_one :webex_teams_service, class_name: 'Integrations::WebexTeams' has_one :webex_teams_service, class_name: 'Integrations::WebexTeams'
has_one :youtrack_service, class_name: 'Integrations::Youtrack' 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 :prometheus_service, inverse_of: :project
has_one :mock_monitoring_service 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 ...@@ -31,21 +31,21 @@ RSpec.describe Projects::Settings::IntegrationsController do
let(:active_services) { assigns(:integrations).map(&:model_name) } let(:active_services) { assigns(:integrations).map(&:model_name) }
let(:disabled_services) { %w[Integrations::Github] } 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 } 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') expect(active_services).not_to include('Integrations::GitlabSlackApplication')
end end
it 'enables GitlabSlackApplication and disables SlackSlashCommandsService' do it 'enables GitlabSlackApplication and disables SlackSlashCommands' do
stub_application_setting(slack_app_enabled: true) stub_application_setting(slack_app_enabled: true)
allow(::Gitlab).to receive(:com?).and_return(true) allow(::Gitlab).to receive(:com?).and_return(true)
get :show, params: { namespace_id: project.namespace, project_id: project } get :show, params: { namespace_id: project.namespace, project_id: project }
expect(active_services).to include('Integrations::GitlabSlackApplication') expect(active_services).to include('Integrations::GitlabSlackApplication')
expect(active_services).not_to include('SlackSlashCommandsService') expect(active_services).not_to include('Integrations::SlackSlashCommands')
end end
context 'without a license key' do context 'without a license key' do
......
...@@ -7,12 +7,6 @@ FactoryBot.define do ...@@ -7,12 +7,6 @@ FactoryBot.define do
type { 'GitlabSlackApplicationService' } type { 'GitlabSlackApplicationService' }
end end
factory :slack_slash_commands_service do
project
active { true }
type { 'SlackSlashCommandsService' }
end
factory :github_service, class: 'Integrations::Github' do factory :github_service, class: 'Integrations::Github' do
project project
type { 'GithubService' } type { 'GithubService' }
......
...@@ -794,6 +794,7 @@ module API ...@@ -794,6 +794,7 @@ module API
::Integrations::Jenkins, ::Integrations::Jenkins,
::Integrations::Jira, ::Integrations::Jira,
::Integrations::Mattermost, ::Integrations::Mattermost,
::Integrations::MattermostSlashCommands,
::Integrations::MicrosoftTeams, ::Integrations::MicrosoftTeams,
::Integrations::Packagist, ::Integrations::Packagist,
::Integrations::PipelinesEmail, ::Integrations::PipelinesEmail,
...@@ -801,10 +802,9 @@ module API ...@@ -801,10 +802,9 @@ module API
::Integrations::Pushover, ::Integrations::Pushover,
::Integrations::Redmine, ::Integrations::Redmine,
::Integrations::Slack, ::Integrations::Slack,
::Integrations::SlackSlashCommands,
::Integrations::Teamcity, ::Integrations::Teamcity,
::Integrations::Youtrack, ::Integrations::Youtrack,
::MattermostSlashCommandsService,
::SlackSlashCommandsService,
::PrometheusService ::PrometheusService
] ]
end end
......
...@@ -6,8 +6,8 @@ module Gitlab ...@@ -6,8 +6,8 @@ module Gitlab
NAMESPACED_INTEGRATIONS = Set.new(%w( NAMESPACED_INTEGRATIONS = Set.new(%w(
Asana Assembla Bamboo Bugzilla Buildkite Campfire Confluence CustomIssueTracker Datadog Asana Assembla Bamboo Bugzilla Buildkite Campfire Confluence CustomIssueTracker Datadog
Discord DroneCi EmailsOnPush Ewm ExternalWiki Flowdock HangoutsChat IssueTracker Irker Discord DroneCi EmailsOnPush Ewm ExternalWiki Flowdock HangoutsChat IssueTracker Irker
Jenkins Jira Mattermost MicrosoftTeams MockCi Packagist PipelinesEmail Pivotaltracker Jenkins Jira Mattermost MattermostSlashCommands MicrosoftTeams MockCi Packagist PipelinesEmail Pivotaltracker
Pushover Redmine Slack Teamcity UnifyCircuit Youtrack WebexTeams Pushover Redmine Slack SlackSlashCommands Teamcity UnifyCircuit Youtrack WebexTeams
)).freeze )).freeze
def cast(value) def cast(value)
......
...@@ -13,7 +13,7 @@ RSpec.describe Projects::MattermostsController do ...@@ -13,7 +13,7 @@ RSpec.describe Projects::MattermostsController do
describe 'GET #new' do describe 'GET #new' do
before 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([]) allow(instance).to receive(:list_teams).and_return([])
end end
end end
...@@ -43,7 +43,7 @@ RSpec.describe Projects::MattermostsController do ...@@ -43,7 +43,7 @@ RSpec.describe Projects::MattermostsController do
context 'no request can be made to mattermost' do context 'no request can be made to mattermost' do
it 'shows the error' 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"]) allow(instance).to receive(:configure).and_return([false, "error message"])
end end
......
...@@ -167,6 +167,12 @@ FactoryBot.define do ...@@ -167,6 +167,12 @@ FactoryBot.define do
type { 'SlackService' } type { 'SlackService' }
end end
factory :slack_slash_commands_service, class: 'Integrations::SlackSlashCommands' do
project
active { true }
type { 'SlackSlashCommandsService' }
end
factory :pipelines_email_service, class: 'Integrations::PipelinesEmail' do factory :pipelines_email_service, class: 'Integrations::PipelinesEmail' do
project project
active { true } active { true }
......
...@@ -84,7 +84,7 @@ RSpec.describe 'Set up Mattermost slash commands', :js do ...@@ -84,7 +84,7 @@ RSpec.describe 'Set up Mattermost slash commands', :js do
end end
it 'shows an error alert with the error message if there is an error requesting teams' do 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' click_link 'Add to Mattermost'
...@@ -113,7 +113,7 @@ RSpec.describe 'Set up Mattermost slash commands', :js do ...@@ -113,7 +113,7 @@ RSpec.describe 'Set up Mattermost slash commands', :js do
def stub_teams(count: 0) def stub_teams(count: 0)
teams = create_teams(count) 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 teams
end end
......
...@@ -2,8 +2,8 @@ ...@@ -2,8 +2,8 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe MattermostSlashCommandsService do RSpec.describe Integrations::MattermostSlashCommands do
it_behaves_like "chat slash commands service" it_behaves_like Integrations::BaseSlashCommands
context 'Mattermost API' do context 'Mattermost API' do
let(:project) { create(:project) } let(:project) { create(:project) }
......
...@@ -2,8 +2,8 @@ ...@@ -2,8 +2,8 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe SlackSlashCommandsService do RSpec.describe Integrations::SlackSlashCommands do
it_behaves_like "chat slash commands service" it_behaves_like Integrations::BaseSlashCommands
describe '#trigger' do describe '#trigger' do
context 'when an auth url is generated' do context 'when an auth url is generated' do
......
# frozen_string_literal: true # frozen_string_literal: true
RSpec.shared_examples 'chat slash commands service' do RSpec.shared_examples Integrations::BaseSlashCommands do
describe "Associations" do describe "Associations" do
it { is_expected.to respond_to :token } it { is_expected.to respond_to :token }
it { is_expected.to have_many :chat_names } 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