jira-auto-tool 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +291 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/Guardfile +105 -0
- data/LICENSE.txt +21 -0
- data/README.md +159 -0
- data/Rakefile +20 -0
- data/bin/jira-auto-tool +57 -0
- data/bin/jira-auto-tool.bat +2 -0
- data/bin/setup +8 -0
- data/bin/setup-dev-win.bat +3 -0
- data/cucumber.yml +7 -0
- data/features/align_sprint_time_in_dates.feature +33 -0
- data/features/assign_tickets_to_team_sprints.feature +73 -0
- data/features/cache_boards.feature +24 -0
- data/features/control_http_request_rate_limit.feature +17 -0
- data/features/create_sprints_using_existing_ones_as_reference.feature +79 -0
- data/features/list_boards.feature +12 -0
- data/features/list_project_fields.feature +89 -0
- data/features/list_sprint_prefixes.feature +77 -0
- data/features/quarterly_add_sprints_using_existing_ones_as_a_reference.feature +71 -0
- data/features/quarterly_create_sprints_until_specific_date.feature +75 -0
- data/features/quarterly_rename_sprints.feature +179 -0
- data/features/rename_sprints.feature +203 -0
- data/features/self_documented_command_line.feature +15 -0
- data/features/sprint_filtering.feature +111 -0
- data/features/step_definitions/execution_context_steps.rb +33 -0
- data/features/step_definitions/jira_board_steps.rb +102 -0
- data/features/step_definitions/jira_ticket_steps.rb +63 -0
- data/features/support/10.setup_cucumber.rb +10 -0
- data/features/support/env.rb +25 -0
- data/features/support/hooks.rb +25 -0
- data/features/support/setup_rspec.rb +14 -0
- data/features/support/setup_simplecov.rb +5 -0
- data/features/update_sprint_end_date_and_shift_following_ones.feature +52 -0
- data/lib/jira/auto/tool/board/cache.rb +67 -0
- data/lib/jira/auto/tool/board/unavailable_board.rb +36 -0
- data/lib/jira/auto/tool/board.rb +105 -0
- data/lib/jira/auto/tool/board_controller/options.rb +32 -0
- data/lib/jira/auto/tool/board_controller.rb +88 -0
- data/lib/jira/auto/tool/common_options.rb +37 -0
- data/lib/jira/auto/tool/config/options.rb +19 -0
- data/lib/jira/auto/tool/config.rb +64 -0
- data/lib/jira/auto/tool/fetch_custom_field_options.rb +47 -0
- data/lib/jira/auto/tool/field.rb +59 -0
- data/lib/jira/auto/tool/field_controller.rb +50 -0
- data/lib/jira/auto/tool/field_option.rb +35 -0
- data/lib/jira/auto/tool/get_createmeta_for_project.rb +24 -0
- data/lib/jira/auto/tool/helpers/environment_based_value.rb +96 -0
- data/lib/jira/auto/tool/helpers/option_parser.rb +16 -0
- data/lib/jira/auto/tool/helpers/overridable_time.rb +18 -0
- data/lib/jira/auto/tool/helpers/pagination.rb +50 -0
- data/lib/jira/auto/tool/jira_http_options.rb +20 -0
- data/lib/jira/auto/tool/next_sprint_creator.rb +60 -0
- data/lib/jira/auto/tool/performer/options.rb +76 -0
- data/lib/jira/auto/tool/performer/planning_increment_sprint_creator.rb +42 -0
- data/lib/jira/auto/tool/performer/prefix_sprint_updater.rb +42 -0
- data/lib/jira/auto/tool/performer/quarterly_sprint_renamer/next_name_generator.rb +60 -0
- data/lib/jira/auto/tool/performer/quarterly_sprint_renamer.rb +19 -0
- data/lib/jira/auto/tool/performer/sprint_end_date_updater.rb +55 -0
- data/lib/jira/auto/tool/performer/sprint_renamer/keep_same_name_generator.rb +19 -0
- data/lib/jira/auto/tool/performer/sprint_renamer/next_name_generator.rb +47 -0
- data/lib/jira/auto/tool/performer/sprint_renamer.rb +55 -0
- data/lib/jira/auto/tool/performer/sprint_time_in_dates_aligner.rb +52 -0
- data/lib/jira/auto/tool/project/options.rb +22 -0
- data/lib/jira/auto/tool/project/ticket_fields.rb +70 -0
- data/lib/jira/auto/tool/project.rb +40 -0
- data/lib/jira/auto/tool/rate_limited_jira_client.rb +50 -0
- data/lib/jira/auto/tool/request_builder/field_context_fetcher.rb +78 -0
- data/lib/jira/auto/tool/request_builder/field_option_fetcher.rb +54 -0
- data/lib/jira/auto/tool/request_builder/get.rb +29 -0
- data/lib/jira/auto/tool/request_builder/sprint_creator.rb +112 -0
- data/lib/jira/auto/tool/request_builder/sprint_state_updater.rb +60 -0
- data/lib/jira/auto/tool/request_builder.rb +89 -0
- data/lib/jira/auto/tool/setup_logging.rb +35 -0
- data/lib/jira/auto/tool/sprint/name.rb +105 -0
- data/lib/jira/auto/tool/sprint/prefix.rb +66 -0
- data/lib/jira/auto/tool/sprint.rb +183 -0
- data/lib/jira/auto/tool/sprint_controller/options.rb +61 -0
- data/lib/jira/auto/tool/sprint_controller.rb +152 -0
- data/lib/jira/auto/tool/sprint_state_controller.rb +58 -0
- data/lib/jira/auto/tool/team.rb +23 -0
- data/lib/jira/auto/tool/team_sprint_prefix_mapper/options.rb +27 -0
- data/lib/jira/auto/tool/team_sprint_prefix_mapper.rb +62 -0
- data/lib/jira/auto/tool/team_sprint_ticket_dispatcher.rb +76 -0
- data/lib/jira/auto/tool/ticket.rb +110 -0
- data/lib/jira/auto/tool/until_date.rb +68 -0
- data/lib/jira/auto/tool/version.rb +9 -0
- data/lib/jira/auto/tool.rb +216 -0
- data/sig/jira/sprint/tool.rbs +8 -0
- data/spec/jira/auto/tool/board/cache_spec.rb +179 -0
- data/spec/jira/auto/tool/board/unavailable_board_spec.rb +34 -0
- data/spec/jira/auto/tool/board_controller/options_spec.rb +52 -0
- data/spec/jira/auto/tool/board_controller_spec.rb +154 -0
- data/spec/jira/auto/tool/board_spec.rb +163 -0
- data/spec/jira/auto/tool/common_options_spec.rb +49 -0
- data/spec/jira/auto/tool/config_spec.rb +108 -0
- data/spec/jira/auto/tool/field_controller_spec.rb +121 -0
- data/spec/jira/auto/tool/field_option_spec.rb +42 -0
- data/spec/jira/auto/tool/field_spec.rb +99 -0
- data/spec/jira/auto/tool/helpers/environment_based_value_spec.rb +21 -0
- data/spec/jira/auto/tool/helpers/option_parser_spec.rb +21 -0
- data/spec/jira/auto/tool/helpers/overridable_time_spec.rb +43 -0
- data/spec/jira/auto/tool/helpers/pagination_spec.rb +72 -0
- data/spec/jira/auto/tool/jira_http_options_spec.rb +32 -0
- data/spec/jira/auto/tool/next_sprint_creator_spec.rb +85 -0
- data/spec/jira/auto/tool/performer/option_spec.rb +55 -0
- data/spec/jira/auto/tool/performer/planning_increment_sprint_creator_spec.rb +62 -0
- data/spec/jira/auto/tool/performer/prefix_sprint_updater_spec.rb +35 -0
- data/spec/jira/auto/tool/performer/quarterly_sprint_renamer/next_name_generator_spec.rb +175 -0
- data/spec/jira/auto/tool/performer/quarterly_sprint_renamer_spec.rb +239 -0
- data/spec/jira/auto/tool/performer/sprint_end_date_updater_spec.rb +90 -0
- data/spec/jira/auto/tool/performer/sprint_renamer/keep_same_name_generator_spec.rb +12 -0
- data/spec/jira/auto/tool/performer/sprint_renamer/next_name_generator_spec.rb +129 -0
- data/spec/jira/auto/tool/performer/sprint_renamer_spec.rb +240 -0
- data/spec/jira/auto/tool/performer/sprint_time_in_dates_aligner_spec.rb +132 -0
- data/spec/jira/auto/tool/project/ticket_fields_spec.rb +390 -0
- data/spec/jira/auto/tool/project_spec.rb +31 -0
- data/spec/jira/auto/tool/rate_limited_jira_client_spec.rb +82 -0
- data/spec/jira/auto/tool/request_builder/field_context_fetcher_spec.rb +54 -0
- data/spec/jira/auto/tool/request_builder/field_option_fetcher_spec.rb +64 -0
- data/spec/jira/auto/tool/request_builder/get_spec.rb +40 -0
- data/spec/jira/auto/tool/request_builder/sprint_creator_spec.rb +179 -0
- data/spec/jira/auto/tool/request_builder/sprint_state_updater_spec.rb +31 -0
- data/spec/jira/auto/tool/request_builder_spec.rb +73 -0
- data/spec/jira/auto/tool/sprint/name_spec.rb +101 -0
- data/spec/jira/auto/tool/sprint/prefix_spec.rb +207 -0
- data/spec/jira/auto/tool/sprint_controller_spec.rb +406 -0
- data/spec/jira/auto/tool/sprint_spec.rb +309 -0
- data/spec/jira/auto/tool/team_spec.rb +21 -0
- data/spec/jira/auto/tool/team_sprint_prefix_mapper_spec.rb +97 -0
- data/spec/jira/auto/tool/team_sprint_ticket_dispatcher_spec.rb +232 -0
- data/spec/jira/auto/tool/ticket_spec.rb +116 -0
- data/spec/jira/auto/tool/until_date_spec.rb +80 -0
- data/spec/jira/auto/tool_spec.rb +458 -0
- data/spec/spec_helper.rb +42 -0
- metadata +368 -0
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "jira/auto/tool/helpers/option_parser"
|
4
|
+
require "jira/auto/tool/board_controller"
|
5
|
+
require "jira/auto/tool/board/cache"
|
6
|
+
|
7
|
+
module Jira
|
8
|
+
module Auto
|
9
|
+
class Tool
|
10
|
+
class BoardController
|
11
|
+
class Options
|
12
|
+
def self.add(tool, parser)
|
13
|
+
parser.section_header "Board"
|
14
|
+
|
15
|
+
parser.on("--board-name=STRING", String) do |board_name|
|
16
|
+
tool.jira_board_name = board_name
|
17
|
+
end
|
18
|
+
|
19
|
+
parser.on("--board-cache-clear", "Clear the board cache so they are requested again.") do
|
20
|
+
tool.board_controller.clear_cache
|
21
|
+
end
|
22
|
+
|
23
|
+
parser.on("--board-list", "List boards. The output can be controlled via the " \
|
24
|
+
"#{Tool::Environment::JIRA_BOARD_NAME_REGEX} environment variable") do
|
25
|
+
tool.board_controller.list_boards
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "jira/auto/tool/board"
|
4
|
+
require "jira/auto/tool/board/cache"
|
5
|
+
|
6
|
+
module Jira
|
7
|
+
module Auto
|
8
|
+
class Tool
|
9
|
+
class BoardController
|
10
|
+
attr_reader :jira_client, :tool
|
11
|
+
|
12
|
+
def initialize(tool)
|
13
|
+
@tool = tool
|
14
|
+
@jira_client = tool.jira_client
|
15
|
+
end
|
16
|
+
|
17
|
+
def list_boards
|
18
|
+
table = Terminal::Table.new(
|
19
|
+
title: "Boards",
|
20
|
+
headings: ["Project Key", "Name", "Board UI URL"],
|
21
|
+
rows: boards.collect { |board| [board.project_key, board.name, board.ui_url] }
|
22
|
+
)
|
23
|
+
|
24
|
+
puts table
|
25
|
+
end
|
26
|
+
|
27
|
+
def boards
|
28
|
+
return cached_boards if valid_cache?
|
29
|
+
|
30
|
+
cache_boards(filtered_boards)
|
31
|
+
end
|
32
|
+
|
33
|
+
def clear_cache
|
34
|
+
cache.clear
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def filtered_boards
|
40
|
+
named_filtered_boards = apply_board_name_filter(unfiltered_boards)
|
41
|
+
|
42
|
+
apply_project_key_filter(named_filtered_boards)
|
43
|
+
end
|
44
|
+
|
45
|
+
def unfiltered_boards
|
46
|
+
@unfiltered_boards ||= request_boards
|
47
|
+
end
|
48
|
+
|
49
|
+
def valid_cache?
|
50
|
+
cache.valid?
|
51
|
+
end
|
52
|
+
|
53
|
+
def cached_boards
|
54
|
+
cache.boards
|
55
|
+
end
|
56
|
+
|
57
|
+
def cache
|
58
|
+
@cache ||= Board::Cache.new(tool)
|
59
|
+
end
|
60
|
+
|
61
|
+
def request_boards
|
62
|
+
jira_client.Board.all.collect { |board| Board.new(tool, board) }
|
63
|
+
end
|
64
|
+
|
65
|
+
def cache_boards(boards)
|
66
|
+
cache.save(boards)
|
67
|
+
end
|
68
|
+
|
69
|
+
def apply_project_key_filter(boards_to_filter)
|
70
|
+
return boards_to_filter unless tool.jira_project_key_defined?
|
71
|
+
|
72
|
+
boards_to_filter.find_all do |board|
|
73
|
+
!board.with_project_information? ||
|
74
|
+
board.project_key == tool.jira_project_key
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def apply_board_name_filter(boards_to_filter)
|
79
|
+
return boards_to_filter unless tool.jira_board_name_regex_defined?
|
80
|
+
|
81
|
+
board_name_regex = Regexp.new(tool.jira_board_name_regex)
|
82
|
+
|
83
|
+
boards_to_filter.find_all { |board| board.name =~ board_name_regex }
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "jira/auto/tool/helpers/option_parser"
|
4
|
+
|
5
|
+
module Jira
|
6
|
+
module Auto
|
7
|
+
class Tool
|
8
|
+
module CommonOptions
|
9
|
+
DISPLAY_HELP_OPTION = "--help"
|
10
|
+
|
11
|
+
def self.add(tool, parser)
|
12
|
+
::ARGV << DISPLAY_HELP_OPTION if ARGV.empty?
|
13
|
+
|
14
|
+
add_help_banner_and_options(parser)
|
15
|
+
|
16
|
+
parser.on("-v", "--version", "Print the version") do
|
17
|
+
Kernel.puts tool.class::VERSION
|
18
|
+
|
19
|
+
Kernel.exit 1
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.add_help_banner_and_options(parser)
|
24
|
+
parser.banner = <<~EOBANNER
|
25
|
+
Usage: #{File.basename($PROGRAM_NAME)} [options]*
|
26
|
+
|
27
|
+
EOBANNER
|
28
|
+
|
29
|
+
parser.on("-h", DISPLAY_HELP_OPTION, "Print this help") do
|
30
|
+
Kernel.puts parser
|
31
|
+
Kernel.exit 1
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Jira
|
4
|
+
module Auto
|
5
|
+
class Tool
|
6
|
+
class Config
|
7
|
+
class Options
|
8
|
+
def self.add(_tool, parser)
|
9
|
+
# parser.section_header "Config"
|
10
|
+
#
|
11
|
+
# parser.on("--config-list") do
|
12
|
+
# # TODO: tool.config.list
|
13
|
+
# end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "fileutils"
|
4
|
+
|
5
|
+
module Jira
|
6
|
+
module Auto
|
7
|
+
class Tool
|
8
|
+
class Config
|
9
|
+
include Comparable
|
10
|
+
|
11
|
+
attr_reader :tool
|
12
|
+
|
13
|
+
def initialize(tool)
|
14
|
+
@tool = tool
|
15
|
+
end
|
16
|
+
|
17
|
+
def []=(key, value)
|
18
|
+
value_store[sanitize_key(key)] = value
|
19
|
+
save
|
20
|
+
end
|
21
|
+
|
22
|
+
def [](key)
|
23
|
+
value_store[sanitize_key(key)]
|
24
|
+
end
|
25
|
+
|
26
|
+
def key?(key)
|
27
|
+
value_store.key?(sanitize_key(key))
|
28
|
+
end
|
29
|
+
|
30
|
+
def <=>(other)
|
31
|
+
value_store <=> (other.is_a?(Config) ? other.value_store : other)
|
32
|
+
end
|
33
|
+
|
34
|
+
def path
|
35
|
+
File.join(dir, "jira-auto-tool.config.yml")
|
36
|
+
end
|
37
|
+
|
38
|
+
def dir
|
39
|
+
config_dir = File.join(Dir.home, ".config/jira-auto-tool")
|
40
|
+
FileUtils.makedirs(config_dir)
|
41
|
+
config_dir
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def sanitize_key(key)
|
47
|
+
key.to_s
|
48
|
+
end
|
49
|
+
|
50
|
+
def save
|
51
|
+
File.write(path, value_store.to_yaml)
|
52
|
+
end
|
53
|
+
|
54
|
+
def value_store
|
55
|
+
@value_store ||= load
|
56
|
+
end
|
57
|
+
|
58
|
+
def load
|
59
|
+
File.exist?(path) ? YAML.safe_load_file(path) : {}
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# require "jira-ruby"
|
5
|
+
# require "json"
|
6
|
+
#
|
7
|
+
# # Jira client setup
|
8
|
+
# options = {
|
9
|
+
# username: "your_username", # Replace with Jira username
|
10
|
+
# password: "your_api_token", # Replace with your API token for authentication
|
11
|
+
# site: "https://your-domain.atlassian.net", # Replace with your Jira instance URL
|
12
|
+
# context_path: "",
|
13
|
+
# auth_type: :basic
|
14
|
+
# }
|
15
|
+
#
|
16
|
+
# client = JIRA::Client.new(options)
|
17
|
+
#
|
18
|
+
# # Get issue metadata for project and custom field
|
19
|
+
# def fetch_custom_field_options(client, project_key, custom_field_id)
|
20
|
+
# # Fetch issue metadata for the project
|
21
|
+
# response = client.get("/rest/api/3/issue/createmeta?projectKeys=#{project_key}&expand=projects.issuetypes.fields")
|
22
|
+
# metadata = JSON.parse(response.body)
|
23
|
+
#
|
24
|
+
# # Parse metadata for the specified custom field
|
25
|
+
# options = []
|
26
|
+
# metadata["projects"].each do |project|
|
27
|
+
# project["issuetypes"].each do |issuetype|
|
28
|
+
# fields = issuetype["fields"] || {}
|
29
|
+
# if fields[custom_field_id] && fields[custom_field_id]["allowedValues"]
|
30
|
+
# options = fields[custom_field_id]["allowedValues"].map { |option| option["value"] }
|
31
|
+
# end
|
32
|
+
# end
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# # Return unique options
|
36
|
+
# options.uniq
|
37
|
+
# rescue JIRA::HTTPError => e
|
38
|
+
# puts "Failed to fetch field metadata: #{e.response.body}"
|
39
|
+
# nil
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# # Replace with your Jira project key and the custom field ID
|
43
|
+
# project_key = "PROJ"
|
44
|
+
# custom_field_id = "customfield_10021" # Replace with your custom field ID
|
45
|
+
# options = fetch_custom_field_options(client, project_key, custom_field_id)
|
46
|
+
#
|
47
|
+
# puts "Possible values: #{options}"
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "jira/auto/tool/request_builder/field_context_fetcher"
|
4
|
+
require "jira/auto/tool/request_builder/field_option_fetcher"
|
5
|
+
|
6
|
+
module Jira
|
7
|
+
module Auto
|
8
|
+
class Tool
|
9
|
+
class Field
|
10
|
+
include Comparable
|
11
|
+
|
12
|
+
attr_reader :jira_client, :jira_field
|
13
|
+
|
14
|
+
def initialize(jira_client, jira_field)
|
15
|
+
@jira_client = jira_client
|
16
|
+
@jira_field = jira_field
|
17
|
+
end
|
18
|
+
|
19
|
+
def name
|
20
|
+
jira_field.name
|
21
|
+
end
|
22
|
+
|
23
|
+
def type
|
24
|
+
jira_field.schema.fetch("type")
|
25
|
+
end
|
26
|
+
|
27
|
+
def id
|
28
|
+
jira_field.id
|
29
|
+
end
|
30
|
+
|
31
|
+
def <=>(other)
|
32
|
+
comparison_values(self) <=> comparison_values(other)
|
33
|
+
end
|
34
|
+
|
35
|
+
def values
|
36
|
+
RequestBuilder::FieldOptionFetcher.fetch_field_options(self)
|
37
|
+
end
|
38
|
+
|
39
|
+
def field_context
|
40
|
+
contexts = field_contexts
|
41
|
+
|
42
|
+
log.warn { "field #{self} has several field contexts" } unless contexts.size == 1
|
43
|
+
|
44
|
+
field_contexts.first # TODO: - handle several field contexts
|
45
|
+
end
|
46
|
+
|
47
|
+
def field_contexts
|
48
|
+
RequestBuilder::FieldContextFetcher.fetch_field_contexts(self)
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def comparison_values(object)
|
54
|
+
%i[name type id].collect { |field| object.send(field) }
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "jira/auto/tool"
|
4
|
+
require_relative "field"
|
5
|
+
|
6
|
+
module Jira
|
7
|
+
module Auto
|
8
|
+
class Tool
|
9
|
+
class FieldController
|
10
|
+
class FieldNotFoundError < StandardError; end
|
11
|
+
class ExpectedFieldTypeError < StandardError; end
|
12
|
+
|
13
|
+
def initialize(jira_client)
|
14
|
+
@jira_client = jira_client
|
15
|
+
end
|
16
|
+
|
17
|
+
def sprint_field(field_name)
|
18
|
+
field_fetcher(field_name, "array")
|
19
|
+
end
|
20
|
+
|
21
|
+
def expected_start_date_field(field_name)
|
22
|
+
field_fetcher(field_name, "date")
|
23
|
+
end
|
24
|
+
|
25
|
+
def implementation_team_field(field_name)
|
26
|
+
field_fetcher(field_name, "option|any")
|
27
|
+
end
|
28
|
+
|
29
|
+
def ticket_fields
|
30
|
+
@ticket_fields ||= @jira_client.Field.all.collect { |field| Field.new(@jira_client, field) }
|
31
|
+
rescue StandardError => e
|
32
|
+
raise "Error fetching project ticket fields: Something went wrong:\n#{e}"
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def field_fetcher(field_name, expected_type)
|
38
|
+
field = ticket_fields.find { |f| f.name == field_name } or
|
39
|
+
raise FieldNotFoundError, "Field '#{field_name}' not found!"
|
40
|
+
|
41
|
+
field.type =~ Regexp.new(expected_type) or
|
42
|
+
raise ExpectedFieldTypeError,
|
43
|
+
"Field '#{field_name}' expected to have type '#{expected_type}', but was '#{field.type}'."
|
44
|
+
|
45
|
+
field
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "jira/auto/tool"
|
4
|
+
|
5
|
+
module Jira
|
6
|
+
module Auto
|
7
|
+
class Tool
|
8
|
+
class FieldOption
|
9
|
+
include Comparable
|
10
|
+
|
11
|
+
attr_reader :jira_client, :id, :value
|
12
|
+
|
13
|
+
def initialize(jira_client, id, value)
|
14
|
+
@jira_client = jira_client
|
15
|
+
@id = id
|
16
|
+
@value = value
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_s
|
20
|
+
"FieldOption(id: #{id}, value: '#{value}')"
|
21
|
+
end
|
22
|
+
|
23
|
+
def <=>(other)
|
24
|
+
comparison_values(self) <=> comparison_values(other)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def comparison_values(object)
|
30
|
+
%i[id value].collect { |field| object.send(field) }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# def get_createmeta_for_project(client, project_key)
|
5
|
+
# response = client.get("/rest/api/3/issue/createmeta?projectKeys=#{project_key}")
|
6
|
+
# metadata = JSON.parse(response.body)
|
7
|
+
#
|
8
|
+
# # Parse issue types and fields for the project
|
9
|
+
# metadata["projects"].each do |project|
|
10
|
+
# project["issuetypes"].each do |issuetype|
|
11
|
+
# puts "Issue Type: #{issuetype["name"]}"
|
12
|
+
#
|
13
|
+
# issuetype["fields"].each do |field_id, field_details|
|
14
|
+
# puts "Field: #{field_details["name"]}, ID: #{field_id}"
|
15
|
+
# end
|
16
|
+
# end
|
17
|
+
# end
|
18
|
+
# rescue JIRA::HTTPError => e
|
19
|
+
# puts "Failed to retrieve create metadata: #{e.response.body}"
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# # Replace with your project key
|
23
|
+
# project_key = "PROJ"
|
24
|
+
# get_createmeta_for_project(client, project_key)
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Jira
|
4
|
+
module Auto
|
5
|
+
class Tool
|
6
|
+
module Helpers
|
7
|
+
module EnvironmentBasedValue
|
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)
|
10
|
+
define_reader(method_name)
|
11
|
+
define_predicate(method_name)
|
12
|
+
define_reader_accepting_default_value(method_name)
|
13
|
+
define_writer(method_name)
|
14
|
+
define_environment_variable_name_constant(method_name)
|
15
|
+
end
|
16
|
+
|
17
|
+
def define_reader(method_name)
|
18
|
+
define_method(method_name) do
|
19
|
+
self.class.instance_variable_name(method_name)
|
20
|
+
|
21
|
+
if config.key?(method_name)
|
22
|
+
config[method_name]
|
23
|
+
else
|
24
|
+
self.class.fetch_corresponding_environment_variable(method_name.to_s)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def define_predicate(method_name)
|
30
|
+
define_method(:"#{method_name}_defined?") do
|
31
|
+
self.class.corresponding_environment_variable_defined?(method_name.to_s)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def define_reader_accepting_default_value(method_name)
|
36
|
+
define_method(:"#{method_name}_when_defined_else") do |value|
|
37
|
+
if config.key?(method_name)
|
38
|
+
config[method_name]
|
39
|
+
elsif self.class.corresponding_environment_variable_defined?(method_name.to_s)
|
40
|
+
send(method_name)
|
41
|
+
else
|
42
|
+
value
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def define_writer(method_name)
|
48
|
+
define_method(:"#{method_name}=") do |value|
|
49
|
+
log.debug { "#{method_name}= #{value.inspect}" }
|
50
|
+
config[method_name] = value
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def define_environment_variable_name_constant(method_name)
|
55
|
+
constant_name = method_name.to_s.upcase
|
56
|
+
class_eval <<-EOCONSTANT, __FILE__, __LINE__ + 1
|
57
|
+
# This module and constant will be interpolated as follows:
|
58
|
+
# module Environment
|
59
|
+
# CONSTANT_NAME = "CONSTANT_NAME"
|
60
|
+
# end
|
61
|
+
module Environment
|
62
|
+
#{constant_name} = #{constant_name.inspect}
|
63
|
+
end
|
64
|
+
EOCONSTANT
|
65
|
+
end
|
66
|
+
|
67
|
+
def fetch_corresponding_environment_variable(
|
68
|
+
caller_method_name = caller_locations(1, 1).first.base_label
|
69
|
+
)
|
70
|
+
environment_variable_name = caller_method_name.upcase
|
71
|
+
|
72
|
+
value = ENV.fetch(environment_variable_name) do |name|
|
73
|
+
raise KeyError, "Missing #{name} environment variable!"
|
74
|
+
end
|
75
|
+
|
76
|
+
log.debug { "fetch_corresponding_environment_variable(#{environment_variable_name}) - #{value.inspect}" }
|
77
|
+
|
78
|
+
value
|
79
|
+
end
|
80
|
+
|
81
|
+
def corresponding_environment_variable_defined?(caller_method_name = caller_locations(1, 1).first.base_label)
|
82
|
+
environment_variable_name = caller_method_name.upcase
|
83
|
+
|
84
|
+
log.debug { "corresponding_environment_variable_defined?(#{environment_variable_name})" }
|
85
|
+
|
86
|
+
ENV.key?(environment_variable_name)
|
87
|
+
end
|
88
|
+
|
89
|
+
def instance_variable_name(method_name)
|
90
|
+
"@#{method_name}"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "optparse"
|
4
|
+
require "jira/auto/tool"
|
5
|
+
|
6
|
+
class OptionParser
|
7
|
+
def section_header(section_name)
|
8
|
+
header_name = "#{section_name}:"
|
9
|
+
|
10
|
+
on <<~EOSH
|
11
|
+
|
12
|
+
#{header_name}
|
13
|
+
#{header_name.gsub(/./, "-")}
|
14
|
+
EOSH
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Jira
|
4
|
+
module Auto
|
5
|
+
class Tool
|
6
|
+
module Helpers
|
7
|
+
class OverridableTime
|
8
|
+
def self.now
|
9
|
+
overridden_date_time = ENV.fetch("JAT_CURRENT_DATE_TIME", nil)
|
10
|
+
log.debug { "JAT_CURRENT_DATE_TIME is set to #{overridden_date_time}" }
|
11
|
+
|
12
|
+
(overridden_date_time ? Time.parse(overridden_date_time) : Time.now)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Jira
|
4
|
+
module Auto
|
5
|
+
class Tool
|
6
|
+
module Helpers
|
7
|
+
module Pagination
|
8
|
+
PAGE_SIZE = 50
|
9
|
+
|
10
|
+
def self.fetch_all_object_pages(parameter_naming_convention = :camelCase)
|
11
|
+
all_objects = []
|
12
|
+
start_at = 0
|
13
|
+
|
14
|
+
loop do
|
15
|
+
pagination_options = build_pagination_options(parameter_naming_convention, PAGE_SIZE, start_at)
|
16
|
+
|
17
|
+
log.debug { "Fetching objects from Jira (#{pagination_options.inspect})..." }
|
18
|
+
|
19
|
+
fetched_objects = yield(pagination_options)
|
20
|
+
|
21
|
+
log.debug { "Fetched #{fetched_objects.size} object." }
|
22
|
+
|
23
|
+
all_objects.concat(fetched_objects)
|
24
|
+
|
25
|
+
start_at += PAGE_SIZE
|
26
|
+
|
27
|
+
break if fetched_objects.empty?
|
28
|
+
end
|
29
|
+
|
30
|
+
log.debug { all_objects.collect(&:name).join(", ") }
|
31
|
+
|
32
|
+
all_objects
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.build_pagination_options(parameter_naming_convention, max_results, start_at)
|
36
|
+
PAGINATION_PARAMETER_STYLES
|
37
|
+
.fetch(parameter_naming_convention) { |k| "#{k.inspect}: not found in #{PAGINATION_PARAMETER_STYLES}" }
|
38
|
+
.zip([max_results, start_at])
|
39
|
+
.to_h
|
40
|
+
end
|
41
|
+
|
42
|
+
PAGINATION_PARAMETER_STYLES = {
|
43
|
+
camelCase: %i[maxResults startAt],
|
44
|
+
snake_case: %i[max_results start_at]
|
45
|
+
}.freeze
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "jira/auto/tool"
|
4
|
+
|
5
|
+
module Jira
|
6
|
+
module Auto
|
7
|
+
class Tool
|
8
|
+
module JiraHttpOptions
|
9
|
+
DISPLAY_HELP_OPTION = "--help"
|
10
|
+
|
11
|
+
def self.add(tool, parser)
|
12
|
+
parser.section_header "JIRA HTTP"
|
13
|
+
parser.on("--[no-]jira-http-debug", "Enable or disable HTTP debug mode") do |v|
|
14
|
+
tool.jira_http_debug = v
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|