appmap 0.51.2 → 0.54.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +3 -1
  3. data/CHANGELOG.md +40 -0
  4. data/Rakefile +21 -5
  5. data/appmap.gemspec +8 -7
  6. data/exe/appmap-agent-init +19 -0
  7. data/exe/appmap-inspect +7 -0
  8. data/lib/appmap.rb +29 -119
  9. data/lib/appmap/agent.rb +115 -0
  10. data/lib/appmap/class_map.rb +2 -2
  11. data/lib/appmap/command/agent_setup/init.rb +44 -0
  12. data/lib/appmap/command/inspect.rb +27 -0
  13. data/lib/appmap/command_error.rb +13 -0
  14. data/lib/appmap/config.rb +13 -4
  15. data/lib/appmap/handler/net_http.rb +2 -1
  16. data/lib/appmap/handler/rails/request_handler.rb +2 -1
  17. data/lib/appmap/metadata.rb +4 -2
  18. data/lib/appmap/minitest.rb +2 -0
  19. data/lib/appmap/node_cli.rb +59 -0
  20. data/lib/appmap/railtie.rb +2 -2
  21. data/lib/appmap/rspec.rb +5 -3
  22. data/lib/appmap/service/guesser.rb +2 -9
  23. data/lib/appmap/swagger.rb +2 -0
  24. data/lib/appmap/swagger/configuration.rb +70 -0
  25. data/lib/appmap/swagger/markdown_descriptions.rb +43 -0
  26. data/lib/appmap/swagger/rake_tasks.rb +43 -0
  27. data/lib/appmap/swagger/stable.rb +37 -0
  28. data/lib/appmap/util.rb +31 -2
  29. data/lib/appmap/version.rb +1 -1
  30. data/package.json +5 -7
  31. data/spec/config_spec.rb +0 -1
  32. data/spec/fixtures/hook/.gitignore +2 -0
  33. data/spec/fixtures/hook/app/controllers/api/api_keys_controller.rb +1 -0
  34. data/spec/fixtures/hook/app/controllers/organizations_controller.rb +1 -0
  35. data/spec/fixtures/hook/app/models/api_key.rb +1 -0
  36. data/spec/fixtures/hook/app/models/configuration.rb +1 -0
  37. data/spec/fixtures/hook/app/models/show.rb +1 -0
  38. data/spec/fixtures/hook/app/models/user.rb +1 -0
  39. data/spec/fixtures/hook/revoke_api_key.appmap.json +847 -0
  40. data/spec/fixtures/hook/spec/api_spec.rb +1 -0
  41. data/spec/fixtures/hook/spec/user_spec.rb +1 -0
  42. data/spec/fixtures/hook/user_page_scenario.appmap.json +1722 -0
  43. data/spec/fixtures/rails5_users_app/config/environments/test.rb +3 -0
  44. data/spec/fixtures/rails5_users_app/docker-compose.yml +1 -1
  45. data/spec/fixtures/rails6_users_app/Dockerfile +9 -1
  46. data/spec/fixtures/rails6_users_app/appmap.yml +3 -1
  47. data/spec/fixtures/rails6_users_app/config/environments/test.rb +3 -0
  48. data/spec/fixtures/rails6_users_app/docker-compose.yml +1 -1
  49. data/spec/fixtures/rails6_users_app/lib/tasks/appmap.rake +13 -0
  50. data/spec/{abstract_controller_base_spec.rb → rails_recording_spec.rb} +2 -21
  51. data/spec/rails_spec_helper.rb +22 -0
  52. data/spec/swagger/swagger_spec.rb +47 -0
  53. data/test/{cli_test.rb → agent_setup_init_test.rb} +4 -4
  54. data/test/fixtures/gem_test/Gemfile +1 -0
  55. data/test/gem_test.rb +1 -0
  56. data/test/inspect_cli_test.rb +12 -0
  57. data/yarn.lock +477 -0
  58. metadata +39 -63
  59. data/exe/appmap-agent-setup +0 -47
  60. data/lib/appmap/algorithm/prune_class_map.rb +0 -67
  61. data/lib/appmap/algorithm/stats.rb +0 -91
  62. data/lib/appmap/command/init.rb +0 -42
  63. data/lib/appmap/command/record.rb +0 -38
  64. data/lib/appmap/command/stats.rb +0 -14
  65. data/lore/pages/2019-05-21-install-and-record/index.pug +0 -51
  66. data/lore/pages/2019-05-21-install-and-record/install_example_appmap.png +0 -0
  67. data/lore/pages/2019-05-21-install-and-record/metadata.yml +0 -5
  68. data/lore/pages/layout.pug +0 -66
  69. data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-grid.css +0 -1912
  70. data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-grid.css.map +0 -1
  71. data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-grid.min.css +0 -7
  72. data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-grid.min.css.map +0 -1
  73. data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-reboot.css +0 -331
  74. data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-reboot.css.map +0 -1
  75. data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-reboot.min.css +0 -8
  76. data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-reboot.min.css.map +0 -1
  77. data/lore/public/lib/bootstrap-4.1.3/css/bootstrap.css +0 -9030
  78. data/lore/public/lib/bootstrap-4.1.3/css/bootstrap.css.map +0 -1
  79. data/lore/public/lib/bootstrap-4.1.3/css/bootstrap.min.css +0 -7
  80. data/lore/public/lib/bootstrap-4.1.3/css/bootstrap.min.css.map +0 -1
  81. data/lore/public/stylesheets/style.css +0 -8
  82. data/package-lock.json +0 -1064
