whatsup_github 0.1.1 → 0.4.1

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.
data/Rakefile CHANGED
@@ -1,14 +1,16 @@
1
- require "bundler/gem_tasks"
2
- require "rspec/core/rake_task"
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
3
5
  require 'cucumber'
4
6
  require 'cucumber/rake/task'
5
7
 
6
8
  RSpec::Core::RakeTask.new(:spec)
7
9
 
8
- task :default => :spec
10
+ task default: :spec
9
11
 
10
12
  Cucumber::Rake::Task.new(:features) do |t|
11
- t.cucumber_opts = "--format pretty" # Any valid command line option can go here.
13
+ t.cucumber_opts = '--format pretty' # Any valid command line option can go here.
12
14
  end
13
15
 
14
- task :test => [:spec, :features]
16
+ task test: %i[spec features]
data/bin/console CHANGED
@@ -1,13 +1,14 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
- require "bundler/setup"
4
- require "whatsup_github"
4
+ require 'bundler/setup'
5
+ require 'whatsup_github'
5
6
 
6
7
  # You can add fixtures and/or initialization code here to make experimenting
7
8
  # with your gem easier. You can also use a different console, if you like.
8
9
 
9
10
  # (If you use this, don't forget to add pry to your Gemfile!)
10
- require "pry"
11
+ require 'pry'
11
12
  Pry.start
12
13
 
13
14
  # require "irb"
data/exe/whatsup_github CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  require 'whatsup_github/cli'
4
5
  WhatsupGithub::CLI.start(ARGV)
@@ -1,16 +1,28 @@
1
1
  # Parameters for a GitHub search query
2
2
  base_branch: master
3
3
 
4
+ # The list of repositories to scan for pull requests
4
5
  repos:
5
6
  - magento/devdocs
7
+ - magento-commerce/devdocs
6
8
 
7
9
  # Labels also will be used as a 'type' value in the output file
8
10
  labels:
9
- - New Topic
10
- - Major Update
11
- - Technical
11
+ required:
12
+ - New Topic
13
+ - Major Update
14
+ optional:
15
+ - Technical
12
16
 
13
17
  # Format of output file
14
18
  output_format:
15
19
  - yaml
16
20
  # - markdown
21
+
22
+ # The phrase that is used as a separator in the pull request descripion.
23
+ # All the lines that follows this phrase are captured as 'description' for this PR's entry in the resulted data file.
24
+ magic_word: whatsnew
25
+
26
+ # An organization to check a contributor for membership.
27
+ # Values: 'true', 'false', empry if not configured.
28
+ membership: magento-commerce
@@ -1,6 +1,6 @@
1
- require "whatsup_github/version"
1
+ # frozen_string_literal: true
2
2
 
3
+ require 'whatsup_github/version'
3
4
 
4
5
  module WhatsupGithub
5
-
6
6
  end
@@ -1,14 +1,15 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'thor'
2
- require "whatsup_github/runner"
4
+ require 'whatsup_github/runner'
3
5
  module WhatsupGithub
4
6
  class CLI < Thor
5
- desc "since DATE", "Filters pull requests since the specified date till now."
7
+ desc 'since DATE', 'Filters pull requests since the specified date till now.'
6
8
 
7
9
  def since(date = Date.today - 7)
8
-
9
10
  runner = WhatsupGithub::Runner.new(Date.parse(date.to_s))
10
11
  runner.run
11
12
  end
12
13
  default_task :since
13
14
  end
