code_review_notifier 0.1.3 → 0.2.4

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: d6de431ed11fa0855c6ed44e294a4b8a76bd69ad845c1ea9bfacdd9a326e0b66
4
- data.tar.gz: 0b084e6c55eda7ee644232ad770ad2bde38b43f12c1bea6f209d2ebce816b6dd
3
+ metadata.gz: fe98a79405c30d5a0ab08d8b8c3f0693cd1605fc81c413ddcfac136e52c54105
4
+ data.tar.gz: 0a31a14ea309f530e8927962f109e8660c6d63a1bad7da504ca46091b01fba92
5
5
  SHA512:
6
- metadata.gz: 28eae7ef98c923678e89c54d3a23c73dd4ff649ce55fcb7ee9f51a80ea0e74e331aae31f878c432fdb6657e27f275afffce16001ffd5bdeb341f095235207f0c
7
- data.tar.gz: b586a847bca12814d570e2f73ace4d5860cf144e0024ea503994cdc5846e489f88416f3b0847731a6feb6f40cc90714fa96aca2afb6f3c1ac17782ac44f9e175
6
+ metadata.gz: 89a05dabf9bb56d0e43cd6295ee64cfd01b114416b420b84cb8147876d047f66533d490820fecda9dea2858877a56ca61b1cc66f053e9b96f07aea952e257c71
7
+ data.tar.gz: 9a2af8b575e1a47781820937fd47b8ee6bc7b2c4dbe5356a470aeb721a60eaa762961ab7b9d36bf53e7cd3c2063661bbca7bb4fc4f2c8088de0b7b11ecb424b2
data/Gemfile CHANGED
@@ -1,6 +1,5 @@
1
- source 'https://rubygems.org'
1
+ source "https://rubygems.org"
2
2
 
3
3
  gemspec
4
4
 
5
- gem "sqlite3"
6
- gem "httparty"
5
+ gem "rubiclifier", "1.0.0"
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require 'code_review_notifier'
3
+ require_relative "../lib/code_review_notifier.rb"
4
4
 
5
- CodeReviewNotifier.call(ARGV)
5
+ CodeReviewNotifier.new(ARGV).call
@@ -0,0 +1,28 @@
1
+ require "rubiclifier"
2
+ require_relative "./api.rb"
3
+
4
+ class CodeChangeNotification
5
+ attr_reader :code_change_activity
6
+ private :code_change_activity
7
+
8
+ def initialize(code_change_activity)
9
+ @code_change_activity = code_change_activity
10
+ end
11
+
12
+ def send
13
+ code_change = code_change_activity.code_change
14
+ id = code_change.id
15
+ owner = code_change.owner
16
+ subject = code_change.subject
17
+
18
+ message = code_change_activity.message
19
+ author = code_change_activity.author
20
+ Rubiclifier::Notification.new(
21
+ author,
22
+ message,
23
+ "#{owner}: #{subject}",
24
+ Api.current_api.favicon,
25
+ Api.current_api.code_change_url(code_change)
26
+ ).send
27
+ end
28
+ end
@@ -1,48 +1,24 @@
1
- require "io/console"
1
+ require "rubiclifier"
2
2
  require_relative "./api.rb"
3
- require_relative "./notifier.rb"
3
+ require_relative "./code_change_notification.rb"
4
4
 
5
5
  SECONDS_BETWEEN_RUNS = 90
6
6
  SECONDS_BETWEEN_NOTIFICATIONS = 5
7
7
 
