apadmi_grout 1.1.0 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +11 -0
  3. data/lib/apadmi/grout/actions/find_tickets_to_move_action/find_tickets_to_move_action.rb +24 -0
  4. data/lib/apadmi/grout/actions/find_tickets_to_move_action/find_tickets_to_move_ado_action.rb +91 -0
  5. data/lib/apadmi/grout/actions/find_tickets_to_move_action/find_tickets_to_move_jira_action.rb +114 -0
  6. data/lib/apadmi/grout/actions/generate_release_notes_action/generate_release_notes_action.rb +48 -0
  7. data/lib/apadmi/grout/actions/generate_release_notes_action/issue_classifier.rb +51 -0
  8. data/lib/apadmi/grout/{release_notes/actions → actions}/issues_from_changelog_action.rb +11 -8
  9. data/lib/apadmi/grout/actions/move_tickets_action.rb +34 -0
  10. data/lib/apadmi/grout/di.rb +59 -9
  11. data/lib/apadmi/grout/{jira/models/find_tickets_options.rb → models/ado_config.rb} +2 -2
  12. data/lib/apadmi/grout/models/bitrise.rb +38 -0
  13. data/lib/apadmi/grout/models/find_tickets_options.rb +44 -0
  14. data/lib/apadmi/grout/{jira/models → models}/flag_messages.rb +2 -1
  15. data/lib/apadmi/grout/models/issue.rb +49 -0
  16. data/lib/apadmi/grout/{jira/models → models}/pull_request.rb +0 -0
  17. data/lib/apadmi/grout/models/release_notes_config.rb +47 -0
  18. data/lib/apadmi/grout/{release_notes/models → models}/release_notes_templates.rb +6 -6
  19. data/lib/apadmi/grout/service/bitrise_service/bitrise_service.rb +103 -0
  20. data/lib/apadmi/grout/service/board_service/ado_board_service.rb +199 -0
  21. data/lib/apadmi/grout/service/board_service/board_service.rb +59 -0
  22. data/lib/apadmi/grout/{jira/wrapper/jira_wrapper.rb → service/board_service/jira_board_service.rb} +65 -107
  23. data/lib/apadmi/grout/utils/git_utils.rb +36 -0
  24. data/lib/apadmi/grout/utils/network_service.rb +123 -0
  25. data/lib/apadmi/grout/version.rb +1 -1
  26. data/lib/apadmi_grout.rb +3 -21
  27. metadata +23 -14
  28. data/lib/apadmi/grout/jira/actions/find_tickets_to_move_action.rb +0 -80
  29. data/lib/apadmi/grout/jira/actions/move_jira_tickets_action.rb +0 -58
  30. data/lib/apadmi/grout/jira/models/version.rb +0 -23
  31. data/lib/apadmi/grout/release_notes/actions/generate_release_notes_action.rb +0 -39
  32. data/lib/apadmi/grout/release_notes/models/release_notes_config.rb +0 -74
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f779ff10f50f15d2ce74311339c8407ae32eec3bcc8fb49f0ab3a85d8a0cbdc7
4
- data.tar.gz: e0f9034584fdcafb95355ade65f64ccdd7e82acc2e07d62a5bd61129e3fc5464
3
+ metadata.gz: f1a387b90108f0657af3875457b1eb98440b54d7fdf6d62a9750fe1af10aeca5
4
+ data.tar.gz: 25fe6b6ca1083a991f088d2161f49997f04944dda1225617192c9fcf2494f511
5
5
  SHA512:
6
- metadata.gz: 10b206d1d56814b6288bf0db35259c02c81baf0c3554de6286627498c0d65ee6f8808fafcd5908038be9a196ae264952180fc4988ca15b4023dd03a1380c98be
7
- data.tar.gz: ecbf93eb1f3bd3141a54949568edc1196ad6b7aaf7348f89f93e9fb46bf76aa424504cf2a5bc5d9effd78f811543322980c65a7e83231cdb283ca492aec53fc8
6
+ metadata.gz: ca2dee35cca80456ca3ebd01b8ab1e1c89eb6c9bad069b36a1e20f8ae36bb511144fed3de12d8dccc31cc8c9dd364684bbdcee4af69244ff180d6e8af4ac24d2
7
+ data.tar.gz: 49414ee4d8507110fed1bbf50946c16357039aacbc1e6657f32c1c4b4777d0ea013559da5c99fc65c77ea5a55d5d4e61479ed7b433dac81885d532dcab4eeaaa
data/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # Core Changelog
2
2
 
