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,110 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "jira/auto/tool"
|
4
|
+
require_relative "helpers/environment_based_value"
|
5
|
+
|
6
|
+
module Jira
|
7
|
+
module Auto
|
8
|
+
class Tool
|
9
|
+
class Ticket
|
10
|
+
include Helpers::EnvironmentBasedValue
|
11
|
+
|
12
|
+
attr_reader :tool, :jira_ticket
|
13
|
+
|
14
|
+
def initialize(tool, jira_ticket, implementation_team = nil, expected_start_date = nil)
|
15
|
+
@tool = tool
|
16
|
+
@jira_ticket = jira_ticket
|
17
|
+
@implementation_team = implementation_team
|
18
|
+
@expected_start_date = expected_start_date
|
19
|
+
end
|
20
|
+
|
21
|
+
def key
|
22
|
+
jira_ticket.key
|
23
|
+
end
|
24
|
+
|
25
|
+
def sprint=(sprint)
|
26
|
+
@sprint = sprint
|
27
|
+
|
28
|
+
jira_ticket.save!({ "fields" => { tool.jira_sprint_field.id => sprint.id } })
|
29
|
+
rescue StandardError => e
|
30
|
+
message = "Failed to set sprint for ticket #{key} to #{sprint.id}! #{self}"
|
31
|
+
|
32
|
+
log.error { message }
|
33
|
+
|
34
|
+
raise e.class, message
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_s
|
38
|
+
"Ticket(#{to_s_fields.collect { |field| "#{field}: #{send(field)}" }.join(", ")})"
|
39
|
+
end
|
40
|
+
|
41
|
+
def to_s_fields
|
42
|
+
%i[key summary sprint implementation_team expected_start_date]
|
43
|
+
end
|
44
|
+
|
45
|
+
def sprint
|
46
|
+
jira_ticket.fields.fetch(tool.jira_sprint_field.id)
|
47
|
+
end
|
48
|
+
|
49
|
+
def jira_client
|
50
|
+
tool.jira_client
|
51
|
+
end
|
52
|
+
|
53
|
+
def jira_sprint_field
|
54
|
+
tool.jira_sprint_field
|
55
|
+
end
|
56
|
+
|
57
|
+
def expected_start_date
|
58
|
+
@expected_start_date || jira_field_value(expected_start_date_field.id)
|
59
|
+
end
|
60
|
+
|
61
|
+
IMPLEMENTATION_TEAM_VALUE_ATTRIBUTES = %w[value name].freeze
|
62
|
+
|
63
|
+
def implementation_team
|
64
|
+
@implementation_team ||=
|
65
|
+
begin
|
66
|
+
attributes = implementation_team_attributes
|
67
|
+
|
68
|
+
if attributes.nil?
|
69
|
+
nil
|
70
|
+
else
|
71
|
+
IMPLEMENTATION_TEAM_VALUE_ATTRIBUTES.any? { |attr| attributes.key?(attr) } or
|
72
|
+
raise "Implementation team #{IMPLEMENTATION_TEAM_VALUE_ATTRIBUTES.join(" and ")} " \
|
73
|
+
"attributes not found in #{attributes}!"
|
74
|
+
|
75
|
+
attributes.values_at(*IMPLEMENTATION_TEAM_VALUE_ATTRIBUTES).compact.first
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def implementation_team_attributes
|
81
|
+
jira_field_value(implementation_team_field.id)
|
82
|
+
end
|
83
|
+
|
84
|
+
def implementation_team_field
|
85
|
+
tool.implementation_team_field
|
86
|
+
end
|
87
|
+
|
88
|
+
def expected_start_date_field
|
89
|
+
tool.expected_start_date_field
|
90
|
+
end
|
91
|
+
|
92
|
+
def summary
|
93
|
+
jira_ticket.summary
|
94
|
+
end
|
95
|
+
|
96
|
+
def jira_field_value(field_id = caller_locations(1, 1).first.base_label)
|
97
|
+
log.debug { "jira_field_value(#{field_id})" }
|
98
|
+
|
99
|
+
field = jira_ticket.fields.fetch(field_id) do |id|
|
100
|
+
raise "#{id}: value not found in #{jira_ticket.fields}"
|
101
|
+
end
|
102
|
+
|
103
|
+
log.debug { "jira_field_value(#{field_id}), field: #{field}" }
|
104
|
+
|
105
|
+
field
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "jira/auto/tool/helpers/overridable_time"
|
4
|
+
require_relative "request_builder/sprint_creator"
|
5
|
+
require_relative "request_builder/sprint_state_updater"
|
6
|
+
|
7
|
+
module Jira
|
8
|
+
module Auto
|
9
|
+
class Tool
|
10
|
+
class UntilDate
|
11
|
+
class FormatError < RuntimeError; end
|
12
|
+
|
13
|
+
attr_reader :time
|
14
|
+
|
15
|
+
NAMED_DATES = %i[
|
16
|
+
today
|
17
|
+
current_quarter
|
18
|
+
coming_quarter
|
19
|
+
].freeze
|
20
|
+
|
21
|
+
NAMED_DATE_REGEX = /^(#{NAMED_DATES.join("|")})$/
|
22
|
+
|
23
|
+
INCLUDES_TIME_REGEX = /.+\s\d{2}:\d{2}.+/
|
24
|
+
STARTS_WITH_DATE_REGEX = /^\d{4}-\d{2}-\d{2}$/ # TODO : use something more reliable like the Chronic gem
|
25
|
+
TIME_UNTIL_MIDNIGHT_UTC = " 23:59:59 UTC"
|
26
|
+
|
27
|
+
def initialize(date_string)
|
28
|
+
@time =
|
29
|
+
case date_string
|
30
|
+
when NAMED_DATE_REGEX
|
31
|
+
send(date_string.intern)
|
32
|
+
else
|
33
|
+
parse_date(date_string)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def current_date_time
|
38
|
+
Helpers::OverridableTime.now.utc
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def parse_date(date_string)
|
44
|
+
case date_string
|
45
|
+
when INCLUDES_TIME_REGEX
|
46
|
+
Time.parse(date_string)
|
47
|
+
when STARTS_WITH_DATE_REGEX
|
48
|
+
Time.parse(date_string + TIME_UNTIL_MIDNIGHT_UTC).end_of_day
|
49
|
+
else
|
50
|
+
raise FormatError, "date string '#{date_string}' is not in a supported format"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def today
|
55
|
+
current_date_time.end_of_day
|
56
|
+
end
|
57
|
+
|
58
|
+
def current_quarter
|
59
|
+
today.end_of_quarter
|
60
|
+
end
|
61
|
+
|
62
|
+
def coming_quarter
|
63
|
+
(current_quarter + 1.day).end_of_quarter
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,216 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/object/blank"
|
4
|
+
require "date"
|
5
|
+
require "active_support/core_ext/numeric/time"
|
6
|
+
require "active_support/core_ext/date/calculations"
|
7
|
+
require "jira-ruby"
|
8
|
+
|
9
|
+
require_relative "tool/config"
|
10
|
+
require_relative "tool/board_controller"
|
11
|
+
require_relative "tool/helpers/environment_based_value"
|
12
|
+
require_relative "tool/project"
|
13
|
+
require_relative "tool/rate_limited_jira_client"
|
14
|
+
require_relative "tool/request_builder"
|
15
|
+
require_relative "tool/setup_logging"
|
16
|
+
require_relative "tool/sprint_controller"
|
17
|
+
require_relative "tool/sprint_state_controller"
|
18
|
+
require_relative "tool/team"
|
19
|
+
require_relative "tool/team_sprint_prefix_mapper"
|
20
|
+
require_relative "tool/team_sprint_ticket_dispatcher"
|
21
|
+
require_relative "tool/ticket"
|
22
|
+
require_relative "tool/version"
|
23
|
+
|
24
|
+
module Jira
|
25
|
+
module Auto
|
26
|
+
class Tool
|
27
|
+
extend Helpers::EnvironmentBasedValue
|
28
|
+
|
29
|
+
class Error < StandardError; end
|
30
|
+
|
31
|
+
def config
|
32
|
+
@config ||= Config.new(self)
|
33
|
+
end
|
34
|
+
|
35
|
+
def board_name
|
36
|
+
jira_board_name
|
37
|
+
end
|
38
|
+
|
39
|
+
def board
|
40
|
+
found_board = boards.find { |a_board| a_board.name == board_name } or
|
41
|
+
raise KeyError, "Board '#{board_name}' not found!"
|
42
|
+
|
43
|
+
log.debug { "Jira board '#{board_name}' found: #{found_board}!" }
|
44
|
+
|
45
|
+
found_board
|
46
|
+
end
|
47
|
+
|
48
|
+
def boards
|
49
|
+
board_controller.boards
|
50
|
+
end
|
51
|
+
|
52
|
+
def fetch_sprint(sprint_name)
|
53
|
+
sprint_controller.sprints.find { |sprint| sprint.name == sprint_name } or
|
54
|
+
raise KeyError, "Sprint '#{sprint_name}' not found for #{board.name}!" # TODO: - test this condition
|
55
|
+
end
|
56
|
+
|
57
|
+
ATTRIBUTES_TO_IGNORE_FOR_SPRINT_CREATION = %i[state].freeze
|
58
|
+
|
59
|
+
def create_sprint(attributes)
|
60
|
+
attributes_for_sprint_creation = attributes.except(*ATTRIBUTES_TO_IGNORE_FOR_SPRINT_CREATION)
|
61
|
+
|
62
|
+
created_sprint = create_future_sprint(attributes_for_sprint_creation)
|
63
|
+
|
64
|
+
log.debug { created_sprint.inspect }
|
65
|
+
|
66
|
+
transition_sprint_state(created_sprint, desired_state: attributes.fetch(:state))
|
67
|
+
end
|
68
|
+
|
69
|
+
def transition_sprint_state(created_sprint, desired_state:)
|
70
|
+
SprintStateController.new(jira_client, created_sprint).transition_to(desired_state)
|
71
|
+
end
|
72
|
+
|
73
|
+
def jira_client
|
74
|
+
RateLimitedJiraClient.new(jira_client_options,
|
75
|
+
rate_interval:
|
76
|
+
jat_rate_interval_in_seconds_when_defined_else(
|
77
|
+
RateLimitedJiraClient::NO_RATE_INTERVAL_IN_SECONDS
|
78
|
+
).to_i,
|
79
|
+
rate_limit:
|
80
|
+
jat_rate_limit_in_seconds_when_defined_else(
|
81
|
+
RateLimitedJiraClient::NO_RATE_LIMIT_IN_SECONDS
|
82
|
+
).to_i)
|
83
|
+
end
|
84
|
+
|
85
|
+
def jira_client_options
|
86
|
+
{
|
87
|
+
username: jira_username,
|
88
|
+
password: jira_api_token,
|
89
|
+
site: jira_site_url,
|
90
|
+
context_path: jira_context_path_when_defined_else(""),
|
91
|
+
auth_type: :basic,
|
92
|
+
http_debug: jira_http_debug?
|
93
|
+
}
|
94
|
+
end
|
95
|
+
|
96
|
+
def jira_http_debug?
|
97
|
+
value = if config.key?(:jira_http_debug)
|
98
|
+
config[:jira_http_debug]
|
99
|
+
else
|
100
|
+
jira_http_debug_defined? && jira_http_debug
|
101
|
+
end
|
102
|
+
|
103
|
+
result = case value
|
104
|
+
when String
|
105
|
+
value =~ /^(true|yes|1)$/i
|
106
|
+
else
|
107
|
+
value
|
108
|
+
end
|
109
|
+
|
110
|
+
log.debug { "jira_http_debug? = #{result} (jira_http_debug = #{value}, config = #{config.inspect})" }
|
111
|
+
|
112
|
+
result
|
113
|
+
end
|
114
|
+
|
115
|
+
def jira_request_path(path)
|
116
|
+
jira_client.options[:context_path] + path
|
117
|
+
end
|
118
|
+
|
119
|
+
def jira_base_url
|
120
|
+
jira_client.options[:site] + jira_client.options[:context_path]
|
121
|
+
end
|
122
|
+
|
123
|
+
def jira_url(url)
|
124
|
+
jira_base_url + url
|
125
|
+
end
|
126
|
+
|
127
|
+
%i[
|
128
|
+
art_sprint_regex
|
129
|
+
expected_start_date_field_name
|
130
|
+
implementation_team_field_name
|
131
|
+
jat_rate_limit_in_seconds
|
132
|
+
jat_rate_interval_in_seconds
|
133
|
+
jat_tickets_for_team_sprint_ticket_dispatcher_jql
|
134
|
+
jira_api_token
|
135
|
+
jira_board_name
|
136
|
+
jira_board_name_regex
|
137
|
+
jira_context_path
|
138
|
+
jira_http_debug
|
139
|
+
jira_project_key
|
140
|
+
jira_site_url
|
141
|
+
jira_username
|
142
|
+
jira_sprint_field_name
|
143
|
+
].each do |method_name|
|
144
|
+
define_overridable_environment_based_value(method_name)
|
145
|
+
end
|
146
|
+
|
147
|
+
def board_controller
|
148
|
+
@board_controller ||= BoardController.new(self)
|
149
|
+
end
|
150
|
+
|
151
|
+
def sprint_controller
|
152
|
+
@sprint_controller ||= SprintController.new(self, board)
|
153
|
+
end
|
154
|
+
|
155
|
+
def project_ticket_fields
|
156
|
+
project.ticket_fields
|
157
|
+
end
|
158
|
+
|
159
|
+
def project
|
160
|
+
@project ||= Project.find(self, jira_project_key)
|
161
|
+
end
|
162
|
+
|
163
|
+
def expected_start_date_field(field_name = expected_start_date_field_name)
|
164
|
+
field_controller.expected_start_date_field(field_name)
|
165
|
+
end
|
166
|
+
|
167
|
+
def implementation_team_field(field_name = implementation_team_field_name)
|
168
|
+
field_controller.implementation_team_field(field_name)
|
169
|
+
end
|
170
|
+
|
171
|
+
def jira_sprint_field(field_name = jira_sprint_field_name)
|
172
|
+
field_controller.sprint_field(field_name)
|
173
|
+
end
|
174
|
+
|
175
|
+
def field_controller
|
176
|
+
@field_controller ||= FieldController.new(jira_client)
|
177
|
+
end
|
178
|
+
|
179
|
+
def unclosed_sprints
|
180
|
+
sprint_controller.unclosed_sprints
|
181
|
+
end
|
182
|
+
|
183
|
+
def unclosed_sprint_prefixes
|
184
|
+
sprint_controller.unclosed_sprint_prefixes
|
185
|
+
end
|
186
|
+
|
187
|
+
def teams
|
188
|
+
implementation_team_field.values.collect { |value| Team.new(value) }.collect(&:name)
|
189
|
+
end
|
190
|
+
|
191
|
+
def tickets(jql = "project = #{project.key}")
|
192
|
+
jira_client.Issue.jql(jql).collect { |jira_ticket| Ticket.new(self, jira_ticket) }
|
193
|
+
rescue StandardError => e
|
194
|
+
raise <<~EOEM
|
195
|
+
Error fetching project tickets: Something went wrong:
|
196
|
+
__________ Please check your Jira configuration and your query ______
|
197
|
+
jql = #{jql}
|
198
|
+
#{e.class}: #{e.message}
|
199
|
+
EOEM
|
200
|
+
end
|
201
|
+
|
202
|
+
def team_sprint_ticket_dispatcher
|
203
|
+
TeamSprintTicketDispatcher.new(jira_client,
|
204
|
+
tickets(jat_tickets_for_team_sprint_ticket_dispatcher_jql),
|
205
|
+
unclosed_sprint_prefixes)
|
206
|
+
end
|
207
|
+
|
208
|
+
private
|
209
|
+
|
210
|
+
def create_future_sprint(attributes)
|
211
|
+
RequestBuilder::SprintCreator
|
212
|
+
.create_sprint(self, board.id, attributes)
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
@@ -0,0 +1,179 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rspec"
|
4
|
+
|
5
|
+
require "jira/auto/tool/board/cache"
|
6
|
+
|
7
|
+
module Jira
|
8
|
+
module Auto
|
9
|
+
class Tool
|
10
|
+
class Board
|
11
|
+
class Cache
|
12
|
+
RSpec.describe Cache do
|
13
|
+
let(:cache) { described_class.new(tool) }
|
14
|
+
let(:tool) { instance_double(Tool, jira_client: jira_client) }
|
15
|
+
let(:jira_client) { jira_resource_double(JIRA::Client) }
|
16
|
+
let(:board_factory) { double("BoardFactory") } # rubocop:disable RSpec/VerifiedDoubles
|
17
|
+
|
18
|
+
describe "#boards" do
|
19
|
+
before { allow(cache).to receive_messages(valid?: valid?) }
|
20
|
+
|
21
|
+
context "when cache is valid" do
|
22
|
+
let(:valid?) { true }
|
23
|
+
|
24
|
+
let(:raw_content) do
|
25
|
+
{
|
26
|
+
"boards" => [
|
27
|
+
{ "id" => 16, "name" => "name for board #16" },
|
28
|
+
{ "id" => 32, "name" => "name for board #32" }
|
29
|
+
]
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
before do
|
34
|
+
allow(cache).to receive_messages(raw_content: raw_content)
|
35
|
+
allow(jira_client).to receive_messages(Board: board_factory)
|
36
|
+
|
37
|
+
allow(board_factory)
|
38
|
+
.to receive(:build).with({ "id" => 16, "name" => "name for board #16" })
|
39
|
+
.and_return(:first_jira_board)
|
40
|
+
|
41
|
+
allow(Board).to receive(:new).with(tool, :first_jira_board).and_return(:first_board)
|
42
|
+
|
43
|
+
allow(board_factory)
|
44
|
+
.to receive(:build).with({ "id" => 32, "name" => "name for board #32" })
|
45
|
+
.and_return(:second_jira_board)
|
46
|
+
|
47
|
+
allow(Board).to receive(:new).with(tool, :second_jira_board).and_return(:second_board)
|
48
|
+
end
|
49
|
+
|
50
|
+
it "returns the boards" do
|
51
|
+
expect(cache.boards).to eq(%i[first_board second_board])
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
context "when cache is invalid" do
|
56
|
+
let(:valid?) { false }
|
57
|
+
|
58
|
+
it do
|
59
|
+
expect { cache.boards }
|
60
|
+
.to raise_error(RuntimeError, "This method should not be used since the cache is invalid")
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe "#save" do
|
66
|
+
def build_board(id)
|
67
|
+
instance_double(
|
68
|
+
Board,
|
69
|
+
jira_board: jira_resource_double(attrs: { "id" => id, "name" => "name for board ##{id}" })
|
70
|
+
)
|
71
|
+
end
|
72
|
+
|
73
|
+
let(:boards) { [4, 8].collect { |id| build_board(id) } }
|
74
|
+
let(:expected_board_yaml) do
|
75
|
+
<<~EOBYAML
|
76
|
+
---
|
77
|
+
boards:
|
78
|
+
- id: 4
|
79
|
+
name: 'name for board #4'
|
80
|
+
- id: 8
|
81
|
+
name: 'name for board #8'
|
82
|
+
EOBYAML
|
83
|
+
end
|
84
|
+
|
85
|
+
before do
|
86
|
+
allow(cache).to receive_messages(file_path: "path/to/cache.yml")
|
87
|
+
end
|
88
|
+
|
89
|
+
it "returns the boards" do
|
90
|
+
expect(File).to receive(:write).with("path/to/cache.yml", expected_board_yaml)
|
91
|
+
|
92
|
+
expect(cache.save(boards)).to eq(boards)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
describe "#clear" do
|
97
|
+
before do
|
98
|
+
allow(cache).to receive_messages(file_path: "path/to/cache.yml")
|
99
|
+
allow(FileUtils).to receive(:rm_f).with("path/to/cache.yml")
|
100
|
+
end
|
101
|
+
|
102
|
+
it "deletes the cache file" do
|
103
|
+
cache.clear
|
104
|
+
|
105
|
+
expect(FileUtils).to have_received(:rm_f).with("path/to/cache.yml")
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
describe "#valid?" do
|
110
|
+
before do
|
111
|
+
allow(cache).to receive_messages(file_path: "path/to/cache.yml")
|
112
|
+
|
113
|
+
allow(File).to receive(:exist?).with("path/to/cache.yml").and_return(exist?)
|
114
|
+
end
|
115
|
+
|
116
|
+
context "when cache file exists" do
|
117
|
+
let(:exist?) { true }
|
118
|
+
|
119
|
+
before do
|
120
|
+
allow(cache).to receive_messages(expired?: expired?)
|
121
|
+
end
|
122
|
+
|
123
|
+
context "when cache has not expired" do
|
124
|
+
let(:expired?) { false }
|
125
|
+
|
126
|
+
it { expect(cache).to be_valid }
|
127
|
+
end
|
128
|
+
|
129
|
+
context "when cache has expired" do
|
130
|
+
let(:expired?) { true }
|
131
|
+
|
132
|
+
it { expect(cache).not_to be_valid }
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
describe "#expired?" do
|
138
|
+
before do
|
139
|
+
allow(cache).to receive_messages(cached_at: cached_at)
|
140
|
+
end
|
141
|
+
|
142
|
+
context "when expired" do
|
143
|
+
let(:cached_at) { Time.now - 1.day }
|
144
|
+
|
145
|
+
it { expect(cache.send(:expired?)).to be_truthy }
|
146
|
+
end
|
147
|
+
|
148
|
+
context "when not expired" do
|
149
|
+
let(:cached_at) { Time.now }
|
150
|
+
|
151
|
+
it { expect(cache.send(:expired?)).to be_falsy }
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
describe "#one_hour_ago" do
|
156
|
+
let(:time_now) { Time.parse("2025-02-21 20:55:00 UTC") }
|
157
|
+
|
158
|
+
before { allow(Helpers::OverridableTime).to receive_messages(now: time_now) }
|
159
|
+
|
160
|
+
it { expect(cache.send(:one_day_ago)).to eq(Time.parse("2025-02-20 20:55:00 UTC")) }
|
161
|
+
end
|
162
|
+
|
163
|
+
describe "#file_path" do
|
164
|
+
let(:config) { instance_double(Config, dir: "path/to/config/dir") }
|
165
|
+
|
166
|
+
before do
|
167
|
+
allow(tool).to receive_messages(config: config)
|
168
|
+
end
|
169
|
+
|
170
|
+
it "is located in the config dir" do
|
171
|
+
expect(cache.send(:file_path)).to eq("path/to/config/dir/jira-auto-tool.cache.yml")
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "jira/auto/tool/board/unavailable_board"
|
4
|
+
|
5
|
+
module Jira
|
6
|
+
module Auto
|
7
|
+
class Tool
|
8
|
+
class Board
|
9
|
+
class UnavailableBoard < Board
|
10
|
+
RSpec.describe UnavailableBoard do
|
11
|
+
let(:tool) { instance_double(Tool) }
|
12
|
+
let(:board) { described_class.new(tool, 4) }
|
13
|
+
|
14
|
+
it { expect(board.id).to eq(4) }
|
15
|
+
it { expect(board.name).to eq("N/A") }
|
16
|
+
it { expect(board.url).to eq("N/A") }
|
17
|
+
|
18
|
+
describe "#<=>" do
|
19
|
+
def new_board(id)
|
20
|
+
described_class.new(tool, id)
|
21
|
+
end
|
22
|
+
|
23
|
+
let(:board_with_same_id) { new_board(4) }
|
24
|
+
|
25
|
+
it { expect(board <=> board_with_same_id).to eq(0) }
|
26
|
+
it { expect(new_board(1) <=> board).to eq(-1) }
|
27
|
+
it { expect(new_board(5) <=> board).to eq(1) }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rspec"
|
4
|
+
|
5
|
+
require "jira/auto/tool/board_controller/options"
|
6
|
+
|
7
|
+
module Jira
|
8
|
+
module Auto
|
9
|
+
class Tool
|
10
|
+
class BoardController
|
11
|
+
class Options
|
12
|
+
# TODO: clean this autogenerated code
|
13
|
+
# rubocop:disable RSpec/MultipleExpectations
|
14
|
+
RSpec.describe Options do
|
15
|
+
let(:tool) { instance_double(Tool, board_controller: board_controller) }
|
16
|
+
let(:board_controller) { instance_double(BoardController) }
|
17
|
+
let(:parser) { instance_double(OptionParser) }
|
18
|
+
|
19
|
+
describe ".add" do
|
20
|
+
it "configures board cache clear option" do
|
21
|
+
expect(parser).to receive(:section_header).with("Board")
|
22
|
+
expect(parser).to receive(:on).with("--board-name=STRING", String)
|
23
|
+
expect(parser).to receive(:on).with("--board-cache-clear", any_args) do |&block|
|
24
|
+
expect(board_controller).to receive(:clear_cache)
|
25
|
+
block.call
|
26
|
+
end
|
27
|
+
|
28
|
+
expect(parser).to receive(:on).with("--board-list", any_args)
|
29
|
+
|
30
|
+
described_class.add(tool, parser)
|
31
|
+
end
|
32
|
+
|
33
|
+
it "configures board list option" do
|
34
|
+
expect(parser).to receive(:section_header).with("Board")
|
35
|
+
expect(parser).to receive(:on).with("--board-name=STRING", String)
|
36
|
+
expect(parser).to receive(:on).with("--board-cache-clear", any_args)
|
37
|
+
|
38
|
+
expect(parser).to receive(:on).with("--board-list", any_args) do |&block|
|
39
|
+
expect(board_controller).to receive(:list_boards)
|
40
|
+
block.call
|
41
|
+
end
|
42
|
+
|
43
|
+
described_class.add(tool, parser)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
# rubocop:enable RSpec/MultipleExpectations
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|