code_review_notifier 0.3.5 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1b7b5a5c91aa7b2d74cea6c33661b4b3546cdde5b2783f117162f91ece438897
4
- data.tar.gz: 9dd69c5e3421bb3652c3fb7f7f0bf811d9f1bd3a9d97a44d02d53017e1d26f4f
3
+ metadata.gz: 5db0d8afcc1a0657f612a68d670b51aace38ac362dbd8ee2a3e4728eeed3b68d
4
+ data.tar.gz: f03fbd2d8aa52caa9e263e2e57465cf5017a713b341d6cdd98526e902b5efccc
5
5
  SHA512:
6
- metadata.gz: 3ba039eeead30bf76ec8e8611211b768fb8fc4412b8e97cd549e21328db401d400c54722a212d0f7f783eb9b0d38d542a122da0a8c7867715820e6ffada6286f
7
- data.tar.gz: 16a5339ad014a010711743789c317de5db793094024589c13d671321912cf36a4d51740b31df26c82c1b325ef0a8291e56b08182d1fc6d32b499928328afed1a
6
+ metadata.gz: ac5eef5dafb3832377600de260711d5a26ef1bf18d029ba1a4217ab57e57ddd0809e992f85d3f59a7a9c519322b3d038bfb0c9b01dc818ef3fe9d0707e233805
7
+ data.tar.gz: 6d31866de3088d69095cb875aca057e424b8aa4ac167fa3596adb25cf118722aa10e0f758afe3d196369c568113e1f0cc4d04cd88ad16ed39a26e88eb74d02d7
data/Gemfile CHANGED
@@ -2,4 +2,6 @@ source "https://rubygems.org"
2
2
 
3
3
  gemspec
4
4
 
5
- gem "rubiclifier", "2.1.3"
5
+ gem "rubiclifier", "2.1.4"
6
+ gem "graphql-client", "0.16.0"
7
+ gem "byebug"
data/lib/api.rb CHANGED
@@ -1,7 +1,7 @@
1
- require_relative "./gerrit_api.rb"
1
+ require_relative "./gitlab_api.rb"
2
2
 
3
3
  class Api
4
4
  def self.current_api
5
- GerritApi
5
+ GitlabApi
6
6
  end
7
7
  end
@@ -11,16 +11,15 @@ class CodeChangeNotification
11
11
 
12
12
  def send
13
13
  code_change = code_change_activity.code_change
14
- id = code_change.id
15
14
  owner = code_change.owner
16
15
  subject = code_change.subject
17
16
 
18
17
  message = code_change_activity.message
19
18
  author = code_change_activity.author
20
19
  Rubiclifier::Notification.new(
21
- author,
22
- message,
23
20
  "#{owner}: #{subject}",
21
+ message,
22
+ author,
24
23
  Api.current_api.favicon,
25
24
  Api.current_api.code_change_url(code_change)
26
25
  ).send
@@ -2,7 +2,7 @@ require "rubiclifier"
2
2
  require_relative "./api.rb"
3
3
  require_relative "./code_change_notification.rb"
4
4
 
5
- SECONDS_BETWEEN_RUNS = 90
5
+ SECONDS_BETWEEN_RUNS = 60
6
6
  SECONDS_BETWEEN_NOTIFICATIONS = 5
7
7
 
8
8
  class CodeReviewNotifier < Rubiclifier::BaseApplication
@@ -19,10 +19,12 @@ class CodeReviewNotifier < Rubiclifier::BaseApplication
19
19
  end
20
20
 
21
21
  def run_application
22
+ $stdout.sync = true
22
23
  while true
23
24
  unless Rubiclifier::IdleDetector.is_idle?
24
25
  is_first_run = is_first_run?
25
26
  puts
27
+ puts(Time.now().to_s)
26
28
  puts("Querying API...")
27
29
  all_code_changes = Api.current_api.all_code_changes
28
30
  puts("Checking for notifications to display...")