3
+ ## [2.1.0] - 22-07-26
4
+ * Added a few bitrise utils to support listing builds, triggering builds and downloading artifacts.
5
+ * Added utilities to the network service to upload files
6
+
7
+ ## [2.0.0] - 2022-05-25
8
+ * Almost total refactor to support having different board providers (other than jira)
9
+ * Implement ADO support for all existing actions
10
+
11
+ ## [1.2.0] - 2022-05-17
12
+ * Filter out issues missing from git changelogs when finding tickets to move.
13
+
3
14
  ## [1.1.0] - 2022-05-12
4
15
  * Support allowing tickets that aren't assigned to a sprint to be moved.
5
16
  * Created utilities for getting the git root and number of commits
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apadmi
4
+ module Grout
5
+ # Generic action for finding tickets to move
6
+ class FindTicketsToMoveAction
7
+ # @param _component [String] Only include tickets tagged with this component
8
+ # @param _status [String] The status of tickets to be moved (Usually "Awaiting QA Release")
9
+ # @param _excluded_ticket_keys [Array<String>] ticket keys to be excluded from consideration
10
+ # @param _custom_flag_messages [Apadmi::Grout::FlagMessages]
11
+ # @param _options [Apadmi::Grout::JiraFindTicketsOptions|Apadmi::Grout::AdoFindTicketsOptions]
12
+ # @return [Array<Apadmi::Grout::Issue>] the issues ready to move
13
+ def run(
14
+ _component,
15
+ _status,
16
+ _excluded_ticket_keys,
17
+ _custom_flag_messages = nil,
18
+ _options = nil
19
+ )
20
+ raise "Unimplemented :("
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apadmi
4
+ module Grout
5
+ # ATTENTION: Any changes to the logic here should be reflected in the docs (find-tickets.md)
6
+
7
+ # Finds and returns a list of all the issues that are ready to be moved
8
+ # Any tickets found that have the given status but *don't* appear ready to be moved will
9
+ # be flagged.
10
+ class FindTicketsToMoveAdoAction < FindTicketsToMoveAction
11
+ # @param [Apadmi::Grout::AdoBoardService]
12
+ # @param [String]
13
+ # @param [Apadmi::Grout::GitUtils]
14
+ # @param [Apadmi::Grout::IssuesFromChangelogAction]
15
+ # @param [Apadmi::Grout::DefaultLogger] // or your own logger!
16
+ def initialize(ado_board_service, ticket_prefix, git_utils, issues_from_chnglg, logger)
17
+ @ado_board_service = ado_board_service
18
+ @ticket_prefix = ticket_prefix
19
+ @git_utils = git_utils
20
+ @issues_from_changelog_action = issues_from_chnglg
21
+ @logger = logger
22
+ end
23
+
24
+ # @param component [String] Only include tickets tagged with this component
25
+ # @param status [String] The status of tickets to be moved (Usually "Awaiting QA Release")
26
+ # @param excluded_ticket_keys [Array<String>] ticket keys to be excluded from consideration
27
+ # @param custom_flag_messages [Apadmi::Grout::FlagMessages]
28
+ # @param options [Apadmi::Grout::JiraFindTicketsOptions]
29
+ # @return [Array<Apadmi::Grout::Issue>] the issues ready to move
30
+ def run(
31
+ component,
32
+ status,
33
+ excluded_ticket_keys,
34
+ custom_flag_messages = nil,
35
+ options = nil
36
+ )
37
+ custom_flag_messages ||= Apadmi::Grout::FlagMessages.default(status)
38
+ options ||= Apadmi::Grout::AdoFindTicketsOptions.new(required_tags: [],
39
+ not_tags: [])
40
+
41
+ issues = @ado_board_service.search_unblocked_issues(
42
+ component, status, [], options
43
+ ).reject { |issue| excluded_ticket_keys.include? issue.key }
44
+
45
+ issue_keys = issues.map(&:key)
46
+ @logger.message("Found issues to consider #{issue_keys.join(", ")}")
47
+
48
+ # Get the issues in the git changelog, filtered for the issues being considered
49
+ changelog_ids = @issues_from_changelog_action.issue_ids_from_changelog(
50
+ @git_utils.merge_changelog(issue_keys)
51
+ ).map { |id| id.delete_prefix(@ticket_prefix) }
52
+
53
+ @git_utils.fetch_all
54
+ # issues that have been merged, but aren't in this build.
55
+ # This is a valid state (e.g. merged into a release branch but not develop)
56
+ invert_changelog_ids = @issues_from_changelog_action.issue_ids_from_changelog(
57
+ @git_utils.invert_changelog(issue_keys)
58
+ ).map { |id| id.delete_prefix(@ticket_prefix) }
59
+
60
+ final_list = issues.filter do |issue|
61
+ # Flag the ticket if it doesn't include in either changelog since
62
+ # this means the ticket is likely in an incorrect state
63
+ decide_should_include(changelog_ids, custom_flag_messages, invert_changelog_ids, issue)
64
+ end
65
+
66
+ @logger.message("Final list: #{final_list.map(&:key).join(", ")}")
67
+ final_list
68
+ end
69
+
70
+ private
71
+
72
+ def decide_should_include(changelog_ids, custom_flag_messages, invert_changelog_ids, issue)
73
+ if !changelog_ids.include?(issue.key) && !invert_changelog_ids.include?(issue.key)
74
+ @ado_board_service.flag_ticket(
75
+ issue.key,
76
+ custom_flag_messages.no_prs_flag_msg
77
+ )
78
+ @logger.message("Not including #{issue.key} since it's not in the changelog at all")
79
+ return false
80
+ end
81
+
82
+ if !changelog_ids.include?(issue.key) && invert_changelog_ids.include?(issue.key)
83
+ @logger.message("Not including #{issue.key} since it's merged but not in this build")
84
+ return false
85
+ end
86
+
87
+ true
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apadmi
4
+ module Grout
5
+ module PrStatus
6
+ MERGED = 1
7
+ UNMERGED_BUT_INCLUDED = 2
8
+ UN_INCLUDED = 3
9
+ end
10
+
11
+ # ATTENTION: Any changes to the logic here should be reflected in the docs (find-tickets.md)
12
+
13
+ # Finds and returns a list of all the issues that are ready to be moved
14
+ # Any tickets found that have the given status but *don't* appear ready to be moved will
15
+ # be flagged.
16
+ class FindTicketsToMoveJiraAction < FindTicketsToMoveAction
17
+ # @param [Apadmi::Grout::JiraBoardService]
18
+ # @param [Apadmi::Grout::GitUtils]
19
+ # @param [Apadmi::Grout::IssuesFromChangelogAction]
20
+ # @param [Apadmi::Grout::DefaultLogger] // or your own logger!
21
+ def initialize(jira_board_service, git_utils, issues_from_chnglg, logger)
22
+ @jira_board_service = jira_board_service
23
+ @git_utils = git_utils
24
+ @issues_from_changelog_action = issues_from_chnglg
25
+ @logger = logger
26
+ end
27
+
28
+ # @param component [String] Only include tickets tagged with this component
29
+ # @param status [String] The status of tickets to be moved (Usually "Awaiting QA Release")
30
+ # @param excluded_ticket_keys [Array<String>] ticket keys to be excluded from consideration
31
+ # @param custom_flag_messages [Apadmi::Grout::FlagMessages]
32
+ # @param options [Apadmi::Grout::JiraFindTicketsOptions]
33
+ # @return [Array<Apadmi::Grout::Issue>] the issues ready to move
34
+ def run(
35
+ component,
36
+ status,
37
+ excluded_ticket_keys,
38
+ custom_flag_messages = nil,
39
+ options = nil
40
+ )
41
+ custom_flag_messages ||= Apadmi::Grout::FlagMessages.default(status)
42
+ options ||= Apadmi::Grout::JiraFindTicketsOptions.new(include_no_sprint_tickets: false)
43
+
44
+ issues = @jira_board_service.search_unblocked_issues(
45
+ component, status, [], options
46
+ ).reject do |issue|
47
+ excluded_ticket_keys.include? issue.key
48
+ end
49
+
50
+ issue_keys = issues.map(&:key)
51
+ @logger.message("Found issues to consider #{issue_keys.join(", ")}")
52
+
53
+ # Get the issues in the git changelog, filtered for the issues being considered
54
+ changelog_ids = @issues_from_changelog_action.issue_ids_from_changelog(
55
+ @git_utils.merge_changelog(issue_keys)
56
+ )
57
+
58
+ final_list = issues.filter do |issue|
59
+ # Decide whether to include this ticket based on PRs
60
+ status = process_prs(issue, custom_flag_messages)
61
+ decide_should_include(issue, status, changelog_ids)
62
+ end
63
+
64
+ @logger.message("Final list: #{final_list.map(&:key).join(", ")}")
65
+ final_list
66
+ end
67
+
68
+ private
69
+
70
+ # @param issue [Apadmi::Grout::Issue]
71
+ # @param pr_status [Int] status of whether or not we can move
72
+ # @param changelog_ids [Array<String>] the ticket ids pulled from git
73
+ def decide_should_include(issue, pr_status, changelog_ids)
74
+ # For merged PRs, check if the ticket appears in the changelog.
75
+ # If it doesn't then it's likely it was merged AFTER this release build was triggered and shouldn't be moved
76
+ # by this build
77
+ if pr_status == PrStatus::MERGED
78
+ is_in_changelog = changelog_ids.include?(issue.key)
79
+ @logger.message("NOTE: Not including #{issue.key} since it's not in the git changelog.") unless is_in_changelog
80
+ return is_in_changelog
81
+ end
82
+
83
+ pr_status != PrStatus::UN_INCLUDED
84
+ end
85
+
86
+ # @param issue [Apadmi::Grout::Issue]
87
+ # @param custom_flag_messages [FlagMessages]
88
+ # @return [Int] status of whether or not we can move
89
+ def process_prs(issue, custom_flag_messages)
90
+ prs = @jira_board_service.get_ticket_prs(issue)
91
+
92
+ if prs.empty?
93
+ @logger.message("#{issue.key} has no PRs. Flagging it: STILL MOVABLE")
94
+ @jira_board_service.flag_ticket(issue.key, custom_flag_messages.no_prs_flag_msg)
95
+ return PrStatus::UNMERGED_BUT_INCLUDED
96
+ elsif prs.all?(&:open)
97
+ @logger.message("#{issue.key} has only open PRs. Flagging it: NOT MOVABLE")
98
+ @jira_board_service.flag_ticket(issue.key, custom_flag_messages.open_prs_flag_msg)
99
+ return PrStatus::UN_INCLUDED
100
+ elsif prs.all?(&:declined)
101
+ @logger.message("#{issue.key} has only declined PRs. Flagging it: NOT MOVABLE")
102
+ @jira_board_service.flag_ticket(issue.key, custom_flag_messages.declined_prs_flag_msg)
103
+ return PrStatus::UN_INCLUDED
104
+ elsif prs.none?(&:merged)
105
+ @logger.message("#{issue.key} has no merged PRs. Flagging it: NOT MOVABLE")
106
+ @jira_board_service.flag_ticket(issue.key, custom_flag_messages.no_merged_prs_flag_msg)
107
+ return PrStatus::UN_INCLUDED
108
+ end
109
+
110
+ PrStatus::MERGED # At least one merged PR
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "mustache"
4
+
5
+ module Apadmi
6
+ module Grout
7
+ # Finds and returns a list of all the issues who's ids exist in the given changelog
8
+ class GenerateReleaseNotesAction
9
+ # @param classifier [Apadmi::Grout::IssueClassifier]
10
+ def initialize(classifier)
11
+ @classifier = classifier
12
+ end
13
+
14
+ # @param config [Apadmi::Grout::ReleaseNotesConfig]
15
+ def run(config)
16
+ moved = @classifier.classify(config.moved_issues)
17
+ release = @classifier.classify(config.release_issues)
18
+
19
+ CustomMustache.render(
20
+ config.templates.document_template,
21
+ config: config,
22
+ rendered_moved_issues: render_classified_issues(config.templates.list_template, moved),
23
+ rendered_release_issues: render_classified_issues(config.templates.list_template, release)
24
+ ).strip
25
+ end
26
+
27
+ private
28
+
29
+ def render_classified_issues(template, classified_issues)
30
+ return if classified_issues.empty
31
+
32
+ CustomMustache.render(
33
+ template,
34
+ classified_issues: classified_issues
35
+ )
36
+ end
37
+ end
38
+
39
+ # Custom mustache class which doesn't escape HTML, since we're generating markdown
40
+ class CustomMustache < Mustache
41
+ # rubocop:disable Naming/MethodName
42
+ def escapeHTML(txt)
43
+ txt
44
+ end
45
+ # rubocop:enable Naming/MethodName
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apadmi
4
+ module Grout
5
+ # Generic issue classifier
6
+ class IssueClassifier
7
+ # @param types [Array<String>] List of types to match on
8
+ # @param issues [Array<Apadmi::Grout::Issue>]
9
+ # @return [Array<Apadmi::Grout::Issue>]
10
+ def filter_issues_by_type(types, issues)
11
+ issues.find_all { |issue| types.include?(issue.issue_type) }
12
+ end
13
+ end
14
+
15
+ # Jira specific issue classifier
16
+ class JiraIssueClassifier < IssueClassifier
17
+ # @param issues [Array<Apadmi::Grout::Issue>]
18
+ def classify(issues)
19
+ tasks = filter_issues_by_type(%w[Task], issues)
20
+ features = filter_issues_by_type(%w[Story], issues)
21
+ improvements = filter_issues_by_type(%w[Improvement Rework Debt], issues)
22
+ defects = filter_issues_by_type(%w[Bug], issues)
23
+ ClassifiedIssues.new(
24
+ tasks,
25
+ features,
26
+ improvements,
27
+ defects,
28
+ issues - tasks - features - improvements - defects
29
+ )
30
+ end
31
+ end
32
+
33
+ # Ado specific issue classifier
34
+ class AdoIssueClassifier < IssueClassifier
35
+ # @param issues [Array<Apadmi::Grout::Issue>]
36
+ def classify(issues)
37
+ tasks = filter_issues_by_type(%w[Task], issues)
38
+ features = filter_issues_by_type(["User Story", "Feature"], issues)
39
+ improvements = filter_issues_by_type(["Improvement", "Tech Debt"], issues)
40
+ defects = filter_issues_by_type(%w[Bug], issues)
41
+ ClassifiedIssues.new(
42
+ tasks,
43
+ features,
44
+ improvements,
45
+ defects,
46
+ issues - tasks - features - improvements - defects
47
+ )
48
+ end
49
+ end
50
+ end
51
+ end
@@ -4,28 +4,31 @@ module Apadmi
4
4
  module Grout
