whatsup_github 0.0.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -1,6 +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'
5
+ require 'cucumber'
6
+ require 'cucumber/rake/task'
3
7
 
4
8
  RSpec::Core::RakeTask.new(:spec)
5
9
 
6
- task :default => :spec
10
+ task default: :spec
11
+
12
+ Cucumber::Rake::Task.new(:features) do |t|
13
+ t.cucumber_opts = '--format pretty' # Any valid command line option can go here.
14
+ end
15
+
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,18 +1,28 @@
1
- # Name of the base branch in GitHub PRs
1
+ # Parameters for a GitHub search query
2
2
  base_branch: master
3
3
 
4
- # Repositories for scanning. If you want to search private repositories, use credentials to specify
4
+ # The list of repositories to scan for pull requests
5
5
  repos:
6
6
  - magento/devdocs
7
+ - magento-commerce/devdocs
7
8
 
8
- # Labels for filtering (currently, hard-coded)
9
+ # Labels also will be used as a 'type' value in the output file
9
10
  labels:
10
- - New doc
11
- - Major update
12
- - Technical
11
+ required:
12
+ - New Topic
13
+ - Major Update
14
+ optional:
15
+ - Technical
13
16
 
14
- # Output format
17
+ # Format of output file
15
18
  output_format:
16
- # - markdown
17
19
  - yaml
18
-
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,82 @@ 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
 
40
50
  def client
41
- Octokit::Client.new(:netrc => true)
51
+ Octokit::Client.new(netrc: true)
42
52
  end
43
53
 
44
54
  def search_issues(label)
45
55
  auto_paginate
46
- client.search_issues("repo:#{repo} label:\"#{label}\" merged:>=#{since} base:#{base_branch}")
56
+ query = "repo:#{repo} label:\"#{label}\" merged:>=#{since} base:#{base_branch}"
57
+ puts "Searching on GitHub by query #{query}"
58
+ client.search_issues(query)
59
+ end
60
+
61
+ def search_issues_with_magic_word(label)
62
+ auto_paginate
63
+ query = "repo:#{repo} label:\"#{label}\" merged:>=#{since} base:#{base_branch} \"#{magic_word}\" in:body"
64
+ puts "Searching on GitHub by query #{query}"
65
+ client.search_issues(query)
47
66
  end
48
67
 
49
68
  def auto_paginate
50
69
  Octokit.auto_paginate = true
51
70
  end
71
+
72
+ def filtered_issues
73
+ issues = []
74
+ required_labels.each do |label|
75
+ issues += search_issues(label).items
76
+ end
77
+ optional_labels.each do |label|
78
+ issues += search_issues_with_magic_word(label).items
79
+ end
80
+ issues
81
+ end
82
+
83
+ def filtered_numbers
84
+ filtered_issues.map(&:number)
85
+ end
52
86
  end
53
87
  end
88
+
89
+ if $PROGRAM_NAME == __FILE__
90
+ require 'date'
91
+ two_weeks_ago = (Date.today - 14).to_s
92
+ pulls = WhatsupGithub::Pulls.new(repo: 'magento/devdocs', since: two_weeks_ago)
93
+ p pulls.data
94
+ end
@@ -1,24 +1,49 @@
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]
33
+ @config = Config.instance
34
+ end
35
+
36
+ def labels_from_config
37
+ @config.labels
14
38
  end
15
39
 
16
- UPDATED_MASK = 'Major update'.freeze
17
- UPDATED_PHRASE = 'Major update'.freeze
18
- NEW_MASK = 'New topic'.freeze
19
- NEW_PHRASE = 'New topic'.freeze
20
- TECHNICAL_MASK = 'Technical'.freeze
21
- TECHNICAL_PHRASE = 'Technical changes'.freeze
40
+ def required_labels
41
+ @config.required_labels
42
+ end
43
+
44
+ def magic_word
45
+ @config.magic_word
46
+ end
22
47
 
23
48
  def versions
24
49
  label_versions = labels.select { |label| label.start_with?(/\d\./) }
@@ -30,21 +55,14 @@ module WhatsupGithub
30
55
  end
31
56
 
32
57
  def type
33
- labels_string = labels.join(' ')
34
- label_type = /#{ UPDATED_MASK }|#{ NEW_MASK }|#{ TECHNICAL_MASK }/.match(labels_string)
35
- case label_type.to_s
36
- when /#{ UPDATED_MASK }/
37
- UPDATED_PHRASE
38
- when /#{ NEW_MASK }/
39
- NEW_PHRASE
40
- when /#{ TECHNICAL_MASK }/
41
- TECHNICAL_PHRASE
42
- end
58
+ (labels & labels_from_config).join(', ')
43
59
  end
44
60
 
45
61
  def parse_body
46
- whatsnew_splited = body.split('whatsnew')[-1]
47
- 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")
48
66
  cleaned_array = newline_splited.map { |e| e.delete "\r\*" }
49
67
  cleaned_array.delete('')
50
68
  striped_array = cleaned_array.map(&:strip)
@@ -52,10 +70,12 @@ module WhatsupGithub
52
70
  end
53
71
 
54
72
  def description
55
- 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)
56
76
  parse_body
57
77
  else
58
- 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})"
59
79
  puts message
60
80
  message
61
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,38 @@ 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
+ p 'Hello'
87
+ end
88
+
89
+ def member_logins
90
+ load_members
91
+ @members.map(&:login)
64
92
  end
65
93
 
66
94
  def config