@@ -39,6 +41,7 @@ class CodeReviewNotifier < Rubiclifier::BaseApplication
39
41
  sleep(SECONDS_BETWEEN_NOTIFICATIONS)
40
42
  end
41
43
  end
44
+ puts("Sleeping for #{SECONDS_BETWEEN_RUNS} seconds...")
42
45
  end
43
46
  sleep(SECONDS_BETWEEN_RUNS)
44
47
  end
@@ -63,9 +66,9 @@ class CodeReviewNotifier < Rubiclifier::BaseApplication
63
66
  def settings
64
67
  @settings ||= [
65
68
  Rubiclifier::Setting.new("base_api_url", "base URL", explanation: "e.g. https://gerrit.google.com"),
66
- Rubiclifier::Setting.new("username", "account username"),
67
- Rubiclifier::Setting.new("password", "account password", explanation: "input hidden", is_secret: true),
68
- Rubiclifier::Setting.new("account_id", "account ID", explanation: -> {"check #{Api.current_api.base_api_url}/settings/"})
69
+ Rubiclifier::Setting.new("api_token", "API token"),
70
+ Rubiclifier::Setting.new("username", "Gitlab username"),
71
+ Rubiclifier::Setting.new("team_name", "Gitlab team")
69
72
  ]
70
73
  end
71
74
 
data/lib/gerrit_api.rb CHANGED
@@ -41,7 +41,7 @@ class GerritApi < Rubiclifier::BaseApi
41
41
  end
42
42
 
43
43
  def self.code_change_from_json(json)
44
- code_change = CodeChange.new(json["_number"].to_s, json["owner"]["name"], json["project"], json["subject"], Time.parse(json["updated"]).to_i)
44
+ code_change = CodeChange.new(json["_number"].to_s, json["owner"]["name"], json["project"], json["subject"])
45
45
  code_change.code_change_activity = json["messages"].map { |m| code_change_activity_from_json(code_change, m) }
46
46
  code_change
47
47
  end