5
5
  # Finds and returns a list of all the issues who's ids exist in the given changelog
6
6
  class IssuesFromChangelogAction
7
- # @param [Apadmi::Grout::JiraWrapper]
7
+ # @param board_service [Apadmi::Grout::BoardService]
8
+ # @param ticket_prefix [String] The prefix for the ticket numbers in git e.g "PPT-"
8
9
  # @param [Apadmi::Grout::DefaultLogger] // or your own logger!
9
- def initialize(jira_wrapper, logger)
10
- @jira_wrapper = jira_wrapper
10
+ def initialize(board_service, ticket_prefix, logger)
11
+ @board_service = board_service
11
12
  @logger = logger
13
+ @ticket_prefix = ticket_prefix
12
14
  end
13
15
 
14
16
  # @param changelog [String] raw git changelog
15
- # @return [Array<JIRA::Resource::Issue>] list of issues from changelog
17
+ # @return [Array<Apadmi::Grout::Issue>] list of issues from changelog
16
18
  def run(changelog)
17
19
  ids = issue_ids_from_changelog(changelog)
18
20
 
19
21
  @logger.message("Found issue ids: #{ids.join(", ")}")
20
- @jira_wrapper.find_issues_by_keys(ids)
22
+ @board_service.find_issues_by_keys(ids)
21
23
  end