14
- end
15
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+ require 'singleton'
5
+
6
+ module WhatsupGithub
7
+ # Creates readable objects from confirurarion files
8
+ class Config
9
+ attr_reader :config
10
+
11
+ include Singleton
12
+
13
+ def initialize
14
+ @file = '.whatsup.yml'
15
+ @config = {}
16
+ end
17
+
18
+ def read
19
+ unless File.exist?(@file)
20
+ dist_file = File.expand_path("../template/#{@file}", __dir__)
21
+ FileUtils.cp dist_file, @file
22
+ end
23
+ @config = YAML.load_file @file
24
+ return {} unless @config
25
+
26
+ @config
27
+ end
28
+
29
+ def repos
30
+ read['repos']
31
+ end
32
+
33
+ def base_branch
34
+ read['base_branch']
35
+ end
36
+
37
+ def output_format
38
+ read['output_format']
39
+ end
40
+
41
+ def labels
42
+ required_labels + optional_labels
43
+ end
44
+
45
+ def required_labels
46
+ res = read.dig 'labels', 'required'
47
+ return [] unless res
48
+
49
+ res
50
+ end
51
+
52
+ def optional_labels
53
+ res = read.dig 'labels', 'optional'
54
+ return [] unless res
55
+
56
+ res
57
+ end
58
+
59
+ def membership
60
+ read['membership']
61
+ end
62
+
63
+ def magic_word
64
+ read['magic_word']
65
+ end
66
+ end
67
+ end
68
+
69
+ if $PROGRAM_NAME == __FILE__
70
+ config = WhatsupGithub::Config.instance
71
+ p config.repos
72
+ p config.base_branch
73
+ p config.output_format
74
+ p config.labels
75
+ p config.required_labels
76
+ p config.optional_labels
77
+ p config.magic_word
78
+ p config.membership
79
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module WhatsupGithub
2
4
  # Creates the final table
3
5
  class Generator
@@ -9,7 +11,7 @@ module WhatsupGithub
9
11
  @collector.sort_by_date
10
12
  end
11
13
 
12
- def run formatter, content
14
+ def run(formatter, content)
13
15
  formatter.generate_output_from content
14
16
  end
15
17
  end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WhatsupGithub
4
+ # Members of an organization
5
+ class Members
6
+ def initialize(org)
7
+ @org = org
8
+ end
9
+
10
+ def client
11
+ Octokit::Client.new(netrc: true)
12
+ end
13
+
14
+ def members
15
+ client.org_members @org
16
+ end
17
+ end
18
+ end
@@ -1,8 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'octokit'
2
- require_relative 'config-reader'
4
+ require_relative 'config_reader'
3
5
 
4
6
  module WhatsupGithub
5
- # Gets pull filtered pull requests from GitHub
7
+ # Gets issues found on GitHub by query
6
8
  class Pulls
7
9
  attr_reader :since, :repo
8
10
 
@@ -11,43 +13,94 @@ module WhatsupGithub
11
13
  @since = args[:since]
12
14
  end
13
15
 
14
- def filtered
15
- issues = []
16
- labels.each do |label|
17
- issues += search_issues(label).items
16
+ def data
17
+ pull_requests = []
18
+ filtered_numbers.each do |number|
19
+ pull_requests << client.pull_request(@repo, number)
18
20
  end
19
- issues
21
+ pull_requests
20
22
  end
21
23
 
22
24
  private
23
25
 
24
26
  # def access_token
25
- # credentials.read 'github_token'
27
+ # credentials.dig 'github_token'
26
28
  # end
27
29
 
28
30
  def configuration
29
31
  Config.instance
30
32
  end
31
33
 
32
- def labels
33
- configuration.read 'labels'
34
+ def optional_labels
35
+ configuration.optional_labels
36
+ end
37
+
38
+ def required_labels
39
+ configuration.required_labels
40
+ end
41
+
42
+ def magic_word
43
+ configuration.magic_word
34
44
  end
35
45
 
36
46
  def base_branch
37
- configuration.read 'base_branch'
47
+ configuration.base_branch
38
48
  end
39
49
 
50
+ # Authorize with a GitHub token from $WHATSUP_GITHUB_ACCESS_TOKEN if available
51
+ # Otherwise, use credentials from ~/.netrc
52
+ # Otherwise, continue as a Guest
40
53
  def client