data/lib/gitlab_api.rb ADDED
@@ -0,0 +1,180 @@
1
+ require "rubiclifier"
2
+ require "graphql/client"
3
+ require "graphql/client/http"
4
+ require "byebug"
5
+ require_relative "./models/code_change.rb"
6
+ require_relative "./models/gitlab_code_change_activity.rb"
7
+
8
+ class StoredValues
9
+ def self.base_api_url
10
+ @base_api_url ||= Rubiclifier::DB.get_setting("base_api_url")
11
+ end
12
+
13
+ def self.username
14
+ @username ||= Rubiclifier::DB.get_setting("username")
15
+ end
16
+
17
+ def self.team_name
18
+ @team_name ||= Rubiclifier::DB.get_setting("team_name")
19
+ end
20
+
21
+ def self.api_token
22
+ @password ||= Rubiclifier::DB.get_setting("api_token")
23
+ end
24
+ end
25
+
26
+ class GitlabApi < Rubiclifier::BaseApi
27
+ Rubiclifier::DB.hydrate("~/.code_review_notifier", "/Users/kyle.grinstead@divvypay.com/Developer/code_review_notifier/migrations.rb")
28
+
29
+ HTTP = GraphQL::Client::HTTP.new(StoredValues.base_api_url) do
30
+ def headers(context)
31
+ { "Authorization": "Bearer #{StoredValues.api_token}" }
32
+ end
33
+ end
34
+
35
+ Schema = GraphQL::Client.load_schema(HTTP)
36
+
37
+ Client = GraphQL::Client.new(schema: Schema, execute: HTTP)
38
+
39
+ OpenMrCommentQuery = Client.parse <<-"GRAPHQL"
40
+ query {
41
+ group(fullPath: "#{StoredValues.team_name}") {
42
+ groupMembers {
43
+ nodes {
44
+ user {
45
+ name
46
+ username
47
+ openMRs: assignedMergeRequests(state: opened) {
48
+ nodes {
49
+ createdAt
50
+ headPipeline {
51
+ updatedAt
52
+ status
53
+ }
54
+ participants {
55
+ nodes {
56
+ username
57
+ }
58
+ }
59
+ approved
60
+ title
61
+ project {
62
+ group {
63
+ path
64
+ }
65
+ name
66
+ }
67
+ id
68
+ webUrl
69
+ discussions {
70
+ nodes {
71
+ notes {
72
+ nodes {
73
+ body
74
+ system
75
+ createdAt
76
+ id
77
+ author {
78
+ username
79
+ name
80
+ }
81
+ }
82
+ }
83
+ }
84
+ }
85
+ }
86
+ }
87
+ }
88
+ }
89
+ }
90
+ }
91
+ }
92
+ GRAPHQL
93
+
94
+ MergedMrCommentQuery = Client.parse <<-"GRAPHQL"
95
+ query($mergedAfter: Time) {
96
+ group(fullPath: "#{StoredValues.team_name}") {
97
+ groupMembers {
98
+ nodes {
99
+ user {
100
+ name
101
+ username
102
+ mergedMRs: assignedMergeRequests(state: merged, mergedAfter: $mergedAfter) {
103
+ nodes {
104
+ createdAt
105
+ headPipeline {
106
+ updatedAt
107
+ status
108
+ }
109
+ participants {
110
+ nodes {
111
+ username
112
+ }
113
+ }
114
+ approved
115
+ title
116
+ project {
117
+ group {
118
+ path
119
+ }
120
+ name
121
+ }
122
+ id
123
+ webUrl
124
+ }
125
+ }
126
+ }
127
+ }
128
+ }
129
+ }
130
+ }
131
+ GRAPHQL
132
+
133
+ def self.invalid_credentials_error
134
+ Rubiclifier::Notification.new(
135
+ "Incorrect Credentials",
136
+ "Trying running `code_review_notifier --setup` again."
137
+ ).send
138
+ sleep(120)
139
+ exit
140
+ end
141
+
142
+ def self.all_code_changes
143
+ open_mr_query = Client.query(OpenMrCommentQuery)
144
+ open_mrs = open_mr_query.data.group.group_members.nodes.flat_map { |n| n.user.open_m_rs.nodes.map { |n2| {user: n.user.name, username: n.user.username, mr: n2 } } }
145
+ merged_mr_query = Client.query(MergedMrCommentQuery, variables: {mergedAfter: Time.now - 61})
146
+ merged_mrs = merged_mr_query.data.group.group_members.nodes.flat_map { |n| n.user.merged_m_rs.nodes.map { |n2| {user: n.user.name, username: n.user.username, mr: n2 } } }
147
+ merged_mrs = merged_mrs.map { |mr| code_change_from_graphql(mr[:mr], mr[:user], mr[:username], true) }
148
+ open_mrs = open_mrs.map { |mr| code_change_from_graphql(mr[:mr], mr[:user], mr[:username], false) }
149
+ open_mrs.concat(merged_mrs)
150
+ end
151
+
152
+ def self.code_change_from_graphql(data, name, username, is_merged)
153
+ includes_self = data.participants.nodes.map { |p| p.username }.include?(StoredValues.username)
154
+ code_change = CodeChange.new(data.id, name, "#{data.project.group.path}/#{data.project.name}", data.title, data.web_url, data.head_pipeline&.updated_at && Time.parse(data.head_pipeline&.updated_at), data.head_pipeline&.status, data.approved, is_merged, username == StoredValues.username, includes_self)
155
+ if is_merged
156
+ code_change.code_change_activity = [
157
+ GitlabCodeChangeActivity.new("#{data.id}-merged-placeholder", name, true, "placeholder", Time.now() - 10000, code_change),
158
+ GitlabCodeChangeActivity.new("#{data.id}-merged", name, false, "merged", Time.now(), code_change)
159
+ ]
160
+ else
161
+ messages = data.discussions.nodes.flat_map { |n| n.notes.nodes }
162
+ code_change.code_change_activity = messages.map { |m| code_change_activity_from_json(code_change, m) }
163
+ code_change.generate_additional_activity
164
+ end
165
+ code_change.persist
166
+ code_change
167
+ end
168
+
169
+ def self.code_change_activity_from_json(code_change, message)
170
+ GitlabCodeChangeActivity.new(message.id, message.author.name, message.author.username == StoredValues.username, message.body, Time.parse(message.created_at), code_change)
171
+ end
172
+
173
+ def self.favicon
174
+ "https://about.gitlab.com/images/press/logo/png/gitlab-icon-rgb.png"
175
+ end
176
+
177
+ def self.code_change_url(code_change)
178
+ code_change.web_url
179
+ end
180
+ end
@@ -2,17 +2,51 @@ require "date"
2
2
 
