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,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "ratelimit"
|
4
|
+
require "redis"
|
5
|
+
|
6
|
+
require "jira/auto/tool"
|
7
|
+
|
8
|
+
module Jira
|
9
|
+
module Auto
|
10
|
+
class Tool
|
11
|
+
class RateLimitedJiraClient < JIRA::Client
|
12
|
+
NO_RATE_LIMIT_IN_SECONDS = 0
|
13
|
+
NO_RATE_INTERVAL_IN_SECONDS = 0
|
14
|
+
|
15
|
+
attr_reader :rate_interval, :rate_limit
|
16
|
+
|
17
|
+
def initialize(options, rate_interval: 1, rate_limit: 1)
|
18
|
+
super(options)
|
19
|
+
@rate_interval = rate_interval
|
20
|
+
@rate_limit = rate_limit
|
21
|
+
end
|
22
|
+
|
23
|
+
alias original_request request
|
24
|
+
def request(*args)
|
25
|
+
return original_request(*args) if rate_limit == NO_RATE_LIMIT_IN_SECONDS
|
26
|
+
|
27
|
+
rate_limiter.exec_within_threshold(rate_limiter_key, interval: rate_interval, threshold: rate_limit) do
|
28
|
+
response = original_request(*args)
|
29
|
+
|
30
|
+
rate_limiter.add(rate_limiter_key)
|
31
|
+
|
32
|
+
response
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def rate_limiter_key
|
37
|
+
"jira_auto_tool_api_requests"
|
38
|
+
end
|
39
|
+
|
40
|
+
def rate_limiter
|
41
|
+
self.class.rate_limiter(rate_limiter_key, rate_interval)
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.rate_limiter(rate_limiter_key, rate_interval)
|
45
|
+
@rate_limiter ||= Ratelimit.new(rate_limiter_key, bucket_interval: rate_interval)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "jira/auto/tool/request_builder/get"
|
4
|
+
require "jira/auto/tool/field_option"
|
5
|
+
|
6
|
+
module Jira
|
7
|
+
module Auto
|
8
|
+
class Tool
|
9
|
+
class RequestBuilder
|
10
|
+
class FieldContextFetcher < Get
|
11
|
+
def self.fetch_field_contexts(field)
|
12
|
+
log.debug { "fetching field contexts for #{field}" }
|
13
|
+
|
14
|
+
response = new(field.jira_client, field).run
|
15
|
+
|
16
|
+
field_contexts =
|
17
|
+
JSON
|
18
|
+
.parse(response.body)
|
19
|
+
.symbolize_keys
|
20
|
+
.fetch(:values)
|
21
|
+
.collect(&:symbolize_keys)
|
22
|
+
|
23
|
+
log.debug { "field #{field} contexts fetched: #{field_contexts}" }
|
24
|
+
|
25
|
+
field_contexts
|
26
|
+
end
|
27
|
+
|
28
|
+
protected
|
29
|
+
|
30
|
+
attr_reader :field
|
31
|
+
|
32
|
+
def initialize(jira_client, field)
|
33
|
+
super(jira_client)
|
34
|
+
@field = field
|
35
|
+
end
|
36
|
+
|
37
|
+
def request_payload
|
38
|
+
nil
|
39
|
+
end
|
40
|
+
|
41
|
+
def http_verb
|
42
|
+
:get
|
43
|
+
end
|
44
|
+
|
45
|
+
def expected_response
|
46
|
+
200
|
47
|
+
end
|
48
|
+
|
49
|
+
def request_headers
|
50
|
+
nil
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def end_date
|
56
|
+
start_date + length_in_days.days
|
57
|
+
end
|
58
|
+
|
59
|
+
def start_date
|
60
|
+
start.is_a?(Time) ? start : Time.parse(start)
|
61
|
+
end
|
62
|
+
|
63
|
+
def request_path
|
64
|
+
"/rest/api/3/field/#{field.id}/context"
|
65
|
+
end
|
66
|
+
|
67
|
+
def success_message_prefix
|
68
|
+
"Sprint created successfully"
|
69
|
+
end
|
70
|
+
|
71
|
+
def error_message_prefix
|
72
|
+
"Error creating sprint"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "jira/auto/tool/request_builder/get"
|
4
|
+
require "jira/auto/tool/field_option"
|
5
|
+
|
6
|
+
module Jira
|
7
|
+
module Auto
|
8
|
+
class Tool
|
9
|
+
class RequestBuilder
|
10
|
+
class FieldOptionFetcher < Get
|
11
|
+
def self.fetch_field_options(field)
|
12
|
+
log.debug { "fetching field options for #{field}" }
|
13
|
+
|
14
|
+
response = new(field.jira_client, field, field.field_context).run
|
15
|
+
|
16
|
+
options = JSON.parse(response.body)
|
17
|
+
|
18
|
+
field_options = options.fetch("values", []).collect do |option|
|
19
|
+
FieldOption.new(field.jira_client, option["id"], option["value"])
|
20
|
+
end
|
21
|
+
|
22
|
+
log.debug { "field #{field} options fetched: #{field_options}" }
|
23
|
+
|
24
|
+
field_options
|
25
|
+
end
|
26
|
+
|
27
|
+
protected
|
28
|
+
|
29
|
+
attr_reader :field
|
30
|
+
|
31
|
+
def initialize(jira_client, field, field_context)
|
32
|
+
super(jira_client)
|
33
|
+
@field = field
|
34
|
+
@field_context = field_context
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def request_path
|
40
|
+
"/rest/api/3/field/#{field.id}/context/#{@field_context.fetch(:id)}/option"
|
41
|
+
end
|
42
|
+
|
43
|
+
def success_message_prefix
|
44
|
+
"Field options fetched successfully"
|
45
|
+
end
|
46
|
+
|
47
|
+
def error_message_prefix
|
48
|
+
"Error fetching field options"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "jira/auto/tool/request_builder"
|
4
|
+
|
5
|
+
module Jira
|
6
|
+
module Auto
|
7
|
+
class Tool
|
8
|
+
class RequestBuilder
|
9
|
+
class Get < RequestBuilder
|
10
|
+
def request_payload
|
11
|
+
nil
|
12
|
+
end
|
13
|
+
|
14
|
+
def http_verb
|
15
|
+
:get
|
16
|
+
end
|
17
|
+
|
18
|
+
def expected_response
|
19
|
+
200
|
20
|
+
end
|
21
|
+
|
22
|
+
def request_headers
|
23
|
+
nil
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/object/blank"
|
4
|
+
|
5
|
+
module Jira
|
6
|
+
module Auto
|
7
|
+
class Tool
|
8
|
+
class RequestBuilder
|
9
|
+
class SprintCreator < RequestBuilder
|
10
|
+
def self.create_sprint(tool, original_board_id, attributes)
|
11
|
+
log.debug { "create_sprint(#{attributes.inspect})" }
|
12
|
+
|
13
|
+
creation_response = new(tool.jira_client, original_board_id, attributes).run
|
14
|
+
|
15
|
+
created_sprint = Sprint.new(tool,
|
16
|
+
tool.jira_client.Sprint.find(JSON.parse(creation_response.body).fetch("id")))
|
17
|
+
|
18
|
+
log.debug { "created_sprint: #{created_sprint.id}" }
|
19
|
+
|
20
|
+
created_sprint
|
21
|
+
end
|
22
|
+
|
23
|
+
protected
|
24
|
+
|
25
|
+
attr_reader :origin_board_id, :attributes
|
26
|
+
|
27
|
+
def initialize(jira_client, origin_board_id, attributes)
|
28
|
+
super(jira_client)
|
29
|
+
|
30
|
+
@origin_board_id = origin_board_id
|
31
|
+
@attributes = attributes
|
32
|
+
end
|
33
|
+
|
34
|
+
def request_payload
|
35
|
+
payload = { originBoardId: origin_board_id, name: attributes.fetch(:name) }
|
36
|
+
|
37
|
+
payload[:startDate] = Sprint.date_for_save(start_date) if start_date
|
38
|
+
payload[:endDate] = Sprint.date_for_save(end_date) if end_date
|
39
|
+
|
40
|
+
payload
|
41
|
+
end
|
42
|
+
|
43
|
+
def start_date
|
44
|
+
@start_date ||= extract_date(attributes[:start_date])
|
45
|
+
end
|
46
|
+
|
47
|
+
def end_date
|
48
|
+
@end_date ||=
|
49
|
+
begin
|
50
|
+
date = extract_date(attributes[:end_date])
|
51
|
+
|
52
|
+
if date
|
53
|
+
length = attributes[:length_in_days]
|
54
|
+
|
55
|
+
unless length.blank?
|
56
|
+
raise ArgumentError,
|
57
|
+
"Should not provide both :end_date (#{date.inspect}) and :length_in_days (#{length})!"
|
58
|
+
end
|
59
|
+
|
60
|
+
date
|
61
|
+
elsif start_date && length_in_days
|
62
|
+
start_date + length_in_days.days
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def length_in_days
|
68
|
+
@length_in_days ||=
|
69
|
+
begin
|
70
|
+
raise ArgumentError, "Should provide :start_date in order to use :length_in_days!" unless start_date
|
71
|
+
|
72
|
+
length = attributes[:length_in_days]
|
73
|
+
|
74
|
+
length.blank? ? nil : Integer(length)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def http_verb
|
79
|
+
:post
|
80
|
+
end
|
81
|
+
|
82
|
+
def expected_response
|
83
|
+
201
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
def extract_date(date)
|
89
|
+
case date
|
90
|
+
when Time
|
91
|
+
date
|
92
|
+
else
|
93
|
+
date.blank? ? nil : Time.parse(date)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def request_path
|
98
|
+
"/rest/agile/1.0/sprint"
|
99
|
+
end
|
100
|
+
|
101
|
+
def success_message_prefix
|
102
|
+
"Sprint created successfully"
|
103
|
+
end
|
104
|
+
|
105
|
+
def error_message_prefix
|
106
|
+
"Error creating sprint"
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Jira
|
4
|
+
module Auto
|
5
|
+
class Tool
|
6
|
+
class RequestBuilder
|
7
|
+
class SprintStateUpdater < RequestBuilder
|
8
|
+
attr_reader :sprint, :new_state
|
9
|
+
|
10
|
+
def initialize(jira_client, sprint:, new_state:)
|
11
|
+
super(jira_client)
|
12
|
+
|
13
|
+
@sprint = sprint
|
14
|
+
@new_state = new_state
|
15
|
+
end
|
16
|
+
|
17
|
+
ATTRIBUTES_TO_INCLUDE_FOR_STATE_UPDATE = %i[
|
18
|
+
id
|
19
|
+
self
|
20
|
+
name
|
21
|
+
startDate
|
22
|
+
endDate
|
23
|
+
originBoardId
|
24
|
+
].freeze
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def request_path
|
29
|
+
"/rest/agile/1.0/sprint/#{sprint.id}"
|
30
|
+
end
|
31
|
+
|
32
|
+
def request_payload
|
33
|
+
attributes = sprint.attrs.symbolize_keys
|
34
|
+
|
35
|
+
ATTRIBUTES_TO_INCLUDE_FOR_STATE_UPDATE.each_with_object({}) do |key, result|
|
36
|
+
result[key] = attributes[key]
|
37
|
+
end
|
38
|
+
.merge({ state: new_state })
|
39
|
+
end
|
40
|
+
|
41
|
+
def http_verb
|
42
|
+
:put
|
43
|
+
end
|
44
|
+
|
45
|
+
def expected_response
|
46
|
+
200
|
47
|
+
end
|
48
|
+
|
49
|
+
def error_message_prefix
|
50
|
+
"Error updating auto state"
|
51
|
+
end
|
52
|
+
|
53
|
+
def success_message_prefix
|
54
|
+
"Sprint state updated successfully"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "request_builder/sprint_creator"
|
4
|
+
require_relative "request_builder/sprint_state_updater"
|
5
|
+
|
6
|
+
module Jira
|
7
|
+
module Auto
|
8
|
+
class Tool
|
9
|
+
class RequestBuilder
|
10
|
+
attr_reader :jira_client
|
11
|
+
|
12
|
+
def initialize(jira_client)
|
13
|
+
@jira_client = jira_client
|
14
|
+
end
|
15
|
+
|
16
|
+
def run
|
17
|
+
response = send_request
|
18
|
+
|
19
|
+
if response.code.to_i == expected_response
|
20
|
+
log.debug { "#{success_message_prefix}: #{response.body}" }
|
21
|
+
else
|
22
|
+
error_message = "#{error_message_prefix}: #{response.code} - #{response.body}"
|
23
|
+
log.error { error_message }
|
24
|
+
raise error_message
|
25
|
+
end
|
26
|
+
|
27
|
+
response
|
28
|
+
end
|
29
|
+
|
30
|
+
protected
|
31
|
+
|
32
|
+
def error_message_prefix
|
33
|
+
raise NotImplementedError, "Subclasses must implement this method"
|
34
|
+
end
|
35
|
+
|
36
|
+
def expected_response
|
37
|
+
raise NotImplementedError, "Subclasses must implement this method"
|
38
|
+
end
|
39
|
+
|
40
|
+
def http_verb
|
41
|
+
raise NotImplementedError, "Subclasses must implement this method"
|
42
|
+
end
|
43
|
+
|
44
|
+
def request_payload
|
45
|
+
raise NotImplementedError, "Subclasses must implement this method"
|
46
|
+
end
|
47
|
+
|
48
|
+
def request_path
|
49
|
+
raise NotImplementedError, "Subclasses must implement this method"
|
50
|
+
end
|
51
|
+
|
52
|
+
def success_message_prefix
|
53
|
+
raise NotImplementedError, "Subclasses must implement this method"
|
54
|
+
end
|
55
|
+
|
56
|
+
def request_headers
|
57
|
+
{ "Content-Type" => "application/json" }
|
58
|
+
end
|
59
|
+
|
60
|
+
def context_path
|
61
|
+
jira_client.options[:context_path]
|
62
|
+
end
|
63
|
+
|
64
|
+
def request_path_with_context
|
65
|
+
"#{context_path}#{request_path}"
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def send_request
|
71
|
+
send_args = [http_verb, *build_request_args(request_path_with_context, request_payload)]
|
72
|
+
|
73
|
+
log.debug { "Sending #{send_args.collect { |arg| %("#{arg}") }.join(" ")}" }
|
74
|
+
|
75
|
+
jira_client.send(*send_args)
|
76
|
+
end
|
77
|
+
|
78
|
+
def build_request_args(request_url, payload)
|
79
|
+
[
|
80
|
+
request_url,
|
81
|
+
payload&.to_json,
|
82
|
+
request_headers
|
83
|
+
]
|
84
|
+
.compact
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "http_logger"
|
4
|
+
require "logging"
|
5
|
+
|
6
|
+
class Object
|
7
|
+
def log
|
8
|
+
@log ||= Logging.logger[self]
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
log_dir = File.join("log")
|
13
|
+
FileUtils.makedirs(log_dir)
|
14
|
+
|
15
|
+
Logging.logger.root.add_appenders(
|
16
|
+
Logging.appenders.stdout(level: :info),
|
17
|
+
Logging.appenders.file(File.join(log_dir, "#{File.basename($PROGRAM_NAME)}.log"), level: :info)
|
18
|
+
)
|
19
|
+
|
20
|
+
Logging.logger.root.level = :info
|
21
|
+
|
22
|
+
logging_logger = Logging.logger["HTTPLogger"]
|
23
|
+
logging_logger.level = :debug # info
|
24
|
+
logging_logger.add_appenders(
|
25
|
+
Logging.appenders.stdout(level: :warn),
|
26
|
+
Logging.appenders.file(File.join(log_dir, "http_requests.log"), level: :debug)
|
27
|
+
)
|
28
|
+
|
29
|
+
HttpLogger.configure do |config|
|
30
|
+
config.logger = logging_logger
|
31
|
+
config.colorize = false
|
32
|
+
config.log_headers = true
|
33
|
+
config.log_request_body = true
|
34
|
+
config.log_response_body = true
|
35
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../sprint"
|
4
|
+
|
5
|
+
module Jira
|
6
|
+
module Auto
|
7
|
+
class Tool
|
8
|
+
class Sprint
|
9
|
+
class Name
|
10
|
+
include Comparable
|
11
|
+
|
12
|
+
class NameConventionError < StandardError; end
|
13
|
+
|
14
|
+
SPRINT_PREFIX_SEPARATOR = "_"
|
15
|
+
NUMBERING_SEPARATOR = "."
|
16
|
+
SPRINT_NAME_REGEX = /
|
17
|
+
(.+) # prefix
|
18
|
+
#{Regexp.escape(SPRINT_PREFIX_SEPARATOR)}
|
19
|
+
(\d\d) # year
|
20
|
+
#{Regexp.escape(NUMBERING_SEPARATOR)}
|
21
|
+
(\d) # quarter
|
22
|
+
#{Regexp.escape(NUMBERING_SEPARATOR)}
|
23
|
+
(\d+) # index in quarter
|
24
|
+
$/x
|
25
|
+
|
26
|
+
def self.respects_naming_convention?(name_string)
|
27
|
+
SPRINT_NAME_REGEX.match? name_string
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.parse(name_string)
|
31
|
+
name_string =~ SPRINT_NAME_REGEX ||
|
32
|
+
raise(NameConventionError,
|
33
|
+
"'#{name_string}': " \
|
34
|
+
"sprint name not matching #{SPRINT_NAME_REGEX}!")
|
35
|
+
|
36
|
+
new(::Regexp.last_match(1), ::Regexp.last_match(2).to_i, ::Regexp.last_match(3).to_i,
|
37
|
+
::Regexp.last_match(4).to_i)
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.build(prefix, year, quarter, index)
|
41
|
+
new(prefix, year, quarter, index)
|
42
|
+
.to_s
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.new_with(prefix, suffix)
|
46
|
+
name = [prefix, suffix].join(SPRINT_PREFIX_SEPARATOR)
|
47
|
+
|
48
|
+
respects_naming_convention?(name) ||
|
49
|
+
raise(NameConventionError,
|
50
|
+
"suffix not following convention '#{suffix}': " \
|
51
|
+
"resulting sprint name '#{name}' " \
|
52
|
+
"not matching #{SPRINT_NAME_REGEX}!")
|
53
|
+
|
54
|
+
parse(name)
|
55
|
+
end
|
56
|
+
|
57
|
+
FIELDS = %i[prefix year quarter index_in_quarter].freeze
|
58
|
+
|
59
|
+
FIELDS.each { |field| attr_accessor field }
|
60
|
+
|
61
|
+
def initialize(prefix, year, quarter, index_in_quarter)
|
62
|
+
@prefix = prefix
|
63
|
+
@year = year
|
64
|
+
@quarter = quarter
|
65
|
+
@index_in_quarter = index_in_quarter
|
66
|
+
end
|
67
|
+
|
68
|
+
YEAR_AS_TWO_DIGIT_RANGE = -2..-1
|
69
|
+
|
70
|
+
def to_s
|
71
|
+
[
|
72
|
+
prefix,
|
73
|
+
SPRINT_PREFIX_SEPARATOR,
|
74
|
+
year.to_s[YEAR_AS_TWO_DIGIT_RANGE],
|
75
|
+
NUMBERING_SEPARATOR,
|
76
|
+
quarter,
|
77
|
+
NUMBERING_SEPARATOR,
|
78
|
+
index_in_quarter
|
79
|
+
].join
|
80
|
+
end
|
81
|
+
|
82
|
+
def planning_interval
|
83
|
+
[year, quarter]
|
84
|
+
end
|
85
|
+
|
86
|
+
def next_in_planning_interval
|
87
|
+
self.class.new(prefix, year, quarter, index_in_quarter + 1)
|
88
|
+
end
|
89
|
+
|
90
|
+
def <=>(other)
|
91
|
+
comparison_values(self) <=> comparison_values(other)
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
def comparison_values(object)
|
97
|
+
return [object] if object.is_a?(String)
|
98
|
+
|
99
|
+
FIELDS.collect { |field| object.send(field) }
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../sprint"
|
4
|
+
|
5
|
+
module Jira
|
6
|
+
module Auto
|
7
|
+
class Tool
|
8
|
+
class Sprint
|
9
|
+
class Prefix
|
10
|
+
include Comparable
|
11
|
+
|
12
|
+
attr_reader :name
|
13
|
+
|
14
|
+
def initialize(name, sprints = [])
|
15
|
+
@name = name
|
16
|
+
@sprints = sprints
|
17
|
+
end
|
18
|
+
|
19
|
+
def sprints
|
20
|
+
@sprints.sort
|
21
|
+
end
|
22
|
+
|
23
|
+
def <<(sprint)
|
24
|
+
@sprints << sprint
|
25
|
+
end
|
26
|
+
|
27
|
+
def quarterly_add_sprints_until(until_date)
|
28
|
+
add_sprint_following_last_one until covered?(until_date)
|
29
|
+
end
|
30
|
+
|
31
|
+
def covered?(until_date)
|
32
|
+
last_sprint.end_date > until_date.time
|
33
|
+
end
|
34
|
+
|
35
|
+
def add_sprint_following_last_one
|
36
|
+
self << NextSprintCreator.create_sprint_following(last_sprint)
|
37
|
+
end
|
38
|
+
|
39
|
+
def to_s
|
40
|
+
"name: #{name}, sprints: #{sprints}"
|
41
|
+
end
|
42
|
+
|
43
|
+
def to_table_row(without_board_information: false)
|
44
|
+
[name].concat(last_sprint.to_table_row(without_board_information:))
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.to_table_row_header(without_board_information: false)
|
48
|
+
sprint_header = Sprint.to_table_row_header(without_board_information: without_board_information)
|
49
|
+
0.upto(1) { |i| sprint_header[i] = "Last Sprint #{sprint_header[i]}" }
|
50
|
+
|
51
|
+
["Sprint Prefix"].concat(sprint_header)
|
52
|
+
end
|
53
|
+
|
54
|
+
def last_sprint
|
55
|
+
log.debug { "name = #{name}, #sprints = #{sprints.size}" }
|
56
|
+
sprints.max
|
57
|
+
end
|
58
|
+
|
59
|
+
def <=>(other)
|
60
|
+
[name, sprints] <=> [other.name, other.sprints]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|