tractive 1.0.14 → 1.0.18
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +1 -1
- data/README.adoc +5 -0
- data/exe/tractive +2 -0
- data/lib/tractive/github_api/graph_ql_client/issues.rb +29 -0
- data/lib/tractive/github_api/graph_ql_client.rb +31 -0
- data/lib/tractive/github_api.rb +1 -0
- data/lib/tractive/http/client/request.rb +1 -1
- data/lib/tractive/info.rb +13 -0
- data/lib/tractive/main.rb +3 -1
- data/lib/tractive/migrator/converter/trac_to_github.rb +15 -11
- data/lib/tractive/migrator/engine/migrate_from_db.rb +41 -0
- data/lib/tractive/migrator/engine.rb +2 -0
- data/lib/tractive/version.rb +1 -1
- data/tractive.gemspec +2 -0
- metadata +32 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e08becfcae314c82bdc793b7eb6bc2f7b52f9a7be040b7141da825753a9f1af2
|
4
|
+
data.tar.gz: 51385bdf7fcf7b12b110c852b9d0b9dc277240abf0b8c2f94b68b77db2839c32
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ad89e67997dd07cb3b6d65c5baca788033991122d8a10a73a64b09df79695ed8e2488b033dfaaa5fdd07d4a65c4ee5b4230121f3f7f094c8286bce352bcd6118
|
7
|
+
data.tar.gz: 04f60f8190dbc644367c3095f28e4f7fddba04dfa5140a820dfbced58430fb3b1df1bb9c72dd10dadba6e4f66e1cfe9a2ea57f647bbf982452643824e87622f8
|
data/.rubocop.yml
CHANGED
data/README.adoc
CHANGED
@@ -506,6 +506,8 @@ milestones:
|
|
506
506
|
==== Attachments migration configuration
|
507
507
|
`ticket | wiki:`:: specifies the options for the tickets or wikis
|
508
508
|
|
509
|
+
`delete_mocked:`::: Whether to delete mocked tickets after migration or not
|
510
|
+
|
509
511
|
`attachments:`::: specifies method of obtaining attachments from Trac.
|
510
512
|
|
511
513
|
`url:`:::: URL to obtain Trac attachments from
|
@@ -517,9 +519,12 @@ milestones:
|
|
517
519
|
`export_script:`:::: output of a script that utilizes `trac-admin` to download
|
518
520
|
all attachments from Trac.
|
519
521
|
|
522
|
+
NOTE: To delete the issues, an organization owner must enable deleting an issue for the organization's repositories, and you must have admin or owner permissions in the repository. For more information, see "https://docs.github.com/en/issues/tracking-your-work-with-issues/deleting-an-issue[deleting an issue]".
|
523
|
+
|
520
524
|
[source,yaml]
|
521
525
|
----
|
522
526
|
ticket:
|
527
|
+
delete_mocked: true
|
523
528
|
attachments:
|
524
529
|
url: https://abc.com/raw-attachment/ticket
|
525
530
|
hashed: true
|
data/exe/tractive
CHANGED
@@ -47,6 +47,8 @@ class TractiveCommand < CommandBase
|
|
47
47
|
desc: "Put all issue comments in the first message."
|
48
48
|
method_option "start", type: :numeric, aliases: ["-s", "--start-at"], banner: "<ID>",
|
49
49
|
desc: "Start migration from ticket with number <ID>"
|
50
|
+
method_option "make-owners-labels", type: :boolean,
|
51
|
+
desc: "If true, this will make a tag like `owner:<owner name>` and add it to the issue."
|
50
52
|
def migrate_tickets
|
51
53
|
Tractive::Main.new(options).run
|
52
54
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GithubApi
|
4
|
+
class GraphQlClient
|
5
|
+
# Methods for the Issues API
|
6
|
+
module Issues
|
7
|
+
DELETE_ISSUE_QUERY = <<~QUERY
|
8
|
+
mutation ($input: DeleteIssueInput!) {
|
9
|
+
deleteIssue(input: $input) {
|
10
|
+
repository {
|
11
|
+
name
|
12
|
+
url
|
13
|
+
}
|
14
|
+
}
|
15
|
+
}
|
16
|
+
QUERY
|
17
|
+
|
18
|
+
def delete_issue(issue_id)
|
19
|
+
variables = {
|
20
|
+
"input" => {
|
21
|
+
"issueId" => issue_id
|
22
|
+
}
|
23
|
+
}
|
24
|
+
|
25
|
+
Client.query(DeleteIssueQuery, variables: variables)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "graph_ql_client/issues"
|
4
|
+
|
5
|
+
require "graphql/client"
|
6
|
+
require "graphql/client/http"
|
7
|
+
|
8
|
+
# Service to perform github actions
|
9
|
+
module GithubApi
|
10
|
+
class GraphQlClient
|
11
|
+
include GithubApi::GraphQlClient::Issues
|
12
|
+
|
13
|
+
HttpAdapter = GraphQL::Client::HTTP.new("https://api.github.com/graphql") do
|
14
|
+
attr_writer :token
|
15
|
+
|
16
|
+
def headers(_context)
|
17
|
+
{
|
18
|
+
"Authorization" => "bearer #{@token}"
|
19
|
+
}
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.add_constants(token)
|
24
|
+
HttpAdapter.token = token
|
25
|
+
|
26
|
+
GithubApi::GraphQlClient.const_set("Schema", GraphQL::Client.load_schema(HttpAdapter))
|
27
|
+
GithubApi::GraphQlClient.const_set("Client", GraphQL::Client.new(schema: Schema, execute: HttpAdapter))
|
28
|
+
GithubApi::GraphQlClient.const_set("DeleteIssueQuery", Client.parse(DELETE_ISSUE_QUERY))
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/tractive/github_api.rb
CHANGED
@@ -22,7 +22,7 @@ module Http
|
|
22
22
|
minutes = retry_after / 60
|
23
23
|
seconds = retry_after % 60
|
24
24
|
|
25
|
-
|
25
|
+
$logger.info "Rate Limit Exceeded, Will retry in #{minutes} min #{seconds} sec"
|
26
26
|
sleep(1)
|
27
27
|
|
28
28
|
retry_after = e.http_headers[:x_ratelimit_reset].to_i - Time.now.to_i
|
data/lib/tractive/info.rb
CHANGED
@@ -29,12 +29,25 @@ module Tractive
|
|
29
29
|
priorities = Ticket.distinct.select(:priority).select_map(:priority).compact
|
30
30
|
tracstates = Ticket.distinct.select(:status).select_map(:status).compact
|
31
31
|
|
32
|
+
keywords = Ticket.distinct
|
33
|
+
.select(:keywords)
|
34
|
+
.select_map(:keywords)
|
35
|
+
.map do |keyword|
|
36
|
+
keyword&.split(",")&.map do |k|
|
37
|
+
k.strip.gsub(" ", "_")
|
38
|
+
end
|
39
|
+
end
|
40
|
+
.flatten
|
41
|
+
.uniq
|
42
|
+
.compact
|
43
|
+
|
32
44
|
{
|
33
45
|
"users" => Utilities.make_each_hash(users, %w[email name username]),
|
34
46
|
"milestones" => milestones,
|
35
47
|
"labels" => {
|
36
48
|
"type" => Utilities.make_hash("type_", types),
|
37
49
|
"resolution" => Utilities.make_hash("resolution_", resolutions),
|
50
|
+
"keywords" => Utilities.make_hash("keyword_", keywords),
|
38
51
|
"component" => Utilities.make_each_hash(components, %w[name color], "component: "),
|
39
52
|
"severity" => Utilities.make_each_hash(severity, %w[name color]),
|
40
53
|
"priority" => Utilities.make_each_hash(priorities, %w[name color]),
|
data/lib/tractive/main.rb
CHANGED
@@ -9,7 +9,9 @@ module Tractive
|
|
9
9
|
@cfg = YAML.load_file(@opts[:config])
|
10
10
|
|
11
11
|
@cfg["github"] ||= {}
|
12
|
-
@cfg["github"]["token"] = @opts["git-token"]
|
12
|
+
@cfg["github"]["token"] = @opts["git-token"] if @opts["git-token"]
|
13
|
+
|
14
|
+
GithubApi::GraphQlClient.add_constants(@cfg["github"]["token"]) unless @opts[:info]
|
13
15
|
|
14
16
|
Tractive::Utilities.setup_logger(output_stream: @opts[:logfile] || $stderr, verbose: @opts[:verbose])
|
15
17
|
@db = Tractive::Utilities.setup_db!(@opts["trac-database-path"] || @cfg["trac"]["database"])
|
@@ -16,6 +16,7 @@ module Migrator
|
|
16
16
|
@client = GithubApi::Client.new(access_token: args[:cfg]["github"]["token"])
|
17
17
|
@wiki_attachments_url = args[:cfg].dig("wiki", "attachments", "url")
|
18
18
|
@revmap_file_path = args[:opts][:revmapfile] || args[:cfg]["revmap_path"]
|
19
|
+
@make_owners_label = args[:opts]["make-owners-labels"] || args[:cfg]["make_owners_labels"]
|
19
20
|
@attachment_options = {
|
20
21
|
url: @attachurl,
|
21
22
|
hashed: args[:cfg].dig("ticket", "attachments", "hashed")
|
@@ -101,14 +102,11 @@ module Migrator
|
|
101
102
|
|
102
103
|
labels.delete(nil)
|
103
104
|
|
104
|
-
keywords = ticket[:keywords]
|
105
|
-
|
106
|
-
|
107
|
-
labels.add(@labels_cfg.fetch("keywords", {})[ticket[:keywords].downcase])
|
108
|
-
else
|
109
|
-
badges.add(@labels_cfg.fetch("keywords", {})[ticket[:keywords]])
|
110
|
-
end
|
105
|
+
keywords = ticket[:keywords]&.split(",") || []
|
106
|
+
keywords.each do |keyword|
|
107
|
+
badges.add(@labels_cfg.fetch("keywords", {})[keyword.strip.gsub(" ", "_")])
|
111
108
|
end
|
109
|
+
|
112
110
|
# If the field is not set, it will be nil and generate an unprocessable json
|
113
111
|
|
114
112
|
milestone = @milestonemap[ticket[:milestone]]
|
@@ -121,6 +119,14 @@ module Migrator
|
|
121
119
|
|
122
120
|
github_assignee = map_assignee(ticket[:owner])
|
123
121
|
|
122
|
+
unless github_assignee.nil? || github_assignee.empty?
|
123
|
+
if @make_owners_label
|
124
|
+
labels.add("name" => "owner:#{github_assignee}")
|
125
|
+
else
|
126
|
+
badges.add("owner:#{github_assignee}")
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
124
130
|
badges = badges.to_a.compact.sort
|
125
131
|
badgetable = badges.map { |i| %(`#{i}`) }.join(" ")
|
126
132
|
badgetable += begin
|
@@ -132,8 +138,6 @@ module Migrator
|
|
132
138
|
|
133
139
|
# compose body
|
134
140
|
body = [badgetable, body, footer].join("\n\n___\n")
|
135
|
-
|
136
|
-
labels.add("name" => "owner:#{github_assignee}") unless github_assignee.nil? || github_assignee.empty?
|
137
141
|
labels = labels.map { |label| label["name"] }
|
138
142
|
|
139
143
|
issue = {
|
@@ -265,7 +269,7 @@ module Migrator
|
|
265
269
|
end
|
266
270
|
|
267
271
|
case kind
|
268
|
-
when "owner", "status", "title", "resolution", "priority", "component", "type", "severity", "platform", "milestone"
|
272
|
+
when "owner", "status", "title", "resolution", "priority", "component", "type", "severity", "platform", "milestone", "keywords"
|
269
273
|
old = meta[:oldvalue]
|
270
274
|
new = meta[:newvalue]
|
271
275
|
if old && new
|
@@ -345,7 +349,7 @@ module Migrator
|
|
345
349
|
end
|
346
350
|
|
347
351
|
def interested_in_change?(kind, newvalue)
|
348
|
-
!(%w[
|
352
|
+
!(%w[cc reporter version].include?(kind) ||
|
349
353
|
(kind == "comment" && (newvalue.nil? || newvalue.lstrip.empty?)))
|
350
354
|
end
|
351
355
|
|
@@ -23,6 +23,7 @@ module Migrator
|
|
23
23
|
begin
|
24
24
|
lasttracid = tractickets.last[:id]
|
25
25
|
rescue StandardError
|
26
|
+
delete_mocked_tickets if can_delete_mocked_tickets?
|
26
27
|
raise("trac has no ticket #{start_ticket}")
|
27
28
|
end
|
28
29
|
|
@@ -89,6 +90,46 @@ module Migrator
|
|
89
90
|
|
90
91
|
@last_created_issue = ticket[:id]
|
91
92
|
end
|
93
|
+
|
94
|
+
delete_mocked_tickets if can_delete_mocked_tickets?
|
95
|
+
end
|
96
|
+
|
97
|
+
def can_delete_mocked_tickets?
|
98
|
+
@delete_mocked_tickets
|
99
|
+
end
|
100
|
+
|
101
|
+
def delete_mocked_tickets
|
102
|
+
page = 1
|
103
|
+
issues = @client.issues(@repo, { filter: "all",
|
104
|
+
state: "closed",
|
105
|
+
page: page })
|
106
|
+
|
107
|
+
until issues.empty?
|
108
|
+
deleted = false
|
109
|
+
|
110
|
+
issues.each do |issue|
|
111
|
+
next if issue["title"] != "Placeholder issue #{issue["number"]} created to align Github issue and trac ticket numbers during migration."
|
112
|
+
|
113
|
+
response = @graph_ql_client.delete_issue(issue["node_id"])
|
114
|
+
|
115
|
+
if response.data.errors.any?
|
116
|
+
error_message = response.data
|
117
|
+
.errors
|
118
|
+
.messages
|
119
|
+
.map { |k, v| "#{k}: #{v}" }
|
120
|
+
.join(", ")
|
121
|
+
raise StandardError, error_message
|
122
|
+
end
|
123
|
+
|
124
|
+
deleted = true
|
125
|
+
puts "Successfully deleted issue ##{issue["number"]}, Title: #{issue["title"]}"
|
126
|
+
end
|
127
|
+
|
128
|
+
page += 1 unless deleted
|
129
|
+
issues = @client.issues(@repo, { filter: "all",
|
130
|
+
state: "closed",
|
131
|
+
page: page })
|
132
|
+
end
|
92
133
|
end
|
93
134
|
end
|
94
135
|
end
|
@@ -32,6 +32,7 @@ module Migrator
|
|
32
32
|
@trac = Tractive::Trac.new(db)
|
33
33
|
@repo = github["repo"]
|
34
34
|
@client = GithubApi::Client.new(access_token: github["token"])
|
35
|
+
@graph_ql_client = GithubApi::GraphQlClient.new
|
35
36
|
|
36
37
|
if input_file_name
|
37
38
|
@from_file = input_file_name
|
@@ -62,6 +63,7 @@ module Migrator
|
|
62
63
|
@safetychecks = safetychecks
|
63
64
|
@start_ticket = (start_ticket || (@last_created_issue + 1)).to_i
|
64
65
|
@filter_closed = filter_closed
|
66
|
+
@delete_mocked_tickets = args[:cfg]["ticket"]["delete_mocked"]
|
65
67
|
end
|
66
68
|
|
67
69
|
def migrate
|
data/lib/tractive/version.rb
CHANGED
data/tractive.gemspec
CHANGED
@@ -27,6 +27,8 @@ Gem::Specification.new do |spec|
|
|
27
27
|
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
28
28
|
spec.require_paths = ["lib"]
|
29
29
|
|
30
|
+
spec.add_dependency "graphql", "1.13.3"
|
31
|
+
spec.add_dependency "graphql-client"
|
30
32
|
spec.add_dependency "mysql2"
|
31
33
|
spec.add_dependency "ox"
|
32
34
|
spec.add_dependency "rest-client"
|
metadata
CHANGED
@@ -1,15 +1,43 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tractive
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.18
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ribose
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-02-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: graphql
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 1.13.3
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 1.13.3
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: graphql-client
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
13
41
|
- !ruby/object:Gem::Dependency
|
14
42
|
name: mysql2
|
15
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -145,6 +173,8 @@ files:
|
|
145
173
|
- lib/tractive/github_api/client/issues.rb
|
146
174
|
- lib/tractive/github_api/client/labels.rb
|
147
175
|
- lib/tractive/github_api/client/milestones.rb
|
176
|
+
- lib/tractive/github_api/graph_ql_client.rb
|
177
|
+
- lib/tractive/github_api/graph_ql_client/issues.rb
|
148
178
|
- lib/tractive/graceful_quit.rb
|
149
179
|
- lib/tractive/http/client.rb
|
150
180
|
- lib/tractive/http/client/request.rb
|