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,111 @@
1
+ Feature: Sprint Filtering
2
+ In order to target specific sprints
3
+ As a user
4
+ I need the ability to filter sprints
5
+
6
+ Background:
7
+ Given a Jira Scrum board
8
+ And the board only has the following sprints:
9
+ | name | length | start_date | state |
10
+ | ART-16_CRM_24.4.1 | 2-week | 2024-12-01 11:00:00 UTC | closed |
11
+ | ART-16_E2E-Test_24.4.1 | 4-day | 2024-12-01 11:00:00 UTC | future |
12
+ | ART-32_Sys-Team_24.4.12 | 1-week | 2024-12-24 11:00:00 UTC | future |
13
+ | ART-32_Sys-Team_25.1.1 | 1-week | 2025-01-07 11:00:00 UTC | future |
14
+ | ART-16_E2E-Test_24.4.2 | 4-day | 2024-12-05 11:00:00 UTC | future |
15
+ | ART-32_Platform_24.4.7 | 3-week | 2024-10-07 11:00:00 UTC | future |
16
+
17
+
18
+ Scenario: No filter applied except ignoring closed sprints
19
+ When I successfully run `jira-auto-tool --sprint-list`
20
+ Then the output should match:
21
+ """
22
+ \+-------+--\+
23
+ \| \s+ Matching Sprints \s+ \|
24
+ \+-------\+-------------------------\+----------------\+-------------------------\+-------------------------\+-----------------------\+---------------------------------+--\+-------------------\+
25
+ \| Id \| Name \| Length In Days \| Start Date \| End Date \| Board Name \| Board UI URL \s+ \| Board Project Key \|
26
+ \+-------\+-------------------------\+----------------\+-------------------------\+-------------------------\+-----------------------\+---------------------------------+--\+-------------------\+
27
+ """
28
+ And the output should match:
29
+ """
30
+ .+-------------------------.+
31
+ .+ Name .+
32
+ .+-------------------------.+
33
+ .+ ART-32_Platform_24.4.7 .+
34
+ .+ ART-16_E2E-Test_24.4.1 .+
35
+ .+ ART-16_E2E-Test_24.4.2 .+
36
+ .+ ART-32_Sys-Team_24.4.12 .+
37
+ .+ ART-32_Sys-Team_25.1.1 .+
38
+ .+-------------------------.+
39
+ """
40
+
41
+ Scenario: No filtering (except closed sprints) and excluding the corresponding board information
42
+ When I successfully run `jira-auto-tool --sprint-list-without-board-info`
43
+ Then the stdout should contain:
44
+ """
45
+ +------------------------------------------------------------------------------------------------------+
46
+ | Matching Sprints |
47
+ +-------+-------------------------+----------------+-------------------------+-------------------------+
48
+ | Id | Name | Length In Days | Start Date | End Date |
49
+ +-------+-------------------------+----------------+-------------------------+-------------------------+
50
+ """
51
+ And the output should match:
52
+ """
53
+ .+-------------------------.+
54
+ .+ Name .+
55
+ .+-------------------------.+
56
+ .+ ART-32_Platform_24.4.7 .+
57
+ .+ ART-16_E2E-Test_24.4.1 .+
58
+ .+ ART-16_E2E-Test_24.4.2 .+
59
+ .+ ART-32_Sys-Team_24.4.12 .+
60
+ .+ ART-32_Sys-Team_25.1.1 .+
61
+ .+-------------------------.+
62
+ """
63
+
64
+ Scenario: Filter sprints with a string
65
+ Given the following environment variables are set:
66
+ | name | value |
67
+ | ART_SPRINT_REGEX | ART-16 |
68
+ When I successfully run `jira-auto-tool --sprint-list`
69
+ Then the output should match:
70
+ """
71
+ \+---------+------------\+
72
+ \| \s+ Matching Sprints \s+ \|
73
+ \+-------\+---------+--------\+----------------\+-------------------------\+-------------------------\+-----------------------\+---------------------------------+--\+-------------------\+
74
+ \| Id \| Name \s+ \| Length In Days \| Start Date \| End Date \| Board Name \| Board UI URL \s+ \| Board Project Key \|
75
+ \+-------\+---------+--------\+----------------\+-------------------------\+-------------------------\+-----------------------\+---------------------------------+--\+-------------------\+
76
+ """
77
+ And the output should match:
78
+ """
79
+ .*------------------------.*
80
+ .* .*
81
+ .*------------------------.*
82
+ .* Name .*
83
+ .*------------------------.*
84
+ .* ART-16_E2E-Test_24.4.1 .*
85
+ .* ART-16_E2E-Test_24.4.2 .*
86
+ .*------------------------.*
87
+ """
88
+
89
+ Scenario: Filter sprints with a regular expression
90
+ Given the following environment variables are set:
91
+ | name | value |
92
+ | ART_SPRINT_REGEX | Platform\|(4\.1)$ |
93
+ When I successfully run `jira-auto-tool --sprint-list`
94
+ Then the output should match:
95
+ """
96
+ \+-----------+------\+
97
+ \| \s+ Matching Sprints \s+ \|
98
+ \+-------\+---------+-------\+----------------\+-------------------------\+-------------------------\+-----------------------\+---------------------------------+--\+-------------------\+
99
+ \| Id \| Name \s+ \| Length In Days \| Start Date \| End Date \| Board Name \| Board UI URL \s+ \| Board Project Key \|
100
+ \+-------\+---------+-------\+----------------\+-------------------------\+-------------------------\+-----------------------\+---------------------------------+--\+-------------------\+
101
+ """
102
+ And the output should match:
103
+ """
104
+ .+------------------------.+
105
+ .+ Name .+
106
+ .+------------------------.+
107
+ .+ ART-32_Platform_24.4.7 .+
108
+ .+ ART-16_E2E-Test_24.4.1 .+
109
+ .+------------------------.+
110
+ """
111
+
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ Given("the current date time is {string}") do |current_date_time|
4
+ set_environment_variable("JAT_CURRENT_DATE_TIME", current_date_time)
5
+ end
6
+
7
+ Given(/^the following environment variables are set:$/) do |table|
8
+ table.hashes.each do |env_var|
9
+ name = env_var.fetch("name")
10
+ value = env_var.fetch("value")
11
+ @jira_auto_tool.send("#{name.downcase}=", value)
12
+ set_environment_variable(name, value)
13
+ end
14
+ end
15
+
16
+ BUFFER_TIME_IN_SECONDS = 10
17
+ Then(/^successfully running `(.*)` takes between (.*) and (.*) seconds$/) do |command_line, minimal_time, maximal_time|
18
+ start_time = Time.now
19
+
20
+ run_command_and_stop(command_line, fail_on_error: true, timeout: maximal_time.to_i + BUFFER_TIME_IN_SECONDS)
21
+
22
+ end_time = Time.now
23
+
24
+ expect(end_time - start_time).to be_between(minimal_time.to_i, maximal_time.to_i)
25
+ end
26
+
27
+ Given(/^I wait for over a day$/) do
28
+ in_over_a_day = (Time.now + 1.day + 2.minute).to_s
29
+
30
+ log.debug { "Waiting until #{in_over_a_day}" }
31
+
32
+ set_environment_variable("JAT_CURRENT_DATE_TIME", in_over_a_day)
33
+ end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "time"
4
+
5
+ Given(/^a Jira Scrum board$/) do
6
+ expect(@board).not_to be_nil
7
+ end
8
+
9
+ Given(/^the board has no sprint$/) do
10
+ expect(@board.jira_board.sprints).to be_empty
11
+ end
12
+
13
+ Given(/^the board has only closed sprints$/) do
14
+ 4.times do |sprint_index|
15
+ @jira_auto_tool.create_sprint(name: "sprint_24.1.#{sprint_index}", state: "closed",
16
+ start_date: Time.now + sprint_index.days, length_in_days: 14)
17
+ end
18
+ end
19
+
20
+ DAYS_IN_A_WEEK = 7
21
+
22
+ def parse_length_in_days(length_string)
23
+ case length_string
24
+ when /(\d+)-week/
25
+ Regexp.last_match(1).to_i * DAYS_IN_A_WEEK
26
+ when /(\d+)-day/
27
+ Regexp.last_match(1).to_i
28
+ else
29
+ raise "#{length_string} is not an expected sprint length"
30
+ end
31
+ end
32
+
33
+ Given(/^an unclosed (.+) sprint named (.+) starting on (.+)$/) do |sprint_length, sprint_name, start_date_time|
34
+ @jira_auto_tool.create_sprint({ name: sprint_name, start_date: start_date_time,
35
+ length_in_days: parse_length_in_days(sprint_length), state: "future" })
36
+ end
37
+
38
+ Then(/^a sprint named (.*) should exist$/) do |expected_name|
39
+ unclosed_sprints = @jira_auto_tool.sprint_controller.unclosed_sprints
40
+ expect(unclosed_sprints.collect(&:name)).to include(expected_name)
41
+ @actual_sprint = unclosed_sprints.find { |sprint| sprint.name == expected_name }
42
+ end
43
+
44
+ And(/^it starts on (.*)$/) do |expected_start|
45
+ expect(@actual_sprint.start_date).to eq(Time.parse(expected_start).utc)
46
+ end
47
+
48
+ And(/^it ends on (.*)$/) do |expected_end|
49
+ expect(@actual_sprint.end_date).to eq(Time.parse(expected_end).utc)
50
+ end
51
+
52
+ And(/^its state is (.*)$/) do |expected_state|
53
+ expect(@actual_sprint.state).to eq(expected_state)
54
+ end
55
+
56
+ Given(/^the board only has the following sprints:$/) do |table|
57
+ table_value_keys = table.raw.first
58
+
59
+ table.hashes.each do |sprint_hash|
60
+ attributes = {}
61
+
62
+ table_value_keys.each do |key|
63
+ value = sprint_hash[key]
64
+
65
+ case key.intern
66
+ when :length
67
+ attributes[:length_in_days] = parse_length_in_days(value)
68
+ when :expecting_added_sprint, :comment
69
+ next
70
+ else
71
+ attributes[key.intern] = value
72
+ end
73
+ end
74
+
75
+ @jira_auto_tool.create_sprint(attributes)
76
+ end
77
+ end
78
+
79
+ Then(/^afterwards the board only has the following sprints:$/) do |table|
80
+ expected_value_keys = table.raw.first
81
+
82
+ expected_sprints = table.hashes.collect(&:values).collect(&:flatten)
83
+
84
+ actual_sprints = @jira_auto_tool.sprint_controller.sprints.collect do |sprint|
85
+ expected_value_keys.collect do |key|
86
+ unavailable_date = key =~ /date/ && !sprint.send("#{key}?")
87
+
88
+ value = unavailable_date ? "" : sprint.send(key)
89
+
90
+ value.is_a?(Time) ? value.utc.to_s : value
91
+ end
92
+ end
93
+
94
+ expect(actual_sprints).to contain_exactly(*expected_sprints)
95
+ end
96
+
97
+ Then(/^the output contains (no )?requests that enumerate the list of boards$/) do |contains_or_not|
98
+ contains_predicate = contains_or_not.nil? || contains_or_not.empty? ? :to : :not_to
99
+
100
+ board_enumeration_pattern = /#{Regexp.escape("get: /rest/agile/1.0/board - []")}/
101
+ expect(last_command_started.output).send(contains_predicate, match(board_enumeration_pattern))
102
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ And(/^tickets on the board have an expected date field named "([^"]*)"$/) do |date_field_name|
4
+ @date_field = @jira_auto_tool.expected_start_date_field(date_field_name)
5
+
6
+ expect(@date_field).not_to be_nil
7
+ end
8
+
9
+ And(/^tickets on the board have a team field named "([^"]*)" with exactly those values:$/) do
10
+ |team_field_name, field_values|
11
+
12
+ expected_field_values = field_values.hashes.collect { |hashes| hashes["values"] }
13
+
14
+ @team_field = @jira_auto_tool.implementation_team_field(team_field_name)
15
+
16
+ expect(@team_field.values.collect(&:value)).to eq(expected_field_values)
17
+ end
18
+
19
+ Given(/^the following tickets exist:$/) do |ticket_table|
20
+ # Summary | Description | Implementation Team | Expected Start |
21
+ # table is a table.hashes.keys # => [:summary, :team, :expected_start]
22
+ ticket_table.hashes.each do |ticket_info|
23
+ log.debug { ticket_info.inspect }
24
+
25
+ jira_ticket = @jira_auto_tool.jira_client.Issue.build
26
+
27
+ jira_ticket.save!({ fields: {
28
+ project: { key: @jira_auto_tool.board.project_key },
29
+ summary: ticket_info[:summary],
30
+ description: ticket_info[:description],
31
+ issuetype: { name: "Task" },
32
+ @jira_auto_tool.implementation_team_field.id.intern =>
33
+ { "value" => ticket_info[:implementation_team] },
34
+ @jira_auto_tool.expected_start_date_field.id.intern => ticket_info[:expected_start_date]
35
+ } })
36
+
37
+ log.debug { "created jira ticket: #{jira_ticket.key}" }
38
+ end
39
+ end
40
+
41
+ Then(/^the tickets should have been assigned to sprints as follows:$/) do |ticket_expectation_table|
42
+ # table is a table.hashes.keys # => [:summary, :sprint]
43
+ ticket_expectations =
44
+ ticket_expectation_table.hashes.collect { |expectation| [expectation[:summary], expectation[:sprint]] }
45
+
46
+ actual_ticket_values = @jira_auto_tool.tickets.collect do |ticket|
47
+ sprint = ticket.sprint
48
+
49
+ log.debug { "ticket: #{ticket.summary} sprint: #{sprint.inspect}" }
50
+
51
+ [ticket.summary, sprint&.first&.fetch("name") || ""]
52
+ end
53
+
54
+ expect(actual_ticket_values.sort).to eq(ticket_expectations.sort)
55
+ end
56
+
57
+ Given(/^a Jira project$/) do
58
+ @project = @jira_auto_tool.project
59
+ end
60
+
61
+ And(/^JAT_TICKETS_FOR_TEAM_SPRINT_TICKET_DISPATCHER_JQL has been defined as an environment variable$/) do
62
+ expect(ENV).to have_key("JAT_TICKETS_FOR_TEAM_SPRINT_TICKET_DISPATCHER_JQL")
63
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "aruba/cucumber"
4
+
5
+ Aruba.configure do |config|
6
+ config.exit_timeout = 300 # seconds
7
+
8
+ config.activate_announcer_on_command_failure = %i[command stdout stderr]
9
+ config.log_level = :debug
10
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JiraSprintToolWorld
4
+ def log
5
+ @log ||= Logging.logger[self]
6
+ end
7
+
8
+ def remove_existing_sprints(jira_auto_tool)
9
+ sprints = jira_auto_tool.sprint_controller.sprints
10
+
11
+ log.debug { "Removing sprints #sprints = #{sprints.size}: #{sprints.map(&:name).join(", ")}" }
12
+
13
+ sprints.each(&:delete)
14
+ end
15
+
16
+ def remove_existing_board_tickets(jira_auto_tool)
17
+ tickets = jira_auto_tool.jira_client.Issue.jql("project = #{jira_auto_tool.board.project_key}")
18
+
19
+ log.debug { "Removing tickets from board #{jira_auto_tool.board.name}: #tickets = #{tickets.size}" }
20
+
21
+ tickets.each(&:delete)
22
+ end
23
+ end
24
+
25
+ World(JiraSprintToolWorld)
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "jira-ruby"
4
+
5
+ require "jira/auto/tool"
6
+
7
+ def ensure_the_aruba_sandbox_is_active
8
+ setup_aruba
9
+ ENV["HOME"] = expand_path(".")
10
+ cd(".")
11
+
12
+ expect(Dir.home).to eq(expand_path("."))
13
+ end
14
+
15
+ Before do
16
+ ensure_the_aruba_sandbox_is_active
17
+
18
+ @jira_auto_tool = Jira::Auto::Tool.new
19
+ @jira_client = @jira_auto_tool.jira_client
20
+ @board = @jira_auto_tool.board
21
+
22
+ remove_existing_sprints(@jira_auto_tool)
23
+ remove_existing_board_tickets(@jira_auto_tool)
24
+ Jira::Auto::Tool::Board::Cache.new(@jira_auto_tool).clear
25
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rspec"
4
+
5
+ RSpec.configure do |config|
6
+ config.expect_with :rspec do |expectations|
7
+ expectations.syntax = :expect
8
+ expectations.max_formatted_output_length = nil
9
+ end
10
+
11
+ config.mock_with :rspec do |mocks|
12
+ mocks.verify_partial_doubles = true
13
+ end
14
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "simplecov"
4
+
5
+ SimpleCov.start
@@ -0,0 +1,52 @@
1
+ Feature: Rename sprints
2
+ In order to adjust the end date of a sprint to cope with specific periods like year end
3
+ As a team member
4
+ I need the ability to update the end date of a sprint and shift the following sprints
5
+
6
+ Background:
7
+ Given a Jira Scrum board
8
+
9
+ Scenario: Adjust the end of a sprint and shift the following ones so there are no gaps
10
+ Given the board only has the following sprints:
11
+ | comment | name | length | start_date | state |
12
+ | "no | Food_Supply_25.1.2 | 2-week | 2025-01-07 11:00:00 UTC | closed |
13
+ | change | Food_Supply_25.1.3 | 2-week | 2025-01-21 11:00:00 UTC | closed |
14
+ | since | Food_Supply_25.1.4 | 2-week | 2025-02-04 11:00:00 UTC | closed |
15
+ | closed" | Food_Supply_25.1.5 | 2-week | 2025-02-18 11:00:00 UTC | closed |
16
+ | | Food_Delivery_25.1.2 | 2-week | 2025-01-07 11:00:00 UTC | future |
17
+ | | Food_Delivery_25.1.3 | 2-week | 2025-01-21 11:00:00 UTC | future |
18
+ | | Food_Delivery_25.1.4 | 2-week | 2025-02-04 11:00:00 UTC | future |
19
+ | new end date | Food_Delivery_25.1.5 | 2-week | 2025-02-18 11:00:00 UTC | future |
20
+ | shifted | Food_Delivery_25.2.1 | 2-week | 2025-03-04 11:00:00 UTC | future |
21
+ | idem | Food_Delivery_25.2.2 | 2-week | 2025-03-18 11:00:00 UTC | future |
22
+ | idem | Food_Delivery_25.2.3 | 2-week | 2025-04-01 11:00:00 UTC | future |
23
+ | idem | Food_Delivery_25.2.4 | 2-week | 2025-04-15 11:00:00 UTC | future |
24
+ | idem | Food_Delivery_25.2.5 | 2-week | 2025-04-29 11:00:00 UTC | future |
25
+ | | Food_Restaurant_24.4.1 | 4-day | 2025-01-07 11:00:00 UTC | future |
26
+ | "last two | Food_Restaurant_25.1.5 | 4-day | 2025-01-18 11:00:00 UTC | future |
27
+ | updated" | Food_Restaurant_25.2.1 | 4-day | 2025-04-01 11:00:00 UTC | future |
28
+ | "no update | Food_Market_25.2.5 | 4-day | 2025-06-14 11:00:00 UTC | future |
29
+ | since | Food_Market_25.3.1 | 4-day | 2025-07-07 11:00:00 UTC | future |
30
+ | not matching" | Food_Market_25.3.2 | 4-day | 2025-07-21 11:00:00 UTC | future |
31
+ When I successfully run `jira-auto-tool --sprint-update-end-date=25.1.5,"2025-02-25 16:00:00 UTC"`
32
+ Then afterwards the board only has the following sprints:
33
+ | name | start_date | end_date | state |
34
+ | Food_Supply_25.1.2 | 2025-01-07 11:00:00 UTC | 2025-01-21 11:00:00 UTC | closed |
35
+ | Food_Supply_25.1.3 | 2025-01-21 11:00:00 UTC | 2025-02-04 11:00:00 UTC | closed |
36
+ | Food_Supply_25.1.4 | 2025-02-04 11:00:00 UTC | 2025-02-18 11:00:00 UTC | closed |
37
+ | Food_Supply_25.1.5 | 2025-02-18 11:00:00 UTC | 2025-03-04 11:00:00 UTC | closed |
38
+ | Food_Delivery_25.1.2 | 2025-01-07 11:00:00 UTC | 2025-01-21 11:00:00 UTC | future |
39
+ | Food_Delivery_25.1.3 | 2025-01-21 11:00:00 UTC | 2025-02-04 11:00:00 UTC | future |
40
+ | Food_Delivery_25.1.4 | 2025-02-04 11:00:00 UTC | 2025-02-18 11:00:00 UTC | future |
41
+ | Food_Delivery_25.1.5 | 2025-02-18 11:00:00 UTC | 2025-02-25 16:00:00 UTC | future |
42
+ | Food_Delivery_25.2.1 | 2025-02-25 16:00:00 UTC | 2025-03-11 16:00:00 UTC | future |
43
+ | Food_Delivery_25.2.2 | 2025-03-11 16:00:00 UTC | 2025-03-25 16:00:00 UTC | future |
44
+ | Food_Delivery_25.2.3 | 2025-03-25 16:00:00 UTC | 2025-04-08 16:00:00 UTC | future |
45
+ | Food_Delivery_25.2.4 | 2025-04-08 16:00:00 UTC | 2025-04-22 16:00:00 UTC | future |
46
+ | Food_Delivery_25.2.5 | 2025-04-22 16:00:00 UTC | 2025-05-06 16:00:00 UTC | future |
47
+ | Food_Restaurant_24.4.1 | 2025-01-07 11:00:00 UTC | 2025-01-11 11:00:00 UTC | future |
48
+ | Food_Restaurant_25.1.5 | 2025-01-18 11:00:00 UTC | 2025-02-25 16:00:00 UTC | future |
49
+ | Food_Restaurant_25.2.1 | 2025-02-25 16:00:00 UTC | 2025-03-01 16:00:00 UTC | future |
50
+ | Food_Market_25.2.5 | 2025-06-14 11:00:00 UTC | 2025-06-18 11:00:00 UTC | future |
51
+ | Food_Market_25.3.1 | 2025-07-07 11:00:00 UTC | 2025-07-11 11:00:00 UTC | future |
52
+ | Food_Market_25.3.2 | 2025-07-21 11:00:00 UTC | 2025-07-25 11:00:00 UTC | future |
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yaml"
4
+ require "fileutils"
5
+ require "jira/auto/tool/helpers/overridable_time"
6
+
7
+ module Jira
8
+ module Auto
9
+ class Tool
10
+ class Board
11
+ class Cache
12
+ attr_reader :tool
13
+
14
+ def initialize(tool)
15
+ @tool = tool
16
+ end
17
+
18
+ def boards
19
+ raise "This method should not be used since the cache is invalid" unless valid?
20
+
21
+ @boards ||= raw_content.fetch("boards").collect do |board_info|
22
+ Board.new(tool, tool.jira_client.Board.build(board_info))
23
+ end
24
+ end
25
+
26
+ def save(boards)
27
+ File.write(file_path, { "boards" => boards.collect { |board| board.jira_board.attrs } }.to_yaml)
28
+
29
+ boards
30
+ end
31
+
32
+ def clear
33
+ FileUtils.rm_f(file_path)
34
+ end
35
+
36
+ def valid?
37
+ File.exist?(file_path) && !expired?
38
+ end
39
+
40
+ private
41
+
42
+ def expired?
43
+ log.debug { "expired? #{cached_at} < #{one_day_ago}" }
44
+
45
+ cached_at < one_day_ago
46
+ end
47
+
48
+ def one_day_ago
49
+ Helpers::OverridableTime.now - 1.day
50
+ end
51
+
52
+ def cached_at
53
+ File.mtime(file_path)
54
+ end
55
+
56
+ def raw_content
57
+ YAML.safe_load_file(file_path) || {}
58
+ end
59
+
60
+ def file_path
61
+ File.join(tool.config.dir, "jira-auto-tool.cache.yml")
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jira
4
+ module Auto
5
+ class Tool
6
+ class Board
7
+ class UnavailableBoard < Board
8
+ include Comparable
9
+
10
+ attr_reader :id
11
+
12
+ def initialize(tool, id)
13
+ super(tool, JIRA::Resource::Board.new(id: id))
14
+ @id = id
15
+ end
16
+
17
+ def name
18
+ "N/A"
19
+ end
20
+
21
+ def url
22
+ "N/A"
23
+ end
24
+
25
+ def ui_url
26
+ "N/A"
27
+ end
28
+
29
+ def <=>(other)
30
+ id <=> other.id
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,105 @@
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
+ include Comparable
10
+
11
+ attr_reader :tool, :jira_board
12
+
13
+ def self.find_by_id(tool, id)
14
+ cached_boards[id] ||=
15
+ begin
16
+ new(tool, JIRA::Resource::Board.find(tool.jira_client, id))
17
+ rescue JIRA::HTTPError => e
18
+ if e.code.to_i == 404
19
+ UnavailableBoard.new(tool, id)
20
+ else
21
+ raise e.class,
22
+ "#{e.class}: code = #{e.code.inspect}: #{self.class}.find_by_id(tool, #{id.inspect}): " \
23
+ "#{e.message}"
24
+ end
25
+ end
26
+ end
27
+
28
+ def self.cached_boards
29
+ @cached_boards ||= {}
30
+ end
31
+
32
+ def initialize(tool, jira_board)
33
+ @tool = tool
34
+ @jira_board = jira_board
35
+ end
36
+
37
+ def id
38
+ jira_board.id
39
+ end
40
+
41
+ def unavailable?
42
+ instance_of?(UnavailableBoard)
43
+ end
44
+
45
+ def <=>(other)
46
+ id <=> other.id
47
+ end
48
+
49
+ def name
50
+ jira_board.name
51
+ end
52
+
53
+ def self.to_table_row_field_names
54
+ %i[name ui_url project_key]
55
+ end
56
+
57
+ ActiveSupport::Inflector.inflections(:en) do |inflect|
58
+ inflect.acronym "UI"
59
+ inflect.acronym "URL"
60
+ end
61
+
62
+ def self.to_table_row_header
63
+ to_table_row_field_names.collect { |field| field.to_s.titleize }
64
+ end
65
+
66
+ def to_table_row
67
+ self.class.to_table_row_field_names.collect { |field| send(field) }
68
+ end
69
+
70
+ PROJECT_INFORMATION_NOT_AVAILABLE = "N/A"
71
+
72
+ def project_key
73
+ if with_project_information?
74
+ jira_board.location.fetch("projectKey")
75
+ else
76
+ PROJECT_INFORMATION_NOT_AVAILABLE
77
+ end
78
+ end
79
+
80
+ def with_project_information?
81
+ jira_board.respond_to?(:location)
82
+ end
83
+
84
+ def sprint_compatible?
85
+ jira_board.type =~ /^(scrum)$/
86
+ end
87
+
88
+ def url
89
+ jira_board.url
90
+ end
91
+
92
+ def ui_url
93
+ request_path =
94
+ if with_project_information?
95
+ "/jira/software/c/projects/#{project_key}/boards/#{id}"
96
+ else
97
+ "/secure/RapidBoard.jspa?rapidView=#{id}"
98
+ end
99
+
100
+ tool.jira_url(request_path)
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end