marty 1.0.43 → 1.0.44
Sign up to get free protection for your applications and to get access to all the features.
- 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
|