code_review_notifier 0.3.5 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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!