marty 5.2.0 → 6.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitlab-ci.yml +1 -1
- data/.rubocop_todo.yml +62 -77
- data/Dockerfile.dummy +1 -1
- data/app/assets/javascripts/marty/extjs/extensions/marty.js +11 -0
- data/app/components/marty/log_view.rb +1 -1
- data/app/components/marty/promise_view.rb +0 -3
- data/app/jobs/marty/remove_old_promises_job.rb +7 -0
- data/app/models/marty/data_grid.rb +0 -163
- data/app/models/marty/event.rb +2 -2
- data/app/models/marty/posting.rb +0 -7
- data/app/models/marty/promise.rb +1 -1
- data/app/services/marty/data_grid_view/save_grid.rb +2 -2
- data/app/services/marty/jobs/schedule.rb +5 -0
- data/db/migrate/510_schedule_job_to_remove_old_promises.rb +19 -0
- data/lib/marty.rb +8 -0
- data/{other → lib}/marty/api/base.rb +14 -17
- data/{other → lib}/marty/diagnostic/aws/ec2_instance.rb +2 -2
- data/{other → lib}/marty/diagnostic/aws/error.rb +0 -0
- data/{other → lib}/marty/diagnostic/base.rb +0 -0
- data/{other → lib}/marty/diagnostic/collection.rb +0 -0
- data/{other → lib}/marty/diagnostic/connections.rb +0 -0
- data/{other → lib}/marty/diagnostic/database.rb +0 -0
- data/{other → lib}/marty/diagnostic/delayed_job_version.rb +0 -0
- data/{other → lib}/marty/diagnostic/delayed_job_workers.rb +0 -0
- data/{other → lib}/marty/diagnostic/environment_variables.rb +0 -0
- data/{other → lib}/marty/diagnostic/fatal.rb +0 -0
- data/{other → lib}/marty/diagnostic/node.rb +0 -0
- data/{other → lib}/marty/diagnostic/nodes.rb +1 -2
- data/{other → lib}/marty/diagnostic/packer.rb +0 -0
- data/{other → lib}/marty/diagnostic/reporter.rb +2 -2
- data/{other → lib}/marty/diagnostic/request.rb +0 -0
- data/{other → lib}/marty/diagnostic/scheduled_jobs.rb +0 -0
- data/{other → lib}/marty/diagnostic/version.rb +0 -0
- data/lib/marty/engine.rb +5 -0
- data/lib/marty/version.rb +3 -1
- data/lib/marty/xl.rb +5 -5
- data/spec/dummy/app/assets/config/manifest.js +0 -0
- data/spec/dummy/app/jobs/test2_job.rb +4 -0
- data/spec/dummy/config/application.rb +1 -4
- data/spec/dummy/config/initializers/assets.rb +1 -1
- data/spec/features/extjs_spec.rb +58 -0
- data/spec/features/schedule_jobs_dashboard_spec.rb +9 -9
- data/spec/lib/logger_spec.rb +2 -2
- data/spec/models/posting_spec.rb +0 -13
- data/spec/services/jobs/schedule_spec.rb +40 -0
- data/spec/support/chromedriver.rb +2 -0
- metadata +27 -22
- data/spec/dummy/app/jobs/test_job2.rb +0 -4
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
@@ -34,8 +34,7 @@ module Marty::Diagnostic; class Nodes < Base
|
|
34
34
|
'shutting_down' => error_if(instances.shutting_down),
|
35
35
|
'terminated' => error_if(instances.terminated),
|
36
36
|
'stopping' => error_if(instances.stopping),
|
37
|
-
'stopped' => error_if(instances.stopped),
|
38
|
-
}.delete_if { |_k, v| v.empty? }
|
37
|
+
'stopped' => error_if(instances.stopped), }.delete_if { |_k, v| v.empty? }
|
39
38
|
rescue StandardError => e
|
40
39
|
error(e.message)
|
41
40
|
end
|
File without changes
|
@@ -9,7 +9,7 @@ module Marty::Diagnostic; class Reporter < Request
|
|
9
9
|
self.request = request
|
10
10
|
|
11
11
|
ops = op.split(/,\s*/).uniq - [unresolve_diagnostic(self)]
|
12
|
-
reps = ops.select { |o| reports.
|
12
|
+
reps = ops.select { |o| reports.key?(o) }
|
13
13
|
|
14
14
|
self.diagnostics = ((ops - reps) + reps.map { |r| reports[r] }.flatten).uniq.
|
15
15
|
map { |d| resolve_diagnostic(d) }
|
@@ -26,7 +26,7 @@ module Marty::Diagnostic; class Reporter < Request
|
|
26
26
|
klass = (n + '::Diagnostic::' + diag_name).constantize rescue nil
|
27
27
|
break if klass
|
28
28
|
end
|
29
|
-
raise NameError
|
29
|
+
raise NameError, "#{diag_name} could not be resolved by #{name}" if
|
30
30
|
klass.nil?
|
31
31
|
|
32
32
|
klass
|
File without changes
|
File without changes
|
File without changes
|
data/lib/marty/engine.rb
CHANGED
data/lib/marty/version.rb
CHANGED
data/lib/marty/xl.rb
CHANGED
@@ -190,7 +190,7 @@ class Marty::Xl
|
|
190
190
|
|
191
191
|
# counter == col0 == (colw - 1) => merge the edges:
|
192
192
|
a = boxborders[edge_h[r.object_id][1].to_sym] =
|
193
|
-
|
193
|
+
merge_cell_edges(a, deep_copy(boxborders[edge_h[r.object_id][1].to_sym])) if
|
194
194
|
counter == (colw - 1)
|
195
195
|
|
196
196
|
a = boxborders[edge_h[r.object_id][2].to_sym] unless
|
@@ -368,11 +368,11 @@ class Marty::Xl
|
|
368
368
|
[d[0], d[1], d[2].select { |inner| inner if inner[0] != 'pos' }]
|
369
369
|
end
|
370
370
|
new_ops = new_ops1 + new_ops2
|
371
|
-
count = new_ops.
|
372
|
-
d[2].
|
371
|
+
count = new_ops.count do |d|
|
372
|
+
d[2].count do |inner_ops|
|
373
373
|
inner_ops if inner_ops[0] == 'pos'
|
374
|
-
end
|
375
|
-
end
|
374
|
+
end > 0
|
375
|
+
end
|
376
376
|
|
377
377
|
count == 0 ? new_ops.sort : recalc_offsets(new_ops)
|
378
378
|
end
|
File without changes
|
@@ -46,10 +46,7 @@ module Dummy
|
|
46
46
|
config.active_support.escape_html_entities_in_json = true
|
47
47
|
|
48
48
|
# eager load paths instead of autoload paths
|
49
|
-
config.eager_load_paths
|
50
|
-
|dir|
|
51
|
-
File.expand_path("../../#{dir}", __FILE__)
|
52
|
-
end
|
49
|
+
config.eager_load_paths << File.expand_path("../../lib", __FILE__)
|
53
50
|
|
54
51
|
# Use SQL instead of Active Record's schema dumper when creating the database.
|
55
52
|
# This is necessary if your schema can't be completely dumped by the schema dumper,
|
@@ -1 +1 @@
|
|
1
|
-
Rails.application.config.assets.precompile
|
1
|
+
Rails.application.config.assets.precompile += ['*.js', '*.css']
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
feature 'on grid cells', js: true do
|
4
|
+
def go_to_user_view
|
5
|
+
press('System')
|
6
|
+
press('User Management')
|
7
|
+
wait_for_ajax
|
8
|
+
end
|
9
|
+
|
10
|
+
def go_to_data_grid_view
|
11
|
+
find(:xpath, "//a[contains(., 'Applications')]").click
|
12
|
+
press('Data Grids')
|
13
|
+
wait_for_ajax
|
14
|
+
end
|
15
|
+
|
16
|
+
let(:uv) { netzke_find('user_view') }
|
17
|
+
|
18
|
+
before do
|
19
|
+
Mcfly.whodunnit = Marty::User.find_by_login('marty')
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'grid cells should encode html' do
|
23
|
+
log_in_as('marty')
|
24
|
+
go_to_user_view
|
25
|
+
|
26
|
+
by 'add user with html in field' do
|
27
|
+
wait_for_ajax
|
28
|
+
press('New User')
|
29
|
+
|
30
|
+
within(:gridpanel, 'add_window', match: :first) do
|
31
|
+
fill_in('Login', with: 'extjs_test_login')
|
32
|
+
fill_in('First Name', with: 'test_fname')
|
33
|
+
fill_in('Last Name',
|
34
|
+
with:
|
35
|
+
"<b class='test class' onclick='alert()'>test text</b>"
|
36
|
+
)
|
37
|
+
press 'Ok'
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
and_by 'check html rendering' do
|
42
|
+
# negative test
|
43
|
+
expect do
|
44
|
+
find(:xpath, "//b[contains(@class, 'test class')]")
|
45
|
+
end.to raise_error(Capybara::ElementNotFound)
|
46
|
+
|
47
|
+
# positive test
|
48
|
+
find('tr', text: 'extjs_test_login').should have_content(
|
49
|
+
"<b class='test class' onclick='alert()'>test text</b>"
|
50
|
+
)
|
51
|
+
|
52
|
+
# positive test
|
53
|
+
find(:xpath, "//td[contains(., 'test text')]").click
|
54
|
+
go_to_data_grid_view
|
55
|
+
expect(URI.parse(current_url).fragment).to eq 'data_grid_user_view'
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -26,7 +26,7 @@ feature 'Schedule Jobs Dashboard', js: true do
|
|
26
26
|
|
27
27
|
let!(:schedule) do
|
28
28
|
Marty::BackgroundJob::Schedule.create(
|
29
|
-
job_class: '
|
29
|
+
job_class: 'Test2Job',
|
30
30
|
cron: '0 0 * * *',
|
31
31
|
state: 'on'
|
32
32
|
).tap do |job|
|
@@ -38,7 +38,7 @@ feature 'Schedule Jobs Dashboard', js: true do
|
|
38
38
|
end
|
39
39
|
|
40
40
|
before do
|
41
|
-
expect(
|
41
|
+
expect(Test2Job.scheduled?).to be true
|
42
42
|
|
43
43
|
log_in_as('admin1')
|
44
44
|
wait_for_ajax
|
@@ -72,34 +72,34 @@ feature 'Schedule Jobs Dashboard', js: true do
|
|
72
72
|
expect(TestJob.scheduled?).to be true
|
73
73
|
expect(TestJob.delayed_job.cron).to eq '1 1 * * *'
|
74
74
|
|
75
|
-
expect(
|
76
|
-
expect(
|
75
|
+
expect(Test2Job.scheduled?).to be true
|
76
|
+
expect(Test2Job.delayed_job.cron).to eq '0 0 * * *'
|
77
77
|
end
|
78
78
|
|
79
79
|
it 'deletes schedule' do
|
80
|
-
find('.x-grid-item', text: '
|
80
|
+
find('.x-grid-item', text: 'Test2Job').click
|
81
81
|
press 'Delete'
|
82
82
|
press 'Yes'
|
83
83
|
|
84
84
|
wait_for_ajax
|
85
85
|
|
86
|
-
expect(
|
86
|
+
expect(Test2Job.scheduled?).to be false
|
87
87
|
end
|
88
88
|
|
89
89
|
it 'turns the schedule off' do
|
90
|
-
find('.x-grid-item', text: '
|
90
|
+
find('.x-grid-item', text: 'Test2Job').click
|
91
91
|
press 'Edit'
|
92
92
|
fill_in('state', with: 'off')
|
93
93
|
|
94
94
|
press 'OK'
|
95
95
|
wait_for_ajax
|
96
|
-
expect(
|
96
|
+
expect(Test2Job.scheduled?).to be false
|
97
97
|
end
|
98
98
|
|
99
99
|
it 'shows validation errors' do
|
100
100
|
press('Add')
|
101
101
|
|
102
|
-
fill_in('Job class', with: '
|
102
|
+
fill_in('Job class', with: 'Test2Job')
|
103
103
|
fill_in('state', with: 'on')
|
104
104
|
fill_in('cron', with: '1')
|
105
105
|
|
data/spec/lib/logger_spec.rb
CHANGED
@@ -101,9 +101,9 @@ module Marty
|
|
101
101
|
line_count = File.readlines('/tmp/logaction.txt').count
|
102
102
|
|
103
103
|
log_count = Marty::Log.all.count
|
104
|
-
failed_count = f.readlines.
|
104
|
+
failed_count = f.readlines.count do |l|
|
105
105
|
l == "Marty::Logger failure: database is locked\n"
|
106
|
-
end
|
106
|
+
end
|
107
107
|
|
108
108
|
expect(Marty::Promise.where.not(result: {}).count).to eq 1000
|
109
109
|
|
data/spec/models/posting_spec.rb
CHANGED
@@ -27,19 +27,6 @@ module Marty
|
|
27
27
|
expect(Posting.lookup_dt('NOW')).to eq Float::INFINITY
|
28
28
|
end
|
29
29
|
|
30
|
-
describe '.get_latest' do
|
31
|
-
it 'provide a list of latest of postings in descending order' do
|
32
|
-
4.times do |d|
|
33
|
-
Posting.do_create('BASE', d.day.from_now, 'a comment')
|
34
|
-
end
|
35
|
-
dt3 = 3.day.from_now
|
36
|
-
|
37
|
-
latest = Posting.get_latest(1)
|
38
|
-
expect(latest.count).to eq 1
|
39
|
-
expect(latest[0].name).to match /BASE-#{dt3.strftime("%Y%m%d-%H%M")}/
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
30
|
describe '.get_latest_by_type' do
|
44
31
|
context 'when invalid parameters are supplied' do
|
45
32
|
it "raises 'posting type list missing' error" do
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Marty
|
4
|
+
describe Jobs::Schedule do
|
5
|
+
let!(:schedule) do
|
6
|
+
Marty::BackgroundJob::Schedule.create!(
|
7
|
+
job_class: 'TestJob',
|
8
|
+
cron: '0 0 * * *',
|
9
|
+
state: 'on'
|
10
|
+
)
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'schedules jobs' do
|
14
|
+
expect(TestJob).to_not be_scheduled
|
15
|
+
described_class.call
|
16
|
+
expect(TestJob).to be_scheduled
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'deletes previously scheduled jobs' do
|
20
|
+
described_class.call
|
21
|
+
expect(TestJob).to be_scheduled
|
22
|
+
schedule.destroy!
|
23
|
+
|
24
|
+
non_cron_job = Delayed::Job.create!(handler: 'Non cron job')
|
25
|
+
|
26
|
+
dj = Delayed::Job.last
|
27
|
+
dj.handler = dj.handler.gsub('TestJob', 'WrongTestJob')
|
28
|
+
dj.save!
|
29
|
+
|
30
|
+
described_class.call
|
31
|
+
|
32
|
+
expect(TestJob).to_not be_scheduled
|
33
|
+
any_old_scheduled_jobs = Delayed::Job.where('handler ILIKE ?', '%WrongTestJob%').any?
|
34
|
+
expect(any_old_scheduled_jobs).to be false
|
35
|
+
|
36
|
+
non_cron_job = Delayed::Job.find_by(handler: 'Non cron job')
|
37
|
+
expect(non_cron_job).to be_present
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -13,6 +13,8 @@ module Marty; module RSpec; module Chromedriver
|
|
13
13
|
}
|
14
14
|
|
15
15
|
options = ::Selenium::WebDriver::Chrome::Options.new
|
16
|
+
options.add_preference(:download, default_directory:
|
17
|
+
Marty::RSpec::DownloadHelper::PATH.to_s)
|
16
18
|
|
17
19
|
# Add arguments to the driver using the Options interface
|
18
20
|
if opts[:args]
|
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:
|
4
|
+
version: 6.1.0
|
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: 2019-
|
17
|
+
date: 2019-10-14 00:00:00.000000000 Z
|
18
18
|
dependencies:
|
19
19
|
- !ruby/object:Gem::Dependency
|
20
20
|
name: aws-sigv4
|
@@ -318,6 +318,7 @@ files:
|
|
318
318
|
- app/helpers/marty/enum_helper.rb
|
319
319
|
- app/helpers/marty/script_set.rb
|
320
320
|
- app/jobs/marty/cron_job.rb
|
321
|
+
- app/jobs/marty/remove_old_promises_job.rb
|
321
322
|
- app/models/marty/api_auth.rb
|
322
323
|
- app/models/marty/api_config.rb
|
323
324
|
- app/models/marty/background_job.rb
|
@@ -415,6 +416,7 @@ files:
|
|
415
416
|
- db/migrate/507_migrate_marty_roles_to_enum.rb
|
416
417
|
- db/migrate/508_add_not_to_data_grids_tables.rb
|
417
418
|
- db/migrate/509_update_dg_plpgsql_v1_fns.rb
|
419
|
+
- db/migrate/510_schedule_job_to_remove_old_promises.rb
|
418
420
|
- db/migrate/511_create_marty_delayed_job_logs.rb
|
419
421
|
- db/seeds.rb
|
420
422
|
- db/sql/lookup_grid_distinct_v1.sql
|
@@ -428,6 +430,7 @@ files:
|
|
428
430
|
- docker-compose.dummy.yml
|
429
431
|
- gemini_deprecations.md
|
430
432
|
- lib/marty.rb
|
433
|
+
- lib/marty/api/base.rb
|
431
434
|
- lib/marty/aws/base.rb
|
432
435
|
- lib/marty/aws/request.rb
|
433
436
|
- lib/marty/cache_adapters.rb
|
@@ -437,6 +440,23 @@ files:
|
|
437
440
|
- lib/marty/data_conversion.rb
|
438
441
|
- lib/marty/data_exporter.rb
|
439
442
|
- lib/marty/data_importer.rb
|
443
|
+
- lib/marty/diagnostic/aws/ec2_instance.rb
|
444
|
+
- lib/marty/diagnostic/aws/error.rb
|
445
|
+
- lib/marty/diagnostic/base.rb
|
446
|
+
- lib/marty/diagnostic/collection.rb
|
447
|
+
- lib/marty/diagnostic/connections.rb
|
448
|
+
- lib/marty/diagnostic/database.rb
|
449
|
+
- lib/marty/diagnostic/delayed_job_version.rb
|
450
|
+
- lib/marty/diagnostic/delayed_job_workers.rb
|
451
|
+
- lib/marty/diagnostic/environment_variables.rb
|
452
|
+
- lib/marty/diagnostic/fatal.rb
|
453
|
+
- lib/marty/diagnostic/node.rb
|
454
|
+
- lib/marty/diagnostic/nodes.rb
|
455
|
+
- lib/marty/diagnostic/packer.rb
|
456
|
+
- lib/marty/diagnostic/reporter.rb
|
457
|
+
- lib/marty/diagnostic/request.rb
|
458
|
+
- lib/marty/diagnostic/scheduled_jobs.rb
|
459
|
+
- lib/marty/diagnostic/version.rb
|
440
460
|
- lib/marty/engine.rb
|
441
461
|
- lib/marty/json_schema.rb
|
442
462
|
- lib/marty/logger.rb
|
@@ -464,24 +484,6 @@ files:
|
|
464
484
|
- lib/tasks/scripts_tasks.rake
|
465
485
|
- make-app.mk
|
466
486
|
- marty.gemspec
|
467
|
-
- other/marty/api/base.rb
|
468
|
-
- other/marty/diagnostic/aws/ec2_instance.rb
|
469
|
-
- other/marty/diagnostic/aws/error.rb
|
470
|
-
- other/marty/diagnostic/base.rb
|
471
|
-
- other/marty/diagnostic/collection.rb
|
472
|
-
- other/marty/diagnostic/connections.rb
|
473
|
-
- other/marty/diagnostic/database.rb
|
474
|
-
- other/marty/diagnostic/delayed_job_version.rb
|
475
|
-
- other/marty/diagnostic/delayed_job_workers.rb
|
476
|
-
- other/marty/diagnostic/environment_variables.rb
|
477
|
-
- other/marty/diagnostic/fatal.rb
|
478
|
-
- other/marty/diagnostic/node.rb
|
479
|
-
- other/marty/diagnostic/nodes.rb
|
480
|
-
- other/marty/diagnostic/packer.rb
|
481
|
-
- other/marty/diagnostic/reporter.rb
|
482
|
-
- other/marty/diagnostic/request.rb
|
483
|
-
- other/marty/diagnostic/scheduled_jobs.rb
|
484
|
-
- other/marty/diagnostic/version.rb
|
485
487
|
- script/rails
|
486
488
|
- spec/controllers/application_controller_spec.rb
|
487
489
|
- spec/controllers/delayed_job_controller_spec.rb
|
@@ -491,6 +493,7 @@ files:
|
|
491
493
|
- spec/controllers/rpc_import_spec.rb
|
492
494
|
- spec/dummy/README.rdoc
|
493
495
|
- spec/dummy/Rakefile
|
496
|
+
- spec/dummy/app/assets/config/manifest.js
|
494
497
|
- spec/dummy/app/components/gemini/cm_auth_app.rb
|
495
498
|
- spec/dummy/app/components/gemini/loan_program_view.rb
|
496
499
|
- spec/dummy/app/components/gemini/my_rule_view.rb
|
@@ -498,8 +501,8 @@ files:
|
|
498
501
|
- spec/dummy/app/controllers/application_controller.rb
|
499
502
|
- spec/dummy/app/controllers/components_controller.rb
|
500
503
|
- spec/dummy/app/helpers/application_helper.rb
|
504
|
+
- spec/dummy/app/jobs/test2_job.rb
|
501
505
|
- spec/dummy/app/jobs/test_job.rb
|
502
|
-
- spec/dummy/app/jobs/test_job2.rb
|
503
506
|
- spec/dummy/app/mailers/.gitkeep
|
504
507
|
- spec/dummy/app/models/.gitkeep
|
505
508
|
- spec/dummy/app/models/gemini/amortization_type.rb
|
@@ -1598,6 +1601,7 @@ files:
|
|
1598
1601
|
- spec/features/data_import_spec.rb
|
1599
1602
|
- spec/features/endpoint_access.rb
|
1600
1603
|
- spec/features/enum_spec.rb
|
1604
|
+
- spec/features/extjs_spec.rb
|
1601
1605
|
- spec/features/javascripts/job_dashboard_live_search.js.coffee
|
1602
1606
|
- spec/features/javascripts/login.js.coffee
|
1603
1607
|
- spec/features/jobs_dashboard_spec.rb
|
@@ -1662,6 +1666,7 @@ files:
|
|
1662
1666
|
- spec/other/diagnostic/delayed_job_workers_spec.rb
|
1663
1667
|
- spec/other/diagnostic/reporter_spec.rb
|
1664
1668
|
- spec/requests/routes_spec.rb
|
1669
|
+
- spec/services/jobs/schedule_spec.rb
|
1665
1670
|
- spec/spec_helper.rb
|
1666
1671
|
- spec/support/chromedriver.rb
|
1667
1672
|
- spec/support/components/netzke_combobox.rb
|
@@ -1700,7 +1705,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
1700
1705
|
- !ruby/object:Gem::Version
|
1701
1706
|
version: '0'
|
1702
1707
|
requirements: []
|
1703
|
-
rubygems_version: 3.0.
|
1708
|
+
rubygems_version: 3.0.6
|
1704
1709
|
signing_key:
|
1705
1710
|
specification_version: 4
|
1706
1711
|
summary: A framework for working with versioned data
|