8
- class CodeReviewNotifier
9
- def self.setup(args)
10
- system("mkdir -p ~/.code_review_notifier")
11
- system("brew bundle --file #{File.expand_path(File.dirname(__FILE__) + "/..")}/Brewfile")
12
-
13
- if args[0] == "--setup"
14
- print("What's the base URL? (i.e. https://gerrit.google.com) ")
15
- DB.save_setting("base_api_url", STDIN.gets.chomp, is_secret: false)
16
-
17
- print("What's the account username? ")
18
- DB.save_setting("username", STDIN.gets.chomp, is_secret: false)
19
-
20
- print("What's the account password? (hiding input) ")
21
- DB.save_setting("password", STDIN.noecho(&:gets).chomp, is_secret: true)
22
- puts
23
-
24
- print("What's the account id? (check #{Api.current_api.base_api_url}/settings/) ")
25
- DB.save_setting("account_id", STDIN.gets.chomp, is_secret: false)
26
-
27
- puts("All setup!")
28
- puts
29
- puts("It's recommended that you set this up as a system service with serviceman. Check it out here: https://git.rootprojects.org/root/serviceman")
30
- puts("Set code_review_notifier to run on startup with `serviceman add --name code_review_notifier code_review_notifier`")
31
- exit
32
- end
33
-
34
- if !Api.current_api.is_setup?
35
- Notifier.notify("Missing Setup Info", "Run `code_review_notifier --setup` to setup.")
36
- puts
37
- puts("You must finish setup first by running with the `--setup` option.")
38
- puts("`code_review_notifier --setup`")
39
- exit
40
- end
8
+ class CodeReviewNotifier < Rubiclifier::BaseApplication
9
+ def show_help
10
+ puts
11
+ puts("This polls for updates to patch sets/pull requests and notifies you about any relevant changes.")
12
+ puts
13
+ puts("Usage:")
14
+ puts(" code_review_notifier --help | Shows this help menu")
15
+ puts(" code_review_notifier --setup | Runs setup")
16
+ puts(" code_review_notifier | Start listening for changes (should be run in background)")
17
+ puts
18
+ exit
41
19
  end
42
20
 
43
- def self.call(args)
44
- setup(args)
45
-
21
+ def run_application
46
22
  while true
47
23
  is_first_run = is_first_run?
48
24
  puts
@@ -51,16 +27,14 @@ class CodeReviewNotifier
51
27
  puts("Checking for notifications to display...")
52
28
  all_activity = []
53
29
  all_code_changes.each do |cc|
54
- activity_for_code_change = cc.code_change_activity
55
- activity_for_code_change.sort! { |a, b| a.created_at <=> b.created_at }
56
- cc.activity_from_self_at = activity_for_code_change.find { |a| a.is_self }&.created_at
57
- all_activity.concat(activity_for_code_change)
30
+ cc.code_change_activity.sort! { |a, b| a.created_at <=> b.created_at }
31
+ all_activity.concat(cc.code_change_activity)
58
32
  end
59
33
  all_activity.select(&:should_notify?).each do |code_change_activity|
60
34
  code_change_activity.notified
61
35
  unless is_first_run
62
36
  puts("Notifying of change!")
63
- Notifier.notify_about_code_change(code_change_activity)
37
+ CodeChangeNotification.new(code_change_activity).send
64
38
  sleep(SECONDS_BETWEEN_NOTIFICATIONS)
65
39
  end
66
40
  end
@@ -68,7 +42,43 @@ class CodeReviewNotifier
68
42
  end
69
43
  end
70
44
 
71
- def self.is_first_run?
72
- DB.query_single_row("SELECT id FROM code_change_activity_notified;").nil?
45
+ def not_setup
46
+ Rubiclifier::Notification.new(
47
+ "Missing Setup Info",
48
+ "Run `code_review_notifier --setup` to setup."
49
+ ).send
50
+ end
51
+
52
+ def features
53
+ [
54
+ Rubiclifier::Feature::BACKGROUND,
55
+ Rubiclifier::Feature::DATABASE,
56
+ Rubiclifier::Feature::NOTIFICATIONS
57
+ ]
58
+ end
59
+
60
+ def settings
61
+ @settings ||= [
62
+ Rubiclifier::Setting.new("base_api_url", "base URL", explanation: "e.g. https://gerrit.google.com"),
63
+ Rubiclifier::Setting.new("username", "account username"),
64
+ Rubiclifier::Setting.new("password", "account password", explanation: "input hidden", is_secret: true),
65
+ Rubiclifier::Setting.new("account_id", "account ID", explanation: -> {"check #{Api.current_api.base_api_url}/settings/"})
66
+ ]
67
+ end
68
+
69
+ def executable_name
70
+ "code_review_notifier"
71
+ end
72
+
73
+ def data_directory
74
+ "~/.code_review_notifier"
75
+ end
76
+
77
+ def migrations_location
78
+ "#{File.expand_path(File.dirname(__FILE__) + "/..")}/migrations.rb"
79
+ end
80
+
81
+ def is_first_run?
82
+ Rubiclifier::DB.query_single_row("SELECT id FROM code_change_activity_notified;").nil?
73
83
  end
74
84
  end
@@ -1,9 +1,15 @@
1
- require_relative "./base_api.rb"
1
+ require "rubiclifier"
2
2
  require_relative "./models/code_change.rb"
