tractive 1.0.12 → 1.0.16

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 07fb75035716d30de8f58206ad93cd0b932d74807646d5577d519d848eb2bfa0
4
- data.tar.gz: c4e50252da3aaf67b919041cdc682e8559abb28163dd847671d30c91808d15e0
3
+ metadata.gz: 71d04fdb0054f5a9ced6c246dc346d2b9c39f9bf115894805cbd1eb9411c7442
4
+ data.tar.gz: 59c54357c8495e8e505228d7eb2a65401505704ad208eb553124f637a5eb00f8
5
5
  SHA512:
6
- metadata.gz: efc63d0e9e8b08d3780cccd3877554c7a1df4408cd8db3c5bcf4fd11634eab73ac7f29fa300fb9191e77a892ba5d795c80e0d1a5a97283e6c541370372ef5725
7
- data.tar.gz: b1dc726435943c2b0cf70f369c21ffd049d07e13147a5d88f162dc725fb1ad7d62a8a034b2adb05903cc9e393dd421497af457fbaf2ad8cf45684b783b63b5ba
6
+ metadata.gz: 2251a5f5cb89a617fcae72eea174736f8df978aa73907b89e7c8a5e4176e2cec9f9ad3e48a12b09f9111cf7ef3a0ede6692fbb1f70b0ef6577e72864b4717875
7
+ data.tar.gz: 802c59f3396270a1ccc072a13c202fe21b6899e3fe3746d95f1b1cce619fddef878c1f255089e72c21595d0fa06ebae4b2da0f28ea4393675b01fb6528bb006a
data/.rubocop.yml CHANGED
@@ -36,7 +36,7 @@ Metrics/MethodLength:
36
36
  Enabled: false
37
37
 
38
38
  Metrics/ModuleLength:
39
- Max: 150
39
+ Max: 250
40
40
 
41
41
  Metrics/ParameterLists:
42
42
  Max: 6
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/command_base.rb CHANGED
@@ -7,5 +7,7 @@ class CommandBase < Thor
7
7
  desc: "Name of the logfile to output logs to."
8
8
  class_option "config", type: :string, default: "tractive.config.yaml", banner: "<PATH>", aliases: "-c",
9
9
  desc: "Set the configuration file"
10
+ class_option "git-token", type: :string,
11
+ desc: "The access token for Github actions."
10
12
  class_option "verbose", type: :boolean, aliases: ["-v", "--verbose"], desc: "Verbose mode"
11
13
  end
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
@@ -1,3 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "github_api/client"
4
+ require_relative "github_api/graph_ql_client"
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
@@ -8,6 +8,11 @@ module Tractive
8
8
  @opts = opts
9
9
  @cfg = YAML.load_file(@opts[:config])
10
10
 
11
+ @cfg["github"] ||= {}
12
+ @cfg["github"]["token"] = @opts["git-token"] if @opts["git-token"]
13
+
14
+ GithubApi::GraphQlClient.add_constants(@cfg["github"]["token"]) unless @opts[:info]
15
+
11
16
  Tractive::Utilities.setup_logger(output_stream: @opts[:logfile] || $stderr, verbose: @opts[:verbose])
12
17
  @db = Tractive::Utilities.setup_db!(@opts["trac-database-path"] || @cfg["trac"]["database"])
13
18
  rescue Sequel::DatabaseConnectionError, Sequel::AdapterNotFound, URI::InvalidURIError, Sequel::DatabaseError => e
@@ -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
- if keywords
106
- if ticket[:keywords].downcase == "discuss"
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])
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
@@ -281,7 +285,7 @@ module Migrator
281
285
  # text += "created the issue\n\n"
282
286
  if body && !body.lstrip.empty?
283
287
  # text += "\n___\n" if not append
284
- text += @twf_to_markdown.convert(body)
288
+ text += @twf_to_markdown.convert(body, id: meta[:ticket])
285
289
  end
286
290
 
287
291
  when "comment"
@@ -295,7 +299,7 @@ module Migrator
295
299
  end
296
300
 
297
301
  text += "\n___\n" unless append
298
- text += @twf_to_markdown.convert(body) if body
302
+ text += @twf_to_markdown.convert(body, id: meta[:ticket]) if body
299
303
 
300
304
  when "attachment"
301
305
  text += "_uploaded file "
@@ -345,7 +349,7 @@ module Migrator
345
349
  end
346
350
 
347
351
  def interested_in_change?(kind, newvalue)
