firespring_dev_commands 2.1.8 → 2.1.10.pre.alpha.2
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|