diff options
-rw-r--r-- | app/controllers/projects/services_controller.rb | 28 | ||||
-rw-r--r-- | app/helpers/mattermost_helper.rb | 13 | ||||
-rw-r--r-- | app/models/project_services/mattermost_slash_commands_service.rb | 15 | ||||
-rw-r--r-- | app/models/service.rb | 4 | ||||
-rw-r--r-- | app/views/projects/services/_form.html.haml | 20 | ||||
-rw-r--r-- | app/views/projects/services/mattermost_slash_commands/_form.html.haml | 3 | ||||
-rw-r--r-- | app/views/projects/services/mattermost_slash_commands/_help.html.haml | 195 | ||||
-rw-r--r-- | app/views/shared/_service_settings.html.haml | 73 | ||||
-rw-r--r-- | config/gitlab.yml.example | 5 | ||||
-rw-r--r-- | config/routes/project.rb | 1 | ||||
-rw-r--r-- | lib/mattermost/command.rb | 26 | ||||
-rw-r--r-- | lib/mattermost/session.rb | 13 | ||||
-rw-r--r-- | lib/mattermost/team.rb | 10 |
13 files changed, 259 insertions, 147 deletions
diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb index 30c2a5d9982..94ea36bbdd9 100644 --- a/app/controllers/projects/services_controller.rb +++ b/app/controllers/projects/services_controller.rb @@ -3,7 +3,7 @@ class Projects::ServicesController < Projects::ApplicationController # Authorize before_action :authorize_admin_project! - before_action :service, only: [:edit, :update, :test] + before_action :service, only: [:edit, :update, :test, :configure] respond_to :html @@ -44,9 +44,35 @@ class Projects::ServicesController < Projects::ApplicationController redirect_back_or_default(options: message) end + def configure + host = Gitlab.config.mattermost.host + if @service.auto_config? && host + @service.configure(host, current_user, params) + + redirect_to( + edit_namespace_project_service_path(@project.namespace, @project, @service.to_param), + notice: 'This service is now configured.' + ) + else + redirect_to( + edit_namespace_project_service_path(@project.namespace, @project, @service.to_param), + alert: 'This service can not be automatticly configured.' + ) + end + rescue Mattermost::NoSessionError + redirect_to( + edit_namespace_project_service_path(@project.namespace, @project, @service.to_param), + alert: 'An error occurred, is Mattermost configured with Single Sign on?' + ) + end + private def service @service ||= @project.find_or_initialize_service(params[:id]) end + + def configure_params + params.require(:auto_configure).permit(:trigger, :team_id) + end end diff --git a/app/helpers/mattermost_helper.rb b/app/helpers/mattermost_helper.rb new file mode 100644 index 00000000000..83434c20c2b --- /dev/null +++ b/app/helpers/mattermost_helper.rb @@ -0,0 +1,13 @@ +module MattermostHelper + def mattermost_teams_for(current_user) + return unless Gitlab.config.mattermost.enabled + # Hack to make frontend work better + return [{"id"=>"qz8gdr1fopncueb8n9on8ohk3h", "create_at"=>1479992105904, "update_at"=>1479992105904, "delete_at"=>0, "display_name"=>"chatops", "name"=>"chatops", "email"=>"admin@example.com", "type"=>"O", "company_name"=>"", "allowed_domains"=>"", "invite_id"=>"gthxi47gj7rxtcx6zama63zd1w", "allow_open_invite"=>false}] + + + host = Gitlab.config.mattermost.host + Mattermost::Mattermost.new(host, current_user).with_session do + Mattermost::Team.all + end + end +end diff --git a/app/models/project_services/mattermost_slash_commands_service.rb b/app/models/project_services/mattermost_slash_commands_service.rb index 33431f41dc2..c0e8e1a9324 100644 --- a/app/models/project_services/mattermost_slash_commands_service.rb +++ b/app/models/project_services/mattermost_slash_commands_service.rb @@ -25,6 +25,21 @@ class MattermostSlashCommandsService < ChatService ] end + def auto_config? + Gitlab.config.mattermost.enabled + end + + def configure(host, current_user, params) + token = Mattermost::Mattermost.new(host, current_user).with_session do + Mattermost::Commands.create(params[:team_id], + trigger: params[:trigger] || @service.project.path, + url: service_trigger_url(@service), + icon_url: asset_url('gitlab_logo.png')) + end + + update_attributes(token: token) + end + def trigger(params) return nil unless valid_token?(params[:token]) diff --git a/app/models/service.rb b/app/models/service.rb index e49a8fa2904..9004d9caa19 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -54,6 +54,10 @@ class Service < ActiveRecord::Base template end + def auto_config? + false + end + def category read_attribute(:category).to_sym end diff --git a/app/views/projects/services/_form.html.haml b/app/views/projects/services/_form.html.haml index db51c4f8a4e..0160a366aaa 100644 --- a/app/views/projects/services/_form.html.haml +++ b/app/views/projects/services/_form.html.haml @@ -6,16 +6,12 @@ %p= @service.description .col-lg-9 - = form_for(@service, as: :service, url: namespace_project_service_path(@project.namespace, @project, @service.to_param), method: :put, html: { class: 'form-horizontal' }) do |form| - = render 'shared/service_settings', form: form, subject: @service + - # This returns an array of hashes, could you make a fancy dropdown :D + - # Also, this is mocked for now, checkout the MattermostHelper to edit the data + = mattermost_teams_for(current_user) + = form_for(:auto_configure, method: :post, url: configure_namespace_project_service_path(@project.namespace, @project, @service.to_param)) do |f| + = "Team ID" + = f.text_field(:team_id) + = "Team ID" + = f.submit 'Save changes', class: 'btn btn-save' - .footer-block.row-content-block - = form.submit 'Save changes', class: 'btn btn-save' - - - if @service.valid? && @service.activated? - - unless @service.can_test? - - disabled_class = 'disabled' - - disabled_title = @service.disabled_title - - = link_to 'Test settings', test_namespace_project_service_path(@project.namespace, @project, @service), class: "btn #{disabled_class}", title: disabled_title - = link_to "Cancel", namespace_project_services_path(@project.namespace, @project), class: "btn btn-cancel" diff --git a/app/views/projects/services/mattermost_slash_commands/_form.html.haml b/app/views/projects/services/mattermost_slash_commands/_form.html.haml new file mode 100644 index 00000000000..9d9c877e791 --- /dev/null +++ b/app/views/projects/services/mattermost_slash_commands/_form.html.haml @@ -0,0 +1,3 @@ +- teams = Mattermost::Mattermost.new(Gitlab.config.mattermost.host, current_user).with_session do + Mattermost::Mattermost::Team.all + end diff --git a/app/views/projects/services/mattermost_slash_commands/_help.html.haml b/app/views/projects/services/mattermost_slash_commands/_help.html.haml index a676c0290a0..70f2ef52135 100644 --- a/app/views/projects/services/mattermost_slash_commands/_help.html.haml +++ b/app/views/projects/services/mattermost_slash_commands/_help.html.haml @@ -1,100 +1,101 @@ - pretty_path_with_namespace = "#{@project ? @project.namespace.name : 'namespace'} / #{@project ? @project.name : 'name'}" - run_actions_text = "Perform common operations on this project: #{pretty_path_with_namespace}" -.well - This service allows GitLab users to perform common operations on this - project by entering slash commands in Mattermost. - %br - See list of available commands in Mattermost after setting up this service, - by entering - %code /<command_trigger_word> help - %br - %br - To setup this service: - %ul.list-unstyled - %li - 1. - = link_to 'Enable custom slash commands', 'https://docs.mattermost.com/developer/slash-commands.html#enabling-custom-commands' - on your Mattermost installation - %li - 2. - = link_to 'Add a slash command', 'https://docs.mattermost.com/developer/slash-commands.html#set-up-a-custom-command' - in Mattermost with these options: - - %hr - - .help-form - .form-group - = label_tag :display_name, 'Display name', class: 'col-sm-2 col-xs-12 control-label' - .col-sm-10.col-xs-12.input-group - = text_field_tag :display_name, "GitLab / #{pretty_path_with_namespace}", class: 'form-control input-sm', readonly: 'readonly' - .input-group-btn - = clipboard_button(clipboard_target: '#display_name') - - .form-group - = label_tag :description, 'Description', class: 'col-sm-2 col-xs-12 control-label' - .col-sm-10.col-xs-12.input-group - = text_field_tag :description, run_actions_text, class: 'form-control input-sm', readonly: 'readonly' - .input-group-btn - = clipboard_button(clipboard_target: '#description') - - .form-group - = label_tag nil, 'Command trigger word', 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 :request_url, 'Request URL', class: 'col-sm-2 col-xs-12 control-label' - .col-sm-10.col-xs-12.input-group - = text_field_tag :request_url, service_trigger_url(subject), class: 'form-control input-sm', readonly: 'readonly' - .input-group-btn - = clipboard_button(clipboard_target: '#request_url') - - .form-group - = label_tag nil, 'Request method', class: 'col-sm-2 col-xs-12 control-label' - .col-sm-10.col-xs-12.text-block POST - - .form-group - = label_tag :response_username, 'Response username', class: 'col-sm-2 col-xs-12 control-label' - .col-sm-10.col-xs-12.input-group - = text_field_tag :response_username, 'GitLab', class: 'form-control input-sm', readonly: 'readonly' - .input-group-btn - = clipboard_button(clipboard_target: '#response_username') - - .form-group - = label_tag :response_icon, 'Response icon', class: 'col-sm-2 col-xs-12 control-label' - .col-sm-10.col-xs-12.input-group - = text_field_tag :response_icon, asset_url('gitlab_logo.png'), class: 'form-control input-sm', readonly: 'readonly' - .input-group-btn - = clipboard_button(clipboard_target: '#response_icon') - - .form-group - = label_tag nil, 'Autocomplete', class: 'col-sm-2 col-xs-12 control-label' - .col-sm-10.col-xs-12.text-block Yes - - .form-group - = label_tag :autocomplete_hint, 'Autocomplete hint', class: 'col-sm-2 col-xs-12 control-label' - .col-sm-10.col-xs-12.input-group - = text_field_tag :autocomplete_hint, '[help]', class: 'form-control input-sm', readonly: 'readonly' - .input-group-btn - = clipboard_button(clipboard_target: '#autocomplete_hint') - - .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') - - %hr - - %ul.list-unstyled - %li - 3. After adding the slash command, paste the - %strong token - into the field below +- unless GitLab.config.mattermost.enabled + .well + This service allows GitLab users to perform common operations on this + project by entering slash commands in Mattermost. + %br + See list of available commands in Mattermost after setting up this service, + by entering + %code /<command_trigger_word> help + %br + %br + To setup this service: + %ul.list-unstyled + %li + 1. + = link_to 'Enable custom slash commands', 'https://docs.mattermost.com/developer/slash-commands.html#enabling-custom-commands' + on your Mattermost installation + %li + 2. + = link_to 'Add a slash command', 'https://docs.mattermost.com/developer/slash-commands.html#set-up-a-custom-command' + in Mattermost with these options: + + %hr + + .help-form + .form-group + = label_tag :display_name, 'Display name', class: 'col-sm-2 col-xs-12 control-label' + .col-sm-10.col-xs-12.input-group + = text_field_tag :display_name, "GitLab / #{pretty_path_with_namespace}", class: 'form-control input-sm', readonly: 'readonly' + .input-group-btn + = clipboard_button(clipboard_target: '#display_name') + + .form-group + = label_tag :description, 'Description', class: 'col-sm-2 col-xs-12 control-label' + .col-sm-10.col-xs-12.input-group + = text_field_tag :description, run_actions_text, class: 'form-control input-sm', readonly: 'readonly' + .input-group-btn + = clipboard_button(clipboard_target: '#description') + + .form-group + = label_tag nil, 'Command trigger word', 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 :request_url, 'Request URL', class: 'col-sm-2 col-xs-12 control-label' + .col-sm-10.col-xs-12.input-group + = text_field_tag :request_url, service_trigger_url(subject), class: 'form-control input-sm', readonly: 'readonly' + .input-group-btn + = clipboard_button(clipboard_target: '#request_url') + + .form-group + = label_tag nil, 'Request method', class: 'col-sm-2 col-xs-12 control-label' + .col-sm-10.col-xs-12.text-block POST + + .form-group + = label_tag :response_username, 'Response username', class: 'col-sm-2 col-xs-12 control-label' + .col-sm-10.col-xs-12.input-group + = text_field_tag :response_username, 'GitLab', class: 'form-control input-sm', readonly: 'readonly' + .input-group-btn + = clipboard_button(clipboard_target: '#response_username') + + .form-group + = label_tag :response_icon, 'Response icon', class: 'col-sm-2 col-xs-12 control-label' + .col-sm-10.col-xs-12.input-group + = text_field_tag :response_icon, asset_url('gitlab_logo.png'), class: 'form-control input-sm', readonly: 'readonly' + .input-group-btn + = clipboard_button(clipboard_target: '#response_icon') + + .form-group + = label_tag nil, 'Autocomplete', class: 'col-sm-2 col-xs-12 control-label' + .col-sm-10.col-xs-12.text-block Yes + + .form-group + = label_tag :autocomplete_hint, 'Autocomplete hint', class: 'col-sm-2 col-xs-12 control-label' + .col-sm-10.col-xs-12.input-group + = text_field_tag :autocomplete_hint, '[help]', class: 'form-control input-sm', readonly: 'readonly' + .input-group-btn + = clipboard_button(clipboard_target: '#autocomplete_hint') + + .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') + + %hr + + %ul.list-unstyled + %li + 3. After adding the slash command, paste the + %strong token + into the field below diff --git a/app/views/shared/_service_settings.html.haml b/app/views/shared/_service_settings.html.haml index 9c5053dace5..e8ab1b2ed46 100644 --- a/app/views/shared/_service_settings.html.haml +++ b/app/views/shared/_service_settings.html.haml @@ -13,38 +13,41 @@ .col-sm-10 = form.check_box :active - - if @service.supported_events.present? - .form-group - = form.label :url, "Trigger", class: 'control-label' - - .col-sm-10 - - @service.supported_events.each do |event| - %div - = form.check_box service_event_field_name(event), class: 'pull-left' - .prepend-left-20 - = form.label service_event_field_name(event), class: 'list-label' do - %strong - = event.humanize - - - field = @service.event_field(event) - - - if field - %p - = form.text_field field[:name], class: "form-control", placeholder: field[:placeholder] - - %p.light - = service_event_description(event) - - - @service.global_fields.each do |field| - - type = field[:type] - - - if type == 'fieldset' - - fields = field[:fields] - - legend = field[:legend] - - %fieldset - %legend= legend - - fields.each do |subfield| - = render 'shared/field', form: form, field: subfield - - else - = render 'shared/field', form: form, field: field + - if @service.auto_config? + + - else + - if @service.supported_events.present? + .form-group + = form.label :url, "Trigger", class: 'control-label' + + .col-sm-10 + - @service.supported_events.each do |event| + %div + = form.check_box service_event_field_name(event), class: 'pull-left' + .prepend-left-20 + = form.label service_event_field_name(event), class: 'list-label' do + %strong + = event.humanize + + - field = @service.event_field(event) + + - if field + %p + = form.text_field field[:name], class: "form-control", placeholder: field[:placeholder] + + %p.light + = service_event_description(event) + + - @service.global_fields.each do |field| + - type = field[:type] + + - if type == 'fieldset' + - fields = field[:fields] + - legend = field[:legend] + + %fieldset + %legend= legend + - fields.each do |subfield| + = render 'shared/field', form: form, field: subfield + - else + = render 'shared/field', form: form, field: field diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 327e4a7937c..e1e76e8bf73 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -153,6 +153,11 @@ production: &base # The location where LFS objects are stored (default: shared/lfs-objects). # storage_path: shared/lfs-objects + # For executing commands from GitLab on Mattermost + mattermost: + enabled: false + host: 'http://locahost:8065' + ## Gravatar ## For Libravatar see: http://doc.gitlab.com/ce/customization/libravatar.html gravatar: diff --git a/config/routes/project.rb b/config/routes/project.rb index 0754f0ec3b0..6f480b9e1a0 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -61,6 +61,7 @@ constraints(ProjectUrlConstrainer.new) do resources :services, constraints: { id: /[^\/]+/ }, only: [:index, :edit, :update] do member do get :test + post :configure end end diff --git a/lib/mattermost/command.rb b/lib/mattermost/command.rb new file mode 100644 index 00000000000..e159458a788 --- /dev/null +++ b/lib/mattermost/command.rb @@ -0,0 +1,26 @@ +module Mattermost + class Command + def self.all(team_id) + Mattermost::Mattermost.get("/teams/#{team_id}/commands/list_team_commands") + end + + # params should be a hash, which supplies _at least_: + # - trigger => The slash command, no spaces, cannot start with a / + # - url => What is the URL to trigger here? + # - icon_url => Supply a link to the icon + def self.create(team_id, params) + params = { + auto_complete: true, + auto_complete_desc: 'List all available commands', + auto_complete_hint: '[help]', + description: 'Perform common operations on GitLab', + display_name: 'GitLab', + method: 'P', + user_name: 'GitLab' + }..merge(params) + + Mattermost::Mattermost.post( "/teams/#{team_id}/commands/create", params.to_json). + parsed_response['token'] + end + end +end diff --git a/lib/mattermost/session.rb b/lib/mattermost/session.rb index 81964666757..cc4cb1f4f12 100644 --- a/lib/mattermost/session.rb +++ b/lib/mattermost/session.rb @@ -20,6 +20,7 @@ module Mattermost attr_accessor :current_resource_owner def initialize(uri, current_user) + uri = normalize_uri(uri) self.class.base_uri(uri) @current_resource_owner = current_user @@ -31,6 +32,8 @@ module Mattermost destroy result + rescue Errno::ECONNREFUSED + raise NoSessionError end # Next methods are needed for Doorkeeper @@ -67,11 +70,11 @@ module Mattermost end def destroy - post('/api/v3/users/logout') + post('/users/logout') end def oauth_uri - response = get("/api/v3/oauth/gitlab/login", follow_redirects: false) + response = get("/oauth/gitlab/login", follow_redirects: false) return unless 300 <= response.code && response.code < 400 redirect_uri = response.headers['location'] @@ -100,5 +103,11 @@ module Mattermost def post(path, options = {}) self.class.post(path, options) end + + def normalize_uri(uri) + uri << '/' unless uri.end_with?('/') + + uri << 'api/v3' + end end end diff --git a/lib/mattermost/team.rb b/lib/mattermost/team.rb new file mode 100644 index 00000000000..76e238a866e --- /dev/null +++ b/lib/mattermost/team.rb @@ -0,0 +1,10 @@ +module Mattermost + class Team < Mattermost + # After normalization this returns an array of hashes + # + # [{"id"=>"paf573pj9t81urupw3fanozeda", "display_name"=>"my team", <snip>}] + def self.all + @all_teams ||= get('/teams/all').parsed_response.values + end + end +end |