diff --git a/caravel/assets/javascripts/dashboard/Dashboard.jsx b/caravel/assets/javascripts/dashboard/Dashboard.jsx index 72d9187902..9142cbebd0 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: ' + 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: ' + 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 cddd41e62a..bf411a25be 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 95399b0806..a887df86cc 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 847ff2ebcd..fd80039c26 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 79aff8fe3b..43850e5c5b 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,16 +336,7 @@ function initExploreView() { prepSaveDialog(); } -function initComponents() { - const queryAndSaveBtnsEl = document.getElementById('js-query-and-save-btns'); - ReactDOM.render( - query(true)} - />, - queryAndSaveBtnsEl - ); - +function renderExploreActions() { const exploreActionsEl = document.getElementById('js-explore-actions'); ReactDOM.render( query(true)} + />, + queryAndSaveBtnsEl + ); + 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 9094a98554..e2c3c72b99 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 3d37ef608b..bc0c0c0b40 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 e0e8f9c27b..e12618a8e1 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 62f73c6d37..a171a9a141 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); }); });