From 593c912151eaef865d31fc2a8307ef6d337c2349 Mon Sep 17 00:00:00 2001
From: Robert Schilling <rschilling@student.tugraz.at>
Date: Mon, 5 Dec 2016 15:40:53 +0100
Subject: [PATCH 1/2] Grapify the service API

---
 .../project_services/buildkite_service.rb     |   3 +-
 .../project_services/drone_ci_service.rb      |   3 +-
 .../emails_on_push_service.rb                 |  18 +-
 .../project_services/hipchat_service.rb       |   6 +-
 app/models/project_services/irker_service.rb  |   3 +-
 doc/api/services.md                           | 102 ++-
 lib/api/helpers.rb                            |  11 -
 lib/api/services.rb                           | 626 ++++++++++++++++--
 .../project_services/hipchat_service_spec.rb  |   2 +-
 spec/requests/api/services_spec.rb            |   5 +-
 spec/support/services_shared_context.rb       |   6 +
 11 files changed, 700 insertions(+), 85 deletions(-)

diff --git a/app/models/project_services/buildkite_service.rb b/app/models/project_services/buildkite_service.rb
index 86a06321e21..fe6d7aabb22 100644
--- a/app/models/project_services/buildkite_service.rb
+++ b/app/models/project_services/buildkite_service.rb
@@ -3,7 +3,8 @@ require "addressable/uri"
 class BuildkiteService < CiService
   ENDPOINT = "https://buildkite.com"
 
-  prop_accessor :project_url, :token, :enable_ssl_verification
+  prop_accessor :project_url, :token
+  boolean_accessor :enable_ssl_verification
 
   validates :project_url, presence: true, url: true, if: :activated?
   validates :token, presence: true, if: :activated?
diff --git a/app/models/project_services/drone_ci_service.rb b/app/models/project_services/drone_ci_service.rb
index 5e4dd101c53..adc78a427ee 100644
--- a/app/models/project_services/drone_ci_service.rb
+++ b/app/models/project_services/drone_ci_service.rb
@@ -1,5 +1,6 @@
 class DroneCiService < CiService
-  prop_accessor :drone_url, :token, :enable_ssl_verification
+  prop_accessor :drone_url, :token
+  boolean_accessor :enable_ssl_verification
 
   validates :drone_url, presence: true, url: true, if: :activated?
   validates :token, presence: true, if: :activated?
diff --git a/app/models/project_services/emails_on_push_service.rb b/app/models/project_services/emails_on_push_service.rb
index e0083c43adb..79285cbd26d 100644
--- a/app/models/project_services/emails_on_push_service.rb
+++ b/app/models/project_services/emails_on_push_service.rb
@@ -1,6 +1,6 @@
 class EmailsOnPushService < Service
-  prop_accessor :send_from_committer_email
-  prop_accessor :disable_diffs
+  boolean_accessor :send_from_committer_email
+  boolean_accessor :disable_diffs
   prop_accessor :recipients
   validates :recipients, presence: true, if: :activated?
 
@@ -24,20 +24,20 @@ class EmailsOnPushService < Service
     return unless supported_events.include?(push_data[:object_kind])
 
     EmailsOnPushWorker.perform_async(
-      project_id, 
-      recipients, 
-      push_data, 
-      send_from_committer_email:  send_from_committer_email?, 
-      disable_diffs:              disable_diffs?
+      project_id,
+      recipients,
+      push_data,
+      send_from_committer_email: send_from_committer_email?,
+      disable_diffs:             disable_diffs?
     )
   end
 
   def send_from_committer_email?
-    self.send_from_committer_email == "1"
+    Gitlab::Utils.to_boolean(self.send_from_committer_email)
   end
 
   def disable_diffs?
-    self.disable_diffs == "1"
+    Gitlab::Utils.to_boolean(self.disable_diffs)
   end
 
   def fields
diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb
index 660a8ae3421..915f6fed74c 100644
--- a/app/models/project_services/hipchat_service.rb
+++ b/app/models/project_services/hipchat_service.rb
@@ -8,8 +8,8 @@ class HipchatService < Service
     ul ol li dl dt dd
   ]
 
-  prop_accessor :token, :room, :server, :notify, :color, :api_version
-  boolean_accessor :notify_only_broken_builds
+  prop_accessor :token, :room, :server, :color, :api_version
+  boolean_accessor :notify_only_broken_builds, :notify
   validates :token, presence: true, if: :activated?
 
   def initialize_properties
@@ -75,7 +75,7 @@ class HipchatService < Service
   end
 
   def message_options(data = nil)
-    { notify: notify.present? && notify == '1', color: message_color(data) }
+    { notify: notify.present? && Gitlab::Utils.to_boolean(notify), color: message_color(data) }
   end
 
   def create_message(data)
diff --git a/app/models/project_services/irker_service.rb b/app/models/project_services/irker_service.rb
index ce7d1c5d5b1..7355918feab 100644
--- a/app/models/project_services/irker_service.rb
+++ b/app/models/project_services/irker_service.rb
@@ -2,7 +2,8 @@ require 'uri'
 
 class IrkerService < Service
   prop_accessor :server_host, :server_port, :default_irc_uri
-  prop_accessor :colorize_messages, :recipients, :channels
+  prop_accessor :recipients, :channels
+  boolean_accessor :colorize_messages
   validates :recipients, presence: true, if: :activated?
 
   before_validation :get_channels
diff --git a/doc/api/services.md b/doc/api/services.md
index a5d733fe6c7..acb54448664 100644
--- a/doc/api/services.md
+++ b/doc/api/services.md
@@ -139,6 +139,40 @@ Get Buildkite service settings for a project.
 GET /projects/:id/services/buildkite
 ```
 
+## Build-Emails
+
+Get emails for GitLab CI builds.
+
+### Create/Edit Build-Emails service
+
+Set Build-Emails service for a project.
+
+```
+PUT /projects/:id/services/builds-email
+```
+
+Parameters:
+
+- `recipients` (**required**) - Comma-separated list of recipient email addresses
+- `add_pusher` (optional) - Add pusher to recipients list
+- `notify_only_broken_builds` (optional) -Notify only broken builds
+
+### Delete Build-Emails service
+
+Delete Build-Emails service for a project.
+
+```
+DELETE /projects/:id/services/builds-email
+```
+
+### Get Build-Emails service settings
+
+Get Build-Emails service settings for a project.
+
+```
+GET /projects/:id/services/builds-email
+```
+
 ## Campfire
 
 Simple web-based real-time group chat
@@ -476,12 +510,11 @@ PUT /projects/:id/services/jira
 
 | Attribute | Type | Required | Description |
 | --------- | ---- | -------- | ----------- |
-| `active`        | boolean| no  | Enable/disable the JIRA service. |
 | `url`           | string | yes | The URL to the JIRA project which is being linked to this GitLab project, e.g., `https://jira.example.com`. |
 | `project_key`   | string | yes | The short identifier for your JIRA project, all uppercase, e.g., `PROJ`. |
 | `username`      | string | no  | The username of the user created to be used with GitLab/JIRA. |
 | `password`      | string | no  | The password of the user created to be used with GitLab/JIRA. |
-| `jira_issue_transition_id` | string | no | The ID of a transition that moves issues to a closed state. You can find this number under the JIRA workflow administration (**Administration > Issues > Workflows**) by selecting **View** under **Operations** of the desired workflow of your project. The ID of each state can be found inside the parenthesis of each transition name under the **Transitions (id)** column ([see screenshot][trans]). By default, this ID is set to `2`. |
+| `jira_issue_transition_id` | integer | no | The ID of a transition that moves issues to a closed state. You can find this number under the JIRA workflow administration (**Administration > Issues > Workflows**) by selecting **View** under **Operations** of the desired workflow of your project. The ID of each state can be found inside the parenthesis of each transition name under the **Transitions (id)** column ([see screenshot][trans]). By default, this ID is set to `2`. |
 
 ### Delete JIRA service
 
