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.
Files changed (139) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +291 -0
  4. data/CHANGELOG.md +5 -0
  5. data/CODE_OF_CONDUCT.md +132 -0
  6. data/Guardfile +105 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +159 -0
  9. data/Rakefile +20 -0
  10. data/bin/jira-auto-tool +57 -0
  11. data/bin/jira-auto-tool.bat +2 -0
  12. data/bin/setup +8 -0
  13. data/bin/setup-dev-win.bat +3 -0
  14. data/cucumber.yml +7 -0
  15. data/features/align_sprint_time_in_dates.feature +33 -0
  16. data/features/assign_tickets_to_team_sprints.feature +73 -0
  17. data/features/cache_boards.feature +24 -0
  18. data/features/control_http_request_rate_limit.feature +17 -0
  19. data/features/create_sprints_using_existing_ones_as_reference.feature +79 -0
  20. data/features/list_boards.feature +12 -0
  21. data/features/list_project_fields.feature +89 -0
  22. data/features/list_sprint_prefixes.feature +77 -0
  23. data/features/quarterly_add_sprints_using_existing_ones_as_a_reference.feature +71 -0
  24. data/features/quarterly_create_sprints_until_specific_date.feature +75 -0
  25. data/features/quarterly_rename_sprints.feature +179 -0
  26. data/features/rename_sprints.feature +203 -0
  27. data/features/self_documented_command_line.feature +15 -0
  28. data/features/sprint_filtering.feature +111 -0
  29. data/features/step_definitions/execution_context_steps.rb +33 -0
  30. data/features/step_definitions/jira_board_steps.rb +102 -0
  31. data/features/step_definitions/jira_ticket_steps.rb +63 -0
  32. data/features/support/10.setup_cucumber.rb +10 -0
  33. data/features/support/env.rb +25 -0
  34. data/features/support/hooks.rb +25 -0
  35. data/features/support/setup_rspec.rb +14 -0
  36. data/features/support/setup_simplecov.rb +5 -0
  37. data/features/update_sprint_end_date_and_shift_following_ones.feature +52 -0
  38. data/lib/jira/auto/tool/board/cache.rb +67 -0
  39. data/lib/jira/auto/tool/board/unavailable_board.rb +36 -0
  40. data/lib/jira/auto/tool/board.rb +105 -0
  41. data/lib/jira/auto/tool/board_controller/options.rb +32 -0
  42. data/lib/jira/auto/tool/board_controller.rb +88 -0
  43. data/lib/jira/auto/tool/common_options.rb +37 -0
  44. data/lib/jira/auto/tool/config/options.rb +19 -0
  45. data/lib/jira/auto/tool/config.rb +64 -0
  46. data/lib/jira/auto/tool/fetch_custom_field_options.rb +47 -0
  47. data/lib/jira/auto/tool/field.rb +59 -0
  48. data/lib/jira/auto/tool/field_controller.rb +50 -0
  49. data/lib/jira/auto/tool/field_option.rb +35 -0
  50. data/lib/jira/auto/tool/get_createmeta_for_project.rb +24 -0
  51. data/lib/jira/auto/tool/helpers/environment_based_value.rb +96 -0
  52. data/lib/jira/auto/tool/helpers/option_parser.rb +16 -0
  53. data/lib/jira/auto/tool/helpers/overridable_time.rb +18 -0
  54. data/lib/jira/auto/tool/helpers/pagination.rb +50 -0
  55. data/lib/jira/auto/tool/jira_http_options.rb +20 -0
  56. data/lib/jira/auto/tool/next_sprint_creator.rb +60 -0
  57. data/lib/jira/auto/tool/performer/options.rb +76 -0
  58. data/lib/jira/auto/tool/performer/planning_increment_sprint_creator.rb +42 -0
  59. data/lib/jira/auto/tool/performer/prefix_sprint_updater.rb +42 -0
  60. data/lib/jira/auto/tool/performer/quarterly_sprint_renamer/next_name_generator.rb +60 -0
  61. data/lib/jira/auto/tool/performer/quarterly_sprint_renamer.rb +19 -0
  62. data/lib/jira/auto/tool/performer/sprint_end_date_updater.rb +55 -0
  63. data/lib/jira/auto/tool/performer/sprint_renamer/keep_same_name_generator.rb +19 -0
  64. data/lib/jira/auto/tool/performer/sprint_renamer/next_name_generator.rb +47 -0
  65. data/lib/jira/auto/tool/performer/sprint_renamer.rb +55 -0
  66. data/lib/jira/auto/tool/performer/sprint_time_in_dates_aligner.rb +52 -0
  67. data/lib/jira/auto/tool/project/options.rb +22 -0
  68. data/lib/jira/auto/tool/project/ticket_fields.rb +70 -0
  69. data/lib/jira/auto/tool/project.rb +40 -0
  70. data/lib/jira/auto/tool/rate_limited_jira_client.rb +50 -0
  71. data/lib/jira/auto/tool/request_builder/field_context_fetcher.rb +78 -0
  72. data/lib/jira/auto/tool/request_builder/field_option_fetcher.rb +54 -0
  73. data/lib/jira/auto/tool/request_builder/get.rb +29 -0
  74. data/lib/jira/auto/tool/request_builder/sprint_creator.rb +112 -0
  75. data/lib/jira/auto/tool/request_builder/sprint_state_updater.rb +60 -0
  76. data/lib/jira/auto/tool/request_builder.rb +89 -0
  77. data/lib/jira/auto/tool/setup_logging.rb +35 -0
  78. data/lib/jira/auto/tool/sprint/name.rb +105 -0
  79. data/lib/jira/auto/tool/sprint/prefix.rb +66 -0
  80. data/lib/jira/auto/tool/sprint.rb +183 -0
  81. data/lib/jira/auto/tool/sprint_controller/options.rb +61 -0
  82. data/lib/jira/auto/tool/sprint_controller.rb +152 -0
  83. data/lib/jira/auto/tool/sprint_state_controller.rb +58 -0
  84. data/lib/jira/auto/tool/team.rb +23 -0
  85. data/lib/jira/auto/tool/team_sprint_prefix_mapper/options.rb +27 -0
  86. data/lib/jira/auto/tool/team_sprint_prefix_mapper.rb +62 -0
  87. data/lib/jira/auto/tool/team_sprint_ticket_dispatcher.rb +76 -0
  88. data/lib/jira/auto/tool/ticket.rb +110 -0
  89. data/lib/jira/auto/tool/until_date.rb +68 -0
  90. data/lib/jira/auto/tool/version.rb +9 -0
  91. data/lib/jira/auto/tool.rb +216 -0
  92. data/sig/jira/sprint/tool.rbs +8 -0
  93. data/spec/jira/auto/tool/board/cache_spec.rb +179 -0
  94. data/spec/jira/auto/tool/board/unavailable_board_spec.rb +34 -0
  95. data/spec/jira/auto/tool/board_controller/options_spec.rb +52 -0
  96. data/spec/jira/auto/tool/board_controller_spec.rb +154 -0
  97. data/spec/jira/auto/tool/board_spec.rb +163 -0
  98. data/spec/jira/auto/tool/common_options_spec.rb +49 -0
  99. data/spec/jira/auto/tool/config_spec.rb +108 -0
  100. data/spec/jira/auto/tool/field_controller_spec.rb +121 -0
  101. data/spec/jira/auto/tool/field_option_spec.rb +42 -0
  102. data/spec/jira/auto/tool/field_spec.rb +99 -0
  103. data/spec/jira/auto/tool/helpers/environment_based_value_spec.rb +21 -0
  104. data/spec/jira/auto/tool/helpers/option_parser_spec.rb +21 -0
  105. data/spec/jira/auto/tool/helpers/overridable_time_spec.rb +43 -0
  106. data/spec/jira/auto/tool/helpers/pagination_spec.rb +72 -0
  107. data/spec/jira/auto/tool/jira_http_options_spec.rb +32 -0
  108. data/spec/jira/auto/tool/next_sprint_creator_spec.rb +85 -0
  109. data/spec/jira/auto/tool/performer/option_spec.rb +55 -0
  110. data/spec/jira/auto/tool/performer/planning_increment_sprint_creator_spec.rb +62 -0
  111. data/spec/jira/auto/tool/performer/prefix_sprint_updater_spec.rb +35 -0
  112. data/spec/jira/auto/tool/performer/quarterly_sprint_renamer/next_name_generator_spec.rb +175 -0
  113. data/spec/jira/auto/tool/performer/quarterly_sprint_renamer_spec.rb +239 -0
  114. data/spec/jira/auto/tool/performer/sprint_end_date_updater_spec.rb +90 -0
  115. data/spec/jira/auto/tool/performer/sprint_renamer/keep_same_name_generator_spec.rb +12 -0
  116. data/spec/jira/auto/tool/performer/sprint_renamer/next_name_generator_spec.rb +129 -0
  117. data/spec/jira/auto/tool/performer/sprint_renamer_spec.rb +240 -0
  118. data/spec/jira/auto/tool/performer/sprint_time_in_dates_aligner_spec.rb +132 -0
  119. data/spec/jira/auto/tool/project/ticket_fields_spec.rb +390 -0
  120. data/spec/jira/auto/tool/project_spec.rb +31 -0
  121. data/spec/jira/auto/tool/rate_limited_jira_client_spec.rb +82 -0
  122. data/spec/jira/auto/tool/request_builder/field_context_fetcher_spec.rb +54 -0
  123. data/spec/jira/auto/tool/request_builder/field_option_fetcher_spec.rb +64 -0
  124. data/spec/jira/auto/tool/request_builder/get_spec.rb +40 -0
  125. data/spec/jira/auto/tool/request_builder/sprint_creator_spec.rb +179 -0
  126. data/spec/jira/auto/tool/request_builder/sprint_state_updater_spec.rb +31 -0
  127. data/spec/jira/auto/tool/request_builder_spec.rb +73 -0
  128. data/spec/jira/auto/tool/sprint/name_spec.rb +101 -0
  129. data/spec/jira/auto/tool/sprint/prefix_spec.rb +207 -0
  130. data/spec/jira/auto/tool/sprint_controller_spec.rb +406 -0
  131. data/spec/jira/auto/tool/sprint_spec.rb +309 -0
  132. data/spec/jira/auto/tool/team_spec.rb +21 -0
  133. data/spec/jira/auto/tool/team_sprint_prefix_mapper_spec.rb +97 -0
  134. data/spec/jira/auto/tool/team_sprint_ticket_dispatcher_spec.rb +232 -0
  135. data/spec/jira/auto/tool/ticket_spec.rb +116 -0
  136. data/spec/jira/auto/tool/until_date_spec.rb +80 -0
  137. data/spec/jira/auto/tool_spec.rb +458 -0
  138. data/spec/spec_helper.rb +42 -0
  139. 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