jira-auto-tool 0.1.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.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +291 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/Guardfile +105 -0
- data/LICENSE.txt +21 -0
- data/README.md +159 -0
- data/Rakefile +20 -0
- data/bin/jira-auto-tool +57 -0
- data/bin/jira-auto-tool.bat +2 -0
- data/bin/setup +8 -0
- data/bin/setup-dev-win.bat +3 -0
- data/cucumber.yml +7 -0
- data/features/align_sprint_time_in_dates.feature +33 -0
- data/features/assign_tickets_to_team_sprints.feature +73 -0
- data/features/cache_boards.feature +24 -0
- data/features/control_http_request_rate_limit.feature +17 -0
- data/features/create_sprints_using_existing_ones_as_reference.feature +79 -0
- data/features/list_boards.feature +12 -0
- data/features/list_project_fields.feature +89 -0
- data/features/list_sprint_prefixes.feature +77 -0
- data/features/quarterly_add_sprints_using_existing_ones_as_a_reference.feature +71 -0
- data/features/quarterly_create_sprints_until_specific_date.feature +75 -0
- data/features/quarterly_rename_sprints.feature +179 -0
- data/features/rename_sprints.feature +203 -0
- data/features/self_documented_command_line.feature +15 -0
- data/features/sprint_filtering.feature +111 -0
- data/features/step_definitions/execution_context_steps.rb +33 -0
- data/features/step_definitions/jira_board_steps.rb +102 -0
- data/features/step_definitions/jira_ticket_steps.rb +63 -0
- data/features/support/10.setup_cucumber.rb +10 -0
- data/features/support/env.rb +25 -0
- data/features/support/hooks.rb +25 -0
- data/features/support/setup_rspec.rb +14 -0
- data/features/support/setup_simplecov.rb +5 -0
- data/features/update_sprint_end_date_and_shift_following_ones.feature +52 -0
- data/lib/jira/auto/tool/board/cache.rb +67 -0
- data/lib/jira/auto/tool/board/unavailable_board.rb +36 -0
- data/lib/jira/auto/tool/board.rb +105 -0
- data/lib/jira/auto/tool/board_controller/options.rb +32 -0
- data/lib/jira/auto/tool/board_controller.rb +88 -0
- data/lib/jira/auto/tool/common_options.rb +37 -0
- data/lib/jira/auto/tool/config/options.rb +19 -0
- data/lib/jira/auto/tool/config.rb +64 -0
- data/lib/jira/auto/tool/fetch_custom_field_options.rb +47 -0
- data/lib/jira/auto/tool/field.rb +59 -0
- data/lib/jira/auto/tool/field_controller.rb +50 -0
- data/lib/jira/auto/tool/field_option.rb +35 -0
- data/lib/jira/auto/tool/get_createmeta_for_project.rb +24 -0
- data/lib/jira/auto/tool/helpers/environment_based_value.rb +96 -0
- data/lib/jira/auto/tool/helpers/option_parser.rb +16 -0
- data/lib/jira/auto/tool/helpers/overridable_time.rb +18 -0
- data/lib/jira/auto/tool/helpers/pagination.rb +50 -0
- data/lib/jira/auto/tool/jira_http_options.rb +20 -0
- data/lib/jira/auto/tool/next_sprint_creator.rb +60 -0
- data/lib/jira/auto/tool/performer/options.rb +76 -0
- data/lib/jira/auto/tool/performer/planning_increment_sprint_creator.rb +42 -0
- data/lib/jira/auto/tool/performer/prefix_sprint_updater.rb +42 -0
- data/lib/jira/auto/tool/performer/quarterly_sprint_renamer/next_name_generator.rb +60 -0
- data/lib/jira/auto/tool/performer/quarterly_sprint_renamer.rb +19 -0
- data/lib/jira/auto/tool/performer/sprint_end_date_updater.rb +55 -0
- data/lib/jira/auto/tool/performer/sprint_renamer/keep_same_name_generator.rb +19 -0
- data/lib/jira/auto/tool/performer/sprint_renamer/next_name_generator.rb +47 -0
- data/lib/jira/auto/tool/performer/sprint_renamer.rb +55 -0
- data/lib/jira/auto/tool/performer/sprint_time_in_dates_aligner.rb +52 -0
- data/lib/jira/auto/tool/project/options.rb +22 -0
- data/lib/jira/auto/tool/project/ticket_fields.rb +70 -0
- data/lib/jira/auto/tool/project.rb +40 -0
- data/lib/jira/auto/tool/rate_limited_jira_client.rb +50 -0
- data/lib/jira/auto/tool/request_builder/field_context_fetcher.rb +78 -0
- data/lib/jira/auto/tool/request_builder/field_option_fetcher.rb +54 -0
- data/lib/jira/auto/tool/request_builder/get.rb +29 -0
- data/lib/jira/auto/tool/request_builder/sprint_creator.rb +112 -0
- data/lib/jira/auto/tool/request_builder/sprint_state_updater.rb +60 -0
- data/lib/jira/auto/tool/request_builder.rb +89 -0
- data/lib/jira/auto/tool/setup_logging.rb +35 -0
- data/lib/jira/auto/tool/sprint/name.rb +105 -0
- data/lib/jira/auto/tool/sprint/prefix.rb +66 -0
- data/lib/jira/auto/tool/sprint.rb +183 -0
- data/lib/jira/auto/tool/sprint_controller/options.rb +61 -0
- data/lib/jira/auto/tool/sprint_controller.rb +152 -0
- data/lib/jira/auto/tool/sprint_state_controller.rb +58 -0
- data/lib/jira/auto/tool/team.rb +23 -0
- data/lib/jira/auto/tool/team_sprint_prefix_mapper/options.rb +27 -0
- data/lib/jira/auto/tool/team_sprint_prefix_mapper.rb +62 -0
- data/lib/jira/auto/tool/team_sprint_ticket_dispatcher.rb +76 -0
- data/lib/jira/auto/tool/ticket.rb +110 -0
- data/lib/jira/auto/tool/until_date.rb +68 -0
- data/lib/jira/auto/tool/version.rb +9 -0
- data/lib/jira/auto/tool.rb +216 -0
- data/sig/jira/sprint/tool.rbs +8 -0
- data/spec/jira/auto/tool/board/cache_spec.rb +179 -0
- data/spec/jira/auto/tool/board/unavailable_board_spec.rb +34 -0
- data/spec/jira/auto/tool/board_controller/options_spec.rb +52 -0
- data/spec/jira/auto/tool/board_controller_spec.rb +154 -0
- data/spec/jira/auto/tool/board_spec.rb +163 -0
- data/spec/jira/auto/tool/common_options_spec.rb +49 -0
- data/spec/jira/auto/tool/config_spec.rb +108 -0
- data/spec/jira/auto/tool/field_controller_spec.rb +121 -0
- data/spec/jira/auto/tool/field_option_spec.rb +42 -0
- data/spec/jira/auto/tool/field_spec.rb +99 -0
- data/spec/jira/auto/tool/helpers/environment_based_value_spec.rb +21 -0
- data/spec/jira/auto/tool/helpers/option_parser_spec.rb +21 -0
- data/spec/jira/auto/tool/helpers/overridable_time_spec.rb +43 -0
- data/spec/jira/auto/tool/helpers/pagination_spec.rb +72 -0
- data/spec/jira/auto/tool/jira_http_options_spec.rb +32 -0
- data/spec/jira/auto/tool/next_sprint_creator_spec.rb +85 -0
- data/spec/jira/auto/tool/performer/option_spec.rb +55 -0
- data/spec/jira/auto/tool/performer/planning_increment_sprint_creator_spec.rb +62 -0
- data/spec/jira/auto/tool/performer/prefix_sprint_updater_spec.rb +35 -0
- data/spec/jira/auto/tool/performer/quarterly_sprint_renamer/next_name_generator_spec.rb +175 -0
- data/spec/jira/auto/tool/performer/quarterly_sprint_renamer_spec.rb +239 -0
- data/spec/jira/auto/tool/performer/sprint_end_date_updater_spec.rb +90 -0
- data/spec/jira/auto/tool/performer/sprint_renamer/keep_same_name_generator_spec.rb +12 -0
- data/spec/jira/auto/tool/performer/sprint_renamer/next_name_generator_spec.rb +129 -0
- data/spec/jira/auto/tool/performer/sprint_renamer_spec.rb +240 -0
- data/spec/jira/auto/tool/performer/sprint_time_in_dates_aligner_spec.rb +132 -0
- data/spec/jira/auto/tool/project/ticket_fields_spec.rb +390 -0
- data/spec/jira/auto/tool/project_spec.rb +31 -0
- data/spec/jira/auto/tool/rate_limited_jira_client_spec.rb +82 -0
- data/spec/jira/auto/tool/request_builder/field_context_fetcher_spec.rb +54 -0
- data/spec/jira/auto/tool/request_builder/field_option_fetcher_spec.rb +64 -0
- data/spec/jira/auto/tool/request_builder/get_spec.rb +40 -0
- data/spec/jira/auto/tool/request_builder/sprint_creator_spec.rb +179 -0
- data/spec/jira/auto/tool/request_builder/sprint_state_updater_spec.rb +31 -0
- data/spec/jira/auto/tool/request_builder_spec.rb +73 -0
- data/spec/jira/auto/tool/sprint/name_spec.rb +101 -0
- data/spec/jira/auto/tool/sprint/prefix_spec.rb +207 -0
- data/spec/jira/auto/tool/sprint_controller_spec.rb +406 -0
- data/spec/jira/auto/tool/sprint_spec.rb +309 -0
- data/spec/jira/auto/tool/team_spec.rb +21 -0
- data/spec/jira/auto/tool/team_sprint_prefix_mapper_spec.rb +97 -0
- data/spec/jira/auto/tool/team_sprint_ticket_dispatcher_spec.rb +232 -0
- data/spec/jira/auto/tool/ticket_spec.rb +116 -0
- data/spec/jira/auto/tool/until_date_spec.rb +80 -0
- data/spec/jira/auto/tool_spec.rb +458 -0
- data/spec/spec_helper.rb +42 -0
- metadata +368 -0
@@ -0,0 +1,183 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "delegate"
|
4
|
+
require "jira-ruby"
|
5
|
+
|
6
|
+
require_relative "sprint_state_controller"
|
7
|
+
|
8
|
+
module Jira
|
9
|
+
module Auto
|
10
|
+
class Tool
|
11
|
+
class Sprint < SimpleDelegator
|
12
|
+
include Comparable
|
13
|
+
|
14
|
+
def self.date_for_save(date)
|
15
|
+
case date
|
16
|
+
when Time, Date, DateTime
|
17
|
+
date
|
18
|
+
when String
|
19
|
+
Time.parse(date)
|
20
|
+
else
|
21
|
+
raise ArgumentError, "#{date.inspect} (#{date.class}), date must be a Time, Date, DateTime or a String"
|
22
|
+
end
|
23
|
+
.utc.iso8601
|
24
|
+
end
|
25
|
+
|
26
|
+
attr_reader :jira_sprint, :tool
|
27
|
+
|
28
|
+
def initialize(tool, jira_sprint)
|
29
|
+
super(jira_sprint)
|
30
|
+
@tool = tool
|
31
|
+
@jira_sprint = jira_sprint
|
32
|
+
end
|
33
|
+
|
34
|
+
def id
|
35
|
+
@jira_sprint.id
|
36
|
+
end
|
37
|
+
|
38
|
+
def name
|
39
|
+
@jira_sprint.name
|
40
|
+
end
|
41
|
+
|
42
|
+
def length_in_days
|
43
|
+
(end_date - start_date) / 1.day
|
44
|
+
end
|
45
|
+
|
46
|
+
def start_date
|
47
|
+
get_optional_date :startDate
|
48
|
+
end
|
49
|
+
|
50
|
+
def start_date=(date)
|
51
|
+
jira_sprint.attrs["startDate"] = self.class.date_for_save(date)
|
52
|
+
end
|
53
|
+
|
54
|
+
def start_date?
|
55
|
+
start_date != UNDEFINED_DATE
|
56
|
+
end
|
57
|
+
|
58
|
+
def end_date
|
59
|
+
get_optional_date :endDate
|
60
|
+
end
|
61
|
+
|
62
|
+
def end_date=(date)
|
63
|
+
jira_sprint.attrs["endDate"] = self.class.date_for_save(date)
|
64
|
+
end
|
65
|
+
|
66
|
+
def end_date?
|
67
|
+
end_date != UNDEFINED_DATE
|
68
|
+
end
|
69
|
+
|
70
|
+
UNDEFINED_DATE = Time.new(1970, 1, 1, 0, 0, 0, "UTC")
|
71
|
+
|
72
|
+
def get_optional_date(jira_field_id)
|
73
|
+
return UNDEFINED_DATE unless jira_sprint.respond_to?(jira_field_id)
|
74
|
+
|
75
|
+
parse_date(jira_sprint.send(jira_field_id))
|
76
|
+
end
|
77
|
+
|
78
|
+
def missing_dates?
|
79
|
+
start_date == UNDEFINED_DATE || end_date == UNDEFINED_DATE
|
80
|
+
end
|
81
|
+
|
82
|
+
def rename_to(new_name)
|
83
|
+
return if new_name == name || closed?
|
84
|
+
|
85
|
+
jira_sprint.attrs["name"] = new_name
|
86
|
+
|
87
|
+
save
|
88
|
+
end
|
89
|
+
|
90
|
+
def save
|
91
|
+
remove_attributes_that_causes_errors_on_save
|
92
|
+
|
93
|
+
jira_sprint.save!
|
94
|
+
end
|
95
|
+
|
96
|
+
def remove_attributes_that_causes_errors_on_save
|
97
|
+
jira_sprint.attrs.delete("rapidview_id")
|
98
|
+
end
|
99
|
+
|
100
|
+
def state
|
101
|
+
@jira_sprint.state
|
102
|
+
end
|
103
|
+
|
104
|
+
def closed?
|
105
|
+
state == SprintStateController::SprintState::CLOSED
|
106
|
+
end
|
107
|
+
|
108
|
+
def origin_board_id
|
109
|
+
@jira_sprint.originBoardId
|
110
|
+
end
|
111
|
+
|
112
|
+
def index_in_quarter
|
113
|
+
parsed_name.index_in_quarter
|
114
|
+
end
|
115
|
+
|
116
|
+
def board
|
117
|
+
@board ||= Board.find_by_id(tool, origin_board_id)
|
118
|
+
end
|
119
|
+
|
120
|
+
def jira_client
|
121
|
+
@jira_sprint.client
|
122
|
+
end
|
123
|
+
|
124
|
+
def name_prefix
|
125
|
+
parsed_name.prefix
|
126
|
+
end
|
127
|
+
|
128
|
+
def parsed_name
|
129
|
+
Name.parse(name)
|
130
|
+
end
|
131
|
+
|
132
|
+
def <=>(other)
|
133
|
+
comparison_values(self) <=> comparison_values(other)
|
134
|
+
end
|
135
|
+
|
136
|
+
def to_s
|
137
|
+
"name = #{name}, start_date = #{start_date}, end_date = #{end_date}, length_in_days = #{length_in_days}"
|
138
|
+
end
|
139
|
+
|
140
|
+
def self.to_table_row_field_names
|
141
|
+
%i[id name length_in_days start_date end_date]
|
142
|
+
end
|
143
|
+
|
144
|
+
def self.to_table_row_header(without_board_information: false)
|
145
|
+
header = to_table_row_field_names.collect { |field| field.to_s.titleize }
|
146
|
+
|
147
|
+
header.concat(Board.to_table_row_header.collect { |field| "Board #{field}" }) unless without_board_information
|
148
|
+
|
149
|
+
header
|
150
|
+
end
|
151
|
+
|
152
|
+
def to_table_row(without_board_information: false)
|
153
|
+
row = self.class.to_table_row_field_names.collect { |field| send(field) }
|
154
|
+
|
155
|
+
row.concat(board.to_table_row) unless without_board_information
|
156
|
+
|
157
|
+
row
|
158
|
+
rescue StandardError => e
|
159
|
+
raise e.class, "#{e.class}: sprint #{name.inspect}: #{e.message}:\n#{inspect}"
|
160
|
+
end
|
161
|
+
|
162
|
+
private
|
163
|
+
|
164
|
+
def comparison_values(object)
|
165
|
+
comparison_fields(object).collect { |field| object.send(field) }
|
166
|
+
end
|
167
|
+
|
168
|
+
def comparison_fields(object)
|
169
|
+
%i[start_date end_date] +
|
170
|
+
if Name.respects_naming_convention?(object.name)
|
171
|
+
[:parsed_name]
|
172
|
+
else
|
173
|
+
[:name]
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def parse_date(date)
|
178
|
+
Time.parse(date).utc
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "jira/auto/tool/until_date"
|
4
|
+
|
5
|
+
module Jira
|
6
|
+
module Auto
|
7
|
+
class Tool
|
8
|
+
class SprintController
|
9
|
+
class Options
|
10
|
+
def self.add(tool, parser)
|
11
|
+
add_quarterly_sprint_adding_options(parser, tool)
|
12
|
+
add_sprint_listing_options(parser, tool)
|
13
|
+
add_sprint_prefix_listing_options(parser, tool)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.add_quarterly_sprint_adding_options(parser, tool)
|
17
|
+
parser.on("--quarterly-sprint-add-one", "--qsao",
|
18
|
+
"Create a follow up sprint for each of the existing unclosed sprint prefixes") do
|
19
|
+
tool.sprint_controller.quarterly_add_one_sprint_for_each_unclosed_sprint_prefix
|
20
|
+
end
|
21
|
+
|
22
|
+
parser.accept(UntilDate) { |until_date_string| UntilDate.new(until_date_string) }
|
23
|
+
|
24
|
+
parser.on("--quarterly-sprint-add-until=DATE", "--qsau", UntilDate,
|
25
|
+
"Create sprints until date is included for each of the unclosed sprint prefixes") do |until_date|
|
26
|
+
tool.sprint_controller.quarterly_add_sprints_until(until_date)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.add_sprint_listing_options(parser, tool)
|
31
|
+
parser.on("--sprint-list",
|
32
|
+
"List sprints. The output may be controlled via the ART_PREFIX_REGEX environment variable") do
|
33
|
+
tool.sprint_controller.list_sprints
|
34
|
+
end
|
35
|
+
|
36
|
+
parser.on("--sprint-list-without-board-info",
|
37
|
+
"List sprints without the board information. " \
|
38
|
+
"The output may be controlled via the ART_PREFIX_REGEX environment variable") do
|
39
|
+
tool.sprint_controller.list_sprints(without_board_information: true)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.add_sprint_prefix_listing_options(parser, tool)
|
44
|
+
parser.section_header "Sprint Prefix"
|
45
|
+
|
46
|
+
parser.on("--sprint-prefix-list",
|
47
|
+
"List sprints. The output may be controlled via the ART_PREFIX_REGEX environment variable") do
|
48
|
+
tool.sprint_controller.list_sprint_prefixes
|
49
|
+
end
|
50
|
+
|
51
|
+
parser.on("--sprint-prefix-list-without-board-info",
|
52
|
+
"List sprints without the board information. " \
|
53
|
+
"The output may be controlled via the ART_PREFIX_REGEX environment variable") do
|
54
|
+
tool.sprint_controller.list_sprint_prefixes(without_board_information: true)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "next_sprint_creator"
|
4
|
+
require_relative "sprint"
|
5
|
+
require_relative "sprint/prefix"
|
6
|
+
require_relative "sprint_state_controller"
|
7
|
+
require_relative "helpers/pagination"
|
8
|
+
|
9
|
+
module Jira
|
10
|
+
module Auto
|
11
|
+
class Tool
|
12
|
+
class SprintController
|
13
|
+
attr_accessor :tool, :board
|
14
|
+
|
15
|
+
def initialize(tool, board)
|
16
|
+
@tool = tool
|
17
|
+
@board = board
|
18
|
+
end
|
19
|
+
|
20
|
+
def quarterly_add_sprints_until(until_date)
|
21
|
+
unclosed_sprint_prefixes.each { |prefix| prefix.quarterly_add_sprints_until(until_date) }
|
22
|
+
end
|
23
|
+
|
24
|
+
def quarterly_add_one_sprint_for_each_unclosed_sprint_prefix
|
25
|
+
exit_with_board_warning "No sprint added since no reference sprint was found!" unless sprint_exist?
|
26
|
+
unless unclosed_sprint_exist?
|
27
|
+
exit_with_board_warning "No sprint added since no unclosed reference sprint was found!"
|
28
|
+
end
|
29
|
+
|
30
|
+
unclosed_sprint_prefixes.each(&:add_sprint_following_last_one)
|
31
|
+
end
|
32
|
+
|
33
|
+
SUCCESSFUL_EXECUTION_EXIT_CODE = 0
|
34
|
+
UNSUCCESSFUL_EXECUTION_EXIT_CODE = 1
|
35
|
+
|
36
|
+
def exit_with_board_warning(message, exit_code = SUCCESSFUL_EXECUTION_EXIT_CODE)
|
37
|
+
log.warn { "Jira board '#{board.name}': #{message}" }
|
38
|
+
exit(exit_code)
|
39
|
+
end
|
40
|
+
|
41
|
+
def unclosed_sprint_prefixes
|
42
|
+
@unclosed_sprint_prefixes ||= calculate_unclosed_sprint_prefixes.sort_by(&:name)
|
43
|
+
end
|
44
|
+
|
45
|
+
def unclosed_sprint_exist?
|
46
|
+
!unclosed_sprints.empty?
|
47
|
+
end
|
48
|
+
|
49
|
+
def sprint_exist?
|
50
|
+
!sprints.empty?
|
51
|
+
end
|
52
|
+
|
53
|
+
def list_sprint_prefixes(without_board_information: false)
|
54
|
+
list_object(Sprint::Prefix, :unclosed_sprint_prefixes,
|
55
|
+
"Sprint Prefixes With Corresponding Last Sprints",
|
56
|
+
without_board_information: without_board_information)
|
57
|
+
end
|
58
|
+
|
59
|
+
def list_sprints(without_board_information: false)
|
60
|
+
list_object(Sprint, :unclosed_sprints, "Matching Sprints",
|
61
|
+
without_board_information: without_board_information)
|
62
|
+
end
|
63
|
+
|
64
|
+
def list_object(object_class, object_method, title, without_board_information: false)
|
65
|
+
table_row_header = object_class.to_table_row_header(without_board_information: without_board_information)
|
66
|
+
|
67
|
+
rows = send(object_method).collect do |sprint|
|
68
|
+
sprint.to_table_row(without_board_information: without_board_information)
|
69
|
+
end
|
70
|
+
|
71
|
+
table = Terminal::Table.new(
|
72
|
+
title: title,
|
73
|
+
headings: table_row_header,
|
74
|
+
rows: rows
|
75
|
+
)
|
76
|
+
|
77
|
+
puts table
|
78
|
+
end
|
79
|
+
|
80
|
+
def sprints
|
81
|
+
jira_sprints.collect { |jira_sprint| Sprint.new(tool, jira_sprint) }
|
82
|
+
.uniq(&:id)
|
83
|
+
.sort
|
84
|
+
end
|
85
|
+
|
86
|
+
def jira_sprints
|
87
|
+
sprint_filter_string = tool.art_sprint_regex_defined? ? tool.art_sprint_regex : ""
|
88
|
+
sprint_filter_regex = Regexp.new(sprint_filter_string)
|
89
|
+
|
90
|
+
unfiltered_board_sprints.find_all { |sprint| sprint.name =~ sprint_filter_regex }
|
91
|
+
end
|
92
|
+
|
93
|
+
def unfiltered_board_sprints
|
94
|
+
all_board_sprints = []
|
95
|
+
|
96
|
+
sprint_compatible_boards.each do |board|
|
97
|
+
all_board_sprints.concat(unfiltered_jira_sprints(board))
|
98
|
+
end
|
99
|
+
|
100
|
+
all_board_sprints
|
101
|
+
end
|
102
|
+
|
103
|
+
def sprint_compatible_boards
|
104
|
+
boards.find_all(&:sprint_compatible?)
|
105
|
+
end
|
106
|
+
|
107
|
+
def boards
|
108
|
+
tool.board_controller.boards
|
109
|
+
end
|
110
|
+
|
111
|
+
def unfiltered_jira_sprints(board)
|
112
|
+
Helpers::Pagination.fetch_all_object_pages do |pagination_options|
|
113
|
+
fetch_jira_sprints(board, pagination_options)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def fetch_jira_sprints(board, options)
|
118
|
+
log.debug { "Fetching sprints from Jira (#{options.inspect})" }
|
119
|
+
|
120
|
+
fetched_sprints = board.jira_board.sprints(options)
|
121
|
+
|
122
|
+
log.debug do
|
123
|
+
"Board: #{board.name}: Fetched #sprints #{fetched_sprints.size} sprints from Jira: " \
|
124
|
+
"#{fetched_sprints.map(&:name).join(", ")}"
|
125
|
+
end
|
126
|
+
|
127
|
+
fetched_sprints
|
128
|
+
end
|
129
|
+
|
130
|
+
def unclosed_sprints
|
131
|
+
sprints.find_all { |sprint| sprint.state != SprintStateController::SprintState::CLOSED }
|
132
|
+
end
|
133
|
+
|
134
|
+
private
|
135
|
+
|
136
|
+
def calculate_unclosed_sprint_prefixes
|
137
|
+
unclosed_sprints.each_with_object({}) do |sprint, prefixes|
|
138
|
+
if Sprint::Name.respects_naming_convention?(sprint.name)
|
139
|
+
prefix = prefixes[sprint.name_prefix] ||= Sprint::Prefix.new(sprint.name_prefix)
|
140
|
+
prefix << sprint
|
141
|
+
else
|
142
|
+
log.warn do
|
143
|
+
"IGNORING sprint '#{sprint.name}' (board '#{board.name}'): " \
|
144
|
+
"it does not respect the naming conventions #{Sprint::Name::SPRINT_NAME_REGEX}."
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end.values
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Jira
|
4
|
+
module Auto
|
5
|
+
class Tool
|
6
|
+
class SprintStateController
|
7
|
+
attr_reader :jira_client, :sprint
|
8
|
+
|
9
|
+
def initialize(jira_client, sprint)
|
10
|
+
@jira_client = jira_client
|
11
|
+
@sprint = sprint
|
12
|
+
end
|
13
|
+
|
14
|
+
def transition_to(desired_state)
|
15
|
+
transition_state(desired_state)
|
16
|
+
end
|
17
|
+
|
18
|
+
module SprintState
|
19
|
+
ACTIVE = "active"
|
20
|
+
FUTURE = "future"
|
21
|
+
CLOSED = "closed"
|
22
|
+
end
|
23
|
+
|
24
|
+
STATE_TRANSITIONS =
|
25
|
+
{
|
26
|
+
SprintState::FUTURE => SprintState::ACTIVE,
|
27
|
+
SprintState::ACTIVE => SprintState::CLOSED
|
28
|
+
}.freeze
|
29
|
+
|
30
|
+
# TODO: - write unit tests
|
31
|
+
# TODO - fix infinite loop in case of invalid/in-existing state
|
32
|
+
def transition_state(desired_state)
|
33
|
+
current_state = sprint.state
|
34
|
+
|
35
|
+
loop do
|
36
|
+
log.debug do
|
37
|
+
"sprint_to_update = #{sprint.name}, current_state = #{current_state} desired_state = #{desired_state}"
|
38
|
+
end
|
39
|
+
|
40
|
+
break if current_state == desired_state
|
41
|
+
|
42
|
+
new_state = STATE_TRANSITIONS[current_state]
|
43
|
+
|
44
|
+
update_sprint_state(new_state)
|
45
|
+
|
46
|
+
current_state = new_state
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def update_sprint_state(new_state)
|
51
|
+
RequestBuilder::SprintStateUpdater
|
52
|
+
.new(jira_client, sprint: sprint, new_state: new_state)
|
53
|
+
.run
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "jira/auto/tool"
|
4
|
+
|
5
|
+
module Jira
|
6
|
+
module Auto
|
7
|
+
class Tool
|
8
|
+
class Team
|
9
|
+
def initialize(field_option)
|
10
|
+
@field_option = field_option
|
11
|
+
end
|
12
|
+
|
13
|
+
def name
|
14
|
+
@field_option.value
|
15
|
+
end
|
16
|
+
|
17
|
+
def id
|
18
|
+
@field_option.id
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "jira/auto/tool/until_date"
|
4
|
+
|
5
|
+
module Jira
|
6
|
+
module Auto
|
7
|
+
class Tool
|
8
|
+
class TeamSprintPrefixMapper
|
9
|
+
class Options
|
10
|
+
def self.add(tool, parser)
|
11
|
+
parser.section_header "Team Sprint Prefix Mapping"
|
12
|
+
|
13
|
+
parser.on("--team-sprint-mapping-list", "--tsm-list",
|
14
|
+
"List the sprint and team owning their content") do
|
15
|
+
tool.team_sprint_ticket_dispatcher.team_sprint_prefix_mapper.list_mappings
|
16
|
+
end
|
17
|
+
|
18
|
+
parser.on("--team-sprint-mapping-dispatch-tickets", "--tsm-dispatch",
|
19
|
+
"Dispatch tickets to sprint so the teams start timely working on them") do
|
20
|
+
tool.team_sprint_ticket_dispatcher.dispatch_tickets
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "terminal-table"
|
4
|
+
|
5
|
+
module Jira
|
6
|
+
module Auto
|
7
|
+
class Tool
|
8
|
+
class TeamSprintPrefixMapper
|
9
|
+
class NoMatchingTeamError < StandardError; end
|
10
|
+
|
11
|
+
class NoMatchingSprintPrefixError < StandardError; end
|
12
|
+
|
13
|
+
attr_reader :teams, :sprint_prefixes
|
14
|
+
|
15
|
+
def initialize(teams, sprint_prefixes)
|
16
|
+
@sprint_prefixes = sprint_prefixes
|
17
|
+
@teams = teams
|
18
|
+
end
|
19
|
+
|
20
|
+
def list_mappings
|
21
|
+
table = Terminal::Table.new(
|
22
|
+
title: "Team Sprint Mappings",
|
23
|
+
headings: ["Team", "Sprint Prefix"],
|
24
|
+
rows: teams.collect do |team|
|
25
|
+
[team, team_sprint_prefix_mappings.fetch(team, "!!__no matching sprint prefix__!!")]
|
26
|
+
end
|
27
|
+
)
|
28
|
+
|
29
|
+
puts table
|
30
|
+
end
|
31
|
+
|
32
|
+
def fetch_for(team_name)
|
33
|
+
team_sprint_prefix_mappings[team_name]
|
34
|
+
end
|
35
|
+
|
36
|
+
def fetch_for!(team_name)
|
37
|
+
fetch_for(team_name) or
|
38
|
+
raise NoMatchingSprintPrefixError,
|
39
|
+
no_matching_sprint_prefix_for_team_message(team_name)
|
40
|
+
end
|
41
|
+
|
42
|
+
def team_sprint_prefix_mappings
|
43
|
+
@team_sprint_prefix_mappings ||=
|
44
|
+
sprint_prefixes.collect(&:name).each_with_object({}) do |prefix_name, mappings|
|
45
|
+
found_team = map_prefix_name_to_team_name(prefix_name)
|
46
|
+
mappings[prefix_name] = found_team if found_team
|
47
|
+
end.invert
|
48
|
+
end
|
49
|
+
|
50
|
+
def map_prefix_name_to_team_name(prefix_name)
|
51
|
+
sub_team_in_prefix = prefix_name.split(Sprint::Name::SPRINT_PREFIX_SEPARATOR).last
|
52
|
+
|
53
|
+
teams.find { |team| team.end_with?(sub_team_in_prefix) }
|
54
|
+
end
|
55
|
+
|
56
|
+
def no_matching_sprint_prefix_for_team_message(team_name)
|
57
|
+
"No matching sprint prefix for team '#{team_name}' in #{team_sprint_prefix_mappings.inspect}"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Jira
|
4
|
+
module Auto
|
5
|
+
class Tool
|
6
|
+
class TeamSprintTicketDispatcher
|
7
|
+
attr_reader :jira_client, :tickets, :sprint_prefixes
|
8
|
+
|
9
|
+
def initialize(jira_client, tickets, sprint_prefixes)
|
10
|
+
@jira_client = jira_client
|
11
|
+
@tickets = tickets
|
12
|
+
@sprint_prefixes = sprint_prefixes
|
13
|
+
end
|
14
|
+
|
15
|
+
def dispatch_tickets
|
16
|
+
per_team_tickets do |team, tickets|
|
17
|
+
log.debug { "#{team}: dispatching tickets #{tickets.collect(&:key).join(", ")}" }
|
18
|
+
|
19
|
+
sprint_prefix_matching_team = sprint_prefix_for(team)
|
20
|
+
|
21
|
+
if sprint_prefix_matching_team.nil?
|
22
|
+
log.warn { team_sprint_prefix_mapper.no_matching_sprint_prefix_for_team_message(team) }
|
23
|
+
else
|
24
|
+
dispatch_tickets_to_prefix_sprints(sprint_prefix_matching_team, tickets)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def sprint_prefix_for(team)
|
30
|
+
team_sprint_prefix_mapper.fetch_for(team)
|
31
|
+
end
|
32
|
+
|
33
|
+
def dispatch_tickets_to_prefix_sprints(prefix_name, tickets)
|
34
|
+
prefix = sprint_prefixes.find { |sprint_prefix| sprint_prefix.name == prefix_name }
|
35
|
+
|
36
|
+
tickets.each do |ticket|
|
37
|
+
matched_sprint = match_ticket_to_prefix_sprint(prefix, ticket)
|
38
|
+
|
39
|
+
ticket.sprint = matched_sprint if matched_sprint
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def match_ticket_to_prefix_sprint(prefix, ticket)
|
44
|
+
ticket_start_time = Time.parse(ticket.expected_start_date).end_of_day
|
45
|
+
|
46
|
+
first_sprint_when_overdue_ticket(ticket_start_time, prefix) ||
|
47
|
+
prefix.sprints.find do |sprint|
|
48
|
+
ticket_start_time.between?(sprint.start_date, sprint.end_date)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def first_sprint_when_overdue_ticket(ticket_start_time, prefix)
|
53
|
+
first_sprint = prefix.sprints.first
|
54
|
+
|
55
|
+
ticket_start_time < first_sprint.start_date ? first_sprint : nil
|
56
|
+
end
|
57
|
+
|
58
|
+
def per_team_tickets(&)
|
59
|
+
return enum_for(:per_team_tickets) unless block_given?
|
60
|
+
|
61
|
+
team_ticket_map = tickets.group_by(&:implementation_team)
|
62
|
+
|
63
|
+
team_ticket_map.each(&)
|
64
|
+
end
|
65
|
+
|
66
|
+
def team_sprint_prefix_mapper
|
67
|
+
TeamSprintPrefixMapper.new(teams, sprint_prefixes)
|
68
|
+
end
|
69
|
+
|
70
|
+
def teams
|
71
|
+
tickets.collect(&:implementation_team).uniq.sort
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|