jira-auto-tool 1.1.5 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +72 -18
- data/config/examples/jira-auto-tool.env.yaml.erb +4 -1
- data/ext/no_wrappers_win.rb +6 -0
- data/features/configure_environment.feature +59 -14
- data/features/control_http_request_rate_limit.feature +19 -7
- data/features/create_sprints_using_existing_ones_as_reference.feature +32 -4
- data/lib/jira/auto/tool/environment_loader.rb +25 -1
- data/lib/jira/auto/tool/helpers/environment_based_value.rb +6 -1
- data/lib/jira/auto/tool/performer/planning_increment_sprint_creator.rb +4 -3
- data/lib/jira/auto/tool/rate_limited_jira_client/in_process_based.rb +26 -0
- data/lib/jira/auto/tool/rate_limited_jira_client/redis_based.rb +40 -0
- data/lib/jira/auto/tool/rate_limited_jira_client.rb +28 -28
- data/lib/jira/auto/tool/version.rb +1 -1
- data/lib/jira/auto/tool.rb +35 -27
- data/lib/tasks/version.rake +1 -1
- data/spec/jira/auto/tool/environment_loader_spec.rb +64 -9
- data/spec/jira/auto/tool/performer/planning_increment_sprint_creator_spec.rb +76 -13
- data/spec/jira/auto/tool/rate_limited_jira_client/in_process_based_spec.rb +65 -0
- data/spec/jira/auto/tool/rate_limited_jira_client/redis_based_spec.rb +56 -0
- data/spec/jira/auto/tool/rate_limited_jira_client_spec.rb +69 -48
- data/spec/jira/auto/tool_spec.rb +27 -24
- metadata +20 -4
- data/bin/setup +0 -8
- data/bin/setup-dev-win.bat +0 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a991321a0f8768d42b913499a80fd76ff32cbea71f243e651c0097a6e9c1f10a
|
4
|
+
data.tar.gz: 346c3e37814218b13f5b3cb57d74b0431655b9dbca95edf3406f7b1d036252b8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c66c77d800f993de4821e03711585976c1cb021258add0d2ee5cb940d7fa92915d66d1b8754a2a4f245d768bbf19b7e71d791673ac9d30b0cf6bff23bcaadfc8
|
7
|
+
data.tar.gz: 8e3c54c724e47e4267a3ed4c65d23ff723a5c67764fdce38a05aae3ffdbc6a10d35691c4618fabc2b429a8f26af09e3303ac1004ffa0cc6ce94b1f0999b65b4c
|
data/README.md
CHANGED
@@ -4,18 +4,44 @@
|
|
4
4
|

