diff --git a/caravel/assets/javascripts/dashboard/Dashboard.jsx b/caravel/assets/javascripts/dashboard/Dashboard.jsx
index 72d9187902f76..9142cbebd088b 100644
--- a/caravel/assets/javascripts/dashboard/Dashboard.jsx
+++ b/caravel/assets/javascripts/dashboard/Dashboard.jsx
@@ -3,7 +3,7 @@ const jQuery = window.jQuery = require('jquery'); // eslint-disable-line
const px = require('../modules/caravel.js');
const d3 = require('d3');
const urlLib = require('url');
-const showModal = require('../modules/utils.js').showModal;
+const utils = require('../modules/utils.js');
import React from 'react';
import { render } from 'react-dom';
@@ -41,7 +41,8 @@ function injectCss(className, css) {
}
function dashboardContainer(dashboardData) {
- let dashboard = $.extend(dashboardData, {
+ let dashboard = Object.assign({}, utils.controllerInterface, dashboardData, {
+ type: 'dashboard',
filters: {},
init() {
this.initDashboardView();
@@ -82,6 +83,23 @@ function dashboardContainer(dashboardData) {
setFilter(sliceId, col, vals, refresh) {
this.addFilter(sliceId, col, vals, false, refresh);
},
+ done(slice) {
+ const refresh = slice.getWidgetHeader().find('.refresh');
+ const data = slice.data;
+ if (data !== undefined && data.is_cached) {
+ refresh
+ .addClass('danger')
+ .attr('title',
+ 'Served from data cached at ' + data.cached_dttm +
+ '. Click to force refresh')
+ .tooltip('fixTitle');
+ } else {
+ refresh
+ .removeClass('danger')
+ .attr('title', 'Click to force refresh')
+ .tooltip('fixTitle');
+ }
+ },
effectiveExtraFilters(sliceId) {
// Summarized filter, not defined by sliceId
// returns k=field, v=array of values
@@ -250,7 +268,7 @@ function dashboardContainer(dashboardData) {
},
error(error) {
const errorMsg = getAjaxErrorMsg(error);
- showModal({
+ utils.showModal({
title: 'Error',
body: 'Sorry, there was an error adding slices to this dashboard: br>' + errorMsg,
});
@@ -279,14 +297,14 @@ function dashboardContainer(dashboardData) {
data: JSON.stringify(data),
},
success() {
- showModal({
+ utils.showModal({
title: 'Success',
body: 'This dashboard was saved successfully.',
});
},
error(error) {
const errorMsg = this.getAjaxErrorMsg(error);
- showModal({
+ utils.showModal({
title: 'Error',
body: 'Sorry, there was an error saving this dashboard: br>' + errorMsg,
});
@@ -344,7 +362,7 @@ function dashboardContainer(dashboardData) {
injectCss('dashboard-template', css);
});
$('#filters').click(() => {
- showModal({
+ utils.showModal({
title: ' Current Global Filters',
body: 'The following global filters are currently applied:
' +
dashboard.readFilters(),
diff --git a/caravel/assets/javascripts/explore/components/EmbedCodeButton.jsx b/caravel/assets/javascripts/explore/components/EmbedCodeButton.jsx
index cddd41e62a406..bf411a25be1bf 100644
--- a/caravel/assets/javascripts/explore/components/EmbedCodeButton.jsx
+++ b/caravel/assets/javascripts/explore/components/EmbedCodeButton.jsx
@@ -12,7 +12,6 @@ export default class EmbedCodeButton extends React.Component {
this.state = {
height: '400',
width: '600',
- srcLink: window.location.origin + props.slice.data.standalone_endpoint,
};
this.handleInputChange = this.handleInputChange.bind(this);
}
@@ -26,11 +25,15 @@ export default class EmbedCodeButton extends React.Component {
}
generateEmbedHTML() {
- const { width, height, srcLink } = this.state;
+ const srcLink = window.location.origin + this.props.slice.data.standalone_endpoint;
/* eslint max-len: 0 */
- const embedHTML =
- ``;
- return embedHTML;
+ return `
+ `;
}
renderPopover() {
diff --git a/caravel/assets/javascripts/explore/components/ExploreActionButtons.jsx b/caravel/assets/javascripts/explore/components/ExploreActionButtons.jsx
index 95399b080677d..a887df86ccbd9 100644
--- a/caravel/assets/javascripts/explore/components/ExploreActionButtons.jsx
+++ b/caravel/assets/javascripts/explore/components/ExploreActionButtons.jsx
@@ -13,7 +13,6 @@ export default function ExploreActionButtons({ canDownload, slice }) {
const exportToCSVClasses = cx('btn btn-default btn-sm', {
'disabled disabledButton': !canDownload,
});
-
return (
diff --git a/caravel/assets/javascripts/explore/components/URLShortLinkButton.jsx b/caravel/assets/javascripts/explore/components/URLShortLinkButton.jsx
index 847ff2ebcd997..fd80039c26cf0 100644
--- a/caravel/assets/javascripts/explore/components/URLShortLinkButton.jsx
+++ b/caravel/assets/javascripts/explore/components/URLShortLinkButton.jsx
@@ -13,8 +13,6 @@ export default class URLShortLinkButton extends React.Component {
this.state = {
shortUrl: '',
};
-
- this.getShortUrl();
}
getShortUrl() {
@@ -22,7 +20,7 @@ export default class URLShortLinkButton extends React.Component {
type: 'POST',
url: '/r/shortner/',
data: {
- data: '/' + window.location.pathname + this.props.slice.querystring(),
+ data: '/' + window.location.pathname + window.location.search,
},
success: (data) => {
this.setState({
@@ -51,16 +49,15 @@ export default class URLShortLinkButton extends React.Component {
}
render() {
- const shortUrl = this.state.shortUrl;
- const isDisabled = shortUrl === '';
return (
-
+
diff --git a/caravel/assets/javascripts/explore/explore.jsx b/caravel/assets/javascripts/explore/explore.jsx
index 79aff8fe3b902..43850e5c5bdfb 100644
--- a/caravel/assets/javascripts/explore/explore.jsx
+++ b/caravel/assets/javascripts/explore/explore.jsx
@@ -5,7 +5,7 @@
// js
const $ = window.$ = require('jquery');
const px = require('./../modules/caravel.js');
-const showModal = require('./../modules/utils.js').showModal;
+const utils = require('./../modules/utils.js');
const jQuery = window.jQuery = require('jquery'); // eslint-disable-line
import React from 'react';
@@ -81,7 +81,7 @@ function saveSlice() {
if (action === 'saveas') {
const sliceName = $('input[name=new_slice_name]').val();
if (sliceName === '') {
- showModal({
+ utils.showModal({
title: 'Error',
body: 'You must pick a name for the new slice',
});
@@ -91,13 +91,13 @@ function saveSlice() {
}
const addToDash = $('input[name=addToDash]:checked').val();
if (addToDash === 'existing' && $('#save_to_dashboard_id').val() === '') {
- showModal({
+ utils.showModal({
title: 'Error',
body: 'You must pick an existing dashboard',
});
return;
} else if (addToDash === 'new' && $('input[name=new_dashboard_name]').val() === '') {
- showModal({
+ utils.showModal({
title: 'Error',
body: 'Please enter a name for the new dashboard',
});
@@ -336,6 +336,17 @@ function initExploreView() {
prepSaveDialog();
}
+function renderExploreActions() {
+ const exploreActionsEl = document.getElementById('js-explore-actions');
+ ReactDOM.render(
+ ,
+ exploreActionsEl
+ );
+}
+
function initComponents() {
const queryAndSaveBtnsEl = document.getElementById('js-query-and-save-btns');
ReactDOM.render(
@@ -345,25 +356,40 @@ function initComponents() {
/>,
queryAndSaveBtnsEl
);
-
- const exploreActionsEl = document.getElementById('js-explore-actions');
- ReactDOM.render(
- ,
- exploreActionsEl
- );
+ renderExploreActions();
}
+let exploreController = {
+ type: 'slice',
+ done: (sliceObj) => {
+ slice = sliceObj;
+ renderExploreActions();
+ const cachedSelector = $('#is_cached');
+ if (slice.data !== undefined && slice.data.is_cached) {
+ cachedSelector
+ .attr(
+ 'title',
+ `Served from data cached at ${slice.data.cached_dttm}. Click [Query] to force refresh`)
+ .show()
+ .tooltip('fixTitle');
+ } else {
+ cachedSelector.hide();
+ }
+ },
+ error: (sliceObj) => {
+ slice = sliceObj;
+ renderExploreActions();
+ },
+};
+exploreController = Object.assign({}, utils.controllerInterface, exploreController);
+
+
$(document).ready(function () {
const data = $('.slice').data('slice');
initExploreView();
- slice = px.Slice(data);
-
- $('.slice').data('slice', slice);
+ slice = px.Slice(data, exploreController);
// call vis render method, which issues ajax
// calls render on the slice for the first time
diff --git a/caravel/assets/javascripts/modules/caravel.js b/caravel/assets/javascripts/modules/caravel.js
index 9094a985541c0..e2c3c72b99f89 100644
--- a/caravel/assets/javascripts/modules/caravel.js
+++ b/caravel/assets/javascripts/modules/caravel.js
@@ -25,40 +25,41 @@ const px = function () {
}
}
$('.favstar')
- .attr('title', 'Click to favorite/unfavorite')
- .css('cursor', 'pointer')
- .each(show)
- .each(function () {
- let url = baseUrl + $(this).attr('class_name');
- const star = this;
- url += '/' + $(this).attr('obj_id') + '/';
- $.getJSON(url + 'count/', function (data) {
- if (data.count > 0) {
- $(star).addClass('selected').each(show);
- }
- });
- })
- .click(function () {
- $(this).toggleClass('selected');
- let url = baseUrl + $(this).attr('class_name');
- url += '/' + $(this).attr('obj_id') + '/';
- if ($(this).hasClass('selected')) {
- url += 'select/';
- } else {
- url += 'unselect/';
+ .attr('title', 'Click to favorite/unfavorite')
+ .css('cursor', 'pointer')
+ .each(show)
+ .each(function () {
+ let url = baseUrl + $(this).attr('class_name');
+ const star = this;
+ url += '/' + $(this).attr('obj_id') + '/';
+ $.getJSON(url + 'count/', function (data) {
+ if (data.count > 0) {
+ $(star).addClass('selected').each(show);
}
- $.get(url);
- $(this).each(show);
- })
+ });
+ })
+ .click(function () {
+ $(this).toggleClass('selected');
+ let url = baseUrl + $(this).attr('class_name');
+ url += '/' + $(this).attr('obj_id') + '/';
+ if ($(this).hasClass('selected')) {
+ url += 'select/';
+ } else {
+ url += 'unselect/';
+ }
+ $.get(url);
+ $(this).each(show);
+ })
.tooltip();
}
- const Slice = function (data, dashboard) {
+ const Slice = function (data, controller) {
let timer;
const token = $('#' + data.token);
const containerId = data.token + '_con';
const selector = '#' + containerId;
const container = $(selector);
const sliceId = data.slice_id;
+ const origJsonEndpoint = data.json_endpoint;
let dttm = 0;
const stopwatch = function () {
dttm += 10;
@@ -76,16 +77,13 @@ const px = function () {
container,
containerId,
selector,
- querystring(params) {
- const newParams = params || {};
+ querystring() {
const parser = document.createElement('a');
parser.href = data.json_endpoint;
- if (dashboard !== undefined) {
- let flts = '';
- if (newParams.extraFilters !== false) {
- flts = dashboard.effectiveExtraFilters(sliceId);
- flts = encodeURIComponent(JSON.stringify(flts));
- }
+ if (controller.type === 'dashboard') {
+ parser.href = origJsonEndpoint;
+ let flts = controller.effectiveExtraFilters(sliceId);
+ flts = encodeURIComponent(JSON.stringify(flts));
qrystr = parser.search + '&extra_filters=' + flts;
} else if ($('#query').length === 0) {
qrystr = parser.search;
@@ -104,11 +102,10 @@ const px = function () {
};
return Mustache.render(s, context);
},
- jsonEndpoint(params) {
- const newParams = params || {};
+ jsonEndpoint() {
const parser = document.createElement('a');
parser.href = data.json_endpoint;
- let endpoint = parser.pathname + this.querystring({ extraFilters: newParams.extraFilters });
+ let endpoint = parser.pathname + this.querystring();
endpoint += '&json=true';
endpoint += '&force=' + this.force;
return endpoint;
@@ -116,43 +113,16 @@ const px = function () {
d3format(col, number) {
// uses the utils memoized d3format function and formats based on
// column level defined preferences
- const format = this.data.column_formats[col];
+ const format = data.column_formats[col];
return utils.d3format(format, number);
},
/* eslint no-shadow: 0 */
- done(data) {
+ done(payload) {
+ Object.assign(data, payload);
+
clearInterval(timer);
token.find('img.loading').hide();
container.show();
- let cachedSelector = null;
- if (dashboard === undefined) {
- cachedSelector = $('#is_cached');
- if (data !== undefined && data.is_cached) {
- cachedSelector
- .attr(
- 'title',
- `Served from data cached at ${data.cached_dttm}. Click [Query] to force-refresh`)
- .show()
- .tooltip('fixTitle');
- } else {
- cachedSelector.hide();
- }
- } else {
- const refresh = this.getWidgetHeader().find('.refresh');
- if (data !== undefined && data.is_cached) {
- refresh
- .addClass('danger')
- .attr('title',
- 'Served from data cached at ' + data.cached_dttm +
- '. Click to force-refresh')
- .tooltip('fixTitle');
- } else {
- refresh
- .removeClass('danger')
- .attr('title', 'Click to force-refresh')
- .tooltip('fixTitle');
- }
- }
if (data !== undefined) {
slice.viewSqlQuery = data.query;
@@ -162,6 +132,7 @@ const px = function () {
$('#timer').addClass('label-success');
$('.query-and-save button').removeAttr('disabled');
always(data);
+ controller.done(this);
},
getErrorMsg(xhr) {
if (xhr.statusText === 'timeout') {
@@ -193,6 +164,7 @@ const px = function () {
$('#timer').addClass('btn-danger');
$('.query-and-save button').removeAttr('disabled');
always(data);
+ controller.error(this);
},
width() {
return token.width();
@@ -238,30 +210,19 @@ const px = function () {
this.viz.resize();
},
addFilter(col, vals) {
- if (dashboard !== undefined) {
- dashboard.addFilter(sliceId, col, vals);
- }
+ controller.addFilter(sliceId, col, vals);
},
setFilter(col, vals) {
- if (dashboard !== undefined) {
- dashboard.setFilter(sliceId, col, vals);
- }
+ controller.setFilter(sliceId, col, vals);
},
getFilters() {
- if (dashboard !== undefined) {
- return dashboard.filters[sliceId];
- }
- return false;
+ return controller.filters[sliceId];
},
clearFilter() {
- if (dashboard !== undefined) {
- dashboard.clearFilter(sliceId);
- }
+ controller.clearFilter(sliceId);
},
removeFilter(col, vals) {
- if (dashboard !== undefined) {
- dashboard.removeFilter(sliceId, col, vals);
- }
+ controller.removeFilter(sliceId, col, vals);
},
};
slice.viz = vizMap[data.form_data.viz_type](slice);
diff --git a/caravel/assets/javascripts/modules/utils.js b/caravel/assets/javascripts/modules/utils.js
index 3d37ef608bc11..bc0c0c0b40382 100644
--- a/caravel/assets/javascripts/modules/utils.js
+++ b/caravel/assets/javascripts/modules/utils.js
@@ -103,7 +103,23 @@ function d3format(format, number) {
}
return formatters[format](number);
}
+
+// Slice objects interact with their context through objects that implement
+// this controllerInterface (dashboard, explore, standalone)
+const controllerInterface = {
+ type: null,
+ done: () => {},
+ error: () => {},
+ always: () => {},
+ addFiler: () => {},
+ setFilter: () => {},
+ getFilters: () => false,
+ clearFilter: () => {},
+ removeFilter: () => {},
+};
+
module.exports = {
+ controllerInterface,
d3format,
fixDataTableBodyHeight,
showModal,
diff --git a/caravel/assets/javascripts/standalone.js b/caravel/assets/javascripts/standalone.js
index e0e8f9c27b348..e12618a8e1ea6 100644
--- a/caravel/assets/javascripts/standalone.js
+++ b/caravel/assets/javascripts/standalone.js
@@ -2,12 +2,16 @@ const $ = window.$ = require('jquery');
/* eslint no-unused-vars: 0 */
const jQuery = window.jQuery = $;
const px = require('./modules/caravel.js');
+const utils = require('./modules/utils.js');
require('bootstrap');
+const standaloneController = Object.assign(
+ {}, utils.controllerInterface, { type: 'standalone' });
+
$(document).ready(function () {
const data = $('.slice').data('slice');
- const slice = px.Slice(data);
+ const slice = px.Slice(data, standaloneController);
slice.render();
slice.bindResizeToWindowResize();
});
diff --git a/caravel/assets/spec/javascripts/explore/components/EmbedCodeButton_spec.jsx b/caravel/assets/spec/javascripts/explore/components/EmbedCodeButton_spec.jsx
index 62f73c6d37482..a171a9a141559 100644
--- a/caravel/assets/spec/javascripts/explore/components/EmbedCodeButton_spec.jsx
+++ b/caravel/assets/spec/javascripts/explore/components/EmbedCodeButton_spec.jsx
@@ -31,7 +31,13 @@ describe('EmbedCodeButton', () => {
width: '2000',
srcLink: 'http://localhost/endpoint_url',
});
- const embedHTML = ``;
+ const embedHTML = `
+ `;
expect(wrapper.instance().generateEmbedHTML()).to.equal(embedHTML);
});
});