3
3
  class CodeChange
4
4
  attr_accessor :code_change_activity
5
- attr_reader :id, :owner, :project, :subject, :updated_at
5
+ attr_reader :id, :owner, :project, :subject, :pipeline_updated_at, :last_pipeline_updated_at, :is_self, :includes_self,
6
+ :pipeline_status, :last_pipeline_status, :last_approval_status, :approved, :web_url, :is_merged
6
7
 
7
- def initialize(id, owner, project, subject, updated_at)
8
+ def initialize(id, owner, project, subject, web_url = nil, pipeline_updated_at = nil, pipeline_status = nil, approved = nil, is_merged = nil, is_self = nil, includes_self = nil)
8
9
  @id = id
9
10
  @owner = owner
11
+ @is_merged = is_merged
10
12
  @project = project
11
13
  @subject = subject.gsub("'", "’")
12
- @updated_at = updated_at
14
+ @web_url = web_url
15
+ @pipeline_updated_at = pipeline_updated_at
16
+ @pipeline_status = pipeline_status
17
+ @approved = approved
18
+ row = Rubiclifier::DB.query_single_row("SELECT pipeline_updated_at, last_approval_status, last_pipeline_status FROM code_change WHERE id = '#{id}'") || [Time.now().to_s, approved, pipeline_status]
19
+ @last_approval_status = row[1] == 1
20
+ @last_pipeline_status = row[2]
21
+ @last_pipeline_updated_at = row[0] == "" ? Time.now() : Time.parse(row[0])
22
+ @is_self = is_self
23
+ @includes_self = includes_self
13
24
  end
14
25
 
15
26
  def activity_from_self_at
16
27
  @activity_from_self_at ||= code_change_activity.find { |a| a.is_self }&.created_at
17
28
  end
29
+
30
+ def generate_additional_activity
31
+ if is_self
32
+ @code_change_activity.push(GitlabCodeChangeActivity.new("#{id}-placeholder", owner, true, "placeholder", Time.now() - 100000, self))
33
+ end
34
+ if !is_merged
35
+ @code_change_activity.push(GitlabCodeChangeActivity.new("#{id}-opened", owner, is_self, "just opened merge request", Time.now(), self))
36
+ end
37
+ if pipeline_updated_at && pipeline_updated_at > last_pipeline_updated_at && pipeline_status != last_pipeline_status
38
+ @code_change_activity.push(GitlabCodeChangeActivity.new(nil, "Gitlab", false, "pipeline status: #{pipeline_status}", Time.now(), self))
39
+ end
40
+ if approved != last_approval_status
41
+ @code_change_activity.push(GitlabCodeChangeActivity.new(nil, "Gitlab", false, approved ? "fully approved" : "needs approvals again", Time.now(), self))
42
+ end
43
+ end
44
+
45
+ def persist
46
+ if Rubiclifier::DB.query_single_row("SELECT id FROM code_change WHERE id = '#{id}'")
47
+ Rubiclifier::DB.execute("UPDATE code_change SET pipeline_updated_at = '#{pipeline_updated_at.to_s}', last_pipeline_status = '#{pipeline_status}', last_approval_status = #{approved} WHERE id = '#{id}';")
48
+ else
49
+ Rubiclifier::DB.execute("INSERT INTO code_change (id, pipeline_updated_at, last_pipeline_status, last_approval_status) VALUES('#{id}', '#{pipeline_updated_at.to_s}', '#{pipeline_status}', #{approved});")
50
+ end
51
+ end
18
52
  end
