Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion draftlogs/7802_change.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
- Update `sendDataToCloud` modebar button to upload chart to Plotly Cloud [[#7802](https://github.com/plotly/plotly.js/pull/7802)]
- Update `sendDataToCloud` modebar button to upload chart to Plotly Cloud [[#7802](https://github.com/plotly/plotly.js/pull/7802), [#7852](https://github.com/plotly/plotly.js/pull/7852)]
- NOTE: The Plotly Cloud endpoint for receiving charts is not yet functional, so this button won't complete the upload.
7 changes: 3 additions & 4 deletions src/components/modebar/buttons.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,16 +82,15 @@ modeBarButtons.sendChartToCloud = {
// Plotly Cloud origin, used to validate incoming messages and to target outgoing ones.
// `baseUrl` (plotlyServerURL) is the upload page that handles login and signals
// back when authentication succeeds.
var cloudOrigin;
try {
cloudOrigin = new URL(baseUrl).origin;
new URL(baseUrl);
} catch(e) {
console.error('Invalid plotlyServerURL: ' + baseUrl);
return;
}

confirmCloudDialog(gd, serverUrl, function() {
Plots.sendDataToCloud(gd, cloudOrigin);
confirmCloudDialog(gd, baseUrl, function() {
Plots.sendDataToCloud(gd, baseUrl);
});
}
};
Expand Down
7 changes: 7 additions & 0 deletions src/components/modebar/manage.js
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,13 @@ function getButtonGroups(gd) {
// buttons common to all plot types
var commonGroup = ['toImage'];
if(context.showSendToCloud) commonGroup.push('sendChartToCloud');
else if(context.showEditInChartStudio) {
console.warn([
'*showEditInChartStudio* is deprecated.',
'Use *showSendToCloud* instead.'
].join(' '));
commonGroup.push('sendChartToCloud');
}
addGroup(commonGroup);

var zoomGroup = [];
Expand Down
7 changes: 7 additions & 0 deletions src/plot_api/plot_config.js
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,13 @@ var configAttributes = {
'send chart data to an external server.'
].join(' ')
},
showEditInChartStudio: {
valType: 'boolean',
dflt: false,
description: [
'Deprecated. Use `showSendToCloud` instead.'
].join(' ')
},
modeBarButtonsToRemove: {
valType: 'any',
dflt: [],
Expand Down
6 changes: 4 additions & 2 deletions src/plots/plots.js
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,8 @@ function positionPlayWithData(gd, container) {
plots.sendDataToCloud = function(gd, serverURL) {
gd.emit('plotly_beforeexport');

const serverURLOrigin = new URL(serverURL).origin;

// Build the request body: the chart JSON plus the plotly.js version used to
// generate it, so Cloud can host the chart with a compatible plotly.js version.
var chart = plots.graphJson(gd, false, 'keepdata', 'object');
Expand All @@ -220,13 +222,13 @@ plots.sendDataToCloud = function(gd, serverURL) {

var handleMessage = function(event) {
// Only trust messages coming from the Cloud origin.
if(event.origin !== serverURL) return;
if(event.origin !== serverURLOrigin) return;

if(event.data && event.data.type === 'CHART_AUTH_SUCCESS') {
cloudWindow.postMessage({
type: 'chart',
chart: chart
}, serverURL);
}, serverURLOrigin);

window.removeEventListener('message', handleMessage);
gd.emit('plotly_afterexport');
Expand Down
79 changes: 52 additions & 27 deletions test/jasmine/tests/config_test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
var Plotly = require('../../../lib/index');
var Plots = require('../../../src/plots/plots');
var Lib = require('../../../src/lib');
var modeBarButtons = require('../../../src/components/modebar/buttons');

var d3Select = require('../../strict-d3').select;
var createGraphDiv = require('../assets/create_graph_div');
Expand Down Expand Up @@ -522,69 +523,93 @@ describe('config argument', function() {
describe('plotlyServerURL:', function() {
var gd;
var form;
var openSpy;

beforeEach(function() {
gd = createGraphDiv();
spyOn(HTMLFormElement.prototype, 'submit').and.callFake(function() {
form = this;
});
// sendDataToCloud hands off the chart by opening the provided URL in a
// new tab (window.open(url, '_blank')), so spy on window.open.
var cloudWindow = jasmine.createSpyObj('cloudWindow', ['postMessage']);
openSpy = spyOn(window, 'open').and.returnValue(cloudWindow);
});

afterEach(destroyGraphDiv);

it('should not default to an external plotly cloud', function(done) {
it('should default to an empty string', function(done) {
Plotly.newPlot(gd, [], {})
.then(function() {
expect(gd._context.plotlyServerURL).not.toBe('https://plot.ly');
expect(gd._context.plotlyServerURL).not.toBe('https://chart-studio.plotly.com');
expect(gd._context.plotlyServerURL).toBe('');

Plotly.Plots.sendDataToCloud(gd);
expect(form).toBe(undefined);
})
.then(done, done.fail);
});

it('should be able to connect to Chart Studio Cloud when set to https://chart-studio.plotly.com', function(done) {
it('should open confirmation dialog when set to a correctly-formatted URL', function(done) {
Plotly.newPlot(gd, [], {}, {
plotlyServerURL: 'https://chart-studio.plotly.com'
plotlyServerURL: 'https://example.plotly.com/endpoint'
})
.then(function() {
expect(gd._context.plotlyServerURL).toBe('https://chart-studio.plotly.com');

Plotly.Plots.sendDataToCloud(gd);
expect(form.action).toBe('https://chart-studio.plotly.com/external');
expect(form.method).toBe('post');
expect(gd._context.plotlyServerURL).toBe('https://example.plotly.com/endpoint');
modeBarButtons.sendChartToCloud.click(gd);
var msg = document.querySelector('.plotly-cloud-dialog-message');
expect(msg).not.toBe(null, 'confirmation dialog should be shown');
expect(msg.textContent).toContain('https://example.plotly.com/endpoint');
})
.then(done, done.fail);
});

it('can be set to other base urls', function(done) {
Plotly.newPlot(gd, [], {}, {plotlyServerURL: 'dummy'})
it('should NOT open confirmation dialog when set to an invalid URL', function(done) {
Plotly.newPlot(gd, [], {}, {
plotlyServerURL: 'dummy'
})
.then(function() {
expect(gd._context.plotlyServerURL).toBe('dummy');
modeBarButtons.sendChartToCloud.click(gd);
var msg = document.querySelector('.plotly-cloud-dialog-message');
expect(msg).toBe(null, 'confirmation dialog should not be shown');
})
.then(done, done.fail);
});

Plotly.Plots.sendDataToCloud(gd);
expect(form.action).toContain('/dummy/external');
expect(form.method).toBe('post');
it('should open URL in a new tab after clicking confirm button', function(done) {
Plotly.newPlot(gd, [], {}, {
plotlyServerURL: 'https://example.plotly.com/endpoint'
})
.then(function() {
expect(gd._context.plotlyServerURL).toBe('https://example.plotly.com/endpoint');
modeBarButtons.sendChartToCloud.click(gd);

// Click the confirm button in the dialog
var confirmBtn = document.querySelector('.plotly-cloud-dialog-btn--confirm');
expect(confirmBtn).not.toBe(null, 'confirm button should be shown');
mouseEvent('click', 0, 0, {element: confirmBtn});

// Should open the provided URL's origin in a new tab
expect(openSpy).toHaveBeenCalledWith('https://example.plotly.com/endpoint', '_blank');
})
.then(done, done.fail);
});

it('has lesser priotiy then window env', function(done) {
window.PLOTLYENV = {BASE_URL: 'yo'};
it('has lesser priority than window env', function(done) {
window.PLOTLYENV = {BASE_URL: 'https://yo.plotly.com/endpoint'};

Plotly.newPlot(gd, [], {}, {plotlyServerURL: 'dummy'})
Plotly.newPlot(gd, [], {}, {plotlyServerURL: 'https://example.plotly.com/endpoint2'})
.then(function() {
expect(gd._context.plotlyServerURL).toBe('dummy');
expect(gd._context.plotlyServerURL).toBe('https://example.plotly.com/endpoint2');

// Confirmation dialog message should contain window.PLOTLYENV.BASE_URL,
// which takes priority over plotlyServerURL
modeBarButtons.sendChartToCloud.click(gd);

Plotly.Plots.sendDataToCloud(gd);
expect(form.action).toContain('/yo/external');
expect(form.method).toBe('post');
var msg = document.querySelector('.plotly-cloud-dialog-message');
expect(msg).not.toBe(null, 'confirmation dialog should be shown');
expect(msg.textContent).toContain('https://yo.plotly.com/endpoint');
expect(msg.textContent).not.toContain('https://example.plotly.com/endpoint2');
})
.catch(failTest)
.then(function() {
delete window.PLOTLY_ENV;
delete window.PLOTLYENV;
done();
});
});
Expand Down
6 changes: 3 additions & 3 deletions test/jasmine/tests/modebar_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -823,21 +823,21 @@ describe('ModeBar', function() {
gd._context.showEditInChartStudio = false;
manageModeBar(gd);
checkButtons(gd._fullLayout._modeBar, getButtons([
['toImage', 'sendDataToCloud']
['toImage', 'sendChartToCloud']
]), 1);

gd._context.showSendToCloud = false;
gd._context.showEditInChartStudio = true;
manageModeBar(gd);
checkButtons(gd._fullLayout._modeBar, getButtons([
['toImage', 'editInChartStudio']
['toImage', 'sendChartToCloud']
]), 1);

gd._context.showSendToCloud = true;
gd._context.showEditInChartStudio = true;
manageModeBar(gd);
checkButtons(gd._fullLayout._modeBar, getButtons([
['toImage', 'editInChartStudio']
['toImage', 'sendChartToCloud']
]), 1);
});

Expand Down
5 changes: 5 additions & 0 deletions test/plot-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,11 @@
"dflt": true,
"valType": "boolean"
},
"showEditInChartStudio": {
"description": "Deprecated. Use `showSendToCloud` instead.",
"dflt": false,
"valType": "boolean"
},
"showLink": {
"description": "Determines whether a link to Chart Studio Cloud is displayed at the bottom right corner of resulting graphs. Use with `sendData` and `linkText`.",
"dflt": false,
Expand Down
Loading