jira-auto-tool 1.2.3 → 1.3.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3251ceb453c396902d6db2a2b60d521ee62d63c2790def56a98bed4efffb08c1
4
- data.tar.gz: ccc07c34fd13d46b9037d326b9fad1da98c1fe11c33918648e88461f994bc65d
3
+ metadata.gz: 01e73565bdbb82263685fcd3f903a96f53606fd2a742dcd7baf290767535e231
4
+ data.tar.gz: 6caf206fb9f7754362edf4ec3fd1c2c85ba82f13a95711f8716e968b63deb85e
5
5
  SHA512:
6
- metadata.gz: 13a44334523917299d28f617f873212696ec839fa37f64f9056fa811a60aa385641c4f37ea6a528ec459316a28899a03aad4de8162deee77a874db81a199b2bd
7
- data.tar.gz: b910dbd81dfd35759b02d0e44b596b3a191bc6dc587342557ca7187ea233eea90d0761c93104e155e1ff17739f8f7c86e643eebfc6a6b6a4b7de27c135f22181
6
+ metadata.gz: 1f195ea5c9cbeb1a525cc3b9684d71f0a791b05935510f221bfc347b173165abc70598ceacf01918cee660172f385a9ed1ac824aaeecaa78d4cc6894683a6cf5
7
+ data.tar.gz: 807721d720cde7b2142a8c5cae279ea20fe857d79a107e90efa62742f8fa5cb298a5cde6333463085cd00ac6e702101237d4ec0b421d923f2e0e90e65c934952
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.4.8
data/README.md CHANGED
@@ -4,15 +4,33 @@
4
4
  ![Main Workflow - all branches](https://github.com/cbroult/jira-auto-tool/actions/workflows/main.yml/badge.svg?label=Ruby%20-%20all%20branches)
5
5
 
6
6
  ****
7
- The purpose of this tool it support managing the sprints of multiple teams so it is easier to adjust to changes.
7
+ The purpose of this tool is to make it easier to make adjustments to the sprints of multiple teams.
8
8
  See the [feature files](./features) for some behavior examples.
9
9
 
10
10
  ## Table of Contents
11
11
 
12
- <!-- Generated using jekyll-toc -->
13
-
14
- * TOC
15
- {:toc}
12
+ - [Principles](#principles)
13
+ - [Installation](#installation)
14
+ - [Setup](#setup)
15
+ - [Usage](#usage)
16
+ - [Warning](#warning)
17
+ - [Add Sprints](#add-sprints)
18
+ - [Adjusting The End Date Of Sprints](#adjusting-the-end-date-of-sprints)
19
+ - [Align Time In Sprint Dates](#align-time-in-sprint-dates)
20
+ - [List Sprints](#list-sprints)
21
+ - [List Sprint Prefixes (Teams)](#list-sprint-prefixes-teams)
22
+ - [Rename Sprints](#rename-sprints)
23
+ - [Team Sprint Mapping](#team-sprint-mapping)
24
+ - [Team Ticket Sprint Dispatching](#team-ticket-sprint-dispatching)
25
+ - [Development](#development)
26
+ - [Install Dependencies](#install-dependencies)
27
+ - [Continuous Testing While Making Changes](#continuous-testing-while-making-changes)
28
+ - [Experiment Using An Interactive Prompt](#experiment-using-an-interactive-prompt)
29
+ - [Install Locally](#install-locally)
30
+ - [Release](#release)
31
+ - [Contributing](#contributing)
32
+ - [License](#license)
33
+ - [Code of Conduct](#code-of-conduct)
16
34
 
17
35
  ## Principles
18
36
 
@@ -49,7 +67,7 @@ will use the existing ones as a reference for the prefix and the length of the s
49
67
  ```
50
68
  2. Adjust the file to your context.
51
69
 
52
- The following environment variables have to be set to use this tool. **Except** for te `JIRA_API_TOKEN` that should
70
+ The following environment variables have to be set to use this tool. **Except** for the `JIRA_API_TOKEN` that should
53
71
  be done via the configuration file.
54
72
 
55
73
  Some explanations:
@@ -72,9 +90,9 @@ Optional environment variables:
72
90
  See [sprint filtering](./features/sprint_filtering.feature).
73
91
  - `JIRA_CONTEXT_PATH` - Context path for Jira instance (if needed typically "/jira").
74
92
  - `JIRA_HTTP_DEBUG` - Enable HTTP debug logging (set to "true" or "false").
75
- - `JAT_RATE_LIMIT_PER_INTERVAL` - Rate limit for Jira API calls (e.g., "1")
76
- See [Control Jira HTTP request rate.](./features/control_http_request_rate_limit.feature).
77
93
  - `JAT_RATE_INTERVAL_IN_SECONDS` - Interval for rate limiting in seconds (e.g., "1").
94
+ - `JAT_RATE_LIMIT_PER_INTERVAL` - Rate limit for Jira API calls (e.g., "1")
95
+ See [Control Jira HTTP request rate](./features/control_http_request_rate_limit.feature).
78
96
 
79
97
  ## Usage
80
98
 
@@ -106,6 +124,15 @@ to the teams respective sprint prefixes.
106
124
  jira-auto-tool --sprint-add=25.4.3,4
107
125
  ```
108
126
 
127
+ ### Adjusting The End Date Of Sprints
128
+
129
+ The following is going to
130
+ [adjust the end date of sprints](./features/update_sprint_end_date_and_shift_following_ones.feature)
131
+ named `sprint_prefix_25.1.5` and shift the subsequent ones by adjust their start and end dates:
132
+ ```bash
133
+ jira-auto-tool --sprint-update-end-date=25.1.5,"2025-02-25 16:00:00 UTC"
134
+ ```
135
+
109
136
  ### Align Time In Sprint Dates
110
137
 
111
138
  ````bash
@@ -170,7 +197,18 @@ To install this gem onto your local machine, run `bundle exec rake install`.
170
197
 
171
198
  ### Release
172
199
 
173
- To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
200
+ 1. Bump the gem version:
201
+ ```bash
202
+ bundle exec rake version:bump[{major|minor|patch}]
203
+ ```
204
+ 2. Release the version to [rubygems.org](https://rubygems.org):
205
+ ```bash
206
+ bundle exec rake release
207
+ ```
208
+ which will:
209
+ * create a git tag for the version
210
+ * push git commits and the created tag
211
+ * push the `.gem` file to [rubygems.org](https://rubygems.org).
174
212
 
175
213
  ## Contributing
176
214
 
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Disable wrappers only on Windows
4
+ puts "Disabling wrappers for Windows installation"
5
+ RubyGems.configuration.wrappers = false
6
+ exit 0
@@ -63,7 +63,7 @@ Feature: Environment Configuration Management
63
63
  Please remove first before running this again!
64
64
  """
65
65
 
66
- Scenario: Tool successfully loads the config
66
+ Scenario: Tool successfully loads the config from the current directory and hides secret values when listing them
67
67
  Given a file named "jira-auto-tool.env.yaml.erb" with:
68
68
  """
69
69
  ---
@@ -104,7 +104,7 @@ Feature: Environment Configuration Management
104
104
  | JAT_RATE_LIMIT_IMPLEMENTATION | |
105
105
  | JAT_RATE_LIMIT_PER_INTERVAL | |
106
106
  | JAT_TICKETS_FOR_TEAM_SPRINT_TICKET_DISPATCHER_JQL | project = PROJ AND Sprint IS EMPTY |
107
- | JIRA_API_TOKEN | current API TOKEN |
107
+ | JIRA_API_TOKEN | **** |
108
108
  | JIRA_BOARD_NAME | Team Board |
109
109
  | JIRA_BOARD_NAME_REGEX | ART 16|unconventional board name |
110
110
  | JIRA_CONTEXT_PATH | /jira |
@@ -116,7 +116,7 @@ Feature: Environment Configuration Management
116
116
  +---------------------------------------------------+------------------------------------+
117
117
  """
118
118
 
119
- Scenario: Tool looks first for configuration in the current directory
119
+ Scenario: Tool looks first for configuration in the current directory (hiding secret when listing)
120
120
  Given a file named "./jira-auto-tool.env.yaml.erb" with:
121
121
  """
122
122
  ---
@@ -136,8 +136,16 @@ Feature: Environment Configuration Management
136
136
  """
137
137
  Using configuration from ./jira-auto-tool.env.yaml.erb
138
138
  """
139
+ And the output should match:
140
+ """
141
+ JIRA_API_TOKEN\s+\|\s\*{4}
142
+ """
143
+ And the output should match:
144
+ """
145
+ JIRA_USERNAME\s+\|\scurrent@company.com
146
+ """
139
147
 
140
- Scenario: Tool looks for home directory config folder when no config file in the current directory
148
+ Scenario: Tool looks for home directory config folder when no config file in the current directory (hiding secret when listing)
141
149
  Given a file named "./jira-auto-tool.env.yaml.erb" does not exist
142
150
  And a file named "~/.config/jira-auto-tool/jira-auto-tool.env.yaml.erb" with:
143
151
  """
@@ -150,14 +158,23 @@ Feature: Environment Configuration Management
150
158
  """
151
159
  Using configuration from .+/.config/jira-auto-tool/jira-auto-tool.env.yaml.erb
152
160
  """
161
+ And the output should match:
162
+ """
163
+ JIRA_API_TOKEN\s+\|\s\*{4}\s+
164
+ """
165
+ And the output should match:
166
+ """
167
+ JIRA_USERNAME\s+\|\shome@company.com
168
+ """
153
169
 
154
- Scenario: Tool uses the existing environment values if no config file found
170
+ Scenario: Tool uses the existing environment values if no config file found (hiding secret when listing)
155
171
  Given the following files should not exist:
156
172
  | ./jira-auto-tool.env.yaml.erb |
157
173
  | ~/.config/jira-auto-tool/jira-auto-tool.env.yaml.erb |
158
174
  And the following environment variables are set:
159
- | name | value |
160
- | JIRA_API_TOKEN | token-value |
175
+ | name | value |
176
+ | JIRA_API_TOKEN | token-value |
177
+ | JIRA_USERNAME | env@company.com |
161
178
  When I successfully run `jira-auto-tool --env-list`
162
179
  Then the output should match:
163
180
  """
@@ -167,5 +184,28 @@ Feature: Environment Configuration Management
167
184
  """
168
185
  And the output should match:
169
186
  """
170
- JIRA_API_TOKEN\s+|token-value\s+
187
+ JIRA_API_TOKEN\s+\|\s\*{4}
188
+ """
189
+ And the output should match:
190
+ """
191
+ JIRA_USERNAME\s+\|\senv@company.com
171
192
  """
193
+
194
+ Scenario: Tool displays a useful error message in case of error
195
+ Given a file named "./jira-auto-tool.env.yaml.erb" with:
196
+ """
197
+ ---
198
+ JIRA_USERNAME: "error@company.com"
199
+ JIRA_API_TOKEN: "error-token"
200
+ JIRA_SITE_URL: "https://home.atlassian.net"
201
+ <%
202
+ raise "This is meant to fail while loading!"
203
+ %>
204
+ """
205
+ When I run `jira-auto-tool --env-list`
206
+ Then it should fail with:
207
+ """
208
+ ERROR Jira::Auto::Tool::EnvironmentLoader : ./jira-auto-tool.env.yaml.erb:6: failed to load with the following error: (RuntimeError)
209
+ This is meant to fail while loading!
210
+ """
211
+
@@ -4,7 +4,13 @@ Feature: Control the HTTP request rate limit
4
4
  I need the ability to control the HTTP request limit
5
5
 
6
6
  Scenario Outline: Limiting the request rate
7
- Given the following environment variables are set:
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.2 | 4-day | 2024-12-05 11:00:00 UTC | future |
12
+ | ART-32_Platform_24.4.7 | 3-week | 2024-10-07 11:00:00 UTC | future |
13
+ And the following environment variables are set:
8
14
  | name | value |
9
15
  | JAT_RATE_INTERVAL_IN_SECONDS | <rate_interval_in_seconds> |
10
16
  | JAT_RATE_LIMIT_IMPLEMENTATION | <jat_rate_limit_implementation> |
@@ -14,7 +20,7 @@ Feature: Control the HTTP request rate limit
14
20
  Examples:
15
21
  | jat_rate_limit_implementation | rate_limit_per_interval | rate_interval_in_seconds | minimal_time | maximal_time |
16
22
  | | 0 | 0 | 0 | 5 |
17
- | in_process | 1 | 2 | 1 | 20 |
23
+ | in_process | 1 | 1 | 1 | 20 |
18
24
  | redis | 1 | 2 | 1 | 20 |
19
25
  | redis | 1 | 10 | 18 | 120 |
20
26
 
@@ -5,14 +5,14 @@ Feature: Add sprints using existing ones as reference
5
5
 
6
6
  Background:
7
7
  Given a Jira Scrum board
8
- And the board only has the following sprints:
8
+
9
+ Scenario: Add several sprints using existing sprint prefixes
10
+ Given the board only has the following sprints:
9
11
  | comment | name | length | start_date | state |
10
12
  | none added since closed | Food_Supply_25.1.3 | 2-week | 2025-02-01 11:00:00 UTC | closed |
11
13
  | "sprints | Food_Delivery_25.1.4 | 4-day | 2025-02-01 11:00:00 UTC | future |
12
14
  | expected to be | Food_Market_25.2.1 | 3-week | 2025-02-01 11:00:00 UTC | active |
13
15
  | added" | Food_Restaurant_25.2.1 | 4-week | 2025-02-21 11:00:00 UTC | future |
14
-
15
- Scenario: Add several sprints using existing sprint prefixes
16
16
  When I successfully run `jira-auto-tool --sprint-add=25.3.1,4`
17
17
  Then afterwards the board only has the following sprints:
18
18
  | name | start_date | state |
@@ -34,6 +34,12 @@ Feature: Add sprints using existing ones as reference
34
34
  | Food_Restaurant_25.3.4 | 2025-06-13 11:00:00 UTC | future |
35
35
 
36
36
  Scenario: Add several planning interval sprints
37
+ Given the board only has the following sprints:
38
+ | comment | name | length | start_date | state |
39
+ | none added since closed | Food_Supply_25.1.3 | 2-week | 2025-02-01 11:00:00 UTC | closed |
40
+ | "sprints | Food_Delivery_25.1.4 | 4-day | 2025-02-01 11:00:00 UTC | future |
41
+ | expected to be | Food_Market_25.2.1 | 3-week | 2025-02-01 11:00:00 UTC | active |
42
+ | added" | Food_Restaurant_25.2.1 | 4-week | 2025-02-21 11:00:00 UTC | future |
37
43
  When I successfully run `jira-auto-tool --sa=25.2.2,3 --sa=25.3.1,4 --sa=25.4.1,5`
38
44
  Then afterwards the board only has the following sprints:
39
45
  | name | start_date | state |
@@ -77,3 +83,25 @@ Feature: Add sprints using existing ones as reference
77
83
  | Food_Restaurant_25.4.3 | 2025-11-28 11:00:00 UTC | future |
78
84
  | Food_Restaurant_25.4.4 | 2025-12-26 11:00:00 UTC | future |
79
85
  | Food_Restaurant_25.4.5 | 2026-01-23 11:00:00 UTC | future |
86
+
87
+ Scenario: Adding sprints is not creating duplicates or ones anterior to last sprints of prefixes
88
+ Given the board only has the following sprints:
89
+ | comment | name | length | start_date | state |
90
+ | none added since closed | Food_Supply_25.1.3 | 2-week | 2025-02-01 11:00:00 UTC | closed |
91
+ | all sprints added | Food_Delivery_25.1.4 | 4-day | 2025-02-01 11:00:00 UTC | future |
92
+ | the last 3 added since the first already exist | Food_Market_25.2.1 | 3-week | 2025-02-01 11:00:00 UTC | active |
93
+ | none added because would be anterior existing sprint | Food_Restaurant_25.3.1 | 4-week | 2025-02-21 11:00:00 UTC | future |
94
+ When I successfully run `jira-auto-tool --sa=25.2.1,4`
95
+ Then afterwards the board only has the following sprints:
96
+ | name | start_date | state |
97
+ | Food_Supply_25.1.3 | 2025-02-01 11:00:00 UTC | closed |
98
+ | Food_Delivery_25.1.4 | 2025-02-01 11:00:00 UTC | future |
99
+ | Food_Delivery_25.2.1 | 2025-02-05 11:00:00 UTC | future |
100
+ | Food_Delivery_25.2.2 | 2025-02-09 11:00:00 UTC | future |
101
+ | Food_Delivery_25.2.3 | 2025-02-13 11:00:00 UTC | future |
102
+ | Food_Delivery_25.2.4 | 2025-02-17 11:00:00 UTC | future |
103
+ | Food_Market_25.2.1 | 2025-02-01 11:00:00 UTC | active |
104
+ | Food_Market_25.2.2 | 2025-02-22 11:00:00 UTC | future |
105
+ | Food_Market_25.2.3 | 2025-03-15 11:00:00 UTC | future |
106
+ | Food_Market_25.2.4 | 2025-04-05 11:00:00 UTC | future |
107
+ | Food_Restaurant_25.3.1 | 2025-02-21 11:00:00 UTC | future |
@@ -46,11 +46,18 @@ module Jira
46
46
  def tool_environment
47
47
  Environment.constants.sort.to_h do |constant|
48
48
  constant_as_string = constant.to_s
49
+ actual_value = ENV.fetch(constant_as_string, nil)
50
+ value_to_display = environment_variable_holds_a_secret?(constant_as_string) ? "****" : actual_value
49
51
 
50
- [constant_as_string, ENV.fetch(constant_as_string, nil)]
52
+ [constant_as_string, value_to_display]
51
53
  end
52
54
  end
53
55
 
56
+ def environment_variable_holds_a_secret?(env_var_name)
57
+ method_name = env_var_name.to_s.downcase
58
+ tool.send("#{method_name}_holds_a_secret?")
59
+ end
60
+
54
61
  def file_path
55
62
  File.exist?(current_dir_file_path) ? current_dir_file_path : config_dir_file_path
56
63
  end
@@ -81,10 +88,27 @@ module Jira
81
88
 
82
89
  def config_values
83
90
  @config_values ||= YAML.safe_load(config_file_content) || {}
91
+ rescue StandardError => e
92
+ error_line = e.backtrace_locations.first.lineno
93
+ message = <<~EOEMSG
94
+ #{file_path}:#{error_line}: failed to load with the following error:
95
+ #{e.message}
96
+ EOEMSG
97
+
98
+ raise message
84
99
  end
85
100
 
86
101
  def config_file_content
87
102
  @config_file_content ||= File.exist?(file_path) ? ERB.new(File.read(file_path)).result(binding) : ""
103
+ rescue StandardError => e
104
+ error_line = e.backtrace_locations.first.lineno
105
+
106
+ message = <<~EOEMSG
107
+ #{file_path}:#{error_line}: failed to load with the following error:
108
+ #{e.message}
109
+ EOEMSG
110
+
111
+ raise message
88
112
  end
89
113
 
90
114
  def table
@@ -6,12 +6,13 @@ module Jira
6
6
  module Helpers
7
7
  module EnvironmentBasedValue
8
8
  # TODO: overly complex - simplify by moving this to the Config and define a Configurable module
9
- def define_overridable_environment_based_value(method_name)
9
+ def define_overridable_environment_based_value(method_name, holds_a_secret)
10
10
  define_reader(method_name)
11
11
  define_predicate(method_name)
12
12
  define_reader_accepting_default_value(method_name)
13
13
  define_writer(method_name)
14
14
  define_environment_variable_name_constant(method_name)
15
+ define_holds_a_secret_predicate(method_name, holds_a_secret)
15
16
  end
16
17
 
17
18
  def define_reader(method_name)
@@ -32,6 +33,10 @@ module Jira
32
33
  end
33
34
  end
34
35
 
36
+ def define_holds_a_secret_predicate(method_name, holds_a_secret)
37
+ define_method(:"#{method_name}_holds_a_secret?") { holds_a_secret }
38
+ end
39
+
35
40
  def define_reader_accepting_default_value(method_name)
36
41
  define_method(:"#{method_name}_when_defined_else") do |value|
37
42
  if config.key?(method_name)
@@ -21,9 +21,10 @@ module Jira
21
21
  parsed_new_name = Sprint::Name.new_with(sprint_prefix.name, sprint_suffix)
22
22
 
23
23
  iteration_count.times do |_iteration|
24
- last_sprint = create_sprint_for(last_sprint, parsed_new_name.to_s)
25
-
26
- sprint_prefix << last_sprint
24
+ unless parsed_new_name <= last_sprint.parsed_name
25
+ last_sprint = create_sprint_for(last_sprint, parsed_new_name.to_s)
26
+ sprint_prefix << last_sprint
27
+ end
27
28
 
28
29
  parsed_new_name = parsed_new_name.next_in_planning_interval
29
30
  end
@@ -39,12 +39,12 @@ module Jira
39
39
  def each_issue_type_field
40
40
  tool.jira_client.Createmeta.all({ projectKeys: project.key, "expand" => "projects.issuetypes.fields" })
41
41
  .each do |createmeta|
42
- createmeta.attrs["issuetypes"].each do |issue_type|
43
- issue_type_name = issue_type["name"]
44
- issue_type["fields"].each_value do |field|
45
- yield(issue_type_name, field)
46
- end
47
- end
42
+ createmeta.attrs["issuetypes"].each do |issue_type|
43
+ issue_type_name = issue_type["name"]
44
+ issue_type["fields"].each_value do |field|
45
+ yield(issue_type_name, field)
46
+ end
47
+ end
48
48
  end
49
49
  end
50
50
 
@@ -33,11 +33,11 @@ module Jira
33
33
 
34
34
  alias original_request request
35
35
 
36
- def request(*args)
36
+ def request(*)
37
37
  if rate_limit_per_interval == NO_RATE_LIMIT_PER_INTERVAL
38
- original_request(*args)
38
+ original_request(*)
39
39
  else
40
- rate_limit { original_request(*args) }
40
+ rate_limit { original_request(*) }
41
41
  end
42
42
  end
43
43
 
@@ -42,8 +42,23 @@ module Jira
42
42
  %i[key summary sprint implementation_team expected_start_date]
43
43
  end
44
44
 
45
+ # Compatibility helper to access Issue fields across Jira API versions
46
+ # - jira-ruby v2: JIRA::Resource::Issue responds to #fields
47
+ # - jira-ruby v3: fields are nested under #attrs['fields']
48
+ def ticket_fields
49
+ if jira_ticket.respond_to?(:fields)
50
+ jira_ticket.fields
51
+ else
52
+ raise "attrs not found in #{jira_ticket}!" unless jira_ticket.respond_to?(:attrs)
53
+
54
+ attrs = jira_ticket.attrs
55
+ attrs["fields"] || attrs[:fields] ||
56
+ raise("fields not found in #{attrs} from #{jira_ticket}!")
57
+ end
58
+ end
59
+
45
60
  def sprint
46
- jira_ticket.fields.fetch(tool.jira_sprint_field.id)
61
+ ticket_fields.fetch(tool.jira_sprint_field.id)
47
62
  end
48
63
 
49
64
  def jira_client
@@ -96,8 +111,8 @@ module Jira
96
111
  def jira_field_value(field_id = caller_locations(1, 1).first.base_label)
97
112
  log.debug { "jira_field_value(#{field_id})" }
98
113
 
99
- field = jira_ticket.fields.fetch(field_id) do |id|
100
- raise "#{id}: value not found in #{jira_ticket.fields}"
114
+ field = ticket_fields.fetch(field_id) do |id|
115
+ raise "#{id}: value not found in #{ticket_fields}"
101
116
  end
102
117
 
103
118
  log.debug { "jira_field_value(#{field_id}), field: #{field}" }
@@ -3,7 +3,7 @@
3
3
  module Jira
4
4
  module Auto
5
5
  class Tool
6
- VERSION = "1.2.3"
6
+ VERSION = "1.3.0"
7
7
  end
8
8
  end
9
9
  end
@@ -139,27 +139,28 @@ module Jira
139
139
  jira_base_url + url
140
140
  end
141
141
 
142
- ENVIRONMENT_BASED_VALUE_SYMBOLS = %i[
143
- art_sprint_regex
144
- expected_start_date_field_name
145
- implementation_team_field_name
146
- jat_rate_interval_in_seconds
147
- jat_rate_limit_implementation
148
- jat_rate_limit_per_interval
149
- jat_tickets_for_team_sprint_ticket_dispatcher_jql
150
- jira_api_token
151
- jira_board_name
152
- jira_board_name_regex
153
- jira_context_path
154
- jira_http_debug
155
- jira_project_key
156
- jira_site_url
157
- jira_sprint_field_name
158
- jira_username
159
- ].freeze
160
-
161
- ENVIRONMENT_BASED_VALUE_SYMBOLS.each do |method_name|
162
- define_overridable_environment_based_value(method_name)
142
+ HOLDS_A_SECRET = true
143
+ ENVIRONMENT_BASED_VALUE_SYMBOLS =
144
+ ([[:jira_api_token, HOLDS_A_SECRET]] + %i[
145
+ art_sprint_regex
146
+ expected_start_date_field_name
147
+ implementation_team_field_name
148
+ jat_rate_interval_in_seconds
149
+ jat_rate_limit_implementation
150
+ jat_rate_limit_per_interval
151
+ jat_tickets_for_team_sprint_ticket_dispatcher_jql
152
+ jira_board_name
153
+ jira_board_name_regex
154
+ jira_context_path
155
+ jira_http_debug
156
+ jira_project_key
157
+ jira_site_url
158
+ jira_sprint_field_name
159
+ jira_username
160
+ ].collect { |value_name| [value_name, !HOLDS_A_SECRET] }).freeze
161
+
162
+ ENVIRONMENT_BASED_VALUE_SYMBOLS.each do |method_name, holds_a_secret|
163
+ define_overridable_environment_based_value(method_name, holds_a_secret)
163
164
  end
164
165
 
165
166
  def board_controller
@@ -207,7 +208,7 @@ module Jira
207
208
  end
208
209
 
209
210
  def tickets(jql = "project = #{project.key}")
210
- jira_client.Issue.jql(jql).collect { |jira_ticket| Ticket.new(self, jira_ticket) }
211
+ jira_client.Issue.jql(jql, fields: ["*all"]).collect { |jira_ticket| Ticket.new(self, jira_ticket) }
211
212
  rescue StandardError => e
212
213
  raise <<~EOEM
213
214
  Error fetching project tickets: Something went wrong:
@@ -29,7 +29,7 @@ end
29
29
  def amend_commit_to_include_gemfile_lock_changes
30
30
  puts "Amending commit to include Gemfile.lock update..."
31
31
  system("git add .")
32
- system(%(git commit --amend --no-edit))
32
+ system("git commit --amend --no-edit")
33
33
  end
34
34
 
35
35
  namespace :version do
@@ -5,6 +5,7 @@ require "rspec"
5
5
  module Jira
6
6
  module Auto
7
7
  class Tool
8
+ # rubocop:disable Metrics/ClassLength
8
9
  class EnvironmentLoader
9
10
  RSpec.describe EnvironmentLoader do
10
11
  let(:environment_loader) { described_class.new(tool, auto_setup: auto_setup) }
@@ -56,7 +57,7 @@ module Jira
56
57
  end
57
58
 
58
59
  describe "#tool_environment" do
59
- let(:environment_keys) { %i[JIRA_HOST JIRA_USER JIRA_PASSWORD] }
60
+ let(:environment_keys) { %i[JIRA_SITE_URL JIRA_USERNAME JIRA_API_TOKEN] }
60
61
 
61
62
  before do
62
63
  allow(Environment).to receive(:constants).and_return(environment_keys)
@@ -64,14 +65,29 @@ module Jira
64
65
  environment_keys.each do |environment_key|
65
66
  allow(ENV).to receive(:fetch).with(environment_key.to_s, nil).and_return("#{environment_key} value")
66
67
  end
68
+
69
+ allow(tool).to receive_messages(
70
+ jira_api_token_holds_a_secret?: true,
71
+ jira_site_url_holds_a_secret?: false,
72
+ jira_username_holds_a_secret?: false
73
+ )
67
74
  end
68
75
 
69
76
  it do
70
- expect(environment_loader.tool_environment).to eq(
71
- "JIRA_HOST" => "JIRA_HOST value",
72
- "JIRA_USER" => "JIRA_USER value",
73
- "JIRA_PASSWORD" => "JIRA_PASSWORD value"
74
- )
77
+ expect(environment_loader.tool_environment)
78
+ .to eq(
79
+ "JIRA_API_TOKEN" => "****",
80
+ "JIRA_SITE_URL" => "JIRA_SITE_URL value",
81
+ "JIRA_USERNAME" => "JIRA_USERNAME value"
82
+ )
83
+ end
84
+ end
85
+
86
+ describe "#environment_variable_holds_a_secret?" do
87
+ it "allows checking that the corresponding constant is a secret or not" do
88
+ allow(tool).to receive(:jira_api_token_holds_a_secret?).and_return(true)
89
+
90
+ expect(environment_loader).to be_environment_variable_holds_a_secret("JIRA_API_TOKEN")
75
91
  end
76
92
  end
77
93
 
@@ -132,10 +148,12 @@ module Jira
132
148
  end
133
149
 
134
150
  describe "#setup" do
135
- it "sets up the value according to the configuration file content" do
136
- allow(environment_loader).to receive_messages(file_path: "file_path")
151
+ before do
152
+ allow(environment_loader).to receive_messages(file_path: "path/to/config/file.yaml")
137
153
  allow(environment_loader).to receive_messages(config_file_content: "file_content")
154
+ end
138
155
 
156
+ it "sets up the value according to the configuration file content" do
139
157
  allow(YAML)
140
158
  .to receive(:safe_load)
141
159
  .with("file_content")
@@ -147,10 +165,25 @@ module Jira
147
165
 
148
166
  environment_loader.send(:setup)
149
167
  end
168
+
169
+ context "when the YAML parsing fails" do
170
+ it "generates an error message including the file name" do
171
+ allow(YAML).to receive(:safe_load)
172
+ .with("file_content")
173
+ .and_raise(RuntimeError, "could not find expected ':' at line 3 column 6)")
174
+
175
+ expect { environment_loader.send(:setup) }
176
+ .to raise_error(RuntimeError, <<~EOEMSG
177
+ path/to/config/file.yaml:188: failed to load with the following error:
178
+ could not find expected ':' at line 3 column 6)
179
+ EOEMSG
180
+ )
181
+ end
182
+ end
150
183
  end
151
184
 
152
185
  describe "#config_file_content" do
153
- let(:file_path) { "file_path" }
186
+ let(:file_path) { "path/to/config/file" }
154
187
  let(:file_content) do
155
188
  <<-YAML_ERB
156
189
  ---
@@ -174,9 +207,31 @@ module Jira
174
207
  end
175
208
 
176
209
  it { expect(environment_loader.send(:config_file_content)).to eq(erb_result) }
210
+
211
+ context "when the ERB evaluation fails" do
212
+ let(:file_content) do
213
+ <<-YAML_ERB
214
+ ---
215
+ a_key: <%= 4*4 %>
216
+ another_key: <%= 4*4 %>
217
+ <%
218
+ raise "An error that should be caught and reported!"#{" "}
219
+ %>
220
+ YAML_ERB
221
+ end
222
+
223
+ it "generates an error message including the file name" do
224
+ expect { environment_loader.send(:config_file_content) }
225
+ .to raise_error(RuntimeError, <<~EOEMSG)
226
+ path/to/config/file:5: failed to load with the following error:
227
+ An error that should be caught and reported!
228
+ EOEMSG
229
+ end
230
+ end
177
231
  end
178
232
  end
179
233
  end
234
+ # rubocop:enable Metrics/ClassLength
180
235
  end
181
236
  end
182
237
  end
@@ -33,27 +33,90 @@ module Jira
33
33
  end
34
34
  end
35
35
 
36
+ # rubocop:disable Naming/VariableNumber, RSpec/IndexedLet
36
37
  describe "#act_on_sprints_for_sprint_prefix" do
37
- def get_sprint(name, attributes)
38
- instance_double(Sprint, name: name, to_s: name, **attributes)
38
+ def get_sprint(name, attributes = {})
39
+ instance_double(Sprint, name: name, to_s: name, **attributes,
40
+ parsed_name: Sprint::Name.parse(name))
39
41
  end
40
42
 
41
- let(:last_sprint) { get_sprint "Food_Delivery_25.2.1", end_date: "2025-02-16 12:45", length_in_days: 10 }
43
+ let(:sprint_prefix) do
44
+ instance_double(Sprint::Prefix, name: "Food_Delivery", last_sprint: last_sprint)
45
+ end
46
+
47
+ context "when the sprints to create are all posterior to the last sprint from a naming perspective" do
48
+ let(:last_sprint) do
49
+ get_sprint "Food_Delivery_25.2.1", end_date: "2025-02-16 12:45", length_in_days: 10
50
+ end
51
+
52
+ let(:sprint_25_3_1) { get_sprint("Food_Delivery_25.3.1") }
53
+ let(:sprint_25_3_2) { get_sprint("Food_Delivery_25.3.2") }
54
+ let(:sprint_25_3_3) { get_sprint("Food_Delivery_25.3.3") }
55
+ let(:sprint_25_3_4) { get_sprint("Food_Delivery_25.3.4") }
56
+
57
+ before do
58
+ allow(updater).to receive(:create_sprint_for).with(last_sprint,
59
+ "Food_Delivery_25.3.1")
60
+ .and_return(sprint_25_3_1)
61
+
62
+ allow(updater).to receive(:create_sprint_for).with(sprint_25_3_1, "Food_Delivery_25.3.2")
63
+ .and_return(sprint_25_3_2)
64
+
65
+ allow(updater).to receive(:create_sprint_for).with(sprint_25_3_2, "Food_Delivery_25.3.3")
66
+ .and_return(sprint_25_3_3)
67
+
68
+ allow(updater).to receive(:create_sprint_for).with(sprint_25_3_3, "Food_Delivery_25.3.4")
69
+ .and_return(sprint_25_3_4)
70
+ end
71
+
72
+ it "creates the expected number of sprints with the expected names" do
73
+ expect(sprint_prefix).to receive(:<<).with(sprint_25_3_1)
74
+ expect(sprint_prefix).to receive(:<<).with(sprint_25_3_2)
75
+ expect(sprint_prefix).to receive(:<<).with(sprint_25_3_3)
76
+ expect(sprint_prefix).to receive(:<<).with(sprint_25_3_4)
77
+
78
+ updater.act_on_sprints_for_sprint_prefix(sprint_prefix)
79
+ end
80
+ end
81
+
82
+ context "when some sprints to create are anterior to the last sprint from a naming perspective" do
83
+ let(:last_sprint) do
84
+ get_sprint "Food_Delivery_25.3.2", end_date: "2025-02-16 12:45", length_in_days: 10
85
+ end
86
+
87
+ let(:sprint_25_3_3) { get_sprint("Food_Delivery_25.3.3") }
88
+ let(:sprint_25_3_4) { get_sprint("Food_Delivery_25.3.4") }
89
+
90
+ before do
91
+ allow(updater).to receive(:create_sprint_for).with(last_sprint, "Food_Delivery_25.3.3")
92
+ .and_return(sprint_25_3_3)
93
+
94
+ allow(updater).to receive(:create_sprint_for).with(sprint_25_3_3, "Food_Delivery_25.3.4")
95
+ .and_return(sprint_25_3_4)
96
+ end
97
+
98
+ it "only creates the ones posterior to the last sprint" do
99
+ expect(sprint_prefix).to receive(:<<).with(sprint_25_3_3)
100
+ expect(sprint_prefix).to receive(:<<).with(sprint_25_3_4)
101
+
102
+ updater.act_on_sprints_for_sprint_prefix(sprint_prefix)
103
+ end
104
+ end
42
105
 
43
- let(:sprint_prefix) { instance_double(Sprint::Prefix, name: "Food_Delivery", last_sprint: last_sprint) }
106
+ context "when requested sprints to create are anterior to the last sprint from a naming perspective" do
107
+ let(:last_sprint) do
108
+ get_sprint "Food_Delivery_25.4.1", end_date: "2025-02-16 12:45", length_in_days: 10
109
+ end
44
110
 
45
- # rubocop:disable RSpec/MultipleExpectations
46
- it "creates the expected number of sprints with the expected names" do
47
- expect(updater).to receive(:create_sprint_for).ordered.with(last_sprint, "Food_Delivery_25.3.1")
48
- expect(updater).to receive(:create_sprint_for).ordered.with(nil, "Food_Delivery_25.3.2")
49
- expect(updater).to receive(:create_sprint_for).ordered.with(nil, "Food_Delivery_25.3.3")
50
- expect(updater).to receive(:create_sprint_for).ordered.with(nil, "Food_Delivery_25.3.4")
51
- expect(sprint_prefix).to receive(:<<).exactly(4).times
111
+ it "does not create any since they would be anterior to the last sprint" do
112
+ expect(updater).not_to receive(:create_sprint_for)
113
+ expect(sprint_prefix).not_to receive(:<<)
52
114
 
53
- updater.act_on_sprints_for_sprint_prefix(sprint_prefix)
115
+ updater.act_on_sprints_for_sprint_prefix(sprint_prefix)
116
+ end
54
117
  end
55
- # rubocop:enable RSpec/MultipleExpectations
56
118
  end
119
+ # rubocop:enable Naming/VariableNumber, RSpec/IndexedLet
57
120
  end
58
121
  end
59
122
  end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ # //wsl.localhost/Ubuntu/home/cbroult/work/ruby/jira-auto-tool/features/ticket_fields_spec.rb
4
+
5
+ require "spec_helper"
6
+ require "jira/auto/tool/ticket"
7
+
8
+ module Jira
9
+ module Auto
10
+ class Tool
11
+ RSpec.describe Jira::Auto::Tool::Ticket do
12
+ let(:tool) { instance_double(Tool) }
13
+
14
+ describe "#ticket_fields" do
15
+ context "when JIRA API V2, jira_ticket responds to #fields" do
16
+ let(:jira_ticket_with_fields) do
17
+ jira_resource_double(JIRA::Resource::Issue, fields: { "example_field" => "value" })
18
+ end
19
+
20
+ it "returns the fields hash" do
21
+ ticket = described_class.new(tool, jira_ticket_with_fields)
22
+ expect(ticket.ticket_fields).to eq({ "example_field" => "value" })
23
+ end
24
+ end
25
+
26
+ context "when JIRA API V3, jira_ticket does not respond to #fields but responds to #attrs" do
27
+ let(:jira_ticket_with_attrs) do
28
+ jira_resource_double(JIRA::Resource::Issue, attrs: { "fields" => { "example_field" => "value" } })
29
+ end
30
+
31
+ let(:jira_ticket_with_invalid_attrs) do
32
+ jira_resource_double(JIRA::Resource::Issue, attrs: { "invalid" => "data" })
33
+ end
34
+
35
+ it "returns the fields hash from attrs" do
36
+ ticket = described_class.new(tool, jira_ticket_with_attrs)
37
+ expect(ticket.ticket_fields).to eq({ "example_field" => "value" })
38
+ end
39
+
40
+ it "raises an error if fields are not found in attrs" do
41
+ ticket = described_class.new(tool, jira_ticket_with_invalid_attrs)
42
+ expect do
43
+ ticket.ticket_fields
44
+ end
45
+ .to raise_error("fields not found in {\"invalid\" => \"data\"} from #{jira_ticket_with_invalid_attrs}!")
46
+ end
47
+ end
48
+
49
+ context "when jira_ticket does not respond to #fields or #attrs" do
50
+ let(:jira_ticket_without_fields_or_attrs) { jira_resource_double(JIRA::Resource::Issue) }
51
+
52
+ it "raises an error indicating attrs are not found" do
53
+ ticket = described_class.new(tool, jira_ticket_without_fields_or_attrs)
54
+ expect do
55
+ ticket.ticket_fields
56
+ end.to raise_error("attrs not found in #{jira_ticket_without_fields_or_attrs}!")
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -136,7 +136,7 @@ module Jira
136
136
  end
137
137
 
138
138
  # TODO: move that to environment_based_value_spec
139
- RSpec.shared_examples "an overridable environment based value" do |method_name|
139
+ RSpec.shared_examples "an overridable environment based value" do |method_name, holds_a_secret_expectation|
140
140
  let(:env_var_name) { method_name.to_s.upcase }
141
141
  let(:method_name?) { :"#{method_name}_defined?" }
142
142
  let(:config) { Config.new(object_with_overridable_value) }
@@ -146,6 +146,13 @@ module Jira
146
146
  allow(config).to receive_messages(value_store: {})
147
147
  end
148
148
 
149
+ context "when dealing with sensitive information" do
150
+ it "defines a predicate informing about the value being a secret or not" do
151
+ expect(object_with_overridable_value.send("#{method_name}_holds_a_secret?"))
152
+ .to eq(holds_a_secret_expectation)
153
+ end
154
+ end
155
+
149
156
  context "when the environment variable is set" do
150
157
  let(:expected_value) { "#{env_var_name} env_value" }
151
158
 
@@ -222,11 +229,11 @@ module Jira
222
229
  end
223
230
  end
224
231
 
225
- described_class::ENVIRONMENT_BASED_VALUE_SYMBOLS.each do |method_name|
226
- describe "environment based values" do
232
+ described_class::ENVIRONMENT_BASED_VALUE_SYMBOLS.each do |method_name, holds_a_secret|
233
+ describe "environment based values - #{method_name} - holds_a_secret = #{holds_a_secret}}" do
227
234
  let(:object_with_overridable_value) { tool }
228
235
 
229
- it_behaves_like "an overridable environment based value", method_name
236
+ it_behaves_like "an overridable environment based value", method_name, holds_a_secret
230
237
  end
231
238
  end
232
239
 
@@ -426,7 +433,7 @@ module Jira
426
433
  allow(tool)
427
434
  .to receive_messages(project: jira_resource_double(JIRA::Resource::Project, key: "project_key"))
428
435
 
429
- allow(query).to receive(:jql).with(expected_jql).and_return([instance_double(JIRA::Resource::Issue)])
436
+ allow(query).to receive(:jql).with(expected_jql, fields: ["*all"]).and_return([instance_double(JIRA::Resource::Issue)])
430
437
  end
431
438
 
432
439
  context "without arguments" do
data/spec/spec_helper.rb CHANGED
@@ -33,9 +33,9 @@ RSpec.configure do |config|
33
33
  end
34
34
 
35
35
  config.include(Module.new do
36
- def jira_resource_double(*args)
36
+ def jira_resource_double(*)
37
37
  # rubocop:disable RSpec/VerifiedDoubles
38
- double(*args)
38
+ double(*)
39
39
  # rubocop:enable RSpec/VerifiedDoubles
40
40
  end
41
41
  end)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jira-auto-tool
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.3
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Christophe Broult
@@ -23,6 +23,20 @@ dependencies:
23
23
  - - ">="
24
24
  - !ruby/object:Gem::Version
25
25
  version: '0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: cgi
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
26
40
  - !ruby/object:Gem::Dependency
27
41
  name: http_logger
28
42
  requirement: !ruby/object:Gem::Requirement
@@ -209,12 +223,12 @@ email:
209
223
  - cbroult@yahoo.com
210
224
  executables:
211
225
  - jira-auto-tool
212
- - setup
213
226
  extensions: []
214
227
  extra_rdoc_files: []
215
228
  files:
216
229
  - ".rspec"
217
230
  - ".rubocop.yml"
231
+ - ".ruby-version"
218
232
  - CHANGELOG.md
219
233
  - CODE_OF_CONDUCT.md
220
234
  - Guardfile
@@ -223,13 +237,12 @@ files:
223
237
  - Rakefile
224
238
  - bin/jira-auto-tool
225
239
  - bin/jira-auto-tool.bat
226
- - bin/setup
227
- - bin/setup-dev-win.bat
228
240
  - config/examples/jira-auto-tool.env.yaml.erb
229
241
  - cucumber.yml
230
242
  - documentation/JiraToolClassDiagram.uml
231
243
  - documentation/class_diagram.md
232
244
  - documentation/principle.md
245
+ - ext/no_wrappers_win.rb
233
246
  - features/align_sprint_time_in_dates.feature
234
247
  - features/assign_tickets_to_team_sprints.feature
235
248
  - features/cache_boards.feature
@@ -359,6 +372,7 @@ files:
359
372
  - spec/jira/auto/tool/team_spec.rb
360
373
  - spec/jira/auto/tool/team_sprint_prefix_mapper_spec.rb
361
374
  - spec/jira/auto/tool/team_sprint_ticket_dispatcher_spec.rb
375
+ - spec/jira/auto/tool/ticket_fields_spec.rb
362
376
  - spec/jira/auto/tool/ticket_spec.rb
363
377
  - spec/jira/auto/tool/until_date_spec.rb
364
378
  - spec/jira/auto/tool_spec.rb
@@ -379,14 +393,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
379
393
  requirements:
380
394
  - - ">="
381
395
  - !ruby/object:Gem::Version
382
- version: 3.3.5
396
+ version: 3.4.8
383
397
  required_rubygems_version: !ruby/object:Gem::Requirement
384
398
  requirements:
385
399
  - - ">="
386
400
  - !ruby/object:Gem::Version
387
401
  version: '0'
388
402
  requirements: []
389
- rubygems_version: 3.6.9
403
+ rubygems_version: 4.0.3
390
404
  specification_version: 4
391
405
  summary: Automate making adjustments to Jira sprints for multiple teams following
392
406
  some naming conventions.
data/bin/setup DELETED
@@ -1,8 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
- IFS=$'\n\t'
4
- set -vx
5
-
6
- bundle install
7
-
8
- # Do any other automated setup that you need to do here
@@ -1,3 +0,0 @@
1
-
2
-
3
- choco install act-cli