diff --git a/NEWS.md b/NEWS.md
index ec509daef5a5..13e810e750c7 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -2,6 +2,7 @@
-------------------
* Now the owner of the dataset is going to receive an email when the synchronization fails hits the max allowed number [#3501](https://github.com/CartoDB/cartodb/issues/3501)
* If the dataset don't have an associated map we avoid to use the zoom property [#5447](https://github.com/CartoDB/cartodb/issues/5447)
+* Display custom attribution of layers in the editor and embeds [#5388](https://github.com/CartoDB/cartodb/pull/5388)
3.11.0 (2015-09-09)
-------------------
diff --git a/app/controllers/admin/visualizations_controller.rb b/app/controllers/admin/visualizations_controller.rb
index 6a28c9c0ec26..5143a842b9f4 100644
--- a/app/controllers/admin/visualizations_controller.rb
+++ b/app/controllers/admin/visualizations_controller.rb
@@ -226,7 +226,7 @@ def public_map
@disqus_shortname = @visualization.user.disqus_shortname.presence || 'cartodb'
@visualization_count = @visualization.user.public_visualization_count
@related_tables = @visualization.related_tables
- @related_visualizations = @visualization.related_visualizations
+ @related_canonical_visualizations = @visualization.related_canonical_visualizations
@related_tables_owners = Hash.new
@related_tables.each { |table|
unless @related_tables_owners.include?(table.user_id)
@@ -273,7 +273,7 @@ def show_organization_public_map
@disqus_shortname = @visualization.user.disqus_shortname.presence || 'cartodb'
@visualization_count = @visualization.user.public_visualization_count
@related_tables = @visualization.related_tables
- @related_visualizations = @visualization.related_visualizations
+ @related_canonical_visualizations = @visualization.related_canonical_visualizations
@public_tables_count = @visualization.user.public_table_count
@nonpublic_tables_count = @related_tables.select{|p| !p.public? }.count
@@ -323,7 +323,7 @@ def show_protected_public_map
@disqus_shortname = @visualization.user.disqus_shortname.presence || 'cartodb'
@visualization_count = @visualization.user.public_visualization_count
@related_tables = @visualization.related_tables
- @related_visualizations = @visualization.related_visualizations
+ @related_canonical_visualizations = @visualization.related_canonical_visualizations
@public_tables_count = @visualization.user.public_table_count
@nonpublic_tables_count = @related_tables.select{|p| !p.public? }.count
diff --git a/app/controllers/api/json/visualizations_controller.rb b/app/controllers/api/json/visualizations_controller.rb
index 13831cc9fd26..5a202d856fa0 100644
--- a/app/controllers/api/json/visualizations_controller.rb
+++ b/app/controllers/api/json/visualizations_controller.rb
@@ -89,7 +89,6 @@ def update
# Don't allow to modify next_id/prev_id, force to use set_next_id()
vis_data.delete(:prev_id) || vis_data.delete('prev_id')
vis_data.delete(:next_id) || vis_data.delete('next_id')
-
# when a table gets renamed, first it's canonical visualization is renamed, so we must revert renaming if that failed
# This is far from perfect, but works without messing with table-vis sync and their two backends
if vis.table?
diff --git a/app/controllers/carto/admin/visualization_public_map_adapter.rb b/app/controllers/carto/admin/visualization_public_map_adapter.rb
index 045ad6a89eae..43ad83d0954d 100644
--- a/app/controllers/carto/admin/visualization_public_map_adapter.rb
+++ b/app/controllers/carto/admin/visualization_public_map_adapter.rb
@@ -74,14 +74,14 @@ def public_with_link?
@visualization.is_link_privacy?
end
- def related_visualizations
- @visualization.related_visualizations.map { |rv|
+ def related_canonical_visualizations
+ @visualization.related_canonical_visualizations.map { |rv|
Carto::Admin::VisualizationPublicMapAdapter.new(rv, @current_viewer) if rv.is_public?
}.compact
end
def related_visualizations_geometry_types
- related_visualizations.collect(&:geometry_types).flatten.uniq
+ related_canonical_visualizations.collect(&:geometry_types).flatten.uniq
end
def related_tables_geometry_types
diff --git a/app/controllers/carto/api/visualization_vizjson_adapter.rb b/app/controllers/carto/api/visualization_vizjson_adapter.rb
index 42199dc995ec..d0bc2386ec38 100644
--- a/app/controllers/carto/api/visualization_vizjson_adapter.rb
+++ b/app/controllers/carto/api/visualization_vizjson_adapter.rb
@@ -8,7 +8,8 @@ class VisualizationVizJSONAdapter
include Carto::HtmlSafe
delegate [:id, :map, :qualified_name, :likes, :description, :retrieve_named_map?, :password_protected?, :overlays,
- :prev_id, :next_id, :transition_options, :has_password?, :parent_id, :get_auth_tokens, :user, :related_visualizations
+ :prev_id, :next_id, :transition_options, :has_password?, :parent_id, :get_auth_tokens, :user,
+ :related_canonical_visualizations
] => :visualization
attr_reader :visualization
@@ -29,7 +30,7 @@ def layers(kind)
end
def attributions_from_derived_visualizations
- @visualization.related_visualizations.map(&:attributions).reject {|attribution| attribution.blank?}
+ @visualization.related_canonical_visualizations.map(&:attributions).reject {|attribution| attribution.blank?}
end
def children
diff --git a/app/controllers/carto/api/vizjson_presenter.rb b/app/controllers/carto/api/vizjson_presenter.rb
index 74376088d457..bef26f92c58b 100644
--- a/app/controllers/carto/api/vizjson_presenter.rb
+++ b/app/controllers/carto/api/vizjson_presenter.rb
@@ -20,7 +20,6 @@ def to_vizjson(options={})
private
-
def calculate_vizjson(options={})
vizjson_options = {
full: false,
diff --git a/app/helpers/redis_vizjson_cache.rb b/app/helpers/redis_vizjson_cache.rb
index b586d7a74ae4..ade533d62988 100644
--- a/app/helpers/redis_vizjson_cache.rb
+++ b/app/helpers/redis_vizjson_cache.rb
@@ -11,7 +11,6 @@ def initialize(redis_cache=$tables_metadata)
@redis = redis_cache
end
-
def cached(visualization_id, https_flag=false)
key = key(visualization_id, https_flag)
value = redis.get(key)
diff --git a/app/models/carto/visualization.rb b/app/models/carto/visualization.rb
index b610a40d93ee..c23708a7bcc6 100644
--- a/app/models/carto/visualization.rb
+++ b/app/models/carto/visualization.rb
@@ -77,8 +77,8 @@ def related_tables
@related_tables ||= get_related_tables
end
- def related_visualizations
- @related_visualizations ||= get_related_visualizations
+ def related_canonical_visualizations
+ @related_canonical_visualizations ||= get_related_canonical_visualizations
end
def stats
@@ -313,8 +313,12 @@ def get_related_tables
map.carto_and_torque_layers.flat_map { |layer| layer.affected_tables}.uniq
end
- def get_related_visualizations
- Carto::Visualization.where(map_id: related_tables.collect(&:map_id), type: TYPE_CANONICAL).all
+ def get_related_canonical_visualizations
+ get_related_visualizations_by_types([TYPE_CANONICAL])
+ end
+
+ def get_related_visualizations_by_types(types)
+ Carto::Visualization.where(map_id: related_tables.collect(&:map_id), type: types).all
end
def has_write_permission?(user)
diff --git a/app/models/visualization/member.rb b/app/models/visualization/member.rb
index e2eff232d061..cd53ac91d858 100644
--- a/app/models/visualization/member.rb
+++ b/app/models/visualization/member.rb
@@ -319,10 +319,16 @@ def description_html_safe
if description.present?
renderer = Redcarpet::Render::Safe
markdown = Redcarpet::Markdown.new(renderer, extensions = {})
- markdown.render description
+ markdown.render description
end
end
+ def attributions=(value)
+ self.dirty = true if value != @attributions
+ self.attributions_changed = true if value != @attributions
+ super(value)
+ end
+
def permission_id=(permission_id)
self.permission_change_valid = false
self.permission_change_valid = true if (@permission_id.nil? || @permission_id == permission_id)
@@ -439,6 +445,7 @@ def non_dependent?
def invalidate_cache
invalidate_redis_cache
invalidate_varnish_cache
+
parent.invalidate_cache unless parent_id.nil?
end
@@ -475,7 +482,7 @@ def password=(value)
end
def has_password?
- ( !@password_salt.nil? && !@encrypted_password.nil? )
+ ( !@password_salt.nil? && !@encrypted_password.nil? )
end
def is_password_valid?(password)
@@ -494,7 +501,7 @@ def make_auth_token
10.times do
digest = secure_digest(digest, TOKEN_DIGEST)
end
- digest
+ digest
end
def get_auth_tokens
@@ -642,13 +649,13 @@ def license_info
end
def attributions_from_derived_visualizations
- related_visualizations.map(&:attributions).reject {|attribution| attribution.blank?}
+ related_canonical_visualizations.map(&:attributions).reject {|attribution| attribution.blank?}
end
private
attr_reader :repository, :name_checker, :validator
- attr_accessor :privacy_changed, :name_changed, :old_name, :permission_change_valid, :dirty
+ attr_accessor :privacy_changed, :name_changed, :old_name, :permission_change_valid, :dirty, :attributions_changed
def embed_redis_cache
@embed_redis_cache ||= EmbedRedisCache.new($tables_metadata)
@@ -708,11 +715,7 @@ def do_store(propagate_changes=true, table_privacy_changed=false)
raise CartoDB::InvalidMember
end
- # previously we used 'invalidate_cache' but due to public_map displaying all the user public visualizations,
- # now we need to purgue everything to avoid cached stale data or public->priv still showing scenarios
- if name_changed || privacy_changed || table_privacy_changed || dirty
- invalidate_cache
- end
+ perform_invalidations(table_privacy_changed)
set_timestamps
@@ -731,10 +734,26 @@ def do_store(propagate_changes=true, table_privacy_changed=false)
save_named_map
+ propagate_attribution_change if table
if type == TYPE_REMOTE || type == TYPE_CANONICAL
propagate_privacy_and_name_to(table) if table and propagate_changes
else
- propagate_name_to(table) if !table.nil? and propagate_changes
+ propagate_name_to(table) if table and propagate_changes
+ end
+ end
+
+ def perform_invalidations(table_privacy_changed)
+ # previously we used 'invalidate_cache' but due to public_map displaying all the user public visualizations,
+ # now we need to purgue everything to avoid cached stale data or public->priv still showing scenarios
+ if name_changed || privacy_changed || table_privacy_changed || dirty
+ invalidate_cache
+ end
+
+ # When a table's relevant data is changed, propagate to all who use it or relate to it
+ if dirty && table
+ table.affected_visualizations.each do |affected_vis|
+ affected_vis.invalidate_cache
+ end
end
end
@@ -805,6 +824,20 @@ def propagate_name_to(table)
raise CartoDB::InvalidMember.new(exception.to_s)
end
+ def propagate_attribution_change
+ return unless attributions_changed
+
+ # This includes both the canonical and derived visualizations
+ table.affected_visualizations.each do |affected_visualization|
+ affected_visualization.layers(:carto_and_torque).each do |layer|
+ if layer.options['table_name'] == table.name
+ layer.options['attribution'] = self.attributions
+ layer.save
+ end
+ end
+ end
+ end
+
def revert_name_change(previous_name)
self.name = previous_name
store
diff --git a/app/models/visualization/relator.rb b/app/models/visualization/relator.rb
index af322d557682..30f681600c19 100644
--- a/app/models/visualization/relator.rb
+++ b/app/models/visualization/relator.rb
@@ -17,9 +17,9 @@ class Relator
named_map: :named_maps_layers
}
- INTERFACE = %w{ overlays map user table related_templates related_tables related_visualizations layers
- stats mapviews total_mapviews single_data_layer? synchronization is_synced? permission parent
- children support_tables prev_list_item next_list_item likes likes_count reload_likes
+ INTERFACE = %w{ overlays map user table related_templates related_tables related_canonical_visualizations
+ layers stats mapviews total_mapviews single_data_layer? synchronization is_synced? permission
+ parent children support_tables prev_list_item next_list_item likes likes_count reload_likes
estimated_row_count actual_row_count }
def initialize(attributes={})
@@ -105,8 +105,8 @@ def related_tables
.flat_map{|layer| layer.affected_tables.map{|t| t.service}}.uniq
end
- def related_visualizations
- @related_visualizations ||= get_related_visualizations
+ def related_canonical_visualizations
+ @related_canonical_visualizations ||= get_related_canonical_visualizations
end
def layers(kind)
@@ -165,10 +165,15 @@ def likes_search
Like.where(subject: @id)
end
- def get_related_visualizations
+ def get_related_canonical_visualizations
+ get_related_visualizations_by_types([CartoDB::Visualization::Member::TYPE_CANONICAL])
+ end
+
+ def get_related_visualizations_by_types(types)
related_map_ids = related_tables.map(&:map_id)
- CartoDB::Visualization::Collection.new.fetch(map_id: related_map_ids, type: CartoDB::Visualization::Member::TYPE_CANONICAL)
+ CartoDB::Visualization::Collection.new.fetch(map_id: related_map_ids, type: types)
end
+
end
end
end
diff --git a/app/views/admin/visualizations/public_map.html.erb b/app/views/admin/visualizations/public_map.html.erb
index 156495a8e8d6..30533e637b49 100644
--- a/app/views/admin/visualizations/public_map.html.erb
+++ b/app/views/admin/visualizations/public_map.html.erb
@@ -171,8 +171,8 @@
- <% if @related_visualizations.present? %>
- <% @related_visualizations.each do |vis| %>
+ <% if @related_canonical_visualizations.present? %>
+ <% @related_canonical_visualizations.each do |vis| %>
<% if vis.privacy == "public" %>
-
diff --git a/lib/assets/javascripts/cartodb/models/map.js b/lib/assets/javascripts/cartodb/models/map.js
index 188aac34d793..19e8f457bde3 100644
--- a/lib/assets/javascripts/cartodb/models/map.js
+++ b/lib/assets/javascripts/cartodb/models/map.js
@@ -538,6 +538,7 @@ cdb.admin.Map = cdb.geo.Map.extend({
this.layers = new cdb.admin.Layers();
this.layers.map = this;
this.layers.bind('reset add change', this._layersChanged, this);
+ this.layers.bind('reset add remove change:attribution', this._updateAttributions, this);
},
saveLayers: function(opts) {
@@ -613,10 +614,6 @@ cdb.admin.Map = cdb.geo.Map.extend({
var newBaseLayer = baseLayer;
var currentBaseLayer = this.layers.at(0);
var newBaseLayerHasLabels = newBaseLayer.get('labels') && newBaseLayer.get('labels').url;
- var attributionOfCurrentBaseLayer = currentBaseLayer && currentBaseLayer.get('attribution');
-
- // Update Attribution of the map
- this.updateAttribution(attributionOfCurrentBaseLayer, newBaseLayer.get('attribution'));
// Sets the base layer
var options = {
diff --git a/lib/assets/javascripts/cdb b/lib/assets/javascripts/cdb
index 1fc0998faa05..71ff7e142e2b 160000
--- a/lib/assets/javascripts/cdb
+++ b/lib/assets/javascripts/cdb
@@ -1 +1 @@
-Subproject commit 1fc0998faa05243e0fe60bf39a13f23bc669c96f
+Subproject commit 71ff7e142e2b8cd694e80ef655ff8d532a472f49
diff --git a/lib/assets/test/spec/cartodb/models/map.spec.js b/lib/assets/test/spec/cartodb/models/map.spec.js
index 578e1b852f8f..695a803d11c0 100644
--- a/lib/assets/test/spec/cartodb/models/map.spec.js
+++ b/lib/assets/test/spec/cartodb/models/map.spec.js
@@ -46,6 +46,7 @@ describe("cartodb.models.Map", function() {
describe('.setBaseLayer', function() {
var savingLayersFinish;
+ var cartoDBAttribution = 'CartoDB attribution';
it('should set the base layer as the first layer', function(done) {
savingLayersFinishCallback = jasmine.createSpy('savingLayersFinishCallback');
@@ -73,7 +74,7 @@ describe("cartodb.models.Map", function() {
expect(savingLayersFinishCallback.calls.count()).toEqual(1);
done();
}, 0);
- expect(map.get('attribution')).toEqual([ 'CartoDB1.0' ]);
+ expect(map.get('attribution')).toEqual([ 'CartoDB1.0', cartoDBAttribution ]);
})
it('should update the existing base layer if the new layer has the same type', function(done) {
@@ -109,13 +110,14 @@ describe("cartodb.models.Map", function() {
expect(savingLayersFinishCallback.calls.count()).toEqual(1);
done();
}, 0);
- expect(map.get('attribution')).toEqual([ 'CartoDB2.0' ]);
+ expect(map.get('attribution')).toEqual([ 'CartoDB2.0', cartoDBAttribution ]);
expect(map.layers.length).toEqual(1);
})
it('should replace the base layer if the current base layer has a different type', function(done) {
var baseLayer = new cdb.admin.CartoDBLayer({
id: 'XXX-YYY',
+ attribution: 'CartoDB1.0',
tile_style: 'test1',
query: 'sql1',
interactivity: 'int1',
@@ -134,6 +136,8 @@ describe("cartodb.models.Map", function() {
map.setBaseLayer(baseLayer);
+ expect(map.get('attribution')).toEqual([ 'CartoDB1.0', cartoDBAttribution ]);
+
savingLayersFinishCallback = jasmine.createSpy('savingLayersFinishCallback');
map.bind('savingLayersFinish', savingLayersFinishCallback);
@@ -147,35 +151,11 @@ describe("cartodb.models.Map", function() {
expect(savingLayersFinishCallback.calls.count()).toEqual(1);
done();
}, 0);
- expect(map.get('attribution')).toEqual([ 'CartoDB2.0' ]);
+ expect(map.get('attribution')).toEqual([ 'CartoDB2.0', cartoDBAttribution ]);
+
expect(map.layers.length).toEqual(1);
})
- it("should set a new attribution after change base layer", function() {
- var old = new cdb.geo.CartoDBLayer({
- attribution: 'CartoDB1.0',
- type: 'Tiled',
- urlTemplate: 'x',
- base_type: 'x',
- name: 'old'
- });
- map.setBaseLayer(old);
- var old_attribution = map.get('attribution');
-
- var base = new cdb.geo.CartoDBLayer({
- attribution: 'CartoDB2.0',
- type: 'Tiled',
- urlTemplate: 'y',
- base_type: 'y',
- name: 'new'
- });
- sinon.stub(base, "save").yieldsTo("success");
- var r = map.setBaseLayer(base);
- var new_attribution = map.get('attribution');
-
- expect(old_attribution[0]).not.toEqual(new_attribution[0]);
- });
-
it("shouldn't set base layer if the old base layer is the same", function() {
var old = new cdb.geo.TileLayer({
type: 'Tiled',
@@ -484,6 +464,22 @@ describe("cartodb.models.Map", function() {
expect(map.set('center', [0, -185]).clamp().get('center')).toEqual([0, 175])
});
+ it('should update the attribution of the map when layers change', function() {
+ var layer1 = new cdb.geo.CartoDBLayer({ attribution: 'attribution1' });
+ var layer2 = new cdb.geo.CartoDBLayer({ attribution: 'attribution1' });
+ var layer3 = new cdb.geo.CartoDBLayer({ attribution: 'wadus' });
+ var layer4 = new cdb.geo.CartoDBLayer({ attribution: '' });
+
+ map.layers.reset([ layer1, layer2, layer3, layer4 ]);
+
+ // Attributions have been updated removing duplicated and empty attributions
+ expect(map.get('attribution')).toEqual([
+ "attribution1",
+ "wadus",
+ "CartoDB attribution",
+ ]);
+ });
+
describe("layers", function() {
it("should add layers on top", function() {
diff --git a/lib/assets/test/spec/cartodb/table/mapview.spec.js b/lib/assets/test/spec/cartodb/table/mapview.spec.js
index ff0f64e0f50d..f9bce17c3503 100644
--- a/lib/assets/test/spec/cartodb/table/mapview.spec.js
+++ b/lib/assets/test/spec/cartodb/table/mapview.spec.js
@@ -9,13 +9,15 @@ describe("mapview", function() {
var layer = new cdb.admin.CartoDBLayer({
table_name: 'test',
tile_style: 'test',
- user_name: 'test'
+ user_name: 'test',
+ attribution: 'attribution1'
});
map.layers.add(layer);
map.layers.add(new cdb.admin.CartoDBLayer({
table_name: 'test2',
tile_style: 'style',
- user_name: 'test'
+ user_name: 'test',
+ attribution: 'attribution2'
}));
var element = $('');
element.appendTo($('body'));
@@ -164,6 +166,11 @@ describe("mapview", function() {
expect(view.switchMapType).toHaveBeenCalled();
});
+ it('should render attributions of each layer and default CartoDB attribution', function() {
+ var attributions = view.$el.find('.leaflet-control-attribution').html();
+ expect(attributions).toEqual('attribution1, attribution2, CartoDB attribution');
+ });
+
describe("cdb.admin.MapTab.updateDataLayerView", function () {
describe("given MapTab view has a cdb.CartoDB.Layer as data layer view", function() {
beforeEach(function() {
diff --git a/package.json b/package.json
index 6917d5852bcd..20d0f9b4c5a6 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "cartodb-ui",
- "version": "3.19.21",
+ "version": "3.19.22",
"description": "CartoDB UI frontend",
"repository": {
"type": "git",
diff --git a/spec/models/visualization/relator_spec.rb b/spec/models/visualization/relator_spec.rb
index 28ecb19c9abf..23f13e82854e 100644
--- a/spec/models/visualization/relator_spec.rb
+++ b/spec/models/visualization/relator_spec.rb
@@ -62,7 +62,7 @@
Visualization::Relator.any_instance.stubs(:support_tables).returns(support_tables_mock)
end
- describe '#related_visualizations' do
+ describe '#related_canonical_visualizations' do
it 'should return the canonical visualizations associated to a derived visualization' do
table1 = create_table({:name => 'table1', :user_id => @user.id})
@@ -70,7 +70,7 @@
vis_table1 = create_vis_from_table(@user, table1)
vis_table2 = create_vis_from_table(@user, table2)
- vis_table1.related_visualizations.map(&:id).should == [table1.table_visualization.id]
+ vis_table1.related_canonical_visualizations.map(&:id).should == [table1.table_visualization.id]
end
end
diff --git a/spec/requests/carto/api/layers_controller_spec.rb b/spec/requests/carto/api/layers_controller_spec.rb
index 10e4bd3c65b0..7344e661da43 100644
--- a/spec/requests/carto/api/layers_controller_spec.rb
+++ b/spec/requests/carto/api/layers_controller_spec.rb
@@ -10,6 +10,84 @@
it_behaves_like 'layers controllers' do
end
+ describe 'attribution changes' do
+ include Rack::Test::Methods
+ include Warden::Test::Helpers
+
+ before(:all) do
+ @headers = {'CONTENT_TYPE' => 'application/json'}
+ end
+
+ before(:each) do
+ CartoDB::NamedMapsWrapper::NamedMaps.any_instance.stubs(:get => nil, :create => true, :update => true, :delete => true)
+ CartoDB::Visualization::Member.any_instance.stubs(:invalidate_cache).returns(nil)
+ end
+
+ after(:each) do
+ delete_user_data($user_1)
+ end
+
+ it 'attribution chanes in a visualization propagate to associated layers' do
+ table_1_attribution = 'attribution 1'
+ table_2_attribution = 'attribution 2'
+ modified_table_2_attribution = 'modified attribution 2'
+
+ table1 = create_table(privacy: UserTable::PRIVACY_PUBLIC, name: "table#{rand(9999)}_1", user_id: $user_1.id)
+ table2 = create_table(privacy: UserTable::PRIVACY_PUBLIC, name: "table#{rand(9999)}_2", user_id: $user_1.id)
+
+ payload = {
+ name: 'new visualization',
+ tables: [
+ table1.name,
+ table2.name
+ ],
+ privacy: 'public'
+ }
+
+ login_as($user_1, scope: $user_1.username)
+ host! "#{$user_1.username}.localhost.lan"
+ post api_v1_visualizations_create_url(api_key: @api_key), payload.to_json, @headers do |response|
+ response.status.should == 200
+ @visualization_data = JSON.parse(response.body)
+ end
+
+ visualization = Carto::Visualization.find(@visualization_data.fetch('id'))
+
+ table1_visualization = CartoDB::Visualization::Member.new(id: table1.table_visualization.id).fetch
+ table1_visualization.attributions = table_1_attribution
+ table1_visualization.store
+ table2_visualization = CartoDB::Visualization::Member.new(id: table2.table_visualization.id).fetch
+ table2_visualization.attributions = table_2_attribution
+ table2_visualization.store
+
+ get api_v1_maps_layers_index_url(map_id: visualization.map.id, api_key: @api_key) do |response|
+ response.status.should be_success
+ @layers_data = JSON.parse(response.body)
+ end
+ data_layers = @layers_data['layers'].select { |layer| layer['kind'] == 'carto' }
+ data_layers.count.should eq 2
+
+ # Rembember, layers by default added at top
+ data_layers[0]['options']['attribution'].should eq table_2_attribution
+ data_layers[1]['options']['attribution'].should eq table_1_attribution
+
+
+ table2_visualization.attributions = modified_table_2_attribution
+ table2_visualization.store
+
+ get api_v1_maps_layers_index_url(map_id: visualization.map.id, api_key: @api_key) do |response|
+ response.status.should be_success
+ @layers_data = JSON.parse(response.body)
+ end
+ data_layers = @layers_data['layers'].select { |layer| layer['kind'] == 'carto' }
+ data_layers.count.should eq 2
+
+ # Rembember, layers by default added at top
+ data_layers[0]['options']['attribution'].should eq modified_table_2_attribution
+ data_layers[1]['options']['attribution'].should eq table_1_attribution
+ end
+
+ end
describe 'index' do
include Rack::Test::Methods
diff --git a/spec/requests/carto/api/visualizations_controller_spec.rb b/spec/requests/carto/api/visualizations_controller_spec.rb
index 5b30fdfde710..afc1faa94133 100644
--- a/spec/requests/carto/api/visualizations_controller_spec.rb
+++ b/spec/requests/carto/api/visualizations_controller_spec.rb
@@ -1386,6 +1386,52 @@
named_map_attributions.should include('attribution1')
named_map_attributions.should include('attribution2')
end
+
+ it "Updates viz.json's layergroup when attributions in a related layer change" do
+ table_1_attribution = 'attribution 1'
+ modified_table_2_attribution = 'modified attribution 2'
+
+ table1 = table_factory
+ table2 = table_factory
+
+ payload = {
+ name: 'new visualization',
+ tables: [
+ table1.fetch('name'),
+ table2.fetch('name')
+ ],
+ privacy: 'public'
+ }
+
+ post api_v1_visualizations_create_url(api_key: @api_key), payload.to_json, @headers
+ last_response.status.should == 200
+ visualization = JSON.parse(last_response.body)
+
+ table1_visualization = Carto::Visualization.find(table1["table_visualization"]["id"])
+ table1_visualization.update_attribute(:attributions, table_1_attribution)
+ table2_visualization = Carto::Visualization.find(table2["table_visualization"]["id"])
+ table2_visualization.update_attribute(:attributions, 'attribution 2')
+
+ # Call to cache the vizjson after generating it
+ get api_v2_visualizations_vizjson_url(id: visualization.fetch('id'), api_key: @api_key),{}, @headers
+
+ # Now force a change
+ put api_v1_visualizations_update_url(api_key: @api_key, id: table2_visualization.id),
+ { attributions: modified_table_2_attribution }.to_json, @headers
+ last_response.status.should == 200
+
+ get api_v2_visualizations_vizjson_url(id: visualization.fetch('id'), api_key: @api_key),{}, @headers
+ visualization = JSON.parse(last_response.body)
+
+ layer_group_layer = visualization["layers"][1]
+ layer_group_layer["type"].should == 'layergroup'
+ layer_group_attributions = layer_group_layer["options"]["attribution"].split(',').map(&:strip)
+ layer_group_layer.size.should == 2
+
+ layer_group_attributions.should include(table_1_attribution)
+ layer_group_attributions.should include(modified_table_2_attribution)
+ end
+
end
describe 'tests visualization listing filters' do
diff --git a/vendor/assets/javascripts/cartodb.mod.odyssey.uncompressed.js b/vendor/assets/javascripts/cartodb.mod.odyssey.uncompressed.js
index 53c3e12a7d54..2590e6c53a3c 100644
--- a/vendor/assets/javascripts/cartodb.mod.odyssey.uncompressed.js
+++ b/vendor/assets/javascripts/cartodb.mod.odyssey.uncompressed.js
@@ -1,5 +1,5 @@
-// version: 3.15.1
-// sha: c942686a747b755e7448d6438d63ea42e9f1d051
+// version: 3.15.2
+// sha: 5640fee5e39faed217b50f278eacff038c2d82cb
!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.O=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o