firespring_dev_commands 2.1.8 → 2.1.10.pre.alpha.2
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/lib/firespring_dev_commands/git.rb +12 -0
- data/lib/firespring_dev_commands/jira/histories.rb +2 -0
- data/lib/firespring_dev_commands/jira/history.rb +1 -0
- data/lib/firespring_dev_commands/jira/issue.rb +6 -0
- data/lib/firespring_dev_commands/platform.rb +6 -2
- data/lib/firespring_dev_commands/target_process/project.rb +14 -0
- data/lib/firespring_dev_commands/target_process/query.rb +82 -0
- data/lib/firespring_dev_commands/target_process/release.rb +14 -0
- data/lib/firespring_dev_commands/target_process/team.rb +14 -0
- data/lib/firespring_dev_commands/target_process/user.rb +15 -0
- data/lib/firespring_dev_commands/target_process/user_story.rb +48 -0
- data/lib/firespring_dev_commands/target_process.rb +105 -0
- data/lib/firespring_dev_commands/version.rb +1 -1
- metadata +13 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c75f83066598edad92a8d34dd12d898d9f5229cf80bd8eeba166cd7e5451296e
|
4
|
+
data.tar.gz: 832006c6ec28b9b8a288e3a0d510f628f3c468b3c0cb8614d1eae5927f25184a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f3ea07c7f9be119c8ec47914c213560069ee1c17b6a0e8dcbe43b41cb246febd258079b12843d2819cdd4b1cc7aa180e3515fd8edeaca8fea9a5866758614a33
|
7
|
+
data.tar.gz: b395757808d58b999546d5df762ef46caf59ebf670599ec77d0b1c91098381c9f6da96e102141050f1a638ea83972c2757d795016e90cff1bb0474d0865b5db0
|
@@ -203,6 +203,7 @@ module Dev
|
|
203
203
|
|
204
204
|
# Checks out the given branch in the given repo
|
205
205
|
# Defaults to the current directory
|
206
|
+
# optionally raise errors
|
206
207
|
def checkout(branch, dir: default_project_dir, raise_errors: false)
|
207
208
|
raise 'branch is required' if branch.to_s.strip.empty?
|
208
209
|
return unless File.exist?(dir)
|
@@ -231,6 +232,8 @@ module Dev
|
|
231
232
|
end
|
232
233
|
|
233
234
|
# Create the given branch in the given repo
|
235
|
+
# Defaults to the current directory
|
236
|
+
# optionally raise errors
|
234
237
|
def create_branch(branch, dir: default_project_dir, raise_errors: false)
|
235
238
|
raise 'branch is required' if branch.to_s.strip.empty?
|
236
239
|
raise "refusing to create protected branch '#{branch}'" if %w(master develop).any?(branch.to_s.strip)
|
@@ -259,6 +262,9 @@ module Dev
|
|
259
262
|
false
|
260
263
|
end
|
261
264
|
|
265
|
+
# Add the given paths to git
|
266
|
+
# Defaults to the current directory
|
267
|
+
# optionally raise errors
|
262
268
|
def add(*paths, dir: default_project_dir, raise_errors: false)
|
263
269
|
g = ::Git.open(dir)
|
264
270
|
indent g.add(paths)
|
@@ -292,6 +298,8 @@ module Dev
|
|
292
298
|
end
|
293
299
|
|
294
300
|
# Merge the given branch into the given repo
|
301
|
+
# Defaults to the current directory
|
302
|
+
# optionally raise errors
|
295
303
|
def merge(branch, dir: default_project_dir, raise_errors: false)
|
296
304
|
raise 'branch is required' if branch.to_s.strip.empty?
|
297
305
|
return unless File.exist?(dir)
|
@@ -337,6 +345,8 @@ module Dev
|
|
337
345
|
end
|
338
346
|
|
339
347
|
# Pull the given repo
|
348
|
+
# Defaults to the current directory
|
349
|
+
# optionally raise errors
|
340
350
|
def pull(dir: default_project_dir, raise_errors: false)
|
341
351
|
return unless File.exist?(dir)
|
342
352
|
|
@@ -374,6 +384,8 @@ module Dev
|
|
374
384
|
end
|
375
385
|
|
376
386
|
# Push the given repo
|
387
|
+
# Defaults to the current directory
|
388
|
+
# optionally raise errors
|
377
389
|
def push(dir: default_project_dir, raise_errors: false)
|
378
390
|
return unless File.exist?(dir)
|
379
391
|
|
@@ -1,6 +1,8 @@
|
|
1
1
|
module Dev
|
2
2
|
class Jira
|
3
|
+
# Class which provides a helper method for converting the changelog data to history objects
|
3
4
|
class Histories
|
5
|
+
# If changelog is present in the given data, return an array of history objects for each changelog entry
|
4
6
|
def self.populate(data)
|
5
7
|
return nil unless data.attrs.key?('changelog')
|
6
8
|
|
@@ -21,21 +21,25 @@ module Dev
|
|
21
21
|
@last_closed_history = nil
|
22
22
|
end
|
23
23
|
|
24
|
+
# Returns the cycle time of the issue (time between in progress and closed states)
|
24
25
|
def cycle_time
|
25
26
|
# Calculate the difference and convert to days
|
26
27
|
((last_closed_history.created - last_in_progress_history.created) / 60 / 60 / 24).round(2)
|
27
28
|
end
|
28
29
|
|
30
|
+
# Returns the time the issue was in progress (time between in progress and in review states)
|
29
31
|
def in_progress_cycle_time
|
30
32
|
# Calculate the difference and convert to days
|
31
33
|
((first_in_review_history.created - last_in_progress_history.created) / 60 / 60 / 24).round(2)
|
32
34
|
end
|
33
35
|
|
36
|
+
# Returns the time the issue was in review (time between in review and closed states)
|
34
37
|
def in_review_cycle_time
|
35
38
|
# Calculate the difference and convert to days
|
36
39
|
((last_closed_history.created - first_in_review_history.created) / 60 / 60 / 24).round(2)
|
37
40
|
end
|
38
41
|
|
42
|
+
# Loop through the issue history and find the most recent state change from Open to In Progress
|
39
43
|
private def last_in_progress_history
|
40
44
|
raise 'you must expand the changelog field to calculate cycle time' if histories.nil?
|
41
45
|
|
@@ -50,6 +54,7 @@ module Dev
|
|
50
54
|
@last_in_progress_history
|
51
55
|
end
|
52
56
|
|
57
|
+
# Loop through the issue history and find the oldest state change to In Review
|
53
58
|
private def first_in_review_history
|
54
59
|
raise 'you must expand the changelog field to calculate cycle time' if histories.nil?
|
55
60
|
|
@@ -64,6 +69,7 @@ module Dev
|
|
64
69
|
@first_in_review_history
|
65
70
|
end
|
66
71
|
|
72
|
+
# Loop through the issue history and find the most recent state change to closed
|
67
73
|
private def last_closed_history
|
68
74
|
raise 'you must expand the changelog field to calculate cycle time' if histories.nil?
|
69
75
|
|
@@ -1,8 +1,11 @@
|
|
1
1
|
module Dev
|
2
2
|
class Common
|
3
|
+
# Class which returns information about the current platform
|
3
4
|
class Platform
|
5
|
+
# Constant containing all supported architectures
|
4
6
|
ALLOWED_ARCHITECTURES = %w(arm64 amd64).freeze
|
5
7
|
|
8
|
+
# Normalize the ruby platform to return a docker platform architecture format
|
6
9
|
def determine_compute_architecture
|
7
10
|
case RUBY_PLATFORM
|
8
11
|
when /x86_64|amd64/
|
@@ -14,6 +17,9 @@ module Dev
|
|
14
17
|
end
|
15
18
|
end
|
16
19
|
|
20
|
+
# Determine the platform architecture
|
21
|
+
# If one was specified in the DOCKER_ARCHITECTURE variable, use it
|
22
|
+
# Otherwise, use the RUBY_PLATFORM built-in to auto-detect and architecture
|
17
23
|
def architecture
|
18
24
|
docker_architecture = ENV['DOCKER_ARCHITECTURE'].to_s.strip.downcase
|
19
25
|
if docker_architecture.empty?
|
@@ -22,13 +28,11 @@ module Dev
|
|
22
28
|
raise "Missing 'linux/' prefix in DOCKER_ARCHITECTURE: #{docker_architecture}" unless docker_architecture.start_with?('linux/')
|
23
29
|
|
24
30
|
architecture_name = docker_architecture.split('/')[1]
|
25
|
-
|
26
31
|
unless ALLOWED_ARCHITECTURES.include?(architecture_name)
|
27
32
|
raise "Invalid DOCKER_ARCHITECTURE: #{architecture_name}. Allowed architectures are #{ALLOWED_ARCHITECTURES.join(', ')}"
|
28
33
|
end
|
29
34
|
|
30
35
|
docker_architecture
|
31
|
-
|
32
36
|
end
|
33
37
|
end
|
34
38
|
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module Dev
|
2
|
+
class TargetProcess
|
3
|
+
# Class for writing target process query statements
|
4
|
+
class Query
|
5
|
+
attr_accessor :where, :incl, :take
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@where = []
|
9
|
+
@incl = []
|
10
|
+
@take = 250
|
11
|
+
end
|
12
|
+
|
13
|
+
# Add a new query clause
|
14
|
+
def <<(item)
|
15
|
+
where << item
|
16
|
+
end
|
17
|
+
|
18
|
+
# Add the item to the where clause
|
19
|
+
def where=(item)
|
20
|
+
if item.is_a?(Array)
|
21
|
+
where.concat(item)
|
22
|
+
else
|
23
|
+
where << item
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Add the item to the include clause
|
28
|
+
def include=(item)
|
29
|
+
if item.is_a?(Array)
|
30
|
+
incl.concat(item)
|
31
|
+
else
|
32
|
+
incl << item
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Generate the string representation for this query
|
37
|
+
def generate
|
38
|
+
{}.tap do |clause|
|
39
|
+
clause[:where] = where.join(' and ') unless where.nil? || where.empty?
|
40
|
+
clause[:include] = "[#{incl.join(',')}]" unless incl.nil? || incl.empty?
|
41
|
+
clause[:take] = take if take.to_i.positive?
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Generate the string representation for this query
|
46
|
+
def to_s
|
47
|
+
generate
|
48
|
+
end
|
49
|
+
|
50
|
+
# Add a filter that looks for stories whose id is contained in the list of ids given
|
51
|
+
def filter_by_user_story_ids(user_story_ids)
|
52
|
+
self << "(Id in ('#{user_story_ids.join("', '")}'))"
|
53
|
+
end
|
54
|
+
|
55
|
+
# Add a filter that looks for stories whose project id is contained in the list of ids given
|
56
|
+
def filter_by_project(projects)
|
57
|
+
self << "(Project.Name in ('#{projects.join("', '")}'))"
|
58
|
+
end
|
59
|
+
|
60
|
+
# Add a filter that looks for stories whose state is contained in the list of states given
|
61
|
+
def filter_by_states(states)
|
62
|
+
self << "(EntityState.Name in ('#{states.join("', '")}'))" unless states.nil? || states.empty?
|
63
|
+
end
|
64
|
+
|
65
|
+
# Add a filter that looks for stories whose state is set to final
|
66
|
+
def filter_by_final
|
67
|
+
self << "(EntityState.IsFinal eq 'true')"
|
68
|
+
end
|
69
|
+
|
70
|
+
# Add a filter that looks for stories whose end date is between the given dates
|
71
|
+
def filter_by_end_dates(start_date, end_date)
|
72
|
+
self << "(EndDate gt '#{start_date}')" if start_date
|
73
|
+
self << "(EndDate lt '#{end_date}')" if end_date
|
74
|
+
end
|
75
|
+
|
76
|
+
# Add a filter that looks for stories which do not have a linked test plan
|
77
|
+
def filter_by_missing_tests
|
78
|
+
self << '(LinkedTestPlan is nil)'
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Dev
|
2
|
+
class TargetProcess
|
3
|
+
# Class containing user information
|
4
|
+
class User
|
5
|
+
attr_accessor :id, :type, :name, :login
|
6
|
+
|
7
|
+
def initialize(data)
|
8
|
+
@id = data['Id']
|
9
|
+
@type = data['ResourceType']
|
10
|
+
@name = data['FullName']
|
11
|
+
@login = data['Login']
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Dev
|
2
|
+
class TargetProcess
|
3
|
+
# Class containing user story information
|
4
|
+
class UserStory
|
5
|
+
# The api path for user story requests
|
6
|
+
PATH = '/UserStories'.freeze
|
7
|
+
|
8
|
+
attr_accessor :type, :id, :name, :description, :start_date, :end_date, :create_date, :modify_date, :tags, :effort, :time_spent, :last_state_change_date, :project,
|
9
|
+
:owner, :creator, :release, :team, :priority, :state, :original_data
|
10
|
+
|
11
|
+
def initialize(data)
|
12
|
+
@id = data['Id']
|
13
|
+
@type = data['ResourceType']
|
14
|
+
@name = data['Name']
|
15
|
+
@description = data['Description']
|
16
|
+
@state = data['EntityState']['Name']
|
17
|
+
@project = Project.new(data['Project']) if data['Project']
|
18
|
+
@owner = User.new(data['Owner']) if data['Owner']
|
19
|
+
@creator = User.new(data['Creator']) if data['Creator']
|
20
|
+
@release = Release.new(data['Release']) if data['Release']
|
21
|
+
@team = Team.new(data['Team']) if data['Team']
|
22
|
+
@start_date = parse_time(data['StartDate'])
|
23
|
+
@end_date = parse_time(data['EndDate'])
|
24
|
+
@create_date = parse_time(data['CreateDate'])
|
25
|
+
@modify_date = parse_time(data['ModifyDate'])
|
26
|
+
@tags = data['Tags']
|
27
|
+
@effort = data['Effort']
|
28
|
+
@time_spent = data['TimeSpent']
|
29
|
+
@last_state_change_date = parse_time(data['LastStateChangeDate'])
|
30
|
+
@original_data = original_data
|
31
|
+
end
|
32
|
+
|
33
|
+
# Parse the dot net time representation into something that ruby can use
|
34
|
+
def parse_time(string)
|
35
|
+
return nil unless string && !string.empty?
|
36
|
+
|
37
|
+
Time.at(string.slice(6, 10).to_i)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Calculate the cycle time as the amount of time the story was open
|
41
|
+
def cycle_time
|
42
|
+
return 1.0 unless start_date && end_date
|
43
|
+
|
44
|
+
(end_date - start_date).to_f
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
|
3
|
+
module Dev
|
4
|
+
# Class for querying target process data from their api
|
5
|
+
class TargetProcess
|
6
|
+
# The config file to try to load credentials from
|
7
|
+
CONFIG_FILE = "#{Dir.home}/.env.tp".freeze
|
8
|
+
|
9
|
+
# The text of the username variable key
|
10
|
+
TP_USERNAME = 'TP_USERNAME'.freeze
|
11
|
+
|
12
|
+
# The text of the password variable key
|
13
|
+
TP_PASSWORD = 'TP_PASSWORD'.freeze
|
14
|
+
|
15
|
+
# The text of the url variable key
|
16
|
+
TP_URL = 'TP_URL'.freeze
|
17
|
+
|
18
|
+
# Config object for setting top level jira config options
|
19
|
+
Config = Struct.new(:username, :password, :url, :http_debug) do
|
20
|
+
def initialize
|
21
|
+
Dotenv.load(CONFIG_FILE) if File.exist?(CONFIG_FILE)
|
22
|
+
|
23
|
+
self.username = ENV.fetch(TP_USERNAME, nil)
|
24
|
+
self.password = ENV.fetch(TP_PASSWORD, nil)
|
25
|
+
self.url = ENV.fetch(TP_URL, nil)
|
26
|
+
self.http_debug = false
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class << self
|
31
|
+
# Instantiates a new top level config object if one hasn't already been created
|
32
|
+
# Yields that config object to any given block
|
33
|
+
# Returns the resulting config object
|
34
|
+
def config
|
35
|
+
@config ||= Config.new
|
36
|
+
yield(@config) if block_given?
|
37
|
+
@config
|
38
|
+
end
|
39
|
+
|
40
|
+
# Alias the config method to configure for a slightly clearer access syntax
|
41
|
+
alias_method :configure, :config
|
42
|
+
end
|
43
|
+
|
44
|
+
attr_accessor :username, :password, :url, :auth, :client, :headers
|
45
|
+
|
46
|
+
# Initialize a new target process client using the given inputs
|
47
|
+
def initialize(username: self.class.config.username, password: self.class.config.password, url: self.class.config.url)
|
48
|
+
@username = username
|
49
|
+
@password = password
|
50
|
+
@auth = Base64.strict_encode64("#{@username}:#{@password}")
|
51
|
+
@url = url
|
52
|
+
uri = URI.parse(@url)
|
53
|
+
@client = Net::HTTP.new(uri.host, uri.port)
|
54
|
+
@client.use_ssl = true
|
55
|
+
@client.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
56
|
+
@client.set_debug_output(LOG) if self.class.config.http_debug
|
57
|
+
@headers = {
|
58
|
+
'authorization' => "Basic #{auth}",
|
59
|
+
'content-type' => 'application/json',
|
60
|
+
'accept' => 'application/json'
|
61
|
+
}
|
62
|
+
end
|
63
|
+
|
64
|
+
# Perform a query to the user story api path
|
65
|
+
# Call the given block (if present) with each user story
|
66
|
+
# Return all user stories
|
67
|
+
def user_stories(query, &)
|
68
|
+
[].tap do |ary|
|
69
|
+
get(UserStory::PATH, query) do |result|
|
70
|
+
ary << UserStory.new(result)
|
71
|
+
end
|
72
|
+
ary.each(&)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Perform a get request to the given path using the given query
|
77
|
+
# Call the given block (if present) with each piece of data
|
78
|
+
# Return all pieces of data
|
79
|
+
def get(path, query, &)
|
80
|
+
query_string = query.generate
|
81
|
+
url = "/api/v1/#{path}"
|
82
|
+
url << "?#{URI.encode_www_form(query_string)}" unless query_string.empty?
|
83
|
+
|
84
|
+
response = client.request_get(url, headers)
|
85
|
+
raise "Error querying #{url} [#{query_string}]: #{response.inspect}" unless response.response.is_a?(Net::HTTPSuccess)
|
86
|
+
|
87
|
+
parsed_response = JSON.parse(response.body)
|
88
|
+
return parsed_response unless parsed_response.key?('Items')
|
89
|
+
|
90
|
+
parsed_response['Items'].each(&)
|
91
|
+
|
92
|
+
while parsed_response['Next']
|
93
|
+
response = client.request_get(parsed_response['Next'], headers)
|
94
|
+
raise "Error querying #{parsed_response['Next']} [#{query_string}]: #{response.inspect}" unless response.response.is_a?(Net::HTTPSuccess)
|
95
|
+
|
96
|
+
parsed_response = JSON.parse(response.body)
|
97
|
+
return parsed_response unless parsed_response.key?('Items')
|
98
|
+
|
99
|
+
parsed_response['Items'].each(&)
|
100
|
+
end
|
101
|
+
|
102
|
+
nil
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: firespring_dev_commands
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.1.
|
4
|
+
version: 2.1.10.pre.alpha.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Firespring
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-10-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 7.
|
19
|
+
version: 7.1.1
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 7.
|
26
|
+
version: 7.1.1
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: aws-sdk-cloudformation
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -305,6 +305,13 @@ files:
|
|
305
305
|
- lib/firespring_dev_commands/tar.rb
|
306
306
|
- lib/firespring_dev_commands/tar/pax_header.rb
|
307
307
|
- lib/firespring_dev_commands/tar/type_flag.rb
|
308
|
+
- lib/firespring_dev_commands/target_process.rb
|
309
|
+
- lib/firespring_dev_commands/target_process/project.rb
|
310
|
+
- lib/firespring_dev_commands/target_process/query.rb
|
311
|
+
- lib/firespring_dev_commands/target_process/release.rb
|
312
|
+
- lib/firespring_dev_commands/target_process/team.rb
|
313
|
+
- lib/firespring_dev_commands/target_process/user.rb
|
314
|
+
- lib/firespring_dev_commands/target_process/user_story.rb
|
308
315
|
- lib/firespring_dev_commands/templates/aws.rb
|
309
316
|
- lib/firespring_dev_commands/templates/base_interface.rb
|
310
317
|
- lib/firespring_dev_commands/templates/ci.rb
|
@@ -333,9 +340,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
333
340
|
version: '3.1'
|
334
341
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
335
342
|
requirements:
|
336
|
-
- - "
|
343
|
+
- - ">"
|
337
344
|
- !ruby/object:Gem::Version
|
338
|
-
version:
|
345
|
+
version: 1.3.1
|
339
346
|
requirements: []
|
340
347
|
rubygems_version: 3.4.10
|
341
348
|
signing_key:
|