@@ -491,6 +524,71 @@ Remove all previously JIRA settings from a project.
 DELETE /projects/:id/services/jira
 ```
 
+## Mattermost Slash Commands
+
+Ability to receive slash commands from a Mattermost chat instance.
+
+### Create/Edit Mattermost Slash Command service
+
+Set Mattermost Slash Command for a project.
+
+```
+PUT /projects/:id/services/mattermost-slash-commands
+```
+
+Parameters:
+
+- `token` (**required**) - The Mattermost token
+
+### Delete Mattermost Slash Command service
+
+Delete Mattermost Slash Command service for a project.
+
+```
+DELETE /projects/:id/services/mattermost-slash-commands
+```
+
+### Get Mattermost Slash Command service settings
+
+Get Mattermost Slash Command service settings for a project.
+
+```
+GET /projects/:id/services/mattermost-slash-commands
+```
+
+## Pipeline-Emails
+
+Get emails for GitLab CI pipelines.
+
+### Create/Edit Pipeline-Emails service
+
+Set Pipeline-Emails service for a project.
+
+```
+PUT /projects/:id/services/pipelines-email
+```
+
+Parameters:
+
+- `recipients` (**required**) - Comma-separated list of recipient email addresses
+- `notify_only_broken_builds` (optional) -Notify only broken pipelines
+
+### Delete Pipeline-Emails service
+
+Delete Pipeline-Emails service for a project.
+
+```
+DELETE /projects/:id/services/pipelines-email
+```
+
+### Get Pipeline-Emails service settings
+
+Get Pipeline-Emails service settings for a project.
+
+```
+GET /projects/:id/services/pipelines-email
+```
+
 ## PivotalTracker
 
 Project Management Software (Source Commits Endpoint)
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 7f94ede7940..16ac40b7142 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -93,17 +93,6 @@ module API
       end
     end
 
-    def project_service(project = user_project)
-      @project_service ||= project.find_or_initialize_service(params[:service_slug].underscore)
-      @project_service || not_found!("Service")
-    end
-
-    def service_attributes
-      @service_attributes ||= project_service.fields.inject([]) do |arr, hash|
-        arr << hash[:name].to_sym
-      end
-    end
-
     def find_group(id)
       if id =~ /^\d+$/
         Group.find_by(id: id)
diff --git a/lib/api/services.rb b/lib/api/services.rb
index bc427705777..fde2e2746f1 100644
--- a/lib/api/services.rb
+++ b/lib/api/services.rb
@@ -1,84 +1,602 @@
 module API
-  # Projects API
   class Services < Grape::API
+    services = {
+      'asana' => [
+        {
+          required: true,
+          name: :api_key,
+          type: String,
+          desc: 'User API token'
+        },
+        {
+          required: false,
+          name: :restrict_to_branch,
+          type: String,
+          desc: 'Comma-separated list of branches which will be automatically inspected. Leave blank to include all branches'
+        }
+      ],
+      'assembla' => [
+        {
+          required: true,
+          name: :token,
+          type: String,
+          desc: 'The authentication token'
+        },
+        {
+          required: false,
+          name: :subdomain,
+          type: String,
+          desc: 'Subdomain setting'
+        }
+      ],
+      'bamboo' => [
+        {
+          required: true,
+          name: :bamboo_url,
+          type: String,
+          desc: 'Bamboo root URL like https://bamboo.example.com'
+        },
+        {
+          required: true,
+          name: :build_key,
+          type: String,
+          desc: 'Bamboo build plan key like'
+        },
+        {
+          required: true,
+          name: :username,
+          type: String,
+          desc: 'A user with API access, if applicable'
+        },
+        {
+          required: true,
+          name: :password,
+          type: String,
+          desc: 'Passord of the user'
+        }
+      ],
+      'bugzilla' => [
+        {
+          required: true,
+          name: :new_issue_url,
+          type: String,
+          desc: 'New issue URL'
+        },
+        {
+          required: true,
+          name: :issues_url,
+          type: String,
+          desc: 'Issues URL'
+        },
+        {
+          required: true,
+          name: :project_url,
+          type: String,
+          desc: 'Project URL'
+        },
+        {
+          required: false,
+          name: :description,
+          type: String,
+          desc: 'Description'
+        },
+        {
+          required: false,
+          name: :title,
+          type: String,
+          desc: 'Title'
+        }
+      ],
+      'buildkite' => [
+        {
+          required: true,
+          name: :token,
+          type: String,
+          desc: 'Buildkite project GitLab token'
+        },
+        {
+          required: true,
+          name: :project_url,
+          type: String,
+          desc: 'The buildkite project URL'
+        },
+        {
+          required: false,
+          name: :enable_ssl_verification,
+          type: Boolean,
+          desc: 'Enable SSL verification for communication'
+        }
+      ],
+      'builds-email' => [
+        {
+          required: true,
+          name: :recipients,
+          type: String,
+          desc: 'Comma-separated list of recipient email addresses'
+        },
+        {
+          required: false,
+          name: :add_pusher,
+          type: Boolean,
+          desc: 'Add pusher to recipients list'
+        },
+        {
+          required: false,
+          name: :notify_only_broken_builds,
+          type: Boolean,
+          desc: 'Notify only broken builds'
+        }
+      ],
+      'campfire' => [
+        {
+          required: true,
+          name: :token,
+          type: String,
+          desc: 'Campfire token'
+        },
+        {
+          required: false,
+          name: :subdomain,
+          type: String,
+          desc: 'Campfire subdomain'
+        },
+        {
+          required: false,
+          name: :room,
+          type: String,
+          desc: 'Campfire room'
+        },
+      ],
+      'custom-issue-tracker' => [
+        {
+          required: true,
+          name: :new_issue_url,
+          type: String,
+          desc: 'New issue URL'
+        },
+        {
+          required: true,
+          name: :issues_url,
+          type: String,
+          desc: 'Issues URL'
+        },
+        {
+          required: true,
+          name: :project_url,
+          type: String,
+          desc: 'Project URL'
+        },
+        {
+          required: false,
+          name: :description,
+          type: String,
+          desc: 'Description'
+        },
+        {
+          required: false,
+          name: :title,
+          type: String,
+          desc: 'Title'
+        }
+      ],
+      'drone-ci' => [
+        {
+          required: true,
+          name: :token,
+          type: String,
+          desc: 'Drone CI token'
+        },
+        {
+          required: true,
+          name: :drone_url,
+          type: String,
+          desc: 'Drone CI URL'
+        },
+        {
+          required: false,
+          name: :enable_ssl_verification,
+          type: Boolean,
+          desc: 'Enable SSL verification for communication'
+        }
+      ],
+      'emails-on-push' => [
+        {
+          required: true,
+          name: :recipients,
+          type: String,
+          desc: 'Comma-separated list of recipient email addresses'
+        },
+        {
+          required: false,
+          name: :disable_diffs,
+          type: Boolean,
+          desc: 'Disable code diffs'
+        },
+        {
+          required: false,
+          name: :send_from_committer_email,
+          type: Boolean,
+          desc: 'Send from committer'
+        }
+      ],
+      'external-wiki' => [
+        {
+          required: true,
+          name: :external_wiki_url,
+          type: String,
+          desc: 'The URL of the external Wiki'
+        }
+      ],
+      'flowdock' => [
+        {
+          required: true,
+          name: :token,
+          type: String,
+          desc: 'Flowdock token'
+        }
+      ],
+      'gemnasium' => [
+        {
+          required: true,
+          name: :api_key,
+          type: String,
+          desc: 'Your personal API key on gemnasium.com'
+        },
+        {
+          required: true,
+          name: :token,
+          type: String,
+          desc: "The project's slug on gemnasium.com"
+        }
+      ],
+      'hipchat' => [
+        {
+          required: true,
+          name: :token,
+          type: String,
+          desc: 'The room token'
+        },
+        {
+          required: false,
+          name: :room,
+          type: String,
+          desc: 'The room name or ID'
+        },
+        {
+          required: false,
+          name: :color,
+          type: String,
+          desc: 'The room color'
+        },
+        {
+          required: false,
+          name: :notify,
+          type: Boolean,
+          desc: 'Enable notifications'
+        },
+        {
+          required: false,
+          name: :api_version,
+          type: String,
+          desc: 'Leave blank for default (v2)'
+        },
+        {
+          required: false,
+          name: :server,
+          type: String,
+          desc: 'Leave blank for default. https://hipchat.example.com'
+        }
+      ],
+      'irker' => [
+        {
+          required: true,
+          name: :recipients,
+          type: String,
+          desc: 'Recipients/channels separated by whitespaces'
+        },
+        {
+          required: false,
+          name: :default_irc_uri,
+          type: String,
+          desc: 'Default: irc://irc.network.net:6697'
+        },
+        {
+          required: false,
+          name: :server_host,
+          type: String,
+          desc: 'Server host. Default localhost'
+        },
+        {
+          required: false,
+          name: :server_port,
+          type: Integer,
+          desc: 'Server port. Default 6659'
+        },
+        {
+          required: false,
+          name: :colorize_messages,
+          type: Boolean,
+          desc: 'Colorize messages'
+        }
+      ],
+      'jira' => [
+        {
+          required: true,
+          name: :url,
+          type: String,
+          desc: 'The URL to the JIRA project which is being linked to this GitLab project, e.g., https://jira.example.com'
+        },
+        {
+          required: true,
+          name: :project_key,
+          type: String,
+          desc: 'The short identifier for your JIRA project, all uppercase, e.g., PROJ'
+        },
+        {
+          required: false,
+          name: :username,
+          type: String,
+          desc: 'The username of the user created to be used with GitLab/JIRA'
+        },
+        {
+          required: false,
+          name: :password,
+          type: String,
+          desc: 'The password of the user created to be used with GitLab/JIRA'
+        },
+        {
+          required: false,
+          name: :jira_issue_transition_id,
+          type: Integer,
+          desc: 'The ID of a transition that moves issues to a closed state. You can find this number under the JIRA workflow administration (**Administration > Issues > Workflows**) by selecting **View** under **Operations** of the desired workflow of your project. The ID of each state can be found inside the parenthesis of each transition name under the **Transitions (id)** column ([see screenshot][trans]). By default, this ID is set to `2`'
+        }
+      ],
+      'mattermost-slash-commands' => [
+        {
+          required: true,
+          name: :token,
+          type: String,
+          desc: 'The Mattermost token'
+        }
+      ],
+      'pipelines-email' => [
+        {
+          required: true,
+          name: :recipients,
+          type: String,
+          desc: 'Comma-separated list of recipient email addresses'
+        },
+        {
+          required: false,
+          name: :notify_only_broken_builds,
+          type: Boolean,
+          desc: 'Notify only broken builds'
+        }
+      ],
+      'pivotaltracker' => [
+        {
+          required: true,
+          name: :token,
+          type: String,
+          desc: 'The Pivotaltracker token'
+        },
+        {
+          required: false,
+          name: :restrict_to_branch,
+          type: String,
+          desc: 'Comma-separated list of branches which will be automatically inspected. Leave blank to include all branches.'
+        }
+      ],
+      'pushover' => [
+        {
+          required: true,
+          name: :api_key,
+          type: String,
+          desc: 'The application key'
+        },
+        {
+          required: true,
+          name: :user_key,
+          type: String,
+          desc: 'The user key'
+        },
+        {
+          required: true,
+          name: :priority,
+          type: String,
+          desc: 'The priority'
+        },
+        {
+          required: true,
+          name: :device,
+          type: String,
+          desc: 'Leave blank for all active devices'
+        },
+        {
+          required: true,
+          name: :sound,
+          type: String,
+          desc: 'The sound of the notification'
+        }
+      ],
+      'redmine' => [
+        {
+          required: true,
+          name: :new_issue_url,
+          type: String,
+          desc: 'The new issue URL'
+        },
+        {
+          required: true,
+          name: :project_url,
+          type: String,
+          desc: 'The project URL'
+        },
+        {
+          required: true,
+          name: :issues_url,
+          type: String,
+          desc: 'The issues URL'
+        },
+        {
+          required: false,
+          name: :description,
+          type: String,
+          desc: 'The description of the tracker'
+        }
+      ],
+      'slack' => [
+        {
+          required: true,
+          name: :webhook,
+          type: String,
+          desc: 'The Slack webhook. e.g. https://hooks.slack.com/services/...'
+        },
+        {
+          required: false,
+          name: :new_issue_url,
+          type: String,
+          desc: 'The user name'
+        },
+        {
+          required: false,
+          name: :channel,
+          type: String,
+          desc: 'The channel name'
+        }
+      ],
+      'teamcity' => [
+        {
+          required: true,
+          name: :teamcity_url,
+          type: String,
+          desc: 'TeamCity root URL like https://teamcity.example.com'
+        },
+        {
+          required: true,
+          name: :build_type,
+          type: String,
+          desc: 'Build configuration ID'
+        },
+        {
+          required: true,
+          name: :username,
+          type: String,
+          desc: 'A user with permissions to trigger a manual build'
+        },
+        {
+          required: true,
+          name: :password,
+          type: String,
+          desc: 'The password of the user'
+        }
+      ]
+    }.freeze
+
+    trigger_services = {
+      'mattermost-slash-commands' => [
+        {
+          name: :token,
+          type: String,
+          desc: 'The Mattermost token'
+        }
+      ]
+    }.freeze
+
     resource :projects do
       before { authenticate! }
       before { authorize_admin_project }
 
-      # Set <service_slug> service for project
-      #
-      # Example Request:
-      #
-      #   PUT /projects/:id/services/gitlab-ci
-      #
-      put ':id/services/:service_slug' do
-        if project_service
-          validators = project_service.class.validators.select do |s|
-            s.class == ActiveRecord::Validations::PresenceValidator &&
-              s.attributes != [:project_id]
+      helpers do
+        def service_attributes(service)
+          service.fields.inject([]) do |arr, hash|
+            arr << hash[:name].to_sym
           end
+        end
+      end
 
-          required_attributes! validators.map(&:attributes).flatten.uniq
-          attrs = attributes_for_keys service_attributes
+      services.each do |service_slug, settings|
+        desc "Set #{service_slug} service for project"
+        params do
+          settings.each do |setting|
+            if setting[:required]
+              requires setting[:name], type: setting[:type], desc: setting[:desc]
+            else
+              optional setting[:name], type: setting[:type], desc: setting[:desc]
+            end
+          end
+        end
+        put ":id/services/#{service_slug}" do
+          service = user_project.find_or_initialize_service(service_slug.underscore)
+          service_params = declared_params(include_missing: false).merge(active: true)
 
-          if project_service.update_attributes(attrs.merge(active: true))
+          if service.update_attributes(service_params)
             true
           else
-            not_found!
+            render_api_error!('400 Bad Request', 400)
           end
         end
       end
 
-      # Delete <service_slug> service for project
-      #
-      # Example Request:
-      #
-      #   DELETE /project/:id/services/gitlab-ci
-      #
-      delete ':id/services/:service_slug' do
-        if project_service
-          attrs = service_attributes.inject({}) do |hash, key|
-            hash.merge!(key => nil)
-          end
+      desc "Delete a service for project"
+      params do
+        requires :service_slug, type: String, values: services.keys, desc: 'The name of the service'
+      end
+      delete ":id/services/:service_slug" do
+        service = user_project.find_or_initialize_service(params[:service_slug].underscore)
 
-          if project_service.update_attributes(attrs.merge(active: false))
-            true
-          else
-            not_found!
-          end
+        attrs = service_attributes(service).inject({}) do |hash, key|
+          hash.merge!(key => nil)
+        end
+
+        if service.update_attributes(attrs.merge(active: false))
+          true
+        else
+          render_api_error!('400 Bad Request', 400)
         end
       end
 
-      # Get <service_slug> service settings for project
-      #
-      # Example Request:
-      #
-      #   GET /project/:id/services/gitlab-ci
-      #
-      get ':id/services/:service_slug' do
-        present project_service, with: Entities::ProjectService, include_passwords: current_user.is_admin?
+      desc 'Get the service settings for project' do
+        success Entities::ProjectService
+      end
+      params do
+        requires :service_slug, type: String, values: services.keys, desc: 'The name of the service'
+      end
+      get ":id/services/:service_slug" do
+        service = user_project.find_or_initialize_service(params[:service_slug].underscore)
+        present service, with: Entities::ProjectService, include_passwords: current_user.is_admin?
       end
     end
 
-    resource :projects do
-      desc 'Trigger a slash command' do
-        detail 'Added in GitLab 8.13'
+    trigger_services.each do |service_slug, settings|
+      params do
+        requires :id, type: String, desc: 'The ID of a project'
       end
-      post ':id/services/:service_slug/trigger' do
-        project = find_project(params[:id])
+      resource :projects do
+        desc "Trigger a slash command for #{service_slug}" do
+          detail 'Added in GitLab 8.13'
+        end
+        params do
+          settings.each do |setting|
+            requires setting[:name], type: setting[:type], desc: setting[:desc]
+          end
+        end
+        post ":id/services/#{service_slug.underscore}/trigger" do
+          project = find_project(params[:id])
 
-        # This is not accurate, but done to prevent leakage of the project names
-        not_found!('Service') unless project
+          # This is not accurate, but done to prevent leakage of the project names
+          not_found!('Service') unless project
 
-        service = project_service(project)
+          service = project.find_or_initialize_service(service_slug.underscore)
 
-        result = service.try(:active?) && service.try(:trigger, params)
+          result = service.try(:active?) && service.try(:trigger, params)
 
-        if result
-          status result[:status] || 200
-          present result
-        else
-          not_found!('Service')
+          if result
+            status result[:status] || 200
+            present result
+          else
+            not_found!('Service')
+          end
         end
       end
     end
diff --git a/spec/models/project_services/hipchat_service_spec.rb b/spec/models/project_services/hipchat_service_spec.rb
index 2da3a9cb09f..564e49d5459 100644
--- a/spec/models/project_services/hipchat_service_spec.rb
+++ b/spec/models/project_services/hipchat_service_spec.rb
@@ -358,7 +358,7 @@ describe HipchatService, models: true do
       context 'with a failed build' do
         it 'uses the red color' do
           build_data = { object_kind: 'build', commit: { status: 'failed' } }
-
+          
           expect(hipchat.__send__(:message_options, build_data)).to eq({ notify: false, color: 'red' })
         end
       end
diff --git a/spec/requests/api/services_spec.rb b/spec/requests/api/services_spec.rb
index d30361f53d4..668e39f9dba 100644
--- a/spec/requests/api/services_spec.rb
+++ b/spec/requests/api/services_spec.rb
@@ -2,6 +2,7 @@ require "spec_helper"
 
 describe API::Services, api: true  do
   include ApiHelpers
+
   let(:user) { create(:user) }
   let(:admin) { create(:admin) }
   let(:user2) { create(:user) }
@@ -98,7 +99,7 @@ describe API::Services, api: true  do
         post api("/projects/#{project.id}/services/idonotexist/trigger")
 
         expect(response).to have_http_status(404)
-        expect(json_response["message"]).to eq("404 Service Not Found")
+        expect(json_response["error"]).to eq("404 Not Found")
       end
     end
 
@@ -114,7 +115,7 @@ describe API::Services, api: true  do
         end
 
         it 'when the service is inactive' do
-          post api("/projects/#{project.id}/services/mattermost_slash_commands/trigger")
+          post api("/projects/#{project.id}/services/mattermost_slash_commands/trigger"), params
 
           expect(response).to have_http_status(404)
         end
diff --git a/spec/support/services_shared_context.rb b/spec/support/services_shared_context.rb
index d1c999cad4d..66c93890e31 100644
--- a/spec/support/services_shared_context.rb
+++ b/spec/support/services_shared_context.rb
@@ -16,8 +16,14 @@ Service.available_services_names.each do |service|
           hash.merge!(k => 'secrettoken')
         elsif k =~ /^(.*_url|url|webhook)/
           hash.merge!(k => "http://example.com")
+        elsif service_klass.method_defined?("#{k}?")
+          hash.merge!(k => true)
         elsif service == 'irker' && k == :recipients
           hash.merge!(k => 'irc://irc.network.net:666/#channel')
+        elsif service == 'irker' && k == :server_port
+          hash.merge!(k => 1234)
+        elsif service == 'jira' && k == :jira_issue_transition_id
+          hash.merge!(k => 1234)
         else
           hash.merge!(k => "someword")
         end
-- 
GitLab


From 8d0645c2ce3769268020ad6f8e51db07fb1e4bc6 Mon Sep 17 00:00:00 2001
From: Robert Schilling <rschilling@student.tugraz.at>
Date: Mon, 12 Dec 2016 14:27:52 +0100
Subject: [PATCH 2/2] Grapify the service API

---
 doc/api/services.md                           | 22 ++++++++++++++-----
 .../project_services/hipchat_service_spec.rb  |  2 +-
 2 files changed, 17 insertions(+), 7 deletions(-)

diff --git a/doc/api/services.md b/doc/api/services.md
index acb54448664..3dad953cd1e 100644
--- a/doc/api/services.md
+++ b/doc/api/services.md
@@ -153,9 +153,12 @@ PUT /projects/:id/services/builds-email
 
 Parameters:
 
-- `recipients` (**required**) - Comma-separated list of recipient email addresses
-- `add_pusher` (optional) - Add pusher to recipients list
-- `notify_only_broken_builds` (optional) -Notify only broken builds
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `recipients` | string | yes | Comma-separated list of recipient email addresses |
+| `add_pusher` | boolean | no | Add pusher to recipients list |
+| `notify_only_broken_builds` | boolean | no | Notify only broken builds |
+
 
 ### Delete Build-Emails service
 
@@ -538,7 +541,10 @@ PUT /projects/:id/services/mattermost-slash-commands
 
 Parameters:
 
-- `token` (**required**) - The Mattermost token
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `token` | string | yes | The Mattermost token |
+
 
 ### Delete Mattermost Slash Command service
 
@@ -570,8 +576,12 @@ PUT /projects/:id/services/pipelines-email
 
 Parameters:
 
-- `recipients` (**required**) - Comma-separated list of recipient email addresses
-- `notify_only_broken_builds` (optional) -Notify only broken pipelines
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `recipients` | string | yes | Comma-separated list of recipient email addresses |
+| `add_pusher` | boolean | no | Add pusher to recipients list |
+| `notify_only_broken_builds` | boolean | no | Notify only broken pipelines |
+
 
 ### Delete Pipeline-Emails service
 
diff --git a/spec/models/project_services/hipchat_service_spec.rb b/spec/models/project_services/hipchat_service_spec.rb
index 564e49d5459..2da3a9cb09f 100644
--- a/spec/models/project_services/hipchat_service_spec.rb
+++ b/spec/models/project_services/hipchat_service_spec.rb
@@ -358,7 +358,7 @@ describe HipchatService, models: true do
       context 'with a failed build' do
         it 'uses the red color' do
           build_data = { object_kind: 'build', commit: { status: 'failed' } }
-          
+
           expect(hipchat.__send__(:message_options, build_data)).to eq({ notify: false, color: 'red' })
         end
       end
-- 
GitLab