41
- Octokit::Client.new(:netrc => true)
54
+ return @client if @client
55
+
56
+ @client =
57
+ if ENV['WHATSUP_GITHUB_ACCESS_TOKEN']
58
+ Octokit::Client.new(access_token: ENV['WHATSUP_GITHUB_ACCESS_TOKEN'])
59
+ elsif File.exist? "#{ENV['HOME']}/.netrc"
60
+ Octokit::Client.new(netrc: true)
61
+ else
62
+ Octokit::Client.new
63
+ end
42
64
  end
43
65
 
44
66
  def search_issues(label)
45
67
  auto_paginate
46
- client.search_issues("repo:#{repo} label:\"#{label}\" merged:>=#{since} base:#{base_branch}")
68
+ query = "repo:#{repo} label:\"#{label}\" merged:>=#{since} base:#{base_branch}"
69
+ puts "Searching on GitHub by query #{query}"
70
+ client.search_issues(query)
71
+ end
72
+
73
+ def search_issues_with_magic_word(label)
74
+ auto_paginate
75
+ query = "repo:#{repo} label:\"#{label}\" merged:>=#{since} base:#{base_branch} \"#{magic_word}\" in:body"
76
+ puts "Searching on GitHub by query #{query}"
77
+ client.search_issues(query)
47
78
  end
48
79
 
49
80
  def auto_paginate
50
81
  Octokit.auto_paginate = true
51
82
  end
83
+
84
+ def filtered_issues
85
+ issues = []
86
+ required_labels.each do |label|
87
+ issues += search_issues(label).items
88
+ end
89
+ optional_labels.each do |label|
90
+ issues += search_issues_with_magic_word(label).items
91
+ end
92
+ issues
93
+ end
94
+
95
+ def filtered_numbers
96
+ filtered_issues.map(&:number)
97
+ end
52
98
  end
53
99
  end
100
+
101
+ if $PROGRAM_NAME == __FILE__
102
+ require 'date'
103
+ two_weeks_ago = (Date.today - 14).to_s
104
+ pulls = WhatsupGithub::Pulls.new(repo: 'magento/devdocs', since: two_weeks_ago)
105
+ p pulls.data
106
+ end
@@ -1,21 +1,48 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module WhatsupGithub
2
4
  # Row to be converted to entry in future table
3
5
  class Row
4
- attr_reader :body, :title, :labels, :pr_number, :assignee, :link
6
+ attr_reader :body,
7
+ :title,
8
+ :labels,
9
+ :link,
10
+ :pr_number,
11
+ :assignee,
12
+ :author,
13
+ :author_url,
14
+ :merge_commit,
15
+ :is_private,
16
+ :membership
17
+
5
18
  def initialize(args)
6
19
  @repo = args[:repo]
20
+ @repo_url = args[:repo_url]
21
+ @is_private = args[:private]
7
22
  @title = args[:pr_title]
8
23
  @body = args[:pr_body]
9
24
  @date = args[:date]
10
25
  @labels = args[:pr_labels]
11
26
  @assignee = args[:assignee]
27
+ @author = args[:author]
28
+ @author_url = args[:author_url]
12
29
  @pr_number = args[:pr_number]
13
30
  @link = args[:pr_url]
31
+ @merge_commit = args[:merge_commit_sha]
32
+ @membership = args[:membership]
14
33
  @config = Config.instance
15
34
  end
16
35
 
17
36
  def labels_from_config
18
- @config.read('labels')
37
+ @config.labels
38
+ end
39
+
40
+ def required_labels
41
+ @config.required_labels
42
+ end
43
+
44
+ def magic_word
45
+ @config.magic_word
19
46
  end
20
47
 
21
48
  def versions
@@ -32,8 +59,10 @@ module WhatsupGithub
32
59
  end
33
60
 
34
61
  def parse_body