@@ -1,49 +1,23 @@
1
1
  require "rubiclifier"
2
-
3
- MESSAGES_TO_IGNORE = [/Uploaded patch set 1/, /Build Started/, /owns \d+% of/]
4
- AUTHOR_TRANSLATIONS = {
5
- "Service Cloud Jenkins" => "Jenkins",
6
- /\w+ Gergich \(Bot\)/ => "Gergich"
7
- }
2
+ require "byebug"
8
3
 
9
4
  class CodeChangeActivity
10
5
  attr_reader :id, :author, :is_self, :message, :created_at, :code_change
11
6
 
12
- def initialize(id, author, is_self, message, created_at, code_change)
13
- @id = id
14
- @author = CodeChangeActivity.translate_author(author)
15
- @is_self = is_self
16
- @message = CodeChangeActivity.translate_message(message)
17
- @created_at = created_at
18
- @code_change = code_change
19
- end
20
-
21
7
  def notified
22
- Rubiclifier::DB.execute("INSERT INTO code_change_activity_notified (id, notified_at) VALUES('#{id}', CURRENT_TIMESTAMP);")
8
+ if id
9
+ Rubiclifier::DB.execute("INSERT INTO code_change_activity_notified (id, notified_at) VALUES('#{id}', CURRENT_TIMESTAMP);")
10
+ end
23
11
  end
24
12
 
25
13
  def should_notify?
26
14
  !is_self &&
27
- code_change.activity_from_self_at && created_at > code_change.activity_from_self_at &&
28
- MESSAGES_TO_IGNORE.none? { |m| message.match(m) } &&
15
+ (code_change.activity_from_self_at && created_at > code_change.activity_from_self_at && code_change.includes_self || message == "just opened merge request") &&
16
+ messages_to_ignore.none? { |m| message.match(m) } &&
29
17
  !Rubiclifier::DB.query_single_row("SELECT id FROM code_change_activity_notified WHERE id = '#{id}'")
30
18
  end
31
19
 