@@ -111,7 +111,7 @@ module AppMap
111
111
  end
112
112
 
113
113
  comment = method.comment
114
- function_info[:comment] = comment unless comment.blank?
114
+ function_info[:comment] = comment unless Util.blank?(comment)
115
115
 
116
116
  function_info[:labels] = parse_labels(comment) + (method.labels || [])
117
117
  object_infos << function_info
@@ -119,7 +119,7 @@ module AppMap
119
119
  parent = root
120
120
  object_infos.each do |info|
121
121
  parent = find_or_create parent.children, info do
122
- Types.const_get(info[:type].classify).new(info[:name].to_s).tap do |type|
122
+ Types.const_get(Util.classify(info[:type])).new(info[:name].to_s).tap do |type|
123
123
  info.keys.tap do |keys|
124
124
  keys.delete(:name)
125
125
  keys.delete(:type)
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+ require 'appmap/service/guesser'
5
+ require 'appmap/util'
6
+
7
+ module AppMap
8
+ module Command
9
+ module AgentSetup
10
+ InitStruct = Struct.new(:config_file)
11
+
12
+ class Init < InitStruct
13
+ def perform
14
+ if File.exist?(config_file)
15
+ puts AppMap::Util.color(%(The AppMap config file #{config_file} already exists.), :magenta)
16
+ return
17
+ end
18
+
19
+ ensure_directory_exists
20
+
21
+ config = {
22
+ 'name' => Service::Guesser.guess_name,
23
+ 'packages' => Service::Guesser.guess_paths.map { |path| { 'path' => path } }
24
+ }
25
+ content = YAML.dump(config).gsub("---\n", '')
26
+
27
+ File.write(config_file, content)
28
+ puts AppMap::Util.color(
29
+ %(The following AppMap config file #{config_file} has been created:),
30
+ :green
31
+ )
32
+ puts content
33
+ end
34
+
35
+ private
36
+
37
+ def ensure_directory_exists
38
+ dirname = File.dirname(config_file)
39
+ FileUtils.mkdir_p(dirname) unless File.directory?(dirname)
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,27 @@
1
+ require 'appmap/version'
2
+ require 'appmap/config'
3
+ require 'appmap/node_cli'
4
+
5
+ module AppMap
6
+ module Command
7
+ class Inspect < AppMap::NodeCLI
8
+ class << self
9
+ def run
10
+ command = Inspect.new(verbose: ENV['DEBUG'] == 'true')
11
+ command.inspect(ARGV)
12
+ end
13
+ end
14
+
15
+ def inspect(arguments)
16
+ detect_nodejs
17
+ index_appmaps
18
+
19
+ arguments.unshift 'inspect'
20
+ arguments.unshift APPMAP_JS
21
+ arguments.unshift 'node'
22
+
23
+ exec(*arguments)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,13 @@
1
+ module AppMap
2
+ # Raised when a system / shell command fails.
3
+ class CommandError < StandardError
4
+ attr_reader :command, :msg
5
+
6
+ def initialize(command, msg = nil)
7
+ super [ "Command failed: #{command}", msg ].compact.join('; ')
8
+
9
+ @command = command
10
+ @msg = msg
11
+ end
12
+ end
13
+ end
data/lib/appmap/config.rb CHANGED
@@ -1,9 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'yaml'
4
+ require 'appmap/util'
4
5
  require 'appmap/handler/net_http'
5
6
  require 'appmap/handler/rails/template'
6
7
  require 'appmap/service/guesser'
8
+ require 'appmap/swagger/configuration'
7
9
 
8
10
  module AppMap
9
11
  class Config
@@ -54,7 +56,7 @@ module AppMap
54
56
  if path
55
57
  Package.new(path, gem, package_name, exclude, labels, shallow)
56
58
  else
57
- warn "#{gem} is not available in the bundle" if AppMap::Hook::LOG
59
+ AppMap::Util.startup_message "#{gem} is not available in the bundle"
58
60
  end
59
61
  end
60
62
 
@@ -80,8 +82,8 @@ module AppMap
80
82
  package_name: package_name,
81
83
  gem: gem,
82
84
  handler_class: handler_class.name,
83
- exclude: exclude.blank? ? nil : exclude,
84
- labels: labels.blank? ? nil : labels,
85
+ exclude: Util.blank?(exclude) ? nil : exclude,
86
+ labels: Util.blank?(labels) ? nil : labels,
85
87
  shallow: shallow
86
88
  }.compact
87
89
  end
@@ -225,15 +227,17 @@ module AppMap
225
227
  'JSON::Ext::Generator::State' => TargetMethods.new(:generate, Package.build_from_path('json', package_name: 'json', labels: %w[format.json.generate])),
226
228
  }.freeze
227
229
 
228
- attr_reader :name, :appmap_dir, :packages, :exclude, :hooked_methods, :builtin_hooks
230
+ attr_reader :name, :appmap_dir, :packages, :exclude, :swagger_config, :hooked_methods, :builtin_hooks
229
231
 
230
232
  def initialize(name,
231
233
  packages: [],
234
+ swagger_config: Swagger::Configuration.new,
232
235
  exclude: [],
233
236
  functions: [])
234
237
  @name = name
235
238
  @appmap_dir = AppMap::DEFAULT_APPMAP_DIR
236
239
  @packages = packages
240
+ @swagger_config = swagger_config
237
241
  @hook_paths = Set.new(packages.map(&:path))
238
242
  @exclude = exclude
239
243
  @builtin_hooks = BUILTIN_HOOKS
@@ -350,6 +354,11 @@ module AppMap
350
354
  end
351
355
  end
352
356
 
357
+ if config_data['swagger']
358
+ swagger_config = Swagger::Configuration.load(config_data['swagger'])
359
+ config_params[:swagger_config] = swagger_config
360
+ end
361
+
353
362
  Config.new name, config_params
354
363
  end
355
364
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'appmap/event'
4
+ require 'appmap/util'
4
5
 
5
6
  module AppMap
6
7
  module Handler
@@ -38,7 +39,7 @@ module AppMap
38
39
  headers: headers
39
40
  }.compact