22
24
 
23
- private
24
-
25
25
  # @param changelog [String] raw git changelog
26
26
  # @return [Array<String>] list of issue ids from changelog
27
27
  def issue_ids_from_changelog(changelog)
28
- changelog.scan(/(#{@jira_wrapper.issue_id_prefix}-\d+)/).flatten.uniq
28
+ # Changelog often has the pull request number in it, this can mess up the parsing so strip it out if we can
29
+ changelog = changelog.gsub(/\(pull request.*\)/, "")
30
+
31
+ changelog.scan(/(#{@ticket_prefix}\d+)/).flatten.uniq
29
32
  end
30
33
  end
31
34
  end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apadmi
4
+ module Grout
5
+ # Moves all given tickets to a given status and assigns fix versions
6
+ class MoveTicketsAction
7
+ # @param [Apadmi::Grout::BoardService]
8
+ # @param [Apadmi::Grout::DefaultLogger] // or your own logger!
9
+ def initialize(board_service, logger)
10
+ @board_service = board_service
11
+ @logger = logger
12
+ end
13
+
14
+ # @param [Array<String>] version_strings
15
+ # @param [Array<Apadmi::Grout::Issue>] issues
16
+ # @param [String] new_status
17
+ def run(version_strings, issues, new_status)
18
+ if issues.empty?
19
+ @logger.error("No issues found, aborting")
20
+ return
21
+ end
22
+
23
+ @logger.message("Transitioning issues: #{issues.map(&:key).join(", ")}")
24
+
25
+ issues.each do |issue|
26
+ @board_service.transition_issue(issue, new_status)
27
+ @board_service.assign_fixversions(issue.key, version_strings)
28
+ end
29
+
30
+ @logger.success("Issues transitioned successfully :D")
31
+ end
32
+ end
33
+ end
34
+ end
@@ -3,22 +3,72 @@
3
3
  module Apadmi
4
4
  module Grout
5
5
  # Convenience class for initializing and accessing the various actions
6
- class DependencyInjector
7
- attr_reader :jira_wrapper, :move_jira_tickets_action, :find_tickets_to_move_action, :generate_release_notes_action, :issues_from_changelog_action
8
-
6
+ DependencyInjector = Struct.new(
7
+ :board_service,
8
+ :move_tickets_action,
9
+ :find_tickets_to_move_action,
10
+ :issues_from_changelog_action,
11
+ :generate_release_notes_action,
12
+ keyword_init: true
13
+ ) do
9
14
  # @param [String] username
10
15
  # @param [String] token
11
16
  # @param [String] base_url
12
17
  # @param [String] context_path
13
18
  # @param [String] project
14
19
  # @param [Logger] logger
15
- def initialize(username, token, base_url, context_path, project, logger)
16
- @jira_wrapper = Apadmi::Grout::JiraWrapper.new(username, token, base_url, context_path, project)
20
+ def self.init_for_jira(
21
+ username,
22
+ token,
23
+ base_url,
24
+ context_path,
25
+ project,
26
+ logger = Apadmi::Grout::DefaultLogger.new
27
+ )
28
+ network_service = Apadmi::Grout::NetworkService.init_for_basic_auth(username, token, base_url)
29
+ jira_board_service = Apadmi::Grout::JiraBoardService.new(username, token, base_url, context_path, project, network_service)
30
+ git_utils = Apadmi::Grout::GitUtils
31
+ issues_from_changelog_action = Apadmi::Grout::IssuesFromChangelogAction.new(jira_board_service, "#{project}-", logger)
32
+
33
+ DependencyInjector.new(
34
+ board_service: jira_board_service,
35
+ move_tickets_action: Apadmi::Grout::MoveTicketsAction.new(jira_board_service, logger),
36
+ issues_from_changelog_action: issues_from_changelog_action,
37
+ find_tickets_to_move_action: Apadmi::Grout::FindTicketsToMoveJiraAction.new(jira_board_service, git_utils, issues_from_changelog_action, logger),
38
+ generate_release_notes_action: Apadmi::Grout::GenerateReleaseNotesAction.new(Apadmi::Grout::JiraIssueClassifier.new)
39
+ )
40
+ end
41
+
42
+ # @param [String] personal_access_token
43
+ # @param [String] base_url
44
+ # @param [Logger] logger
45
+ def self.init_for_ado(
46
+ personal_access_token,
47
+ base_url,
48
+ logger = Apadmi::Grout::DefaultLogger.new,
49
+ ado_config = Apadmi::Grout::AdoConfig.new(
50
+ flag_tag: "FLAGGED"
51
+ )
52
+ )
53
+ ticket_prefix = "#"
54
+ network_service = Apadmi::Grout::NetworkService.init_for_basic_auth("", personal_access_token, base_url)
55
+ ado_board_service = Apadmi::Grout::AdoBoardService.new(network_service, ado_config, logger)
56
+ git_utils = Apadmi::Grout::GitUtils
57
+ issues_from_changelog_action = Apadmi::Grout::IssuesFromChangelogAction.new(ado_board_service, ticket_prefix, logger)
58
+
59
+ DependencyInjector.new(
60
+ board_service: ado_board_service,
61
+ move_tickets_action: Apadmi::Grout::MoveTicketsAction.new(ado_board_service, logger),
62
+ issues_from_changelog_action: issues_from_changelog_action,
63
+ find_tickets_to_move_action: Apadmi::Grout::FindTicketsToMoveAdoAction.new(ado_board_service, ticket_prefix, git_utils, issues_from_changelog_action,
64
+ logger),
65
+ generate_release_notes_action: Apadmi::Grout::GenerateReleaseNotesAction.new(Apadmi::Grout::AdoIssueClassifier.new)
66
+ )
67
+ end
17
68
 
18
- @move_jira_tickets_action = Apadmi::Grout::MoveJiraTicketsAction.new(@jira_wrapper, logger)
19
- @find_tickets_to_move_action = Apadmi::Grout::FindTicketsToMoveAction.new(@jira_wrapper, logger)
20
- @generate_release_notes_action = Apadmi::Grout::GenerateReleaseNotesAction.new(base_url)
21
- @issues_from_changelog_action = Apadmi::Grout::IssuesFromChangelogAction.new(@jira_wrapper, logger)
69
+ def self.bitrise_service(api_token, base_url, app_slug)
70
+ network_service = Apadmi::Grout::NetworkService.new(api_token, base_url)
71
+ Apadmi::Grout::BitriseService.new(app_slug, network_service)
22
72
  end
23
73
  end
24
74
  end
@@ -2,8 +2,8 @@
2
2
 
3
3
  module Apadmi
4
4
  module Grout
5
- FindTicketsOptions = Struct.new(
6
- :include_no_sprint_tickets,
5
+ AdoConfig = Struct.new(
6
+ :flag_tag,
7
7
  keyword_init: true
8
8
  )
9
9
  end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apadmi
4
+ module Grout
5
+ STATUS_RUNNING = 0
6
+ STATUS_SUCCESS = 1
7
+ STATUS_FAILED = 2
8
+ STATUS_ABORTED_FAIL = 3
9
+ STATUS_ABORTED_SUCCESS = 4
10
+
11
+ BitriseBuild = Struct.new(:triggered_at, :slug, :status, :commit_hash) do
12
+ def finished_with_success
13
+ STATUS_SUCCESS == status
14
+ end
15
+ end
16
+
17
+ Artifact = Struct.new(:title, :download_url)
18
+
19
+ TriggeredBitriseBuild = Struct.new(:build_number, :build_slug, :build_url, :message, :service, :slug, :status, :triggered_workflow) do
20
+ def finished_with_success
21
+ STATUS_SUCCESS == status
22
+ end
23
+
24
+ def self.from_json(json)
25
+ TriggeredBitriseBuild.new(
26
+ json["build_number"],
27
+ json["build_slug"],
28
+ json["build_url"],
29
+ json["message"],
30
+ json["service"],
31
+ json["slug"],
32
+ json["status"],
33
+ json["triggered_workflow"]
34
+ )
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apadmi
4
+ module Grout
5
+ # Generic find tickets options
6
+ class FindTicketsOptions; end
7
+
8
+ # Jira specific find tickets options
9
+ class JiraFindTicketsOptions < FindTicketsOptions
10
+ attr_reader :include_no_sprint_tickets
11
+
12
+ # @param include_no_sprint_tickets [String] whether or not to include tickets with no sprint assigned
13
+ def initialize(include_no_sprint_tickets: false)
14
+ @include_no_sprint_tickets = include_no_sprint_tickets
15
+ end
16
+
17
+ def ==(other)
18
+ other.class == self.class && other.state == state
19
+ end
20
+
21
+ def state
22
+ [@include_no_sprint_tickets]
23
+ end
24
+ end
25
+
26
+ # Ado specific find tickets options
27
+ class AdoFindTicketsOptions < FindTicketsOptions
28
+ attr_reader :required_tags, :not_tags
29
+
30
+ def initialize(required_tags: [], not_tags: [])
31
+ @required_tags = required_tags
32
+ @not_tags = not_tags
33
+ end
34
+
35
+ def ==(other)
36
+ other.class == self.class && other.state == state
37
+ end
38
+
39
+ def state
40
+ [@required_tags, @not_tags]
41
+ end
42
+ end
43
+ end
44
+ end
@@ -13,7 +13,8 @@ module Apadmi
13
13
  # @return [Apadmi::Grout::FlagMessages]
14
14
  def self.default(status)
15
15
  FlagMessages.new(
16
- "REASON FOR FLAG: Check this ticket - it was put in #{status} but has no PRs connected to it. CI still moved it",
16
+ "REASON FOR FLAG: Check this ticket - it was put in #{status} but has no PRs connected to it. CI still moved it. " \
17
+ "(WARNING: this may cause the `fix_versions` to be incorrect)",
17
18
  "REASON FOR FLAG: Check this ticket - it was put in #{status} but has no MERGED PRs and at least one OPEN PR",
18
19
  "REASON FOR FLAG: Check this ticket - it was put in #{status} but has only DECLINED PRs",
19
20
  "REASON FOR FLAG: Check this ticket - it was put in #{status} but has no MERGED PRs"
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apadmi
4
+ module Grout
5
+ # A generic issue type wrapping up the underlying JIRA & ADO object
6
+ # @param key [String] Unique key identifying the issue
7
+ # @param summary [String] The issue title
8
+ # @param status [String] The current status on the board
9
+ # @param issue_type [String] Raw string representing issue type e.g "In Progress"
10
+ # @param url [String] url to the issue
11
+ # @param raw_object [JIRA::Resource::Issue|Hash] The underlying raw object from the platform
12
+ Issue = Struct.new(:key, :summary, :status, :issue_type, :url, :raw_object) do
13
+ # @return [Apadmi::Grout::Issue]
14
+ def self.from_ado_hash(hash)
15
+ Issue.new(
16
+ hash["id"].to_s,
17
+ hash["fields"]["System.Title"],
18
+ hash["fields"]["System.State"],
19
+ hash["fields"]["System.WorkItemType"],
20
+ hash["url"],
21
+ hash
22
+ )
23
+ end
24
+
25
+ # @return [Array<Apadmi::Grout::Issue>]
26
+ def self.from_ado_hashes(hashes)
27
+ hashes.map { |h| Issue.from_ado_hash(h) }
28
+ end
29
+
30
+ # @param issue [JIRA::Resource::Issue]
31
+ # @return [Apadmi::Grout::Issue]
32
+ def self.from_jira_issue(issue, base_url)
33
+ Issue.new(
34
+ issue.key,
35
+ issue.summary,
36
+ issue.status.name,
37
+ issue.issuetype.name,
38
+ "#{base_url}/browse/#{issue.key}",
39
+ issue
40
+ )
41
+ end
42
+
43
+ # @return [Array<Apadmi::Grout::Issue>]
44
+ def self.from_jira_issues(issues, base_url)
45
+ issues.map { |i| Issue.from_jira_issue(i, base_url) }
46
+ end
47
+ end
48
+ end
49
+ end