32
- def self.translate_author(author)
33
- AUTHOR_TRANSLATIONS.keys.each do |pattern|
34
- author.sub!(pattern, AUTHOR_TRANSLATIONS[pattern])
35
- end
36
- author
37
- end
38
-
39
- def self.translate_message(message)
40
- message.sub(/^Patch Set \d+:\s+/, "")
41
- .gsub("'", %q(\\\\\\\\'))
42
- .gsub("\n", " ")
43
- .gsub(" ", " ")
44
- .gsub(">", "")
45
- .sub(/^\(/, "\\(")
46
- .sub(/^\[/, "\\[")
47
- .sub(/^-/, "\\-")
20
+ def messages_to_ignore
21
+ raise NotImplementedError
48
22
  end
49
23
  end
@@ -0,0 +1,40 @@
1
+ require "rubiclifier"
2
+ require_relative "./code_change_activity.rb"
3
+
4
+ AUTHOR_TRANSLATIONS = {
5
+ "Service Cloud Jenkins" => "Jenkins",
6
+ /\w+ Gergich \(Bot\)/ => "Gergich"
7
+ }
8
+
9
+ class GerritCodeChangeActivity < CodeChangeActivity
10
+ def initialize(id, author, is_self, message, created_at, code_change)
11
+ @id = id
12
+ @author = CodeChangeActivity.translate_author(author)
13
+ @is_self = is_self
14
+ @message = CodeChangeActivity.translate_message(message)
15
+ @created_at = created_at
16
+ @code_change = code_change
17
+ end
18
+
19
+ def messages_to_ignore
20
+ [/Uploaded patch set 1/, /Build Started/, /owns \d+% of/]
21
+ end
22
+
23
+ def self.translate_author(author)
24
+ AUTHOR_TRANSLATIONS.keys.each do |pattern|
25
+ author.sub!(pattern, AUTHOR_TRANSLATIONS[pattern])
26
+ end
27
+ author
28
+ end
29
+
30
+ def self.translate_message(message)
31
+ message.sub(/^Patch Set \d+:\s+/, "")
32
+ .gsub("'", %q(\\\\\\\\'))
33
+ .gsub("\n", " ")
34
+ .gsub(" ", " ")
35
+ .gsub(">", "")
36
+ .sub(/^\(/, "\\(")
37
+ .sub(/^\[/, "\\[")
38
+ .sub(/^-/, "\\-")
39
+ end
40
+ end
@@ -0,0 +1,28 @@
1
+ require "rubiclifier"
2
+ require_relative "./code_change_activity.rb"
3
+
4
+ class GitlabCodeChangeActivity < CodeChangeActivity
5
+ def initialize(id, author, is_self, message, created_at, code_change)
6
+ @id = id
7
+ @author = author
8
+ @is_self = is_self
9
+ @message = GitlabCodeChangeActivity.translate_message(message)
10
+ @created_at = created_at
11
+ @code_change = code_change
12
+ end
13
+
14
+ def messages_to_ignore
15
+ [/^marked the task .* as (completed|incomplete)$/, /^changed the description$/, /^resolved all threads$/]
16
+ end
17
+
18
+ def self.translate_message(message)
19
+ message.sub(/^(added \d+ commits?).*/m, '\1')
20
+ .gsub("'", "’")
21
+ .gsub("\n", " ")
22
+ .gsub(" ", " ")
23
+ .gsub(">", "")
24
+ .sub(/^\(/, "\\(")
25
+ .sub(/^\[/, "\\[")
26
+ .sub(/^-/, "\\-")
27
+ end
28
+ end
data/migrations.rb CHANGED
@@ -5,4 +5,12 @@ migrations << <<SQL
5
5
  notified_at TIMESTAMP
6
6
  );
7
7
  SQL
8
+ migrations << <<SQL
9
+ CREATE TABLE IF NOT EXISTS code_change (
10
+ id VARCHAR(50) PRIMARY KEY,
11
+ pipeline_updated_at TIMESTAMP,
12
+ last_pipeline_status VARCHAR(25),
13
+ last_approval_status BOOLEAN
14
+ );
15
+ SQL
8
16
  migrations
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: code_review_notifier
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.5
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kyle Grinstead
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-07-30 00:00:00.000000000 Z
11
+ date: 2021-06-28 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email: kyleag@hey.com
@@ -23,8 +23,11 @@ files:
23
23
  - lib/code_change_notification.rb
24
24
  - lib/code_review_notifier.rb
25
25
  - lib/gerrit_api.rb
26
+ - lib/gitlab_api.rb
26
27
  - lib/models/code_change.rb
27
28
  - lib/models/code_change_activity.rb
29
+ - lib/models/gerrit_code_change_activity.rb
30
+ - lib/models/gitlab_code_change_activity.rb
28
31
  - migrations.rb
29
32
  homepage: https://rubygems.org/gems/code_review_notifier
30
33
  licenses:
@@ -47,8 +50,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
47
50
  - !ruby/object:Gem::Version
48
51
  version: '0'
49
52
  requirements: []
50
- rubyforge_project:
51
- rubygems_version: 2.7.6.2
53
+ rubygems_version: 3.1.4
52
54
  signing_key:
53
55
  specification_version: 4
54
56
  summary: Get notifications when updates happen to patch sets/pull requests!