348
- !(%w[keywords cc reporter version].include?(kind) ||
352
+ !(%w[cc reporter version].include?(kind) ||
349
353
  (kind == "comment" && (newvalue.nil? || newvalue.lstrip.empty?)))
350
354
  end
351
355
 
@@ -16,11 +16,11 @@ module Migrator
16
16
 
17
17
  @git_repo = options[:git_repo]
18
18
  @home_page_name = options[:home_page_name]
19
- @wiki_extensions = options[:wiki_extensions] # || [".py", "changelog", "expire-ids"]
20
- @source_folders = options[:source_folders] # || %w[personal attic sprint branch/hawk]
19
+ @wiki_extensions = options[:wiki_extensions]
20
+ @source_folders = options[:source_folders]
21
21
  end
22
22
 
23
- def convert(str)
23
+ def convert(str, image_options = {})
24
24
  # Fix 'Windows EOL' to 'Linux EOL'
25
25
  str.gsub!("\r\n", "\n")
26
26
 
@@ -33,7 +33,7 @@ module Migrator
33
33
  convert_links(str, @git_repo)
34
34
  convert_font_styles(str)
35
35
  convert_changeset(str, @changeset_base_url)
36
- convert_image(str, @base_url, @attach_url, @wiki_attachments_url)
36
+ convert_image(str, @base_url, @attach_url, @wiki_attachments_url, image_options)
37
37
  convert_ticket(str, @base_url)
38
38
  revert_intermediate_references(str)
39
39
 
@@ -247,13 +247,13 @@ module Migrator
247
247
  def file?(trac_path)
248
248
  return false unless trac_path
249
249
 
250
- @wiki_extensions.any? { |extension| trac_path.end_with?(extension) }
250
+ @wiki_extensions&.any? { |extension| trac_path.end_with?(extension) }
251
251
  end
252
252
 
253
253
  def wiki_path(path, line_number = "")
254
254
  # TODO: This will not work for folders given in the source_folder parameter and
255
255
  # will not work for subfolders paths like `personal/rjs` unless given in the parameters.
256
- return "branches/all?query=#{path}" if @source_folders.any? { |folder| folder == path }
256
+ return "branches/all?query=#{path}" if @source_folders&.any? { |folder| folder == path }
257
257
  return index_paths[path] if index_paths[path]
258
258
 
259
259
  prefix = if file?(path)
@@ -294,7 +294,7 @@ module Migrator
294
294
  "[#{wiki_name}](https://github.com/#{git_repo}/wiki/#{wiki_name})"
295
295
  end
296
296
 
297
- def convert_image(str, base_url, attach_url, wiki_attachments_url)
297
+ def convert_image(str, base_url, attach_url, wiki_attachments_url, options = {})
298
298
  # https://trac.edgewall.org/wiki/WikiFormatting#Images
299
299
  # [[Image(picture.gif)]] Current page (Ticket, Wiki, Comment)
300
300
  # [[Image(wiki:WikiFormatting:picture.gif)]] (referring to attachment on another page)
@@ -316,7 +316,15 @@ module Migrator
316
316
  # [[Image(http://example.org/s.jpg)]]
317
317
  "!{{#{path}}}(#{path})"
318
318
  else
319
- _, id, file = path.split(":")
319
+ tmp = path.split(":")
320
+ id, file = case tmp.size
321
+ when 3
322
+ [tmp[1], tmp[2]]
323
+ when 2
324
+ tmp
325
+ else
326
+ [options[:id].to_s, tmp[0]]
327
+ end
320
328
  file_path = "#{attach_url}/#{Tractive::Utilities.attachment_path(id, file, hashed: @attach_hashed)}"
321
329
  "!{{#{path}}}(#{file_path})"
322
330
  end
@@ -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
@@ -63,7 +63,7 @@ module Migrator
63
63
  $logger.info("Working with file [#{file_name}]")
64
64
  $logger.debug("Object: #{wiki}")
65
65
 
66
- wiki_markdown_text = @twf_to_markdown.convert(wiki[:text])
66
+ wiki_markdown_text = @twf_to_markdown.convert(wiki[:text], id: wiki[:name])
67
67
  wiki_markdown_text += wiki_attachments(wiki)
68
68
 
69
69
  # Create file with content
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Tractive
4
- VERSION = "1.0.12"
4
+ VERSION = "1.0.16"
5
5
  end
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.12
4
+ version: 1.0.16
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-01-04 00:00:00.000000000 Z
11
+ date: 2022-01-24 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