35
- whatsnew_splited = body.split('whatsnew')[-1]
36
- newline_splited = whatsnew_splited.split("\n")
62
+ # Split PR description in two parts by 'magic word', and take the second half
63
+ description_splited = body.split(magic_word)[-1]
64
+ # Convert new line separators to <br> tags
65
+ newline_splited = description_splited.split("\n")
37
66
  cleaned_array = newline_splited.map { |e| e.delete "\r\*" }
38
67
  cleaned_array.delete('')
39
68
  striped_array = cleaned_array.map(&:strip)
@@ -41,10 +70,12 @@ module WhatsupGithub
41
70
  end
42
71
 
43
72
  def description
44
- if body.include?('whatsnew')
73
+ # If a PR body includes a phrase 'whatsnew', then parse the body.
74
+ # If there are at least one required label but PR body does not include what's new, warn about missing 'whatsnew'
75
+ if body.include?(magic_word)
45
76
  parse_body
46
77
  else
47
- message = "MISSING whatsnew in the #{type} PR \##{pr_number}: \"#{title}\" assigned to #{assignee} (#{link})"
78
+ message = "MISSING #{magic_word} in the #{type} PR \##{pr_number}: \"#{title}\" assigned to #{assignee} (#{link})"
48
79
  puts message
49
80
  message
50
81
  end
@@ -1,6 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'row'
2
4
  require_relative 'pulls'
3
- require_relative 'config-reader'
5
+ require_relative 'config_reader'
6
+ require_relative 'members'
4
7
 
5
8
  module WhatsupGithub
6
9
  # Creates Row objects for the future table
@@ -8,7 +11,7 @@ module WhatsupGithub
8
11
  attr_reader :repos, :since
9
12
 
10
13
  def initialize(args = {})
11
- @repos = config.read('repos')
14
+ @repos = config.repos
12
15
  @since = args[:since]
13
16
  end
14
17
 
@@ -24,12 +27,19 @@ module WhatsupGithub
24
27
  pulls(repo).map do |pull|
25
28
  Row.new(
26
29
  repo: repo,
30
+ repo_url: pull.base.repo.html_url,
31
+ private: pull.base.repo.private?,
27
32
  pr_number: pull.number,
28
33
  pr_title: pull.title,
29
34
  pr_body: pull.body,
30
- date: pull.closed_at,
35
+ date: pull.merged_at,
31
36
  pr_labels: label_names(pull.labels),
32
- assignee: assignee(pull.assignee),
37
+ assignee: assignee(pull.assignees),
38
+ membership: member?(pull.user.login),
39
+ merger: pull.merged_by.login,
40
+ merge_commit_sha: pull.merge_commit_sha,
41
+ author: pull.user.login,
42
+ author_url: pull.user.html_url,
33
43
  pr_url: pull.html_url
34
44
  )
35
45
  end
@@ -47,20 +57,37 @@ module WhatsupGithub
47
57
 
48
58
  private
49
59
 
50
- def assignee(assignee)
51
- if assignee.nil?
60
+ def assignee(assignees)
61
+ if assignees.empty?
52
62
  'NOBODY'
53
63
  else
54
- assignee.login
64
+ assignees.map(&:login).join(', ')
55
65
  end
56
66
  end
57
67
 
68
+ def member?(login)
69
+ return nil unless config.membership
70
+
71
+ member_logins.include? login
72
+ end
73
+
58
74
  def label_names(labels)
59
75
  labels.map(&:name)
60
76
  end
61
77
 
62
78
  def pulls(repo)
63
- Pulls.new(repo: repo, since: since).filtered
79
+ Pulls.new(repo: repo, since: since).data
80
+ end
81
+
82
+ def load_members
83
+ return if @members
84
+
85
+ @members = Members.new(config.membership).members
86
+ end
87
+
88
+ def member_logins
89
+ load_members
90
+ @members.map(&:login)
64
91
  end
65
92
 
66
93
  def config