40
41
 
41
- unless params.blank?
42
+ unless Util.blank?(params)
42
43
  h[:message] = params.keys.map do |key|
43
44
  val = params[key]
44
45
  {
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'appmap/event'
4
4
  require 'appmap/hook'
5
+ require 'appmap/util'
5
6
 
6
7
  module AppMap
7
8
  module Handler
@@ -40,7 +41,7 @@ module AppMap
40
41
  headers: headers,
41
42
  }.compact
42
43
 
43
- unless params.blank?
44
+ unless Util.blank?(params)
44
45
  h[:message] = params.keys.map do |key|
45
46
  val = params[key]
46
47
  {
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'appmap/util'
4
+
3
5
  module AppMap
4
6
  module Metadata
5
7
  class << self
@@ -40,9 +42,9 @@ module AppMap
40
42
  git_sha = `git rev-parse HEAD`.strip
41
43
  git_status = `git status -s`.split("\n").map(&:strip)
42
44
  git_last_annotated_tag = `git describe --abbrev=0 2>/dev/null`.strip
43
- git_last_annotated_tag = nil if git_last_annotated_tag.blank?
45
+ git_last_annotated_tag = nil if Util.blank?(git_last_annotated_tag)
44
46
  git_last_tag = `git describe --abbrev=0 --tags 2>/dev/null`.strip
45
- git_last_tag = nil if git_last_tag.blank?
47
+ git_last_tag = nil if Util.blank?(git_last_tag)
46
48
  git_commits_since_last_annotated_tag = `git describe`.strip =~ /-(\d+)-(\w+)$/[1] rescue 0 if git_last_annotated_tag
47
49
  git_commits_since_last_tag = `git describe --tags`.strip =~ /-(\d+)-(\w+)$/[1] rescue 0 if git_last_tag
48
50
 
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'appmap/util'
4
+ require 'active_support'
5
+ require 'active_support/core_ext'
4
6
 
5
7
  module AppMap
6
8
  # Integration of AppMap with Minitest. When enabled with APPMAP=true, the AppMap tracer will
@@ -0,0 +1,59 @@
1
+ require 'open3'
2
+ require 'shellwords'
3
+ require 'appmap/util'
4
+ require 'appmap/command_error'
5
+
6
+ module AppMap
7
+ # Utilities for invoking the +@appland/appmap+ CLI.
8
+ class NodeCLI
9
+ APPMAP_JS = Pathname.new(__dir__).join('../../node_modules/@appland/cli/src/cli.js').expand_path.to_s
10
+
11
+ attr_reader :verbose
12
+ # Directory to scan for AppMaps.
13
+ attr_accessor :appmap_dir
14
+
15
+ def initialize(verbose: false, appmap_dir: AppMap::DEFAULT_APPMAP_DIR)
16
+ @verbose = verbose
17
+ @appmap_dir = appmap_dir
18
+
19
+ detect_nodejs
20
+ end
21
+
22
+ def detect_nodejs
23
+ do_fail('node', 'please install NodeJS') unless system('node --version 2>&1 > /dev/null')
24
+ true
25
+ end
26
+
27
+ def index_appmaps
28
+ command [ 'index', '--appmap-dir', appmap_dir ]
29
+ true
30
+ end
31
+
32
+ def command(command, options = {})
33
+ command.unshift << '--verbose' if verbose
34
+ command.unshift APPMAP_JS
35
+ command.unshift 'node'
36
+
37
+ warn command.join(' ') if verbose
38
+ stdout, stderr, status = Open3.capture3({ 'NODE_OPTIONS' => '--trace-warnings' }, *command.map(&:shellescape), options)
39
+ stdout_msg = stdout.split("\n").map {|line| "stdout: #{line}"}.join("\n") unless Util.blank?(stdout)
40
+ stderr_msg = stderr.split("\n").map {|line| "stderr: #{line}"}.join("\n") unless Util.blank?(stderr)
41
+ if verbose
42
+ warn stdout_msg if stdout_msg
43
+ warn stderr_msg if stderr_msg
44
+ end
45
+ unless status.exitstatus == 0
46
+ raise CommandError.new(command, [ stdout_msg, stderr_msg ].compact.join("\n"))
47
+ end
48
+ [ stdout, stderr ]
49
+ end
50
+
51
+ protected
52
+
53
+ def do_fail(command, msg)
54
+ command = command.join(' ') if command.is_a?(Array)
55
+ warn [ command, msg ].join('; ') if verbose
56
+ raise CommandError.new(command, msg)
57
+ end
58
+ end
59
+ end
@@ -5,8 +5,8 @@ module AppMap
5
5
  class Railtie < ::Rails::Railtie
6
6
  initializer 'appmap.remote_recording' do
7
7
  require 'appmap/middleware/remote_recording'
8
- Rails.application.config.middleware.insert_after \
9
- Rails::Rack::Logger,
8
+ Rails.application.config.middleware.insert_before \
9
+ ActionDispatch::Executor,
10
10
  AppMap::Middleware::RemoteRecording
11
11
  end
12
12
 
data/lib/appmap/rspec.rb CHANGED
@@ -62,8 +62,10 @@ module AppMap
62
62
  example_group_parent = \
63
63
  if example_group.respond_to?(:module_parent)
64
64
  example_group.module_parent
65
- else
65
+ elsif example_group.respond_to?(:parent)
66
66
  example_group.parent
67
+ elsif example_group.respond_to?(:parent_groups)
68
+ example_group.parent_groups.first
67
69
  end
68
70
 
69
71
  example_group_parent != example_group ? ScopeExampleGroup.new(example_group_parent) : nil
@@ -109,13 +111,13 @@ module AppMap
109
111
 
110
112
  description = []
111
113
  scope = ScopeExample.new(example)
112
-
113
114
  while scope
114
115
  description << scope.description
115
116
  scope = scope.parent
116
117
  end
117
118
 
118
- description.reject!(&:nil?).reject!(&:blank?)
119
+ description.reject!(&:nil?)
120
+ description.reject!(&Util.method(:blank?))
119
121
  default_description = description.last
120
122
  description.reverse!
121
123
 
@@ -6,15 +6,8 @@ module AppMap
6
6
  POSSIBLE_PATHS = %w[app/controllers app/models lib]
7
7
  class << self
8
8
  def guess_name
9
- reponame = lambda do
10
- next unless File.directory?('.git')
11
-
12
- repo_name = `git config --get remote.origin.url`.strip
13
- repo_name.split('/').last.split('.').first unless repo_name == ''
14
- end
15
- dirname = -> { Dir.pwd.split('/').last }
16
-
17
- reponame.() || dirname.()
9
+ return Pathname.new(`git rev-parse --show-toplevel`.strip).basename.to_s if File.directory?('.git')
10
+ Dir.pwd.split('/').last
18
11
  end
19
12
 
20
13
  def guess_paths
@@ -0,0 +1,2 @@
1
+ require 'appmap/swagger/configuration'
2
+ require 'appmap/swagger/rake_tasks'
@@ -0,0 +1,70 @@
1
+ require 'yaml'
2
+
3
+ module AppMap
4
+ module Swagger
5
+ class Configuration
6
+ DEFAULT_VERSION = '1.0'
7
+ DEFAULT_OUTPUT_DIR = 'swagger'
8
+ DEFAULT_DESCRIPTION = 'Generate Swagger from AppMaps'
9
+
10
+ attr_accessor :project_version,
11
+ :output_dir,
12
+ :description
13
+ attr_writer :project_name, :template
14
+
15
+ class << self
16
+ def load(config_data)
17
+ Configuration.new.tap do |config|
18
+ config_data.each do |k,v|
19
+ config.send "#{k}=", v
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+ def initialize
26
+ @project_name = nil
27
+ @project_version = DEFAULT_VERSION
28
+ @output_dir = DEFAULT_OUTPUT_DIR
29
+ @description = DEFAULT_DESCRIPTION
30
+ end
31
+
32
+ def project_name
33
+ @project_name || default_project_name
34
+ end
35
+
36
+ def template
37
+ @template || default_template
38
+ end
39
+
40
+ def default_template
41
+ YAML.load <<~TEMPLATE
42
+ openapi: 3.0.1
43
+ info:
44
+ title: #{project_name}
45
+ version: #{project_version}
46
+ paths:
47
+ components:
48
+ servers:
49
+ - url: http://{defaultHost}
50
+ variables:
51
+ defaultHost:
52
+ default: localhost:3000
53
+ TEMPLATE
54
+ end
55
+
56
+ def default_project_name
57
+ # https://www.rubydoc.info/docs/rails/Module#module_parent_name-instance_method
58
+ module_parent_name = ->(cls) { cls.name =~ /::[^:]+\Z/ ? $`.freeze : nil }
59
+
60
+ # Lazy-evaluate this so that Rails.application will be defined.
61
+ # If this code runs too early in the lifecycle, Rails.application is nil.
62
+ if defined?(::Rails)
63
+ [module_parent_name.(::Rails.application.class).humanize.titleize, "API"].join(" ")
64
+ else
65
+ "MyProject API"
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,43 @@
1
+ require 'rdoc'
2
+ require 'reverse_markdown'
3
+
4
+ module AppMap
5
+ module Swagger
6
+ # Transform description fields into Markdown.
7
+ class MarkdownDescriptions
8
+ def initialize(swagger_yaml)
9
+ @swagger_yaml = swagger_yaml
10
+ end
11
+
12
+ def converter
13
+ method(:rdoc_to_markdown)
14
+ end
15
+
16
+ def perform
17
+ to_markdown = lambda do |obj|
18
+ return obj.each(&to_markdown) if obj.is_a?(Array)
19
+ return unless obj.is_a?(Hash)
20
+
21
+ description = obj['description']
22
+ obj['description'] = converter.(description) if description
23
+
24
+ obj.reject { |k,v| k == 'properties' }.each_value(&to_markdown)
25
+
26
+ obj
27
+ end
28
+
29
+ to_markdown.(Util.deep_dup(@swagger_yaml))
30
+ end
31
+
32
+ protected
33
+
34
+ def rdoc_to_markdown(comment)
35
+ # Strip tags
36
+ comment = comment.split("\n").reject { |line| line =~ /^\s*@/ }.join("\n")
37
+ converter = ::RDoc::Markup::ToHtml.new(::RDoc::Options.new)
38
+ html = converter.convert(comment).strip
39
+ ::ReverseMarkdown.convert(html).strip
40
+ end
41
+ end
42
+ end
43
+ end