marty 1.0.43 → 1.0.44
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/Gemfile.lock +1 -1
- data/app/components/marty/log_view.rb +16 -4
- data/app/models/marty/log.rb +8 -55
- data/db/migrate/303_create_marty_logs.rb +10 -0
- data/lib/marty/logger.rb +2 -5
- data/lib/marty/version.rb +1 -1
- data/spec/controllers/rpc_controller_spec.rb +15 -21
- data/spec/features/log_view_spec.rb +25 -30
- data/spec/lib/logger_spec.rb +25 -66
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 817a8f32d8a912597e8941cb60889a6f9e3723bc
|
4
|
+
data.tar.gz: cccaf421c0216156db7c106b69577f0e363c4c58
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 030c85527c4809aeb5a18634425c5dc5924534b7f8b3f44fc83189adccfc7724b87b97e06cdeac5c5004b1301c4f9745918bcb47c8004ac0b07492d6e2fcc2b3
|
7
|
+
data.tar.gz: dcbabb68d76e981e927eda2e9c8e9b3a31a4896ef66561f7bcaa1d553fc31f4b8e5d056b084b7199982e242f67938964ec59366b645588c7e266a97d9ea724aa
|
data/Gemfile.lock
CHANGED
@@ -11,7 +11,7 @@ class Marty::LogView < Marty::Grid
|
|
11
11
|
c.paging = :buffered
|
12
12
|
c.editing = :in_form
|
13
13
|
c.attributes = [
|
14
|
-
:
|
14
|
+
:timestamp_custom,
|
15
15
|
:message_type,
|
16
16
|
:message,
|
17
17
|
:details
|
@@ -26,7 +26,7 @@ class Marty::LogView < Marty::Grid
|
|
26
26
|
|
27
27
|
def default_form_items
|
28
28
|
[
|
29
|
-
:
|
29
|
+
:timestamp_custom,
|
30
30
|
:message_type,
|
31
31
|
:message,
|
32
32
|
textarea_field(:details).merge!({height: 400})
|
@@ -50,26 +50,38 @@ class Marty::LogView < Marty::Grid
|
|
50
50
|
c.read_only = true
|
51
51
|
end
|
52
52
|
|
53
|
-
|
53
|
+
# FIXME?: timestamp_custom is needed to display datetime data proprely
|
54
|
+
# UI does not interact well with AR/PG and doesn't display fractional s
|
55
|
+
# This work around requires explicit sorting/filtering
|
56
|
+
attribute :timestamp_custom do |c|
|
54
57
|
c.text = I18n.t("log_grid.timestamp")
|
55
58
|
c.width = 200
|
56
59
|
c.read_only = true
|
60
|
+
c.filterable = true
|
57
61
|
c.xtype = :datecolumn
|
58
62
|
c.format = 'Y-m-d h:i:s.u'
|
59
63
|
c.field_config = {
|
60
64
|
xtype: :displayfield,
|
61
65
|
}
|
62
66
|
c.getter = lambda { |r| Time.at(r.timestamp) }
|
67
|
+
c.sorting_scope = lambda {|r, dir| r.order("timestamp " + dir.to_s)}
|
68
|
+
|
69
|
+
# FIXME?: The UI AR/PG DateTime workaround requires the timestamp to be cast
|
70
|
+
# to text in order to compare filter input using the LIKE operator.
|
71
|
+
# Otherwise it will fail. '<' and '>' functionality is missing.
|
72
|
+
c.filter_with = lambda {|r, v, op|
|
73
|
+
r.where("timestamp::text #{op} '#{v}%'")}
|
63
74
|
end
|
64
75
|
|
65
76
|
column :details do |c|
|
66
|
-
c.getter = lambda { |r| CGI.escapeHTML(r.details) }
|
77
|
+
c.getter = lambda { |r| CGI.escapeHTML(r.details.pretty_inspect) }
|
67
78
|
end
|
68
79
|
|
69
80
|
attribute :details do |c|
|
70
81
|
c.text = I18n.t("log_grid.details")
|
71
82
|
c.width = 900
|
72
83
|
c.read_only = true
|
84
|
+
c.getter = lambda { |r| r.details.to_s}
|
73
85
|
end
|
74
86
|
end
|
75
87
|
|
data/app/models/marty/log.rb
CHANGED
@@ -1,69 +1,22 @@
|
|
1
1
|
class Marty::Log < Marty::Base
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
end
|
6
|
-
|
7
|
-
establish_connection({
|
8
|
-
adapter: "sqlite3",
|
9
|
-
database: logfile
|
10
|
-
})
|
11
|
-
self.table_name = "log"
|
12
|
-
self.primary_key = "id"
|
13
|
-
|
14
|
-
def self.db_init
|
15
|
-
db = SQLite3::Database.new(Marty::Log.logfile)
|
16
|
-
db.execute <<-SQL
|
17
|
-
CREATE TABLE IF NOT EXISTS log (
|
18
|
-
id INTEGER PRIMARY KEY,
|
19
|
-
message_type TEXT,
|
20
|
-
message TEXT,
|
21
|
-
timestamp REAL,
|
22
|
-
details BLOB )
|
23
|
-
SQL
|
24
|
-
db
|
25
|
-
end
|
2
|
+
# establish_connection creates a connection using the connection pool
|
3
|
+
# based on the current AR connection (i.e. duplicates AR connection)
|
4
|
+
establish_connection
|
26
5
|
|
27
6
|
def self.write_log(type, message, details)
|
28
7
|
begin
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
SQL
|
34
|
-
stmt.bind_param(1, type.to_s)
|
35
|
-
stmt.bind_param(2, message)
|
36
|
-
stmt.bind_param(3, Time.zone.now.to_f)
|
37
|
-
stmt.bind_param(4, details.pretty_inspect)
|
38
|
-
|
39
|
-
sent = false
|
40
|
-
retries = 3
|
41
|
-
delay = 0.1
|
42
|
-
until sent
|
43
|
-
begin
|
44
|
-
stmt.execute
|
45
|
-
sent = true
|
46
|
-
rescue SQLite3::BusyException
|
47
|
-
raise if retries == 0
|
48
|
-
retries -= 1
|
49
|
-
sleep delay
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
8
|
+
create!(message_type: type,
|
9
|
+
message: message,
|
10
|
+
details: details,
|
11
|
+
timestamp: Time.zone.now)
|
53
12
|
rescue => e
|
54
13
|
Marty::Util.logger.error("Marty::Logger failure: #{e.message}")
|
55
|
-
ensure
|
56
|
-
stmt.close if stmt rescue nil
|
57
14
|
end
|
58
15
|
end
|
59
16
|
|
60
17
|
def self.cleanup(days_to_keep)
|
61
18
|
raise "Must give numeric value. (Got '#{days_to_keep}')" unless
|
62
19
|
(Float(days_to_keep) rescue false)
|
63
|
-
|
64
|
-
cutoff = Time.zone.now.to_i - days_to_keep.to_i*60*60*24
|
65
|
-
@db.execute <<-SQL
|
66
|
-
delete from log where timestamp <= #{cutoff}
|
67
|
-
SQL
|
20
|
+
where("timestamp <= ?", Time.zone.now - days_to_keep.to_i.days).delete_all
|
68
21
|
end
|
69
22
|
end
|
data/lib/marty/logger.rb
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'sqlite3'
|
2
|
-
|
3
1
|
class Marty::Logger
|
4
2
|
|
5
3
|
def self.method_missing(m, *args, &block)
|
@@ -17,10 +15,9 @@ class Marty::Logger
|
|
17
15
|
begin
|
18
16
|
yield
|
19
17
|
rescue => e
|
20
|
-
error(error_message, { message
|
21
|
-
data
|
18
|
+
error(error_message, { "message" => e.message,
|
19
|
+
"data" => error_data})
|
22
20
|
raise "#{error_message}: #{e.message}"
|
23
21
|
end
|
24
22
|
end
|
25
|
-
|
26
23
|
end
|
data/lib/marty/version.rb
CHANGED
@@ -353,6 +353,10 @@ describe Marty::RpcController do
|
|
353
353
|
@data_json = [@data].to_json
|
354
354
|
}
|
355
355
|
|
356
|
+
after(:each) do
|
357
|
+
Marty::Log.delete_all
|
358
|
+
end
|
359
|
+
|
356
360
|
let(:t1) { @t1 }
|
357
361
|
let(:t2) { @t2 }
|
358
362
|
let(:p0) { @p0 }
|
@@ -716,15 +720,6 @@ describe Marty::RpcController do
|
|
716
720
|
end
|
717
721
|
|
718
722
|
context "output_validation" do
|
719
|
-
before(:all) do
|
720
|
-
@db = SQLite3::Database.new(Marty::Log.logfile)
|
721
|
-
end
|
722
|
-
before(:each) do
|
723
|
-
@logid = @db.execute('select max(id) from log').first.first || 0 rescue 0
|
724
|
-
end
|
725
|
-
after(:all) do
|
726
|
-
@db.close
|
727
|
-
end
|
728
723
|
it "validates output" do
|
729
724
|
Marty::ApiConfig.create!(script: "M4",
|
730
725
|
node: "A",
|
@@ -744,7 +739,7 @@ describe Marty::RpcController do
|
|
744
739
|
res_hash = JSON.parse(response.body)
|
745
740
|
expect(res_hash).to eq([135,291,[{"a"=>132,"b"=>456},
|
746
741
|
{"a"=>789,"b"=>132}]])
|
747
|
-
logs = Marty::Log.
|
742
|
+
logs = Marty::Log.all
|
748
743
|
expect(logs.count).to eq(0)
|
749
744
|
end
|
750
745
|
|
@@ -781,20 +776,19 @@ describe Marty::RpcController do
|
|
781
776
|
expect(res_hash[0]["error"]).to include(expect1)
|
782
777
|
expect(res_hash[0]["error"]).to include(expect2)
|
783
778
|
|
784
|
-
logs = Marty::Log.
|
779
|
+
logs = Marty::Log.all
|
785
780
|
expect(logs.count).to eq(2)
|
786
781
|
expect(logs[0].message).to eq("API M5:A.result")
|
787
782
|
expect(logs[1].message).to eq("API M5:A.result2")
|
788
783
|
logs.each do |ml|
|
789
|
-
expect(ml.details).to include(expect1)
|
790
|
-
expect(ml.details).to include(expect2)
|
791
|
-
expect(ml.details).to
|
792
|
-
|
784
|
+
expect(ml.details["error"].join).to include(expect1)
|
785
|
+
expect(ml.details["error"].join).to include(expect2)
|
786
|
+
expect(ml.details["data"]).to eq([{"a"=>"str", "b"=>456},
|
787
|
+
{"a"=>789, "b"=>"str"}])
|
793
788
|
end
|
794
789
|
end
|
795
790
|
|
796
791
|
it "validates output (missing item)" do
|
797
|
-
logid = @db.execute('select max(id) from log').first.first rescue 0
|
798
792
|
Marty::ApiConfig.create!(script: "M9",
|
799
793
|
node: "A",
|
800
794
|
attr: nil,
|
@@ -818,13 +812,13 @@ describe Marty::RpcController do
|
|
818
812
|
expect(res_hash[0]["error"]).to include(expect1)
|
819
813
|
expect(res_hash[0]["error"]).to include(expect2)
|
820
814
|
|
821
|
-
logs = Marty::Log.
|
815
|
+
logs = Marty::Log.all
|
822
816
|
expect(logs.count).to eq(1)
|
823
817
|
expect(logs[0].message).to eq("API M9:A.result")
|
824
|
-
expect(logs[0].details).to include(expect1)
|
825
|
-
expect(logs[0].details).to include(expect2)
|
826
|
-
expect(logs[0].details).to
|
827
|
-
|
818
|
+
expect(logs[0].details["error"].join).to include(expect1)
|
819
|
+
expect(logs[0].details["error"].join).to include(expect2)
|
820
|
+
expect(logs[0].details["data"]).to eq([{"a"=>1, "b"=>123},
|
821
|
+
{"a"=>789, "b"=>123}])
|
828
822
|
end
|
829
823
|
end
|
830
824
|
|
@@ -2,23 +2,9 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
feature 'logger view', js: true, capybara: true do
|
4
4
|
|
5
|
-
def manual_insert(type, message, ts, detail)
|
6
|
-
stmt = @db.prepare <<-SQL
|
7
|
-
INSERT INTO log (message_type, message, timestamp, details)
|
8
|
-
VALUES (?, ?, ?, ?)
|
9
|
-
SQL
|
10
|
-
stmt.bind_param(1, type)
|
11
|
-
stmt.bind_param(2, message)
|
12
|
-
stmt.bind_param(3, ts)
|
13
|
-
stmt.bind_param(4, detail)
|
14
|
-
stmt.execute
|
15
|
-
stmt.close
|
16
|
-
end
|
17
|
-
|
18
5
|
before(:all) do
|
19
6
|
self.use_transactional_fixtures = false
|
20
|
-
|
21
|
-
@db.execute("delete from log")
|
7
|
+
Marty::Log.delete_all
|
22
8
|
|
23
9
|
info_s = { info: 'message' }
|
24
10
|
error_s = [1, 2, 3, { error: 'message' }]
|
@@ -26,13 +12,20 @@ feature 'logger view', js: true, capybara: true do
|
|
26
12
|
Marty::Logger.info('info message', nil)
|
27
13
|
Marty::Logger.error('error message', error_s)
|
28
14
|
Marty::Logger.fatal('fatal message', fatal_s)
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
15
|
+
|
16
|
+
Marty::Log.create!(message_type: "debug",
|
17
|
+
message: "hi mom",
|
18
|
+
details: ["one", "two", 3, 4.0],
|
19
|
+
timestamp: Time.zone.now - 5.days)
|
20
|
+
|
21
|
+
Marty::Log.create!(message_type: "warn",
|
22
|
+
message: "all your base",
|
23
|
+
details: [5],
|
24
|
+
timestamp: Time.zone.now - 10.days)
|
25
|
+
|
26
|
+
@ts = Marty::Log.select(:timestamp).order(timestamp: :desc).map do
|
34
27
|
|(ts)|
|
35
|
-
Time.zone.at(ts).strftime('%Y-%m-%dT%H:%M:%S.%L%:z')
|
28
|
+
Time.zone.at(ts[:timestamp]).strftime('%Y-%m-%dT%H:%M:%S.%L%:z')
|
36
29
|
end
|
37
30
|
|
38
31
|
@clean_file = "/tmp/clean_#{Process.pid}.psql"
|
@@ -42,8 +35,7 @@ feature 'logger view', js: true, capybara: true do
|
|
42
35
|
|
43
36
|
after(:all) do
|
44
37
|
restore_clean_db(@clean_file)
|
45
|
-
|
46
|
-
@db.close
|
38
|
+
Marty::Log.delete_all
|
47
39
|
self.use_transactional_fixtures = true
|
48
40
|
end
|
49
41
|
|
@@ -54,16 +46,18 @@ feature 'logger view', js: true, capybara: true do
|
|
54
46
|
show_submenu('Log Maintenance')
|
55
47
|
press('View Log')
|
56
48
|
wait_for_ready
|
49
|
+
|
57
50
|
exp_types = ["fatal", "error", "info", "debug", "warn"]
|
58
51
|
exp_messages = ["fatal message", "error message",
|
59
52
|
"info message", "hi mom", "all your base"]
|
60
|
-
exp_details = [ "[\"string\", 123, {
|
61
|
-
"
|
62
|
-
"[1, 2, 3, {
|
53
|
+
exp_details = [ "[\"string\", 123, {\"fatal\"=>\"message\", "\
|
54
|
+
"\"another_key\"=>\"value\"}]\n",
|
55
|
+
"[1, 2, 3, {\"error\"=>\"message\"}]\n",
|
63
56
|
"nil\n",
|
64
57
|
"[\"one\", \"two\", 3, 4.0]\n",
|
65
|
-
"[5]\n"
|
66
|
-
|
58
|
+
"[5]\n"
|
59
|
+
]
|
60
|
+
[[nil, 5], [7, 4], [3, 3], [0, 0]].each do |days, exp_count|
|
67
61
|
if days
|
68
62
|
press('System')
|
69
63
|
show_submenu('Log Maintenance')
|
@@ -75,13 +69,14 @@ feature 'logger view', js: true, capybara: true do
|
|
75
69
|
find(:refresh).click
|
76
70
|
wait_for_ready
|
77
71
|
end
|
72
|
+
|
78
73
|
cnt = logview.row_count()
|
79
74
|
expect(cnt).to eq(exp_count)
|
80
75
|
types = logview.col_values('message_type', cnt, 0)
|
81
76
|
messages = logview.col_values('message', cnt, 0)
|
82
77
|
details = logview.col_values('details', cnt, 0).
|
83
|
-
|
84
|
-
ts = logview.col_values('
|
78
|
+
map { |d| CGI.unescapeHTML(d) }
|
79
|
+
ts = logview.col_values('timestamp_custom', cnt, 0)
|
85
80
|
expect(ts).to eq(@ts.slice(0,exp_count))
|
86
81
|
expect(types).to eq(exp_types.slice(0,exp_count))
|
87
82
|
expect(messages).to eq(exp_messages.slice(0,exp_count))
|
data/spec/lib/logger_spec.rb
CHANGED
@@ -7,48 +7,36 @@ module Marty
|
|
7
7
|
self.use_transactional_fixtures = false
|
8
8
|
end
|
9
9
|
before(:each) do
|
10
|
-
|
11
|
-
@db.execute "delete from log"
|
12
|
-
end
|
13
|
-
after(:each) do
|
14
|
-
@db.close
|
10
|
+
Marty::Log.delete_all
|
15
11
|
end
|
16
12
|
after(:all) do
|
17
13
|
self.use_transactional_fixtures = true
|
18
14
|
end
|
19
15
|
|
16
|
+
it "log has its own connection" do
|
17
|
+
expect(Marty::Log.connection).not_to equal(Marty::Posting.connection)
|
18
|
+
expect(Marty::Posting.connection).to equal(Marty::Script.connection)
|
19
|
+
end
|
20
|
+
|
20
21
|
it "logs" do
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
log_ts[id] = ts
|
37
|
-
end
|
38
|
-
expect(rails_log).to eq(["info message\n",
|
39
|
-
"error message\n",
|
40
|
-
"fatal message\n"])
|
41
|
-
expect(log_detail[1]).to eq(["info", "info message",
|
42
|
-
info_s.pretty_inspect])
|
43
|
-
expect(log_detail[2]).to eq(["error", "error message",
|
44
|
-
error_s.pretty_inspect])
|
45
|
-
expect(log_detail[3]).to eq(["fatal", "fatal message",
|
46
|
-
fatal_s.pretty_inspect])
|
47
|
-
(1..3).each do |idx|
|
48
|
-
expect(log_ts[idx]).to be_within(5).of(Time.zone.now.to_i)
|
49
|
-
end
|
22
|
+
info_s = {'info' => 'message'}
|
23
|
+
error_s = [1, 2, 3, {'error' =>'message'}]
|
24
|
+
fatal_s = ["string", 123, {'fatal' => "message",
|
25
|
+
'another_key' => 'value'}]
|
26
|
+
Marty::Logger.info('info message', info_s)
|
27
|
+
Marty::Logger.error('error message', error_s)
|
28
|
+
Marty::Logger.fatal('fatal message', fatal_s)
|
29
|
+
log = Marty::Log.all
|
30
|
+
log_detail = log.map{|l| [l[:message_type], l[:message], l[:details]]}
|
31
|
+
log_ts = log.map{|l| l[:timestamp]}
|
32
|
+
expect(log_detail[0]).to eq(["info", "info message", info_s])
|
33
|
+
expect(log_detail[1]).to eq(["error", "error message", error_s])
|
34
|
+
expect(log_detail[2]).to eq(["fatal", "fatal message", fatal_s])
|
35
|
+
log_ts.each do |ts|
|
36
|
+
expect(ts.to_i).to be_within(5).of(Time.zone.now.to_i)
|
50
37
|
end
|
51
38
|
end
|
39
|
+
|
52
40
|
it "with_logging" do
|
53
41
|
bd = 'block description'
|
54
42
|
the_error = 'error during my block'
|
@@ -64,40 +52,11 @@ module Marty
|
|
64
52
|
log = Marty::Log.first
|
65
53
|
expect(log.message_type).to eq('error')
|
66
54
|
expect(log.message).to eq(bd)
|
67
|
-
expect(log.details).to eq({ message
|
68
|
-
data
|
69
|
-
end
|
70
|
-
end
|
71
|
-
describe "Logger errors" do
|
72
|
-
it "fails gracefully" do
|
73
|
-
allow(Marty::Log).to receive(:db_init).
|
74
|
-
and_raise("Error initializing DB")
|
75
|
-
Marty::Log.instance_variable_set(:@db, nil)
|
76
|
-
File.open(Rails.root.join("log/test.log")) do |f|
|
77
|
-
f.seek(0, IO::SEEK_END)
|
78
|
-
expect{Marty::Logger.info('info message', [1,2,3])}.not_to raise_error
|
79
|
-
rails_log = f.readlines
|
80
|
-
expect(rails_log).to eq(["info message\n",
|
81
|
-
"Marty::Logger failure: Error initializing DB\n"])
|
82
|
-
end
|
83
|
-
end
|
84
|
-
it "fails gracefully in ensure" do
|
85
|
-
Marty::Logger.info('init db', [])
|
86
|
-
close_err = 'Error closing statement'
|
87
|
-
allow_any_instance_of(SQLite3::Statement).to receive(:close).
|
88
|
-
and_raise(close_err)
|
89
|
-
File.open(Rails.root.join("log/test.log")) do |f|
|
90
|
-
f.seek(0, IO::SEEK_END)
|
91
|
-
expect{Marty::Logger.info('ensure message', [1,2,3])}.not_to raise_error
|
92
|
-
rails_log = f.readlines
|
93
|
-
expect(rails_log).to eq(["ensure message\n"])
|
94
|
-
allow_any_instance_of(SQLite3::Statement).to receive(:close).
|
95
|
-
and_call_original
|
96
|
-
sleep 1
|
97
|
-
Marty::Log.cleanup(0)
|
98
|
-
end
|
55
|
+
expect(log.details).to eq({ "message" => the_error,
|
56
|
+
"data" => JSON.parse(data.to_json)})
|
99
57
|
end
|
100
58
|
end
|
59
|
+
|
101
60
|
describe "Exercise" do
|
102
61
|
before(:all) do
|
103
62
|
@clean_file = "/tmp/clean_#{Process.pid}.psql"
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: marty
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.44
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Arman Bostani
|
@@ -14,7 +14,7 @@ authors:
|
|
14
14
|
autorequire:
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
|
-
date: 2017-10-
|
17
|
+
date: 2017-10-11 00:00:00.000000000 Z
|
18
18
|
dependencies:
|
19
19
|
- !ruby/object:Gem::Dependency
|
20
20
|
name: pg
|
@@ -477,6 +477,7 @@ files:
|
|
477
477
|
- db/migrate/300_create_marty_api_configs.rb
|
478
478
|
- db/migrate/301_create_marty_api_log.rb
|
479
479
|
- db/migrate/302_add_api_configs_validate_result.rb
|
480
|
+
- db/migrate/303_create_marty_logs.rb
|
480
481
|
- db/seeds.rb
|
481
482
|
- delorean/script_report.dl
|
482
483
|
- gemini_deprecations.md
|