|
5
5
|
|
6
6
|
****
|
7
|
-
The purpose of this tool it
|
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
|
-
##
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
10
|
+
## Table of Contents
|
11
|
+
|
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)
|
34
|
+
|
35
|
+
## Principles
|
36
|
+
|
37
|
+
Following a convention over configuration approach:
|
38
|
+
* All Scrum boards from the Jira instance are scanned to identify the ones matching the search criteria
|
39
|
+
(the list is [cached for a day for performance reasons](./features/cache_boards.feature) to deal with Jira
|
40
|
+
instances having thousands of boards);
|
41
|
+
* For each board, only the unclosed sprints are considered;
|
42
|
+
* Sprint manipulations only apply to those sprints whose names match the following format: `sprint_prefix_25.4.3`
|
43
|
+
* [Creating new sprints](./features/create_sprints_using_existing_ones_as_reference.feature)
|
44
|
+
will use the existing ones as a reference for the prefix and the length of the sprint.
|
19
45
|
|
20
46
|
## Installation
|
21
47
|
|
@@ -41,11 +67,8 @@ in such a cloud sandbox. Though, if the sandbox belongs to the target context
|
|
41
67
|
```
|
42
68
|
2. Adjust the file to your context.
|
43
69
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
While we strive to use convention over configuration as a principle, the following environment variables have to be set
|
48
|
-
in order to use this tool:
|
70
|
+
The following environment variables have to be set to use this tool. **Except** for the `JIRA_API_TOKEN` that should
|
71
|
+
be done via the configuration file.
|
49
72
|
|
50
73
|
Some explanations:
|
51
74
|
|
@@ -67,8 +90,9 @@ Optional environment variables:
|
|
67
90
|
See [sprint filtering](./features/sprint_filtering.feature).
|
68
91
|
- `JIRA_CONTEXT_PATH` - Context path for Jira instance (if needed typically "/jira").
|
69
92
|
- `JIRA_HTTP_DEBUG` - Enable HTTP debug logging (set to "true" or "false").
|
70
|
-
- `
|
71
|
-
- `
|
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).
|
72
96
|
|
73
97
|
## Usage
|
74
98
|
|
@@ -79,6 +103,16 @@ See [sprint filtering](./features/sprint_filtering.feature).
|
|
79
103
|
* Leverage the [specification by examples](./features) for a detailled understand of the features.
|
80
104
|
* Note that usually the long option names have a short version equivalent to reduce typing.
|
81
105
|
|
106
|
+
### Warning
|
107
|
+
|
108
|
+
1. You should familiarize yourself with this tool in a Jira sandbox project **before applying it to your context**.
|
109
|
+
That can be done easily by [creating a free Atlassian account](https://www.atlassian.com/software)
|
110
|
+
like it has been done to document [this tool features](./features) using executable specifications.
|
111
|
+
|
112
|
+
1. Remember that you are **not allowed** to use confidential/sensitive information when familiarizing with this tool
|
113
|
+
in such a cloud sandbox. Though, if the sandbox belongs to the target context
|
114
|
+
(e.g., sandbox project on the client Jira instance) you can experiment with the parameters you intend to use later.
|
115
|
+
|
82
116
|
Below are a few examples.
|
83
117
|
|
84
118
|
### Add Sprints
|
@@ -90,6 +124,15 @@ to the teams respective sprint prefixes.
|
|
90
124
|
jira-auto-tool --sprint-add=25.4.3,4
|
91
125
|
```
|
92
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
|
+
|
93
136
|
### Align Time In Sprint Dates
|
94
137
|
|
95
138
|
````bash
|
@@ -154,7 +197,18 @@ To install this gem onto your local machine, run `bundle exec rake install`.
|
|
154
197
|
|
155
198
|
### Release
|
156
199
|
|
157
|
-
|
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).
|
158
212
|
|
159
213
|
## Contributing
|
160
214
|
|
@@ -10,12 +10,15 @@ DISABLE_COVERAGE: true
|
|
10
10
|
EXPECTED_START_DATE_FIELD_NAME: Expected Start
|
11
11
|
IMPLEMENTATION_TEAM_FIELD_NAME: "Implementation Team"
|
12
12
|
JAT_RATE_INTERVAL_IN_SECONDS:
|
13
|
-
|
13
|
+
JAT_RATE_LIMIT_IMPLEMENTATION:
|
14
|
+
JAT_RATE_LIMIT_PER_INTERVAL:
|
14
15
|
JAT_TICKETS_FOR_TEAM_SPRINT_TICKET_DISPATCHER_JQL: "project = <%= project_key %> AND <%= sprint_field_name %> IS EMPTY"
|
16
|
+
# JIRA_BOARD_NAME: "<<<Name of one board if the project>>>"
|
15
17
|
JIRA_BOARD_NAME: "<%= project_key %> - Delivery"
|
16
18
|
JIRA_BOARD_NAME_REGEX: "<%= project_key %>|ART 16|unconventional board name"
|
17
19
|
#JIRA_CONTEXT_PATH: /jira
|
18
20
|
JIRA_CONTEXT_PATH:
|
21
|
+
JIRA_HTTP_DEBUG:
|
19
22
|
JIRA_PROJECT_KEY: <%= project_key %>
|
20
23
|
JIRA_SITE_URL: http://cbroult.atlassian.net:443/
|
21
24
|
JIRA_SPRINT_FIELD_NAME: "<%= sprint_field_name %>"
|
@@ -17,7 +17,7 @@ Feature: Environment Configuration Management
|
|
17
17
|
"""
|
18
18
|
---
|
19
19
|
<%
|
20
|
-
project_key = "
|
20
|
+
project_key = "JATCIDEVLX"
|
21
21
|
sprint_field_name = "Sprint"
|
22
22
|
jira_username = "cbroult@yahoo.com"
|
23
23
|
%>
|
@@ -27,11 +27,14 @@ Feature: Environment Configuration Management
|
|
27
27
|
EXPECTED_START_DATE_FIELD_NAME: Expected Start
|
28
28
|
IMPLEMENTATION_TEAM_FIELD_NAME: "Implementation Team"
|
29
29
|
JAT_RATE_INTERVAL_IN_SECONDS:
|
30
|
-
|
30
|
+
JAT_RATE_LIMIT_IMPLEMENTATION:
|
31
|
+
JAT_RATE_LIMIT_PER_INTERVAL:
|
31
32
|
JAT_TICKETS_FOR_TEAM_SPRINT_TICKET_DISPATCHER_JQL: "project = <%= project_key %> AND <%= sprint_field_name %> IS EMPTY"
|
32
|
-
JIRA_BOARD_NAME: "
|
33
|
+
# JIRA_BOARD_NAME: "<<<Name of one board if the project>>>"
|
34
|
+
JIRA_BOARD_NAME: "<%= project_key %> - Delivery"
|
33
35
|
JIRA_BOARD_NAME_REGEX: "<%= project_key %>|ART 16|unconventional board name"
|
34
|
-
JIRA_CONTEXT_PATH: /jira
|
36
|
+
#JIRA_CONTEXT_PATH: /jira
|
37
|
+
JIRA_CONTEXT_PATH:
|
35
38
|
JIRA_HTTP_DEBUG:
|
36
39
|
JIRA_PROJECT_KEY: <%= project_key %>
|
37
40
|
JIRA_SITE_URL: http://cbroult.atlassian.net:443/
|
@@ -60,7 +63,7 @@ Feature: Environment Configuration Management
|
|
60
63
|
Please remove first before running this again!
|
61
64
|
"""
|
62
65
|
|
63
|
-
Scenario: Tool successfully loads the config
|
66
|
+
Scenario: Tool successfully loads the config from the current directory and hides secret values when listing them
|
64
67
|
Given a file named "jira-auto-tool.env.yaml.erb" with:
|
65
68
|
"""
|
66
69
|
---
|
@@ -74,7 +77,8 @@ Feature: Environment Configuration Management
|
|
74
77
|
EXPECTED_START_DATE_FIELD_NAME: Expected Start
|
75
78
|
IMPLEMENTATION_TEAM_FIELD_NAME: "Implementation Team"
|
76
79
|
JAT_RATE_INTERVAL_IN_SECONDS:
|
77
|
-
|
80
|
+
JAT_RATE_LIMIT_IMPLEMENTATION:
|
81
|
+
JAT_RATE_LIMIT_PER_INTERVAL:
|
78
82
|
JAT_TICKETS_FOR_TEAM_SPRINT_TICKET_DISPATCHER_JQL: "project = <%= project_key %> AND <%= sprint_field_name %> IS EMPTY"
|
79
83
|
JIRA_API_TOKEN: "current API TOKEN"
|
80
84
|
JIRA_BOARD_NAME: "Team Board"
|
@@ -97,9 +101,10 @@ Feature: Environment Configuration Management
|
|
97
101
|
| EXPECTED_START_DATE_FIELD_NAME | Expected Start |
|
98
102
|
| IMPLEMENTATION_TEAM_FIELD_NAME | Implementation Team |
|
99
103
|
| JAT_RATE_INTERVAL_IN_SECONDS | |
|
100
|
-
|
|
104
|
+
| JAT_RATE_LIMIT_IMPLEMENTATION | |
|
105
|
+
| JAT_RATE_LIMIT_PER_INTERVAL | |
|
101
106
|
| JAT_TICKETS_FOR_TEAM_SPRINT_TICKET_DISPATCHER_JQL | project = PROJ AND Sprint IS EMPTY |
|
102
|
-
| JIRA_API_TOKEN |
|
107
|
+
| JIRA_API_TOKEN | **** |
|
103
108
|
| JIRA_BOARD_NAME | Team Board |
|
104
109
|
| JIRA_BOARD_NAME_REGEX | ART 16|unconventional board name |
|
105
110
|
| JIRA_CONTEXT_PATH | /jira |
|
@@ -111,7 +116,7 @@ Feature: Environment Configuration Management
|
|
111
116
|
+---------------------------------------------------+------------------------------------+
|
112
117
|
"""
|
113
118
|
|
114
|
-
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)
|
115
120
|
Given a file named "./jira-auto-tool.env.yaml.erb" with:
|
116
121
|
"""
|
117
122
|
---
|
@@ -131,8 +136,16 @@ Feature: Environment Configuration Management
|
|
131
136
|
"""
|
132
137
|
Using configuration from ./jira-auto-tool.env.yaml.erb
|
133
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
|
+
"""
|
134
147
|
|
135
|
-
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)
|
136
149
|
Given a file named "./jira-auto-tool.env.yaml.erb" does not exist
|
137
150
|
And a file named "~/.config/jira-auto-tool/jira-auto-tool.env.yaml.erb" with:
|
138
151
|
"""
|
@@ -145,14 +158,23 @@ Feature: Environment Configuration Management
|
|
145
158
|
"""
|
146
159
|
Using configuration from .+/.config/jira-auto-tool/jira-auto-tool.env.yaml.erb
|
147
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
|
+
"""
|
148
169
|
|
149
|
-
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)
|
150
171
|
Given the following files should not exist:
|
151
172
|
| ./jira-auto-tool.env.yaml.erb |
|
152
173
|
| ~/.config/jira-auto-tool/jira-auto-tool.env.yaml.erb |
|
153
174
|
And the following environment variables are set:
|
154
|
-
| name | value
|
155
|
-
| JIRA_API_TOKEN | token-value
|
175
|
+
| name | value |
|
176
|
+
| JIRA_API_TOKEN | token-value |
|
177
|
+
| JIRA_USERNAME | env@company.com |
|
156
178
|
When I successfully run `jira-auto-tool --env-list`
|
157
179
|
Then the output should match:
|
158
180
|
"""
|
@@ -162,5 +184,28 @@ Feature: Environment Configuration Management
|
|
162
184
|
"""
|
163
185
|
And the output should match:
|
164
186
|
"""
|
165
|
-
JIRA_API_TOKEN\s
|
187
|
+
JIRA_API_TOKEN\s+\|\s\*{4}
|
166
188
|
"""
|
189
|
+
And the output should match:
|
190
|
+
"""
|
191
|
+
JIRA_USERNAME\s+\|\senv@company.com
|
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:
|
209
|
+
This is meant to fail while loading!
|
210
|
+
"""
|
211
|
+
|
@@ -5,13 +5,25 @@ Feature: Control the HTTP request rate limit
|
|
5
5
|
|
6
6
|
Scenario Outline: Limiting the request rate
|
7
7
|
Given the following environment variables are set:
|
8
|
-
| name
|
9
|
-
|
|
10
|
-
|
|
8
|
+
| name | value |
|
9
|
+
| JAT_RATE_INTERVAL_IN_SECONDS | <rate_interval_in_seconds> |
|
10
|
+
| JAT_RATE_LIMIT_IMPLEMENTATION | <jat_rate_limit_implementation> |
|
11
|
+
| JAT_RATE_LIMIT_PER_INTERVAL | <rate_limit_per_interval> |
|
11
12
|
Then successfully running `jira-auto-tool --board-list --sprint-prefix` takes between <minimal_time> and <maximal_time> seconds
|
12
13
|
|
13
14
|
Examples:
|
14
|
-
|
|
15
|
-
| 0
|
16
|
-
| 1
|
17
|
-
| 1
|
15
|
+
| jat_rate_limit_implementation | rate_limit_per_interval | rate_interval_in_seconds | minimal_time | maximal_time |
|
16
|
+
| | 0 | 0 | 0 | 5 |
|
17
|
+
| in_process | 1 | 2 | 1 | 20 |
|
18
|
+
| redis | 1 | 2 | 1 | 20 |
|
19
|
+
| redis | 1 | 10 | 18 | 120 |
|
20
|
+
|
21
|
+
Scenario: Unexpected rate limiting implementation generates an error
|
22
|
+
Given the following environment variables are set:
|
23
|
+
| name | value |
|
24
|
+
| JAT_RATE_LIMIT_IMPLEMENTATION | UNKNOWN IMPLEMENTATION |
|
25
|
+
When I run `jira-auto-tool --board-list`
|
26
|
+
Then it should fail with:
|
27
|
+
"""
|
28
|
+
RuntimeError: "UNKNOWN IMPLEMENTATION": unexpected rate limiting implementation specified!
|
29
|
+
"""
|
@@ -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
|
-
|
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,7 +34,13 @@ 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
|
-
|
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 |
|
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 |
|
40
46
|
| Food_Supply_25.1.3 | 2025-02-01 11:00:00 UTC | closed |
|
@@ -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,
|
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
|
-
|
25
|
-
|
26
|
-
|
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
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "ruby-limiter"
|
4
|
+
|
5
|
+
require_relative "../rate_limited_jira_client"
|
6
|
+
|
7
|
+
module Jira
|
8
|
+
module Auto
|
9
|
+
class Tool
|
10
|
+
class RateLimitedJiraClient
|
11
|
+
class InProcessBased < RateLimitedJiraClient
|
12
|
+
def rate_limit(&block)
|
13
|
+
rate_queue.shift
|
14
|
+
|
15
|
+
block.call
|
16
|
+
end
|
17
|
+
|
18
|
+
def rate_queue
|
19
|
+
@rate_queue ||=
|
20
|
+
Limiter::RateQueue.new(rate_limit_per_interval, interval: rate_interval_in_seconds)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../rate_limited_jira_client"
|
4
|
+
require "ratelimit"
|
5
|
+
require "redis"
|
6
|
+
|
7
|
+
require "jira/auto/tool"
|
8
|
+
|
9
|
+
module Jira
|
10
|
+
module Auto
|
11
|
+
class Tool
|
12
|
+
class RateLimitedJiraClient
|
13
|
+
class RedisBased < RateLimitedJiraClient
|
14
|
+
def rate_limit(&block)
|
15
|
+
rate_limiter.exec_within_threshold(rate_limiter_key, interval: rate_interval_in_seconds,
|
16
|
+
threshold: rate_limit_per_interval) do
|
17
|
+
response = block.call
|
18
|
+
|
19
|
+
rate_limiter.add(rate_limiter_key)
|
20
|
+
|
21
|
+
response
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def rate_limiter_key
|
26
|
+
"jira_auto_tool_api_requests"
|
27
|
+
end
|
28
|
+
|
29
|
+
def rate_limiter
|
30
|
+
self.class.rate_limiter(rate_limiter_key, rate_interval_in_seconds)
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.rate_limiter(rate_limiter_key, rate_interval)
|
34
|
+
@rate_limiter ||= Ratelimit.new(rate_limiter_key, bucket_interval: rate_interval)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -1,48 +1,48 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "ratelimit"
|
4
|
-
require "redis"
|
5
|
-
|
6
|
-
require "jira/auto/tool"
|
7
|
-
|
8
3
|
module Jira
|
9
4
|
module Auto
|
10
5
|
class Tool
|
11
6
|
class RateLimitedJiraClient < JIRA::Client
|
12
|
-
|
7
|
+
require_relative "rate_limited_jira_client/in_process_based"
|
8
|
+
require_relative "rate_limited_jira_client/redis_based"
|
9
|
+
|
10
|
+
def self.implementation_class_for(tool)
|
11
|
+
requested_implementation = tool.jat_rate_limit_implementation_when_defined_else nil
|
12
|
+
|
13
|
+
case requested_implementation
|
14
|
+
when "in_process", "", nil
|
15
|
+
InProcessBased
|
16
|
+
when "redis"
|
17
|
+
RedisBased
|
18
|
+
else
|
19
|
+
raise %(#{requested_implementation.inspect}: unexpected rate limiting implementation specified!")
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
NO_RATE_LIMIT_PER_INTERVAL = 0
|
13
24
|
NO_RATE_INTERVAL_IN_SECONDS = 0
|
14
25
|
|
15
|
-
attr_reader :
|
26
|
+
attr_reader :rate_interval_in_seconds, :rate_limit_per_interval
|
16
27
|
|
17
|
-
def initialize(options,
|
28
|
+
def initialize(options, rate_interval_in_seconds: 1, rate_limit_per_interval: 1)
|
18
29
|
super(options)
|
19
|
-
@
|
20
|
-
@
|
30
|
+
@rate_interval_in_seconds = rate_interval_in_seconds
|
31
|
+
@rate_limit_per_interval = rate_limit_per_interval
|
21
32
|
end
|
22
33
|
|
23
34
|
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
35
|
|
30
|
-
|
31
|
-
|
32
|
-
|
36
|
+
def request(*args)
|
37
|
+
if rate_limit_per_interval == NO_RATE_LIMIT_PER_INTERVAL
|
38
|
+
original_request(*args)
|
39
|
+
else
|
40
|
+
rate_limit { original_request(*args) }
|
33
41
|
end
|
34
42
|
end
|
35
43
|
|
36
|
-
def
|
37
|
-
"
|
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)
|
44
|
+
def rate_limit(&)
|
45
|
+
raise "rate_limit must be implemented by a subclass"
|
46
46
|
end
|
47
47
|
end
|
48
48
|
end
|