|
| 1 | +require 'httpclient' |
| 2 | + |
| 3 | +module RedmineHangoutsChatIntegration |
| 4 | + class Hooks < Redmine::Hook::ViewListener |
| 5 | + include ApplicationHelper |
| 6 | + include CustomFieldsHelper |
| 7 | + include IssuesHelper |
| 8 | + |
| 9 | +################################################################################ |
| 10 | +## Hook for issues_new_after_save |
| 11 | +################################################################################ |
| 12 | + def controller_issues_new_after_save(context={}) |
| 13 | + ## check user |
| 14 | + return if is_disabled(User.current) |
| 15 | + |
| 16 | + ## Get issue |
| 17 | + issue = context[:issue] |
| 18 | + |
| 19 | + ## Is private issue |
| 20 | + return if issue.is_private |
| 21 | + |
| 22 | + ## Get issue URL |
| 23 | + issue_url = get_object_url(issue) |
| 24 | + |
| 25 | + ## Get Webhook URL |
| 26 | + webhook_url = get_webhook_url(issue.project) |
| 27 | + return if webhook_url.nil? |
| 28 | + |
| 29 | + ## Make Webhook Thread URL |
| 30 | + thread_key = Digest::MD5.hexdigest(issue_url) |
| 31 | + thread_url = webhook_url + "&thread_key=" + thread_key |
| 32 | + return unless thread_url =~ URI::regexp |
| 33 | + |
| 34 | + ## webhook data |
| 35 | + data = {} |
| 36 | + |
| 37 | + ## Add issue updated_on |
| 38 | + data['text'] = "*#{l(:field_updated_on)}:#{issue.updated_on}*" |
| 39 | + |
| 40 | + ## Add issue subject |
| 41 | + subject = issue.subject.gsub(/[ |\s|]+$/, "") |
| 42 | + data['text'] = data['text'] + "\n*#{l(:field_subject)}:<#{issue_url}|[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] #{subject}>*" |
| 43 | + |
| 44 | + ## Add issue URL |
| 45 | + data['text'] = data['text'] + "\n*URL:* #{issue_url}" |
| 46 | + |
| 47 | + ## Add issue author |
| 48 | + data['text'] = data['text'] + "\n```" + l(:text_issue_added, :id => "##{issue.id}", :author => issue.author) |
| 49 | + |
| 50 | + ## Add issue attributes |
| 51 | + data['text'] = data['text'] + "\n#{''.ljust(37, '-')}\n" + render_email_issue_attributes(issue, User.current) |
| 52 | + |
| 53 | + ## Add issue descripption |
| 54 | + unless issue.description.blank? |
| 55 | + data['text'] = data['text'] + "\n#{''.ljust(37, '-')}\n#{issue.description}" |
| 56 | + end |
| 57 | + |
| 58 | + ## Add issue attachments |
| 59 | + if issue.attachments.any? |
| 60 | + data['text'] = data['text'] + "\n\n#{l(:label_attachment_plural).ljust(37, '-')}" |
| 61 | + issue.attachments.each do |attachment| |
| 62 | + data['text'] = data['text'] + "\n#{attachment.filename} #{number_to_human_size(attachment.filesize)}" |
| 63 | + end |
| 64 | + end |
| 65 | + |
| 66 | + ## Add ``` |
| 67 | + data['text'] = data['text'] + "\n```" |
| 68 | + |
| 69 | + ## Send webhook data |
| 70 | + send_webhook_data(thread_url, data) |
| 71 | + end |
| 72 | + |
| 73 | +################################################################################ |
| 74 | +## Hook for controller_issues_edit_after_save |
| 75 | +################################################################################ |
| 76 | + def controller_issues_edit_after_save(context={}) |
| 77 | + ## check user |
| 78 | + return if is_disabled(User.current) |
| 79 | + |
| 80 | + ## Get issue and journal |
| 81 | + issue = context[:issue] |
| 82 | + journal = context[:journal] |
| 83 | + |
| 84 | + ## Is private issue |
| 85 | + return if issue.is_private |
| 86 | + return if journal.private_notes |
| 87 | + |
| 88 | + ## Get issue URL |
| 89 | + issue_url = get_object_url(issue) |
| 90 | + |
| 91 | + ## Get Webhook URL |
| 92 | + webhook_url = get_webhook_url(issue.project) |
| 93 | + return if webhook_url.nil? |
| 94 | + |
| 95 | + ## Make Webhook Thread URL |
| 96 | + thread_key = Digest::MD5.hexdigest(issue_url) |
| 97 | + thread_url = webhook_url + "&thread_key=" + thread_key |
| 98 | + return unless thread_url =~ URI::regexp |
| 99 | + |
| 100 | + ## webhook data |
| 101 | + data = {} |
| 102 | + |
| 103 | + ## Add issue updated_on |
| 104 | + data['text'] = "*#{l(:field_updated_on)}:#{issue.updated_on}*" |
| 105 | + |
| 106 | + ## Add issue subject |
| 107 | + subject = issue.subject.gsub(/[ |\s|]+$/, "") |
| 108 | + data['text'] = data['text'] + "\n*#{l(:field_subject)}:<#{issue_url}|[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] #{subject}>*" |
| 109 | + |
| 110 | + ## Add issue URL |
| 111 | + data['text'] = data['text'] + "\n*URL:* #{issue_url}" |
| 112 | + |
| 113 | + ## Add issue author |
| 114 | + data['text'] = data['text'] + "\n```" + l(:text_issue_updated, :id => "##{issue.id}", :author => journal.user) |
| 115 | + |
| 116 | + ## Add issue details |
| 117 | + details = details_to_strings(journal.visible_details, true).join("\n") |
| 118 | + unless details.blank? |
| 119 | + data['text'] = data['text'] + "\n#{''.ljust(37, '-')}\n#{details}" |
| 120 | + end |
| 121 | + |
| 122 | + ## Add issue notes |
| 123 | + unless issue.notes.blank? |
| 124 | + data['text'] = data['text'] + "\n#{''.ljust(37, '-')}\n#{issue.notes}" |
| 125 | + end |
| 126 | + |
| 127 | + ## Add ``` |
| 128 | + data['text'] = data['text'] + "\n```" |
| 129 | + |
| 130 | + ## Don't send empty data |
| 131 | + return if details.blank? && issue.notes.blank? |
| 132 | + |
| 133 | + ## Send webhook data |
| 134 | + send_webhook_data(thread_url, data) |
| 135 | + end |
| 136 | + |
| 137 | +################################################################################ |
| 138 | +## Hook for controller_issue_relations_new_after_save |
| 139 | +################################################################################ |
| 140 | + def controller_issue_relations_new_after_save(context={}) |
| 141 | + call_hook(:controller_issues_edit_after_save, context) |
| 142 | + end |
| 143 | + |
| 144 | +################################################################################ |
| 145 | +## Hook for controller_issue_relations_move_after_save |
| 146 | +################################################################################ |
| 147 | + def controller_issue_relations_move_after_save(context={}) |
| 148 | + call_hook(:controller_issues_edit_after_save, context) |
| 149 | + end |
| 150 | + |
| 151 | +################################################################################ |
| 152 | +## Private Method |
| 153 | +################################################################################ |
| 154 | +private |
| 155 | + |
| 156 | +################################################################################ |
| 157 | +## Get Redmine Object URL |
| 158 | +################################################################################ |
| 159 | + def get_object_url(obj) |
| 160 | + routes = Rails.application.routes.url_helpers |
| 161 | + if Setting.host_name.to_s =~ /\A(https?\:\/\/)?(.+?)(\:(\d+))?(\/.+)?\z/i |
| 162 | + host, port, prefix = $2, $4, $5 |
| 163 | + routes.url_for(obj.event_url({ |
| 164 | + :host => host, |
| 165 | + :protocol => Setting.protocol, |
| 166 | + :port => port, |
| 167 | + :script_name => prefix |
| 168 | + })) |
| 169 | + else |
| 170 | + routes.url_for(obj.event_url({ |
| 171 | + :host => Setting.host_name, |
| 172 | + :protocol => Setting.protocol |
| 173 | + })) |
| 174 | + end |
| 175 | + end |
| 176 | + |
| 177 | +################################################################################ |
| 178 | +## Is Hangouts Chat Disabled |
| 179 | +################################################################################ |
| 180 | + def is_disabled(user) |
| 181 | + ## check user |
| 182 | + return true if user.nil? |
| 183 | + |
| 184 | + ## check user custom field |
| 185 | + user_cf = UserCustomField.find_by_name("Hangouts Chat Disabled") |
| 186 | + return true if user_cf.nil? |
| 187 | + |
| 188 | + ## check user custom value |
| 189 | + user_cv = user.custom_value_for(user_cf) |
| 190 | + |
| 191 | + ## user_cv is null |
| 192 | + return false if user_cv.nil? |
| 193 | + |
| 194 | + return false if user_cv.value == '0' |
| 195 | + |
| 196 | + return true |
| 197 | + end |
| 198 | + |
| 199 | +################################################################################ |
| 200 | +## Get Hangouts Chat Webhook URL |
| 201 | +################################################################################ |
| 202 | + def get_webhook_url(proj) |
| 203 | + ## used value from this plugin's setting |
| 204 | + if proj.nil? |
| 205 | + return Setting.plugin_redmine_hangouts_chat_integration['hangouts_chat_webhook'] |
| 206 | + end |
| 207 | + |
| 208 | + ## used value from this project's custom field |
| 209 | + proj_cf = ProjectCustomField.find_by_name("Hangouts Chat Webhook") |
| 210 | + unless proj_cf.nil? |
| 211 | + proj_cv = proj.custom_value_for(proj_cf) |
| 212 | + unless proj_cv.nil? |
| 213 | + url = proj_cv.value |
| 214 | + return url if url =~ URI::regexp |
| 215 | + end |
| 216 | + end |
| 217 | + |
| 218 | + ## used value from parent project's custom field |
| 219 | + return get_webhook_url(proj.parent) |
| 220 | + end |
| 221 | + |
| 222 | +################################################################################ |
| 223 | +## Send data to Hangouts Chat |
| 224 | +################################################################################ |
| 225 | + def send_webhook_data(url, data) |
| 226 | + Rails.logger.debug("Webhook URL: #{url}") |
| 227 | + Rails.logger.debug("Webhook Data: #{data.to_json}") |
| 228 | + |
| 229 | + ## Send data |
| 230 | + begin |
| 231 | + https_proxy = ENV['https_proxy'] || ENV['HTTPS_PROXY'] |
| 232 | + client = HTTPClient.new(https_proxy) |
| 233 | + client.ssl_config.cert_store.set_default_paths |
| 234 | + client.ssl_config.ssl_version = :auto |
| 235 | + client.post_async url, {:body => data.to_json, :header => {'Content-Type' => 'application/json'}} |
| 236 | + rescue Exception => e |
| 237 | + Rails.logger.warn("cannot connect to #{url}") |
| 238 | + Rails.logger.warn(e) |
| 239 | + end |
| 240 | + end |
| 241 | + end |
| 242 | +end |
0 commit comments