Merge branch 'zj-kamil-slack-slash-commands' into 'master'
Slack slash commands ## What does this MR do? Implement Slack Slash Commands by utilizing generalized Mattermost presenter to fulfill Slack requirements. ## Why was this MR needed? We want to expose Slack Slash Commands as a first-class service. ## What are the relevant issue numbers? Supersedes !8007 Closes #22182 See merge request !8126
This commit is contained in:
commit
52278412c7
37 changed files with 483 additions and 242 deletions
2
Gemfile
2
Gemfile
|
@ -169,7 +169,7 @@ gem 'gitlab-flowdock-git-hook', '~> 1.0.1'
|
|||
gem 'gemnasium-gitlab-service', '~> 0.2'
|
||||
|
||||
# Slack integration
|
||||
gem 'slack-notifier', '~> 1.2.0'
|
||||
gem 'slack-notifier', '~> 1.5.1'
|
||||
|
||||
# Asana integration
|
||||
gem 'asana', '~> 0.4.0'
|
||||
|
|
|
@ -683,7 +683,7 @@ GEM
|
|||
json (>= 1.8, < 3)
|
||||
simplecov-html (~> 0.10.0)
|
||||
simplecov-html (0.10.0)
|
||||
slack-notifier (1.2.1)
|
||||
slack-notifier (1.5.1)
|
||||
slop (3.6.0)
|
||||
spinach (0.8.10)
|
||||
colorize
|
||||
|
@ -952,7 +952,7 @@ DEPENDENCIES
|
|||
sidekiq-cron (~> 0.4.4)
|
||||
sidekiq-limit_fetch (~> 3.4)
|
||||
simplecov (= 0.12.0)
|
||||
slack-notifier (~> 1.2.0)
|
||||
slack-notifier (~> 1.5.1)
|
||||
spinach-rails (~> 0.2.1)
|
||||
spinach-rerun-reporter (~> 0.0.2)
|
||||
spring (~> 1.7.0)
|
||||
|
|
|
@ -96,6 +96,10 @@ label {
|
|||
code {
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
img {
|
||||
margin-right: $gl-padding;
|
||||
}
|
||||
}
|
||||
|
||||
@media(max-width: $screen-xs-max) {
|
||||
|
|
|
@ -79,7 +79,6 @@ class Project < ActiveRecord::Base
|
|||
|
||||
has_one :last_event, -> {order 'events.created_at DESC'}, class_name: 'Event'
|
||||
has_many :boards, before_add: :validate_board_limit, dependent: :destroy
|
||||
has_many :chat_services
|
||||
|
||||
# Project services
|
||||
has_one :campfire_service, dependent: :destroy
|
||||
|
@ -96,6 +95,7 @@ class Project < ActiveRecord::Base
|
|||
has_one :gemnasium_service, dependent: :destroy
|
||||
has_one :mattermost_slash_commands_service, dependent: :destroy
|
||||
has_one :mattermost_notification_service, dependent: :destroy
|
||||
has_one :slack_slash_commands_service, dependent: :destroy
|
||||
has_one :slack_notification_service, dependent: :destroy
|
||||
has_one :buildkite_service, dependent: :destroy
|
||||
has_one :bamboo_service, dependent: :destroy
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
# Base class for Chat services
|
||||
# This class is not meant to be used directly, but only to inherit from.
|
||||
class ChatService < Service
|
||||
default_value_for :category, 'chat'
|
||||
|
||||
has_many :chat_names, foreign_key: :service_id
|
||||
|
||||
def valid_token?(token)
|
||||
self.respond_to?(:token) &&
|
||||
self.token.present? &&
|
||||
ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.token)
|
||||
end
|
||||
|
||||
def supported_events
|
||||
[]
|
||||
end
|
||||
|
||||
def trigger(params)
|
||||
raise NotImplementedError
|
||||
end
|
||||
end
|
56
app/models/project_services/chat_slash_commands_service.rb
Normal file
56
app/models/project_services/chat_slash_commands_service.rb
Normal file
|
@ -0,0 +1,56 @@
|
|||
# Base class for Chat services
|
||||
# This class is not meant to be used directly, but only to inherrit from.
|
||||
class ChatSlashCommandsService < Service
|
||||
default_value_for :category, 'chat'
|
||||
|
||||
prop_accessor :token
|
||||
|
||||
has_many :chat_names, foreign_key: :service_id, dependent: :destroy
|
||||
|
||||
def valid_token?(token)
|
||||
self.respond_to?(:token) &&
|
||||
self.token.present? &&
|
||||
ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.token)
|
||||
end
|
||||
|
||||
def supported_events
|
||||
[]
|
||||
end
|
||||
|
||||
def can_test?
|
||||
false
|
||||
end
|
||||
|
||||
def fields
|
||||
[
|
||||
{ type: 'text', name: 'token', placeholder: '' }
|
||||
]
|
||||
end
|
||||
|
||||
def trigger(params)
|
||||
return unless valid_token?(params[:token])
|
||||
|
||||
user = find_chat_user(params)
|
||||
unless user
|
||||
url = authorize_chat_name_url(params)
|
||||
return presenter.authorize_chat_name(url)
|
||||
end
|
||||
|
||||
Gitlab::ChatCommands::Command.new(project, user,
|
||||
params).execute
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def find_chat_user(params)
|
||||
ChatNames::FindUserService.new(self, params).execute
|
||||
end
|
||||
|
||||
def authorize_chat_name_url(params)
|
||||
ChatNames::AuthorizeUserService.new(self, params).execute
|
||||
end
|
||||
|
||||
def presenter
|
||||
Gitlab::ChatCommands::Presenter.new
|
||||
end
|
||||
end
|
|
@ -1,4 +1,4 @@
|
|||
class MattermostSlashCommandsService < ChatService
|
||||
class MattermostSlashCommandsService < ChatSlashCommandsService
|
||||
include TriggersHelper
|
||||
|
||||
prop_accessor :token
|
||||
|
@ -18,32 +18,4 @@ class MattermostSlashCommandsService < ChatService
|
|||
def to_param
|
||||
'mattermost_slash_commands'
|
||||
end
|
||||
|
||||
def fields
|
||||
[
|
||||
{ type: 'text', name: 'token', placeholder: '' }
|
||||
]
|
||||
end
|
||||
|
||||
def trigger(params)
|
||||
return nil unless valid_token?(params[:token])
|
||||
|
||||
user = find_chat_user(params)
|
||||
unless user
|
||||
url = authorize_chat_name_url(params)
|
||||
return Mattermost::Presenter.authorize_chat_name(url)
|
||||
end
|
||||
|
||||
Gitlab::ChatCommands::Command.new(project, user, params).execute
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def find_chat_user(params)
|
||||
ChatNames::FindUserService.new(self, params).execute
|
||||
end
|
||||
|
||||
def authorize_chat_name_url(params)
|
||||
ChatNames::AuthorizeUserService.new(self, params).execute
|
||||
end
|
||||
end
|
||||
|
|
28
app/models/project_services/slack_slash_commands_service.rb
Normal file
28
app/models/project_services/slack_slash_commands_service.rb
Normal file
|
@ -0,0 +1,28 @@
|
|||
class SlackSlashCommandsService < ChatSlashCommandsService
|
||||
include TriggersHelper
|
||||
|
||||
def title
|
||||
'Slack Command'
|
||||
end
|
||||
|
||||
def description
|
||||
"Perform common operations on GitLab in Slack"
|
||||
end
|
||||
|
||||
def to_param
|
||||
'slack_slash_commands'
|
||||
end
|
||||
|
||||
def trigger(params)
|
||||
# Format messages to be Slack-compatible
|
||||
super.tap do |result|
|
||||
result[:text] = format(result[:text])
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def format(text)
|
||||
Slack::Notifier::LinkFormatter.format(text) if text
|
||||
end
|
||||
end
|
|
@ -216,11 +216,12 @@ class Service < ActiveRecord::Base
|
|||
jira
|
||||
kubernetes
|
||||
mattermost_slash_commands
|
||||
mattermost_notification
|
||||
pipelines_email
|
||||
pivotaltracker
|
||||
pushover
|
||||
redmine
|
||||
mattermost_notification
|
||||
slack_slash_commands
|
||||
slack_notification
|
||||
teamcity
|
||||
]
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
- run_actions_text = "Perform common operations on this project: #{@project.name_with_namespace}"
|
||||
|
||||
.well
|
||||
This service allows GitLab users to perform common operations on this
|
||||
project by entering slash commands in Slack.
|
||||
%br
|
||||
See list of available commands in Slack after setting up this service,
|
||||
by entering
|
||||
%code /<command> help
|
||||
%br
|
||||
%br
|
||||
To setup this service:
|
||||
%ul.list-unstyled
|
||||
%li
|
||||
1.
|
||||
= link_to 'Add a slash command', 'https://my.slack.com/services/new/slash-commands'
|
||||
in your Slack team with these options:
|
||||
|
||||
%hr
|
||||
|
||||
.help-form
|
||||
.form-group
|
||||
= label_tag nil, 'Command', class: 'col-sm-2 col-xs-12 control-label'
|
||||
.col-sm-10.col-xs-12.text-block
|
||||
%p Fill in the word that works best for your team.
|
||||
%p
|
||||
Suggestions:
|
||||
%code= 'gitlab'
|
||||
%code= @project.path # Path contains no spaces, but dashes
|
||||
%code= @project.path_with_namespace
|
||||
|
||||
.form-group
|
||||
= label_tag :url, 'URL', class: 'col-sm-2 col-xs-12 control-label'
|
||||
.col-sm-10.col-xs-12.input-group
|
||||
= text_field_tag :url, service_trigger_url(subject), class: 'form-control input-sm', readonly: 'readonly'
|
||||
.input-group-btn
|
||||
= clipboard_button(clipboard_target: '#url')
|
||||
|
||||
.form-group
|
||||
= label_tag nil, 'Method', class: 'col-sm-2 col-xs-12 control-label'
|
||||
.col-sm-10.col-xs-12.text-block POST
|
||||
|
||||
.form-group
|
||||
= label_tag :customize_name, 'Customize name', class: 'col-sm-2 col-xs-12 control-label'
|
||||
.col-sm-10.col-xs-12.input-group
|
||||
= text_field_tag :customize_name, 'GitLab', class: 'form-control input-sm', readonly: 'readonly'
|
||||
.input-group-btn
|
||||
= clipboard_button(clipboard_target: '#customize_name')
|
||||
|
||||
.form-group
|
||||
= label_tag nil, 'Customize icon', class: 'col-sm-2 col-xs-12 control-label'
|
||||
.col-sm-10.col-xs-12.text-block
|
||||
= image_tag(asset_url('gitlab_logo.png'), width: 36, height: 36)
|
||||
= link_to('Download image', asset_url('gitlab_logo.png'), class: 'btn btn-sm', target: '_blank')
|
||||
|
||||
.form-group
|
||||
= label_tag nil, 'Autocomplete', class: 'col-sm-2 col-xs-12 control-label'
|
||||
.col-sm-10.col-xs-12.text-block Show this command in the autocomplete list
|
||||
|
||||
.form-group
|
||||
= label_tag :autocomplete_description, 'Autocomplete description', class: 'col-sm-2 col-xs-12 control-label'
|
||||
.col-sm-10.col-xs-12.input-group
|
||||
= text_field_tag :autocomplete_description, run_actions_text, class: 'form-control input-sm', readonly: 'readonly'
|
||||
.input-group-btn
|
||||
= clipboard_button(clipboard_target: '#autocomplete_description')
|
||||
|
||||
.form-group
|
||||
= label_tag :autocomplete_usage_hint, 'Autocomplete usage hint', class: 'col-sm-2 col-xs-12 control-label'
|
||||
.col-sm-10.col-xs-12.input-group
|
||||
= text_field_tag :autocomplete_usage_hint, '[help]', class: 'form-control input-sm', readonly: 'readonly'
|
||||
.input-group-btn
|
||||
= clipboard_button(clipboard_target: '#autocomplete_usage_hint')
|
||||
|
||||
.form-group
|
||||
= label_tag :descriptive_label, 'Descriptive label', class: 'col-sm-2 col-xs-12 control-label'
|
||||
.col-sm-10.col-xs-12.input-group
|
||||
= text_field_tag :descriptive_label, 'Perform common operations on GitLab project', class: 'form-control input-sm', readonly: 'readonly'
|
||||
.input-group-btn
|
||||
= clipboard_button(clipboard_target: '#descriptive_label')
|
||||
|
||||
%hr
|
||||
|
||||
%ul.list-unstyled
|
||||
%li
|
||||
2. Paste the
|
||||
%strong Token
|
||||
into the field below
|
||||
%li
|
||||
3. Select the
|
||||
%strong Active
|
||||
checkbox, press
|
||||
%strong Save changes
|
||||
and start using GitLab inside Slack!
|
4
changelogs/unreleased/zj-slack-slash-commands.yml
Normal file
4
changelogs/unreleased/zj-slack-slash-commands.yml
Normal file
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Refactor presenters ChatCommands
|
||||
merge_request: 7846
|
||||
author:
|
|
@ -37,11 +37,11 @@ Feature: Project Services
|
|||
And I fill Assembla settings
|
||||
Then I should see Assembla service settings saved
|
||||
|
||||
Scenario: Activate Slack service
|
||||
Scenario: Activate Slack notifications service
|
||||
When I visit project "Shop" services page
|
||||
And I click Slack service link
|
||||
And I fill Slack settings
|
||||
Then I should see Slack service settings saved
|
||||
And I click Slack notifications service link
|
||||
And I fill Slack notifications settings
|
||||
Then I should see Slack Notifications service settings saved
|
||||
|
||||
Scenario: Activate Pushover service
|
||||
When I visit project "Shop" services page
|
||||
|
|
|
@ -137,17 +137,17 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps
|
|||
expect(find_field('Colorize messages').value).to eq '1'
|
||||
end
|
||||
|
||||
step 'I click Slack service link' do
|
||||
click_link 'Slack'
|
||||
step 'I click Slack notifications service link' do
|
||||
click_link 'Slack notifications'
|
||||
end
|
||||
|
||||
step 'I fill Slack settings' do
|
||||
step 'I fill Slack notifications settings' do
|
||||
check 'Active'
|
||||
fill_in 'Webhook', with: 'https://hooks.slack.com/services/SVRWFV0VVAR97N/B02R25XN3/ZBqu7xMupaEEICInN685'
|
||||
click_button 'Save'
|
||||
end
|
||||
|
||||
step 'I should see Slack service settings saved' do
|
||||
step 'I should see Slack Notifications service settings saved' do
|
||||
expect(find_field('Webhook').value).to eq 'https://hooks.slack.com/services/SVRWFV0VVAR97N/B02R25XN3/ZBqu7xMupaEEICInN685'
|
||||
end
|
||||
|
||||
|
|
|
@ -378,7 +378,6 @@ module API
|
|||
desc: 'A custom certificate authority bundle to verify the Kubernetes cluster with (PEM format)'
|
||||
},
|
||||
],
|
||||
|
||||
'mattermost-slash-commands' => [
|
||||
{
|
||||
required: true,
|
||||
|
@ -387,6 +386,14 @@ module API
|
|||
desc: 'The Mattermost token'
|
||||
}
|
||||
],
|
||||
'slack-slash-commands' => [
|
||||
{
|
||||
required: true,
|
||||
name: :token,
|
||||
type: String,
|
||||
desc: 'The Slack token'
|
||||
}
|
||||
],
|
||||
'pipelines-email' => [
|
||||
{
|
||||
required: true,
|
||||
|
|
|
@ -42,6 +42,10 @@ module Gitlab
|
|||
def find_by_iid(iid)
|
||||
collection.find_by(iid: iid)
|
||||
end
|
||||
|
||||
def presenter
|
||||
Gitlab::ChatCommands::Presenter.new
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -22,8 +22,6 @@ module Gitlab
|
|||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def match_command
|
||||
match = nil
|
||||
service = available_commands.find do |klass|
|
||||
|
@ -33,6 +31,8 @@ module Gitlab
|
|||
[service, match]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def help_messages
|
||||
available_commands.map(&:help_message)
|
||||
end
|
||||
|
@ -48,15 +48,15 @@ module Gitlab
|
|||
end
|
||||
|
||||
def help(messages)
|
||||
Mattermost::Presenter.help(messages, params[:command])
|
||||
presenter.help(messages, params[:command])
|
||||
end
|
||||
|
||||
def access_denied
|
||||
Mattermost::Presenter.access_denied
|
||||
presenter.access_denied
|
||||
end
|
||||
|
||||
def present(resource)
|
||||
Mattermost::Presenter.present(resource)
|
||||
presenter.present(resource)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,7 +4,7 @@ module Gitlab
|
|||
include Gitlab::Routing.url_helpers
|
||||
|
||||
def self.match(text)
|
||||
/\Adeploy\s+(?<from>.*)\s+to+\s+(?<to>.*)\z/.match(text)
|
||||
/\Adeploy\s+(?<from>\S+.*)\s+to+\s+(?<to>\S+.*)\z/.match(text)
|
||||
end
|
||||
|
||||
def self.help_message
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
module Mattermost
|
||||
class Presenter
|
||||
class << self
|
||||
include Gitlab::Routing.url_helpers
|
||||
module Gitlab
|
||||
module ChatCommands
|
||||
class Presenter
|
||||
include Gitlab::Routing
|
||||
|
||||
def authorize_chat_name(url)
|
||||
message = if url
|
||||
|
@ -64,7 +64,7 @@ module Mattermost
|
|||
def single_resource(resource)
|
||||
return error(resource) if resource.errors.any? || !resource.persisted?
|
||||
|
||||
message = "### #{title(resource)}"
|
||||
message = "#{title(resource)}:"
|
||||
message << "\n\n#{resource.description}" if resource.try(:description)
|
||||
|
||||
in_channel_response(message)
|
|
@ -17,9 +17,9 @@ feature 'Admin updates settings', feature: true do
|
|||
expect(page).to have_content "Application settings saved successfully"
|
||||
end
|
||||
|
||||
scenario 'Change Slack Service template settings' do
|
||||
scenario 'Change Slack Notifications Service template settings' do
|
||||
click_link 'Service Templates'
|
||||
click_link 'Slack'
|
||||
click_link 'Slack notifications'
|
||||
fill_in 'Webhook', with: 'http://localhost'
|
||||
fill_in 'Username', with: 'test_user'
|
||||
fill_in 'service_push_channel', with: '#test_channel'
|
||||
|
@ -30,7 +30,7 @@ feature 'Admin updates settings', feature: true do
|
|||
|
||||
expect(page).to have_content 'Application settings saved successfully'
|
||||
|
||||
click_link 'Slack'
|
||||
click_link 'Slack notifications'
|
||||
|
||||
page.all('input[type=checkbox]').each do |checkbox|
|
||||
expect(checkbox).to be_checked
|
||||
|
|
40
spec/features/projects/services/slack_slash_command_spec.rb
Normal file
40
spec/features/projects/services/slack_slash_command_spec.rb
Normal file
|
@ -0,0 +1,40 @@
|
|||
require 'spec_helper'
|
||||
|
||||
feature 'Slack slash commands', feature: true do
|
||||
include WaitForAjax
|
||||
|
||||
given(:user) { create(:user) }
|
||||
given(:project) { create(:project) }
|
||||
given(:service) { project.create_slack_slash_commands_service }
|
||||
|
||||
background do
|
||||
project.team << [user, :master]
|
||||
login_as(user)
|
||||
end
|
||||
|
||||
scenario 'user visits the slack slash command config page and shows a help message', js: true do
|
||||
visit edit_namespace_project_service_path(project.namespace, project, service)
|
||||
|
||||
wait_for_ajax
|
||||
|
||||
expect(page).to have_content('This service allows GitLab users to perform common')
|
||||
end
|
||||
|
||||
scenario 'shows the token after saving' do
|
||||
visit edit_namespace_project_service_path(project.namespace, project, service)
|
||||
|
||||
fill_in 'service_token', with: 'token'
|
||||
click_on 'Save'
|
||||
|
||||
value = find_field('service_token').value
|
||||
|
||||
expect(value).to eq('token')
|
||||
end
|
||||
|
||||
scenario 'shows the correct trigger url' do
|
||||
visit edit_namespace_project_service_path(project.namespace, project, service)
|
||||
|
||||
value = find_field('url').value
|
||||
expect(value).to match("api/v3/projects/#{project.id}/services/slack_slash_commands/trigger")
|
||||
end
|
||||
end
|
|
@ -5,7 +5,9 @@ describe Gitlab::ChatCommands::Command, service: true do
|
|||
let(:user) { create(:user) }
|
||||
|
||||
describe '#execute' do
|
||||
subject { described_class.new(project, user, params).execute }
|
||||
subject do
|
||||
described_class.new(project, user, params).execute
|
||||
end
|
||||
|
||||
context 'when no command is available' do
|
||||
let(:params) { { text: 'issue show 1' } }
|
||||
|
@ -74,7 +76,7 @@ describe Gitlab::ChatCommands::Command, service: true do
|
|||
end
|
||||
|
||||
it 'returns action' do
|
||||
expect(subject[:text]).to include('Deployment from staging to production started')
|
||||
expect(subject[:text]).to include('Deployment from staging to production started.')
|
||||
expect(subject[:response_type]).to be(:in_channel)
|
||||
end
|
||||
|
||||
|
@ -91,4 +93,26 @@ describe Gitlab::ChatCommands::Command, service: true do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#match_command' do
|
||||
subject { described_class.new(project, user, params).match_command.first }
|
||||
|
||||
context 'IssueShow is triggered' do
|
||||
let(:params) { { text: 'issue show 123' } }
|
||||
|
||||
it { is_expected.to eq(Gitlab::ChatCommands::IssueShow) }
|
||||
end
|
||||
|
||||
context 'IssueCreate is triggered' do
|
||||
let(:params) { { text: 'issue create my title' } }
|
||||
|
||||
it { is_expected.to eq(Gitlab::ChatCommands::IssueCreate) }
|
||||
end
|
||||
|
||||
context 'IssueSearch is triggered' do
|
||||
let(:params) { { text: 'issue search my query' } }
|
||||
|
||||
it { is_expected.to eq(Gitlab::ChatCommands::IssueSearch) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -129,6 +129,7 @@ project:
|
|||
- builds_email_service
|
||||
- pipelines_email_service
|
||||
- mattermost_slash_commands_service
|
||||
- slack_slash_commands_service
|
||||
- irker_service
|
||||
- pivotaltracker_service
|
||||
- hipchat_service
|
||||
|
|
|
@ -10,7 +10,7 @@ describe ChatMessage::BuildMessage do
|
|||
tag: false,
|
||||
|
||||
project_name: 'project_name',
|
||||
project_url: 'example.gitlab.com',
|
||||
project_url: 'http://example.gitlab.com',
|
||||
|
||||
commit: {
|
||||
status: status,
|
||||
|
@ -48,10 +48,10 @@ describe ChatMessage::BuildMessage do
|
|||
end
|
||||
|
||||
def build_message(status_text = status)
|
||||
"<example.gitlab.com|project_name>:" \
|
||||
" Commit <example.gitlab.com/commit/" \
|
||||
"<http://example.gitlab.com|project_name>:" \
|
||||
" Commit <http://example.gitlab.com/commit/" \
|
||||
"97de212e80737a608d939f648d959671fb0a0142/builds|97de212e>" \
|
||||
" of <example.gitlab.com/commits/develop|develop> branch" \
|
||||
" of <http://example.gitlab.com/commits/develop|develop> branch" \
|
||||
" by hacker #{status_text} in #{duration} #{'second'.pluralize(duration)}"
|
||||
end
|
||||
end
|
||||
|
|
|
@ -10,14 +10,14 @@ describe ChatMessage::IssueMessage, models: true do
|
|||
username: 'test.user'
|
||||
},
|
||||
project_name: 'project_name',
|
||||
project_url: 'somewhere.com',
|
||||
project_url: 'http://somewhere.com',
|
||||
|
||||
object_attributes: {
|
||||
title: 'Issue title',
|
||||
id: 10,
|
||||
iid: 100,
|
||||
assignee_id: 1,
|
||||
url: 'url',
|
||||
url: 'http://url.com',
|
||||
action: 'open',
|
||||
state: 'opened',
|
||||
description: 'issue description'
|
||||
|
@ -40,11 +40,11 @@ describe ChatMessage::IssueMessage, models: true do
|
|||
context 'open' do
|
||||
it 'returns a message regarding opening of issues' do
|
||||
expect(subject.pretext).to eq(
|
||||
'<somewhere.com|[project_name>] Issue opened by test.user')
|
||||
'[<http://somewhere.com|project_name>] Issue opened by test.user')
|
||||
expect(subject.attachments).to eq([
|
||||
{
|
||||
title: "#100 Issue title",
|
||||
title_link: "url",
|
||||
title_link: "http://url.com",
|
||||
text: "issue description",
|
||||
color: color,
|
||||
}
|
||||
|
@ -60,7 +60,7 @@ describe ChatMessage::IssueMessage, models: true do
|
|||
|
||||
it 'returns a message regarding closing of issues' do
|
||||
expect(subject.pretext). to eq(
|
||||
'<somewhere.com|[project_name>] Issue <url|#100 Issue title> closed by test.user')
|
||||
'[<http://somewhere.com|project_name>] Issue <http://url.com|#100 Issue title> closed by test.user')
|
||||
expect(subject.attachments).to be_empty
|
||||
end
|
||||
end
|
||||
|
|
|
@ -10,14 +10,14 @@ describe ChatMessage::MergeMessage, models: true do
|
|||
username: 'test.user'
|
||||
},
|
||||
project_name: 'project_name',
|
||||
project_url: 'somewhere.com',
|
||||
project_url: 'http://somewhere.com',
|
||||
|
||||
object_attributes: {
|
||||
title: "Issue title\nSecond line",
|
||||
id: 10,
|
||||
iid: 100,
|
||||
assignee_id: 1,
|
||||
url: 'url',
|
||||
url: 'http://url.com',
|
||||
state: 'opened',
|
||||
description: 'issue description',
|
||||
source_branch: 'source_branch',
|
||||
|
@ -31,8 +31,8 @@ describe ChatMessage::MergeMessage, models: true do
|
|||
context 'open' do
|
||||
it 'returns a message regarding opening of merge requests' do
|
||||
expect(subject.pretext).to eq(
|
||||
'test.user opened <somewhere.com/merge_requests/100|merge request !100> '\
|
||||
'in <somewhere.com|project_name>: *Issue title*')
|
||||
'test.user opened <http://somewhere.com/merge_requests/100|merge request !100> '\
|
||||
'in <http://somewhere.com|project_name>: *Issue title*')
|
||||
expect(subject.attachments).to be_empty
|
||||
end
|
||||
end
|
||||
|
@ -43,8 +43,8 @@ describe ChatMessage::MergeMessage, models: true do
|
|||
end
|
||||
it 'returns a message regarding closing of merge requests' do
|
||||
expect(subject.pretext).to eq(
|
||||
'test.user closed <somewhere.com/merge_requests/100|merge request !100> '\
|
||||
'in <somewhere.com|project_name>: *Issue title*')
|
||||
'test.user closed <http://somewhere.com/merge_requests/100|merge request !100> '\
|
||||
'in <http://somewhere.com|project_name>: *Issue title*')
|
||||
expect(subject.attachments).to be_empty
|
||||
end
|
||||
end
|
||||
|
|
|
@ -11,15 +11,15 @@ describe ChatMessage::NoteMessage, models: true do
|
|||
avatar_url: 'http://fakeavatar'
|
||||
},
|
||||
project_name: 'project_name',
|
||||
project_url: 'somewhere.com',
|
||||
project_url: 'http://somewhere.com',
|
||||
repository: {
|
||||
name: 'project_name',
|
||||
url: 'somewhere.com',
|
||||
url: 'http://somewhere.com',
|
||||
},
|
||||
object_attributes: {
|
||||
id: 10,
|
||||
note: 'comment on a commit',
|
||||
url: 'url',
|
||||
url: 'http://url.com',
|
||||
noteable_type: 'Commit'
|
||||
}
|
||||
}
|
||||
|
@ -37,8 +37,8 @@ describe ChatMessage::NoteMessage, models: true do
|
|||
|
||||
it 'returns a message regarding notes on commits' do
|
||||
message = described_class.new(@args)
|
||||
expect(message.pretext).to eq("test.user <url|commented on " \
|
||||
"commit 5f163b2b> in <somewhere.com|project_name>: " \
|
||||
expect(message.pretext).to eq("test.user <http://url.com|commented on " \
|
||||
"commit 5f163b2b> in <http://somewhere.com|project_name>: " \
|
||||
"*Added a commit message*")
|
||||
expected_attachments = [
|
||||
{
|
||||
|
@ -63,8 +63,8 @@ describe ChatMessage::NoteMessage, models: true do
|
|||
|
||||
it 'returns a message regarding notes on a merge request' do
|
||||
message = described_class.new(@args)
|
||||
expect(message.pretext).to eq("test.user <url|commented on " \
|
||||
"merge request !30> in <somewhere.com|project_name>: " \
|
||||
expect(message.pretext).to eq("test.user <http://url.com|commented on " \
|
||||
"merge request !30> in <http://somewhere.com|project_name>: " \
|
||||
"*merge request title*")
|
||||
expected_attachments = [
|
||||
{
|
||||
|
@ -90,8 +90,8 @@ describe ChatMessage::NoteMessage, models: true do
|
|||
it 'returns a message regarding notes on an issue' do
|
||||
message = described_class.new(@args)
|
||||
expect(message.pretext).to eq(
|
||||
"test.user <url|commented on " \
|
||||
"issue #20> in <somewhere.com|project_name>: " \
|
||||
"test.user <http://url.com|commented on " \
|
||||
"issue #20> in <http://somewhere.com|project_name>: " \
|
||||
"*issue title*")
|
||||
expected_attachments = [
|
||||
{
|
||||
|
@ -115,8 +115,8 @@ describe ChatMessage::NoteMessage, models: true do
|
|||
|
||||
it 'returns a message regarding notes on a project snippet' do
|
||||
message = described_class.new(@args)
|
||||
expect(message.pretext).to eq("test.user <url|commented on " \
|
||||
"snippet #5> in <somewhere.com|project_name>: " \
|
||||
expect(message.pretext).to eq("test.user <http://url.com|commented on " \
|
||||
"snippet #5> in <http://somewhere.com|project_name>: " \
|
||||
"*snippet title*")
|
||||
expected_attachments = [
|
||||
{
|
||||
|
|
|
@ -15,7 +15,7 @@ describe ChatMessage::PipelineMessage do
|
|||
duration: duration
|
||||
},
|
||||
project: { path_with_namespace: 'project_name',
|
||||
web_url: 'example.gitlab.com' },
|
||||
web_url: 'http://example.gitlab.com' },
|
||||
user: user
|
||||
}
|
||||
end
|
||||
|
@ -59,9 +59,9 @@ describe ChatMessage::PipelineMessage do
|
|||
end
|
||||
|
||||
def build_message(status_text = status, name = user[:name])
|
||||
"<example.gitlab.com|project_name>:" \
|
||||
" Pipeline <example.gitlab.com/pipelines/123|#123>" \
|
||||
" of <example.gitlab.com/commits/develop|develop> branch" \
|
||||
"<http://example.gitlab.com|project_name>:" \
|
||||
" Pipeline <http://example.gitlab.com/pipelines/123|#123>" \
|
||||
" of <http://example.gitlab.com/commits/develop|develop> branch" \
|
||||
" by #{name} #{status_text} in #{duration} #{'second'.pluralize(duration)}"
|
||||
end
|
||||
end
|
||||
|
|
|
@ -10,7 +10,7 @@ describe ChatMessage::PushMessage, models: true do
|
|||
project_name: 'project_name',
|
||||
ref: 'refs/heads/master',
|
||||
user_name: 'test.user',
|
||||
project_url: 'url'
|
||||
project_url: 'http://url.com'
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -19,20 +19,20 @@ describe ChatMessage::PushMessage, models: true do
|
|||
context 'push' do
|
||||
before do
|
||||
args[:commits] = [
|
||||
{ message: 'message1', url: 'url1', id: 'abcdefghijkl', author: { name: 'author1' } },
|
||||
{ message: 'message2', url: 'url2', id: '123456789012', author: { name: 'author2' } },
|
||||
{ message: 'message1', url: 'http://url1.com', id: 'abcdefghijkl', author: { name: 'author1' } },
|
||||
{ message: 'message2', url: 'http://url2.com', id: '123456789012', author: { name: 'author2' } },
|
||||
]
|
||||
end
|
||||
|
||||
it 'returns a message regarding pushes' do
|
||||
expect(subject.pretext).to eq(
|
||||
'test.user pushed to branch <url/commits/master|master> of '\
|
||||
'<url|project_name> (<url/compare/before...after|Compare changes>)'
|
||||
'test.user pushed to branch <http://url.com/commits/master|master> of '\
|
||||
'<http://url.com|project_name> (<http://url.com/compare/before...after|Compare changes>)'
|
||||
)
|
||||
expect(subject.attachments).to eq([
|
||||
{
|
||||
text: "<url1|abcdefgh>: message1 - author1\n"\
|
||||
"<url2|12345678>: message2 - author2",
|
||||
text: "<http://url1.com|abcdefgh>: message1 - author1\n"\
|
||||
"<http://url2.com|12345678>: message2 - author2",
|
||||
color: color,
|
||||
}
|
||||
])
|
||||
|
@ -47,14 +47,14 @@ describe ChatMessage::PushMessage, models: true do
|
|||
project_name: 'project_name',
|
||||
ref: 'refs/tags/new_tag',
|
||||
user_name: 'test.user',
|
||||
project_url: 'url'
|
||||
project_url: 'http://url.com'
|
||||
}
|
||||
end
|
||||
|
||||
it 'returns a message regarding pushes' do
|
||||
expect(subject.pretext).to eq('test.user pushed new tag ' \
|
||||
'<url/commits/new_tag|new_tag> to ' \
|
||||
'<url|project_name>')
|
||||
'<http://url.com/commits/new_tag|new_tag> to ' \
|
||||
'<http://url.com|project_name>')
|
||||
expect(subject.attachments).to be_empty
|
||||
end
|
||||
end
|
||||
|
@ -66,8 +66,8 @@ describe ChatMessage::PushMessage, models: true do
|
|||
|
||||
it 'returns a message regarding a new branch' do
|
||||
expect(subject.pretext).to eq(
|
||||
'test.user pushed new branch <url/commits/master|master> to '\
|
||||
'<url|project_name>'
|
||||
'test.user pushed new branch <http://url.com/commits/master|master> to '\
|
||||
'<http://url.com|project_name>'
|
||||
)
|
||||
expect(subject.attachments).to be_empty
|
||||
end
|
||||
|
@ -80,7 +80,7 @@ describe ChatMessage::PushMessage, models: true do
|
|||
|
||||
it 'returns a message regarding a removed branch' do
|
||||
expect(subject.pretext).to eq(
|
||||
'test.user removed branch master from <url|project_name>'
|
||||
'test.user removed branch master from <http://url.com|project_name>'
|
||||
)
|
||||
expect(subject.attachments).to be_empty
|
||||
end
|
||||
|
|
|
@ -10,10 +10,10 @@ describe ChatMessage::WikiPageMessage, models: true do
|
|||
username: 'test.user'
|
||||
},
|
||||
project_name: 'project_name',
|
||||
project_url: 'somewhere.com',
|
||||
project_url: 'http://somewhere.com',
|
||||
object_attributes: {
|
||||
title: 'Wiki page title',
|
||||
url: 'url',
|
||||
url: 'http://url.com',
|
||||
content: 'Wiki page description'
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ describe ChatMessage::WikiPageMessage, models: true do
|
|||
|
||||
it 'returns a message that a new wiki page was created' do
|
||||
expect(subject.pretext).to eq(
|
||||
'test.user created <url|wiki page> in <somewhere.com|project_name>: '\
|
||||
'test.user created <http://url.com|wiki page> in <http://somewhere.com|project_name>: '\
|
||||
'*Wiki page title*')
|
||||
end
|
||||
end
|
||||
|
@ -35,7 +35,7 @@ describe ChatMessage::WikiPageMessage, models: true do
|
|||
|
||||
it 'returns a message that a wiki page was updated' do
|
||||
expect(subject.pretext).to eq(
|
||||
'test.user edited <url|wiki page> in <somewhere.com|project_name>: '\
|
||||
'test.user edited <http://url.com|wiki page> in <http://somewhere.com|project_name>: '\
|
||||
'*Wiki page title*')
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe ChatService, models: true do
|
||||
describe "Associations" do
|
||||
it { is_expected.to have_many :chat_names }
|
||||
end
|
||||
|
||||
describe '#valid_token?' do
|
||||
subject { described_class.new }
|
||||
|
||||
it 'is false as it has no token' do
|
||||
expect(subject.valid_token?('wer')).to be_falsey
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,5 +1,5 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe MattermostNotificationService, models: true do
|
||||
it_behaves_like "slack or mattermost"
|
||||
it_behaves_like "slack or mattermost notifications"
|
||||
end
|
||||
|
|
|
@ -1,99 +1,5 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe MattermostSlashCommandsService, models: true do
|
||||
describe "Associations" do
|
||||
it { is_expected.to respond_to :token }
|
||||
end
|
||||
|
||||
describe '#valid_token?' do
|
||||
subject { described_class.new }
|
||||
|
||||
context 'when the token is empty' do
|
||||
it 'is false' do
|
||||
expect(subject.valid_token?('wer')).to be_falsey
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there is a token' do
|
||||
before do
|
||||
subject.token = '123'
|
||||
end
|
||||
|
||||
it 'accepts equal tokens' do
|
||||
expect(subject.valid_token?('123')).to be_truthy
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#trigger' do
|
||||
subject { described_class.new }
|
||||
|
||||
context 'no token is passed' do
|
||||
let(:params) { Hash.new }
|
||||
|
||||
it 'returns nil' do
|
||||
expect(subject.trigger(params)).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a token passed' do
|
||||
let(:project) { create(:empty_project) }
|
||||
let(:params) { { token: 'token' } }
|
||||
|
||||
before do
|
||||
allow(subject).to receive(:token).and_return('token')
|
||||
end
|
||||
|
||||
context 'no user can be found' do
|
||||
context 'when no url can be generated' do
|
||||
it 'responds with the authorize url' do
|
||||
response = subject.trigger(params)
|
||||
|
||||
expect(response[:response_type]).to eq :ephemeral
|
||||
expect(response[:text]).to start_with ":sweat_smile: Couldn't identify you"
|
||||
end
|
||||
end
|
||||
|
||||
context 'when an auth url can be generated' do
|
||||
let(:params) do
|
||||
{
|
||||
team_domain: 'http://domain.tld',
|
||||
team_id: 'T3423423',
|
||||
user_id: 'U234234',
|
||||
user_name: 'mepmep',
|
||||
token: 'token'
|
||||
}
|
||||
end
|
||||
|
||||
let(:service) do
|
||||
project.create_mattermost_slash_commands_service(
|
||||
properties: { token: 'token' }
|
||||
)
|
||||
end
|
||||
|
||||
it 'generates the url' do
|
||||
response = service.trigger(params)
|
||||
|
||||
expect(response[:text]).to start_with(':wave: Hi there!')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the user is authenticated' do
|
||||
let!(:chat_name) { create(:chat_name, service: service) }
|
||||
let(:service) do
|
||||
project.create_mattermost_slash_commands_service(
|
||||
properties: { token: 'token' }
|
||||
)
|
||||
end
|
||||
let(:params) { { token: 'token', team_id: chat_name.team_id, user_id: chat_name.chat_id } }
|
||||
|
||||
it 'triggers the command' do
|
||||
expect_any_instance_of(Gitlab::ChatCommands::Command).to receive(:execute)
|
||||
|
||||
service.trigger(params)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
describe MattermostSlashCommandsService, :models do
|
||||
it_behaves_like "chat slash commands service"
|
||||
end
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe SlackNotificationService, models: true do
|
||||
it_behaves_like "slack or mattermost"
|
||||
it_behaves_like "slack or mattermost notifications"
|
||||
end
|
||||
|
|
40
spec/models/project_services/slack_slash_commands_service.rb
Normal file
40
spec/models/project_services/slack_slash_commands_service.rb
Normal file
|
@ -0,0 +1,40 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe SlackSlashCommandsService, :models do
|
||||
it_behaves_like "chat slash commands service"
|
||||
|
||||
describe '#trigger' do
|
||||
context 'when an auth url is generated' do
|
||||
let(:project) { create(:empty_project) }
|
||||
let(:params) do
|
||||
{
|
||||
team_domain: 'http://domain.tld',
|
||||
team_id: 'T3423423',
|
||||
user_id: 'U234234',
|
||||
user_name: 'mepmep',
|
||||
token: 'token'
|
||||
}
|
||||
end
|
||||
|
||||
let(:service) do
|
||||
project.create_slack_slash_commands_service(
|
||||
properties: { token: 'token' }
|
||||
)
|
||||
end
|
||||
|
||||
let(:authorize_url) do
|
||||
'http://authorize.example.com/'
|
||||
end
|
||||
|
||||
before do
|
||||
allow(service).to receive(:authorize_chat_name_url).and_return(authorize_url)
|
||||
end
|
||||
|
||||
it 'uses slack compatible links' do
|
||||
response = service.trigger(params)
|
||||
|
||||
expect(response[:text]).to include("<#{authorize_url}|connect your GitLab account>")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -20,7 +20,6 @@ describe Project, models: true do
|
|||
it { is_expected.to have_many(:deploy_keys) }
|
||||
it { is_expected.to have_many(:hooks).dependent(:destroy) }
|
||||
it { is_expected.to have_many(:protected_branches).dependent(:destroy) }
|
||||
it { is_expected.to have_many(:chat_services) }
|
||||
it { is_expected.to have_one(:forked_project_link).dependent(:destroy) }
|
||||
it { is_expected.to have_one(:slack_notification_service).dependent(:destroy) }
|
||||
it { is_expected.to have_one(:mattermost_notification_service).dependent(:destroy) }
|
||||
|
@ -37,6 +36,7 @@ describe Project, models: true do
|
|||
it { is_expected.to have_one(:hipchat_service).dependent(:destroy) }
|
||||
it { is_expected.to have_one(:flowdock_service).dependent(:destroy) }
|
||||
it { is_expected.to have_one(:assembla_service).dependent(:destroy) }
|
||||
it { is_expected.to have_one(:slack_slash_commands_service).dependent(:destroy) }
|
||||
it { is_expected.to have_one(:mattermost_slash_commands_service).dependent(:destroy) }
|
||||
it { is_expected.to have_one(:gemnasium_service).dependent(:destroy) }
|
||||
it { is_expected.to have_one(:buildkite_service).dependent(:destroy) }
|
||||
|
|
97
spec/support/chat_slash_commands_shared_examples.rb
Normal file
97
spec/support/chat_slash_commands_shared_examples.rb
Normal file
|
@ -0,0 +1,97 @@
|
|||
RSpec.shared_examples 'chat slash commands service' do
|
||||
describe "Associations" do
|
||||
it { is_expected.to respond_to :token }
|
||||
it { is_expected.to have_many :chat_names }
|
||||
end
|
||||
|
||||
describe '#valid_token?' do
|
||||
subject { described_class.new }
|
||||
|
||||
context 'when the token is empty' do
|
||||
it 'is false' do
|
||||
expect(subject.valid_token?('wer')).to be_falsey
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there is a token' do
|
||||
before do
|
||||
subject.token = '123'
|
||||
end
|
||||
|
||||
it 'accepts equal tokens' do
|
||||
expect(subject.valid_token?('123')).to be_truthy
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#trigger' do
|
||||
subject { described_class.new }
|
||||
|
||||
context 'no token is passed' do
|
||||
let(:params) { Hash.new }
|
||||
|
||||
it 'returns nil' do
|
||||
expect(subject.trigger(params)).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a token passed' do
|
||||
let(:project) { create(:empty_project) }
|
||||
let(:params) { { token: 'token' } }
|
||||
|
||||
before do
|
||||
allow(subject).to receive(:token).and_return('token')
|
||||
end
|
||||
|
||||
context 'no user can be found' do
|
||||
context 'when no url can be generated' do
|
||||
it 'responds with the authorize url' do
|
||||
response = subject.trigger(params)
|
||||
|
||||
expect(response[:response_type]).to eq :ephemeral
|
||||
expect(response[:text]).to start_with ":sweat_smile: Couldn't identify you"
|
||||
end
|
||||
end
|
||||
|
||||
context 'when an auth url can be generated' do
|
||||
let(:params) do
|
||||
{
|
||||
team_domain: 'http://domain.tld',
|
||||
team_id: 'T3423423',
|
||||
user_id: 'U234234',
|
||||
user_name: 'mepmep',
|
||||
token: 'token'
|
||||
}
|
||||
end
|
||||
|
||||
let(:service) do
|
||||
project.create_mattermost_slash_commands_service(
|
||||
properties: { token: 'token' }
|
||||
)
|
||||
end
|
||||
|
||||
it 'generates the url' do
|
||||
response = service.trigger(params)
|
||||
|
||||
expect(response[:text]).to start_with(':wave: Hi there!')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the user is authenticated' do
|
||||
let!(:chat_name) { create(:chat_name, service: subject) }
|
||||
let(:params) { { token: 'token', team_id: chat_name.team_id, user_id: chat_name.chat_id } }
|
||||
|
||||
subject do
|
||||
described_class.create(project: project, properties: { token: 'token' })
|
||||
end
|
||||
|
||||
it 'triggers the command' do
|
||||
expect_any_instance_of(Gitlab::ChatCommands::Command).to receive(:execute)
|
||||
|
||||
subject.trigger(params)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,6 +1,6 @@
|
|||
Dir[Rails.root.join("app/models/project_services/chat_message/*.rb")].each { |f| require f }
|
||||
|
||||
RSpec.shared_examples 'slack or mattermost' do
|
||||
RSpec.shared_examples 'slack or mattermost notifications' do
|
||||
let(:chat_service) { described_class.new }
|
||||
let(:webhook_url) { 'https://example.gitlab.com/' }
|
||||
|
Loading…
Reference in a new issue