3
3
  require_relative "./models/code_change_activity.rb"
4
4
 
5
- class GerritApi < BaseApi
6
- def self.authenticate
5
+ class HTTParty::Parser
6
+ def json
7
+ JSON.parse(body.gsub(")]}'", ""))
8
+ end
9
+ end
10
+
11
+ class GerritApi < Rubiclifier::BaseApi
12
+ def self.login_and_get_api_token
7
13
  res = post("/login/", {
8
14
  body: "username=#{username}&password=#{URI.escape(password)}&rememberme=1",
9
15
  headers: {
@@ -12,19 +18,23 @@ class GerritApi < BaseApi
12
18
  follow_redirects: false
13
19
  })
14
20
  set_cookie_header = res.headers["set-cookie"] || ""
15
- @token = set_cookie_header.match("GerritAccount=(.*?);")&.to_a&.fetch(1)
16
- if @token.nil?
17
- Notifier.notify("Incorrect Credentials", "Trying running `code_review_notifier --setup` again.")
18
- sleep(120)
19
- exit
20
- end
21
+ set_cookie_header.match("GerritAccount=(.*?);")&.to_a&.fetch(1)
22
+ end
23
+
24
+ def self.invalid_credentials_error
25
+ Rubiclifier::Notification.new(
26
+ "Incorrect Credentials",
27
+ "Trying running `code_review_notifier --setup` again."
28
+ ).send
29
+ sleep(120)
30
+ exit
21
31
  end
22
32
 
23
33
  def self.all_code_changes
24
34
  wrap_with_authentication do
25
35
  get("/changes/?S=0&q=is%3Aopen%20owner%3Aself%20-is%3Awip%20-is%3Aignored%20limit%3A25&q=is%3Aopen%20owner%3Aself%20is%3Awip%20limit%3A25&q=is%3Aopen%20-owner%3Aself%20-is%3Awip%20-is%3Aignored%20reviewer%3Aself%20limit%3A25&q=is%3Aclosed%20-is%3Aignored%20%28-is%3Awip%20OR%20owner%3Aself%29%20%28owner%3Aself%20OR%20reviewer%3Aself%29%20-age%3A4w%20limit%3A10&o=DETAILED_ACCOUNTS&o=MESSAGES", {
26
36
  headers: {
27
- "Cookie" => "GerritAccount=#{@token};"
37
+ "Cookie" => "GerritAccount=#{api_token};"
28
38
  }
29
39
  })
30
40
  end.parsed_response.flat_map { |js| js.map { |j| code_change_from_json(j) } }
@@ -47,4 +57,24 @@ class GerritApi < BaseApi
47
57
  def self.code_change_url(code_change)
48
58
  "#{base_api_url}/c/#{code_change.id}"
49
59
  end
60
+
61
+ def self.api_token_db_key
62
+ "api_token"
63
+ end
64
+
65
+ def self.base_api_url_db_key
66
+ "base_api_url"
67
+ end
68
+
69
+ def self.username
70
+ @username ||= Rubiclifier::DB.get_setting("username")
71
+ end
72
+
73
+ def self.password
74
+ @password ||= Rubiclifier::DB.get_setting("password")
75
+ end
76
+
77
+ def self.account_id
78
+ @account_id ||= Rubiclifier::DB.get_setting("account_id")
79
+ end
50
80
  end
@@ -1,21 +1,18 @@
1
1
  require "date"
2
- require_relative "../database.rb"
3
2
 
4
3
  class CodeChange
5
4
  attr_accessor :code_change_activity
6
5
  attr_reader :id, :owner, :project, :subject, :updated_at
7
- attr_writer :activity_from_self_at
8
6
 
9
7
  def initialize(id, owner, project, subject, updated_at)
10
8
  @id = id
11
9
  @owner = owner
12
10
  @project = project
13
- @subject = subject.gsub("'", "")
11
+ @subject = subject.gsub("'", "")
14
12
  @updated_at = updated_at
15
13
  end
16
14
 
17
15
  def activity_from_self_at
18
- raise "@activity_from_self_at is not set" unless instance_variable_defined?("@activity_from_self_at")
19
- @activity_from_self_at
16
+ @activity_from_self_at ||= code_change_activity.find { |a| a.is_self }&.created_at
20
17
  end
21
18
  end
@@ -1,3 +1,5 @@
1
+ require "rubiclifier"
2
+
1
3
  MESSAGES_TO_IGNORE = [/Uploaded patch set 1/, /Build Started/, /owns \d+% of/]
2
4
  AUTHOR_TRANSLATIONS = {
3
5
  "Service Cloud Jenkins" => "Jenkins",
@@ -17,14 +19,14 @@ class CodeChangeActivity
17
19
  end
18
20
 
19
21
  def notified
20
- DB.execute("INSERT INTO code_change_activity_notified (id, notified_at) VALUES('#{id}', CURRENT_TIMESTAMP);")
22
+ Rubiclifier::DB.execute("INSERT INTO code_change_activity_notified (id, notified_at) VALUES('#{id}', CURRENT_TIMESTAMP);")
21
23
  end
22
24
 
23
25
  def should_notify?
24
26
  !is_self &&
25
27
  code_change.activity_from_self_at && created_at > code_change.activity_from_self_at &&
26
28
  MESSAGES_TO_IGNORE.none? { |m| message.match(m) } &&
27
- !DB.query_single_row("SELECT id FROM code_change_activity_notified WHERE id = '#{id}'")
29
+ !Rubiclifier::DB.query_single_row("SELECT id FROM code_change_activity_notified WHERE id = '#{id}'")
28
30
  end
29
31
 
30
32
  def self.translate_author(author)
@@ -0,0 +1,8 @@
1
+ migrations = []
2
+ migrations << <<SQL
3
+ CREATE TABLE IF NOT EXISTS code_change_activity_notified (
4
+ id VARCHAR(50) PRIMARY KEY,
5
+ notified_at TIMESTAMP
6
+ );
7
+ SQL
8
+ 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.1.3
4
+ version: 0.2.4
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-08 00:00:00.000000000 Z
11
+ date: 2020-07-10 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email: kyleag@hey.com
@@ -17,24 +17,22 @@ executables:
17
17
  extensions: []
18
18
  extra_rdoc_files: []
19
19
  files:
20
- - Brewfile
21
20
  - Gemfile
22
21
  - bin/code_review_notifier
23
22
  - lib/api.rb
24
- - lib/base_api.rb
25
- - lib/cipher.rb
23
+ - lib/code_change_notification.rb
26
24
  - lib/code_review_notifier.rb
27
- - lib/database.rb
28
25
  - lib/gerrit_api.rb
29
26
  - lib/models/code_change.rb
30
27
  - lib/models/code_change_activity.rb
31
- - lib/notifier.rb
28
+ - migrations.rb
32
29
  homepage: https://rubygems.org/gems/code_review_notifier
33
30
  licenses:
34
31
  - MIT
35
32
  metadata:
36
33
  source_code_uri: https://github.com/MrGrinst/code_review_notifier
37
- post_install_message:
34
+ post_install_message: "\n\e[32mThanks for installing code_review_notifier!\e[0m\n\e[32mSet
35
+ it up by running `\e[0mcode_review_notifier --setup\e[32m`\e[0m\n\n"
38
36
  rdoc_options: []
39
37
  require_paths:
40
38
  - lib
data/Brewfile DELETED
@@ -1,2 +0,0 @@
1
- brew 'sqlite'
2
- brew 'terminal-notifier'
@@ -1,62 +0,0 @@
1
- require "httparty"
2
- require_relative "./gerrit_api.rb"
3
-
4
- class HTTParty::Parser
5
- def json
6
- JSON.parse(body.gsub(")]}'", ""))
7
- end
8
- end
9
-
10
- class BaseApi
11
- include HTTParty
12
-
13
- def self.authenticate
14
- raise NotImplementedError
15
- end
16
-
17
- def self.all_code_changes
18
- raise NotImplementedError
19
- end
20
-
21
- def self.favicon
22
- raise NotImplementedError
23
- end
24
-
25
- def self.code_change_url(code_change)
26
- raise NotImplementedError
27
- end
28
-
29
- def self.wrap_with_authentication(&block)
30
- res = block.call
31
- if res.code == 401 || res.code == 403
32
- self.authenticate
33
- block.call
34
- else
35
- res
36
- end
37
- end
38
-
39
- def self.is_setup?
40
- base_api_url && username && password && account_id
41
- end
42
-
43
- def self.base_api_url
44
- @base_api_url ||= begin
45
- url = DB.get_setting("base_api_url")
46
- base_uri(url)
47
- url.chomp("/")
48
- end
49
- end
50
-
51
- def self.username
52
- @username ||= DB.get_setting("username")
53
- end
54
-
55
- def self.password
56
- @password ||= DB.get_setting("password")
57
- end
58
-
59
- def self.account_id
60
- @account_id ||= DB.get_setting("account_id")
61
- end
62
- end
@@ -1,31 +0,0 @@
1
- require "base64"
2
- require "openssl"
3
- require "digest/sha1"
4
-
5
- KEY = ""
6
-
7
- class Cipher
8
- def self.encrypt(text)
9
- cipher = OpenSSL::Cipher.new("aes-256-cbc")
10
- cipher.encrypt
11
- key = Digest::SHA1.hexdigest("yourpass")[0..31]
12
- iv = cipher.random_iv
13
- cipher.key = key
14
- cipher.iv = iv
15
- encrypted = cipher.update(text)
16
- encrypted << cipher.final
17
- ["#{key}:#{Base64.encode64(iv).encode('utf-8')}", Base64.encode64(encrypted).encode('utf-8')]
18
- end
19
-
20
- def self.decrypt(encrypted, salt)
21
- key, iv = salt.split(":")
22
- iv = Base64.decode64(iv.encode('ascii-8bit'))
23
- cipher = OpenSSL::Cipher.new("aes-256-cbc")
24
- cipher.decrypt
25
- cipher.key = key
26
- cipher.iv = iv
27
- decrypted = cipher.update(Base64.decode64(encrypted.encode('ascii-8bit')))
28
- decrypted << cipher.final
29
- decrypted
30
- end
31
- end
@@ -1,48 +0,0 @@
1
- require "sqlite3"
2
- require_relative "./cipher.rb"
3
-
4
- class DB
5
- def self.db
6
- @db ||= begin
7
- db = SQLite3::Database.new(File.expand_path("~/.code_review_notifier/data.db"))
8
- migrate_if_needed(db)
9
- db
10
- end
11
- end
12
-
13
- def self.migrate_if_needed(db)
14
- db.execute("CREATE TABLE IF NOT EXISTS code_change_activity_notified (id VARCHAR(50) PRIMARY KEY, notified_at TIMESTAMP);")
15
- db.execute("CREATE TABLE IF NOT EXISTS settings (key VARCHAR(50) PRIMARY KEY, value TEXT, salt TEXT);")
16
- end
17
-
18
- def self.execute(sql)
19
- db.execute(sql)
20
- end
21
-
22
- def self.query_single_row(sql)
23
- db.execute(sql) do |row|
24
- return row
25
- end
26
- return nil
27
- end
28
-
29
- def self.save_setting(key, value, is_secret:)
30
- salt = "NULL"
31
- if is_secret
32
- salt, encrypted = Cipher.encrypt(value)
33
- salt = "'#{salt}'"
34
- value = encrypted
35
- end
36
- db.execute("DELETE FROM settings WHERE key = '#{key}';")
37
- db.execute("INSERT INTO settings (key, value, salt) VALUES('#{key}', '#{value}', #{salt});")
38
- end
39
-
40
- def self.get_setting(key)
41
- row = query_single_row("SELECT value, salt FROM settings WHERE key = '#{key}'")
42
- if row && row[1]
43
- Cipher.decrypt(row[0], row[1])
44
- elsif row
45
- row[0]
46
- end
47
- end
48
- end
@@ -1,31 +0,0 @@
1
- require_relative './api.rb'
2
-
3
- class Notifier
4
- def self.notify_about_code_change(code_change_activity)
5
- code_change = code_change_activity.code_change
6
- id = code_change.id
7
- owner = code_change.owner
8
- subject = code_change.subject
9
-
10
- message = code_change_activity.message
11
- author = code_change_activity.author
12
- notify(author, message, "#{owner}: #{subject}", Api.current_api.favicon, Api.current_api.code_change_url(code_change))
13
- end
14
-
15
- def self.notify(title, message, subtitle = nil, icon = nil, url = nil)
16
- args = {
17
- "title" => title,
18
- "message" => message,
19
- "subtitle" => subtitle,
20
- "appIcon" => icon,
21
- "open" => url
22
- }
23
- all_args = args.keys.reduce("") do |arg_string, key|
24
- if args[key]
25
- arg_string += " -#{key} '#{args[key]}'"
26
- end
27
- arg_string
28
- end
29
- system("/usr/local/bin/terminal-notifier #{all_args}")
30
- end
31
- end