appmap 0.50.0 → 0.52.0
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/.travis.yml +3 -1
- data/CHANGELOG.md +38 -0
- data/Rakefile +21 -5
- data/appmap.gemspec +11 -6
- data/exe/appmap-agent-setup +47 -0
- data/exe/appmap-inspect +7 -0
- data/lib/appmap.rb +56 -17
- data/lib/appmap/command/agent_setup/init.rb +44 -0
- data/lib/appmap/command/inspect.rb +27 -0
- data/lib/appmap/command_error.rb +13 -0
- data/lib/appmap/config.rb +96 -29
- data/lib/appmap/handler/rails/template.rb +19 -5
- data/lib/appmap/node_cli.rb +59 -0
- data/lib/appmap/service/guesser.rb +26 -0
- data/lib/appmap/trace.rb +4 -2
- data/lib/appmap/util.rb +52 -2
- data/lib/appmap/version.rb +4 -1
- data/package.json +6 -7
- data/spec/config_spec.rb +21 -0
- data/spec/fixtures/rails5_users_app/docker-compose.yml +1 -1
- data/spec/fixtures/rails6_users_app/Dockerfile +9 -0
- data/spec/fixtures/rails6_users_app/docker-compose.yml +1 -1
- data/spec/hook_spec.rb +2 -2
- data/spec/{abstract_controller_base_spec.rb → rails_recording_spec.rb} +39 -19
- data/spec/rails_spec_helper.rb +22 -0
- data/spec/record_net_http_spec.rb +1 -1
- data/test/agent_setup_cli_test.rb +37 -0
- data/test/fixtures/gem_test/Gemfile +1 -0
- data/test/inspect_cli_test.rb +12 -0
- data/yarn.lock +477 -0
- metadata +23 -47
- data/lib/appmap/algorithm/prune_class_map.rb +0 -67
- data/lib/appmap/algorithm/stats.rb +0 -91
- data/lib/appmap/command/record.rb +0 -38
- data/lib/appmap/command/stats.rb +0 -14
- data/lore/pages/2019-05-21-install-and-record/index.pug +0 -51
- data/lore/pages/2019-05-21-install-and-record/install_example_appmap.png +0 -0
- data/lore/pages/2019-05-21-install-and-record/metadata.yml +0 -5
- data/lore/pages/layout.pug +0 -66
- data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-grid.css +0 -1912
- data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-grid.css.map +0 -1
- data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-grid.min.css +0 -7
- data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-grid.min.css.map +0 -1
- data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-reboot.css +0 -331
- data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-reboot.css.map +0 -1
- data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-reboot.min.css +0 -8
- data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-reboot.min.css.map +0 -1
- data/lore/public/lib/bootstrap-4.1.3/css/bootstrap.css +0 -9030
- data/lore/public/lib/bootstrap-4.1.3/css/bootstrap.css.map +0 -1
- data/lore/public/lib/bootstrap-4.1.3/css/bootstrap.min.css +0 -7
- data/lore/public/lib/bootstrap-4.1.3/css/bootstrap.min.css.map +0 -1
- data/lore/public/stylesheets/style.css +0 -8
- data/package-lock.json +0 -1064
@@ -14,16 +14,30 @@ module AppMap
|
|
14
14
|
# The class name is generated from the template path. The package name is
|
15
15
|
# 'app/views', and the method name is 'render'. The source location of the method
|
16
16
|
# is, of course, the path to the view template.
|
17
|
-
TemplateMethod
|
18
|
-
private_instance_methods :path
|
17
|
+
class TemplateMethod
|
19
18
|
attr_reader :class_name
|
20
|
-
|
19
|
+
|
20
|
+
attr_reader :path
|
21
|
+
private_instance_methods :path
|
22
|
+
|
21
23
|
def initialize(path)
|
22
|
-
|
24
|
+
@path = path
|
23
25
|
|
24
26
|
@class_name = path.parameterize.underscore
|
25
27
|
end
|
26
|
-
|
28
|
+
|
29
|
+
def id
|
30
|
+
[ package, path, name ]
|
31
|
+
end
|
32
|
+
|
33
|
+
def hash
|
34
|
+
id.hash
|
35
|
+
end
|
36
|
+
|
37
|
+
def eql?(other)
|
38
|
+
other.is_a?(TemplateMethod) && id.eql?(other.id)
|
39
|
+
end
|
40
|
+
|
27
41
|
def package
|
28
42
|
'app/views'
|
29
43
|
end
|
@@ -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
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AppMap
|
4
|
+
module Service
|
5
|
+
class Guesser
|
6
|
+
POSSIBLE_PATHS = %w[app/controllers app/models lib]
|
7
|
+
class << self
|
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.()
|
18
|
+
end
|
19
|
+
|
20
|
+
def guess_paths
|
21
|
+
POSSIBLE_PATHS.select { |path| File.directory?(path) }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/appmap/trace.rb
CHANGED
@@ -2,10 +2,12 @@
|
|
2
2
|
|
3
3
|
module AppMap
|
4
4
|
module Trace
|
5
|
-
class RubyMethod
|
5
|
+
class RubyMethod < SimpleDelegator
|
6
6
|
attr_reader :class_name, :static
|
7
7
|
|
8
8
|
def initialize(package, class_name, method, static)
|
9
|
+
super(method)
|
10
|
+
|
9
11
|
@package = package
|
10
12
|
@class_name = class_name
|
11
13
|
@method = method
|
@@ -111,7 +113,7 @@ module AppMap
|
|
111
113
|
@last_package_for_thread[Thread.current.object_id] = package if package
|
112
114
|
@events << event
|
113
115
|
static = event.static if event.respond_to?(:static)
|
114
|
-
|
116
|
+
record_method Trace::RubyMethod.new(package, defined_class, method, static) \
|
115
117
|
if package && defined_class && method && (event.event == :call)
|
116
118
|
end
|
117
119
|
|
data/lib/appmap/util.rb
CHANGED
@@ -4,6 +4,21 @@ require 'bundler'
|
|
4
4
|
|
5
5
|
module AppMap
|
6
6
|
module Util
|
7
|
+
# https://wynnnetherland.com/journal/a-stylesheet-author-s-guide-to-terminal-colors/
|
8
|
+
# Embed in a String to clear all previous ANSI sequences.
|
9
|
+
CLEAR = "\e[0m"
|
10
|
+
BOLD = "\e[1m"
|
11
|
+
|
12
|
+
# Colors
|
13
|
+
BLACK = "\e[30m"
|
14
|
+
RED = "\e[31m"
|
15
|
+
GREEN = "\e[32m"
|
16
|
+
YELLOW = "\e[33m"
|
17
|
+
BLUE = "\e[34m"
|
18
|
+
MAGENTA = "\e[35m"
|
19
|
+
CYAN = "\e[36m"
|
20
|
+
WHITE = "\e[37m"
|
21
|
+
|
7
22
|
class << self
|
8
23
|
# scenario_filename builds a suitable file name from a scenario name.
|
9
24
|
# Special characters are removed, and the file name is truncated to fit within
|
@@ -86,13 +101,13 @@ module AppMap
|
|
86
101
|
# Rack prepends HTTP_ to all client-sent headers.
|
87
102
|
matching_headers = env
|
88
103
|
.select { |k,v| k.start_with? 'HTTP_'}
|
89
|
-
.reject { |k,v|
|
104
|
+
.reject { |k,v| blank?(v) }
|
90
105
|
.each_with_object({}) do |kv, memo|
|
91
106
|
key = kv[0].sub(/^HTTP_/, '').split('_').map(&:capitalize).join('-')
|
92
107
|
value = kv[1]
|
93
108
|
memo[key] = value
|
94
109
|
end
|
95
|
-
|
110
|
+
blank?(matching_headers) ? nil : matching_headers
|
96
111
|
end
|
97
112
|
|
98
113
|
def normalize_path(path)
|
@@ -128,6 +143,41 @@ module AppMap
|
|
128
143
|
FileUtils.mv tempfile.path, filename
|
129
144
|
end
|
130
145
|
end
|
146
|
+
|
147
|
+
def color(text, color, bold: false)
|
148
|
+
color = Util.const_get(color.to_s.upcase) if color.is_a?(Symbol)
|
149
|
+
bold = bold ? BOLD : ""
|
150
|
+
"#{bold}#{color}#{text}#{CLEAR}"
|
151
|
+
end
|
152
|
+
|
153
|
+
def classify(word)
|
154
|
+
word.split(/[\-_]/).map(&:capitalize).join
|
155
|
+
end
|
156
|
+
|
157
|
+
def deep_dup(hash)
|
158
|
+
# This is a simple way to avoid the need for deep_dup from activesupport.
|
159
|
+
Marshal.load(Marshal.dump(hash))
|
160
|
+
end
|
161
|
+
|
162
|
+
def blank?(obj)
|
163
|
+
return true if obj.nil?
|
164
|
+
|
165
|
+
return true if obj.is_a?(String) && obj == ''
|
166
|
+
|
167
|
+
return true if obj.respond_to?(:length) && obj.length == 0
|
168
|
+
|
169
|
+
return true if obj.respond_to?(:size) && obj.size == 0
|
170
|
+
|
171
|
+
false
|
172
|
+
end
|
173
|
+
|
174
|
+
def startup_message(msg)
|
175
|
+
if defined?(::Rails) && defined?(::Rails.logger) && ::Rails.logger
|
176
|
+
::Rails.logger.debug msg
|
177
|
+
elsif ENV['DEBUG'] == 'true'
|
178
|
+
warn msg
|
179
|
+
end
|
180
|
+
end
|
131
181
|
end
|
132
182
|
end
|
133
183
|
end
|
data/lib/appmap/version.rb
CHANGED
data/package.json
CHANGED
@@ -1,12 +1,8 @@
|
|
1
1
|
{
|
2
|
-
"name": "appmap-ruby
|
2
|
+
"name": "appmap-ruby",
|
3
3
|
"version": "1.0.0",
|
4
|
-
"description": "
|
5
|
-
"scripts": {
|
6
|
-
"start": "./node_modules/applore/bin/lore serve"
|
7
|
-
},
|
4
|
+
"description": "AppMap client agent for Ruby",
|
8
5
|
"directories": {
|
9
|
-
"example": "examples",
|
10
6
|
"lib": "lib",
|
11
7
|
"test": "test"
|
12
8
|
},
|
@@ -14,11 +10,14 @@
|
|
14
10
|
"type": "git",
|
15
11
|
"url": "git+https://github.com/applandinc/appmap-ruby.git"
|
16
12
|
},
|
13
|
+
"author": "kgilpin@gmail.com",
|
14
|
+
"license": "MIT",
|
17
15
|
"bugs": {
|
18
16
|
"url": "https://github.com/applandinc/appmap-ruby/issues"
|
19
17
|
},
|
20
18
|
"homepage": "https://github.com/applandinc/appmap-ruby#readme",
|
21
19
|
"dependencies": {
|
22
|
-
"
|
20
|
+
"@appland/cli": "^1.1.0"
|
23
21
|
}
|
24
22
|
}
|
23
|
+
|
data/spec/config_spec.rb
CHANGED
@@ -55,4 +55,25 @@ describe AppMap::Config, docker: false do
|
|
55
55
|
|
56
56
|
expect(config.to_h.deep_stringify_keys!).to eq(config_expectation)
|
57
57
|
end
|
58
|
+
|
59
|
+
context do
|
60
|
+
let(:warnings) { @warnings ||= [] }
|
61
|
+
let(:warning) { warnings.join }
|
62
|
+
before do
|
63
|
+
expect(AppMap::Config).to receive(:warn).at_least(1) { |msg| warnings << msg }
|
64
|
+
end
|
65
|
+
it 'prints a warning and uses a default config' do
|
66
|
+
config = AppMap::Config.load_from_file 'no/such/file'
|
67
|
+
expect(config.to_h).to eq(YAML.load(<<~CONFIG))
|
68
|
+
:name: appmap-ruby
|
69
|
+
:packages:
|
70
|
+
- :path: lib
|
71
|
+
:handler_class: AppMap::Handler::Function
|
72
|
+
:shallow: false
|
73
|
+
:functions: []
|
74
|
+
:exclude: []
|
75
|
+
CONFIG
|
76
|
+
expect(warning).to include('NOTICE: The AppMap config file no/such/file was not found!')
|
77
|
+
end
|
78
|
+
end
|
58
79
|
end
|
@@ -4,9 +4,18 @@ ARG RUBY_VERSION
|
|
4
4
|
FROM appmap:${GEM_VERSION} as appmap
|
5
5
|
|
6
6
|
FROM ruby:${RUBY_VERSION}
|
7
|
+
|
8
|
+
SHELL ["/bin/bash", "-c"]
|
9
|
+
|
7
10
|
RUN apt-get update && apt-get install -y vim less
|
8
11
|
RUN apt-get install -y postgresql-client
|
9
12
|
|
13
|
+
RUN curl -fsSL https://fnm.vercel.app/install | bash \
|
14
|
+
&& source /root/.bashrc \
|
15
|
+
&& fnm install --lts \
|
16
|
+
&& echo 'fnm default $(fnm current)' >> ~/.bashrc \
|
17
|
+
&& ln -s $(which node) /usr/local/bin/node
|
18
|
+
|
10
19
|
RUN mkdir /app
|
11
20
|
WORKDIR /app
|
12
21
|
|
data/spec/hook_spec.rb
CHANGED
@@ -21,7 +21,7 @@ describe 'AppMap class Hooking', docker: false do
|
|
21
21
|
def invoke_test_file(file, setup: nil, &block)
|
22
22
|
AppMap.configuration = nil
|
23
23
|
package = AppMap::Config::Package.build_from_path(file)
|
24
|
-
config = AppMap::Config.new('hook_spec', [ package ])
|
24
|
+
config = AppMap::Config.new('hook_spec', packages: [ package ])
|
25
25
|
AppMap.configuration = config
|
26
26
|
tracer = nil
|
27
27
|
AppMap::Hook.new(config).enable do
|
@@ -57,7 +57,7 @@ describe 'AppMap class Hooking', docker: false do
|
|
57
57
|
it 'excludes named classes and methods' do
|
58
58
|
load 'spec/fixtures/hook/exclude.rb'
|
59
59
|
package = AppMap::Config::Package.build_from_path('spec/fixtures/hook/exclude.rb')
|
60
|
-
config = AppMap::Config.new('hook_spec', [ package ], exclude: %w[ExcludeTest])
|
60
|
+
config = AppMap::Config.new('hook_spec', packages: [ package ], exclude: %w[ExcludeTest])
|
61
61
|
AppMap.configuration = config
|
62
62
|
|
63
63
|
expect(config.never_hook?(ExcludeTest, ExcludeTest.new.method(:instance_method))).to be_truthy
|
@@ -4,6 +4,7 @@ describe 'Rails' do
|
|
4
4
|
%w[5 6].each do |rails_major_version| # rubocop:disable Metrics/BlockLength
|
5
5
|
context "#{rails_major_version}" do
|
6
6
|
include_context 'Rails app pg database', "spec/fixtures/rails#{rails_major_version}_users_app" unless use_existing_data?
|
7
|
+
include_context 'rails integration test setup'
|
7
8
|
|
8
9
|
def run_spec(spec_name)
|
9
10
|
cmd = <<~CMD.gsub "\n", ' '
|
@@ -13,24 +14,6 @@ describe 'Rails' do
|
|
13
14
|
run_cmd cmd, chdir: fixture_dir
|
14
15
|
end
|
15
16
|
|
16
|
-
def tmpdir
|
17
|
-
'tmp/spec/AbstractControllerBase'
|
18
|
-
end
|
19
|
-
|
20
|
-
unless use_existing_data?
|
21
|
-
before(:all) do
|
22
|
-
FileUtils.rm_rf tmpdir
|
23
|
-
FileUtils.mkdir_p tmpdir
|
24
|
-
run_spec 'spec/controllers/users_controller_spec.rb'
|
25
|
-
run_spec 'spec/controllers/users_controller_api_spec.rb'
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
let(:appmap) { JSON.parse File.read File.join tmpdir, 'appmap/rspec', appmap_json_file }
|
30
|
-
let(:appmap_json_path) { File.join(tmpdir, 'appmap/rspec', appmap_json_file) }
|
31
|
-
let(:appmap) { JSON.parse File.read(appmap_json_path) }
|
32
|
-
let(:events) { appmap['events'] }
|
33
|
-
|
34
17
|
describe 'an API route' do
|
35
18
|
describe 'creating an object' do
|
36
19
|
let(:appmap_json_file) do
|
@@ -240,7 +223,8 @@ describe 'Rails' do
|
|
240
223
|
'children' => include(hash_including(
|
241
224
|
'name' => 'ActionView',
|
242
225
|
'children' => include(hash_including(
|
243
|
-
|
226
|
+
# Rails 6/5 difference
|
227
|
+
'name' => /^(Template)?Renderer$/,
|
244
228
|
'children' => include(hash_including(
|
245
229
|
'name' => 'render',
|
246
230
|
'labels' => ['mvc.view']
|
@@ -253,4 +237,40 @@ describe 'Rails' do
|
|
253
237
|
end
|
254
238
|
end
|
255
239
|
end
|
240
|
+
|
241
|
+
describe 'with default appmap.yml' do
|
242
|
+
include_context 'Rails app pg database', "spec/fixtures/rails5_users_app" unless use_existing_data?
|
243
|
+
include_context 'rails integration test setup'
|
244
|
+
|
245
|
+
def run_spec(spec_name)
|
246
|
+
cmd = <<~CMD.gsub "\n", ' '
|
247
|
+
docker-compose run --rm -e RAILS_ENV=test -e APPMAP=true -e APPMAP_CONFIG_FILE=no/such/file
|
248
|
+
-v #{File.absolute_path tmpdir}:/app/tmp app ./bin/rspec #{spec_name}
|
249
|
+
CMD
|
250
|
+
run_cmd cmd, chdir: fixture_dir
|
251
|
+
end
|
252
|
+
|
253
|
+
let(:appmap_json_file) do
|
254
|
+
'Api_UsersController_POST_api_users_with_required_parameters_creates_a_user.appmap.json'
|
255
|
+
end
|
256
|
+
|
257
|
+
it 'http_server_request is recorded' do
|
258
|
+
expect(events).to include(
|
259
|
+
hash_including(
|
260
|
+
'http_server_request' => hash_including(
|
261
|
+
'request_method' => 'POST',
|
262
|
+
'path_info' => '/api/users'
|
263
|
+
)
|
264
|
+
)
|
265
|
+
)
|
266
|
+
end
|
267
|
+
|
268
|
+
it 'controller method is recorded' do
|
269
|
+
expect(events).to include hash_including(
|
270
|
+
'defined_class' => 'Api::UsersController',
|
271
|
+
'method_id' => 'build_user',
|
272
|
+
'path' => 'app/controllers/api/users_controller.rb',
|
273
|
+
)
|
274
|
+
end
|
275
|
+
end
|
256
276
|
end
|
data/spec/rails_spec_helper.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'spec_helper'
|
4
|
+
require 'active_support'
|
5
|
+
require 'active_support/core_ext'
|
4
6
|
require 'open3'
|
5
7
|
|
6
8
|
def wait_for_container(app_name)
|
@@ -58,3 +60,23 @@ shared_context 'Rails app pg database' do |fixture_dir|
|
|
58
60
|
end
|
59
61
|
end
|
60
62
|
end
|
63
|
+
|
64
|
+
shared_context 'rails integration test setup' do
|
65
|
+
def tmpdir
|
66
|
+
'tmp/spec/AbstractControllerBase'
|
67
|
+
end
|
68
|
+
|
69
|
+
unless use_existing_data?
|
70
|
+
before(:all) do
|
71
|
+
FileUtils.rm_rf tmpdir
|
72
|
+
FileUtils.mkdir_p tmpdir
|
73
|
+
run_spec 'spec/controllers/users_controller_spec.rb'
|
74
|
+
run_spec 'spec/controllers/users_controller_api_spec.rb'
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
let(:appmap) { JSON.parse File.read File.join tmpdir, 'appmap/rspec', appmap_json_file }
|
79
|
+
let(:appmap_json_path) { File.join(tmpdir, 'appmap/rspec', appmap_json_file) }
|
80
|
+
let(:appmap) { JSON.parse File.read(appmap_json_path) }
|
81
|
+
let(:events) { appmap['events'] }
|
82
|
+
end
|