[WiP] rename project from Caravel to Superset (#1576)

* Change in files

* Renamin files and folders

* cleaning up a single piece of lint

* Removing boat picture from docs

* add superset word mark

* Update rename note in docs

* Fixing images

* Pinning datatables

* Fixing issues with mapbox-gl

* Forgot to rename one file

* Linting

* v0.13.0

* adding pyyaml to dev-reqs
This commit is contained in:
Maxime Beauchemin
2016-11-09 23:08:22 -08:00
committed by GitHub
parent 973537fd9a
commit 15b67b2c6c
408 changed files with 2795 additions and 2787 deletions

View File

@@ -0,0 +1,37 @@
import React, { PropTypes } from 'react';
import ModalTrigger from './../../components/ModalTrigger';
const propTypes = {
slice: PropTypes.object.isRequired,
};
export default class DisplayQueryButton extends React.Component {
constructor(props) {
super(props);
this.state = {
viewSqlQuery: '',
};
this.beforeOpen = this.beforeOpen.bind(this);
}
beforeOpen() {
this.setState({
viewSqlQuery: this.props.slice.viewSqlQuery,
});
}
render() {
const modalBody = (<pre>{this.state.viewSqlQuery}</pre>);
return (
<ModalTrigger
isButton
triggerNode={<span>Query</span>}
modalTitle="Query"
modalBody={modalBody}
beforeOpen={this.beforeOpen}
/>
);
}
}
DisplayQueryButton.propTypes = propTypes;

View File

@@ -0,0 +1,108 @@
import React, { PropTypes } from 'react';
import CopyToClipboard from './../../components/CopyToClipboard';
import { Popover, OverlayTrigger } from 'react-bootstrap';
const propTypes = {
slice: PropTypes.object.isRequired,
};
export default class EmbedCodeButton extends React.Component {
constructor(props) {
super(props);
this.state = {
height: '400',
width: '600',
};
this.handleInputChange = this.handleInputChange.bind(this);
}
handleInputChange(e) {
const value = e.currentTarget.value;
const name = e.currentTarget.name;
const data = {};
data[name] = value;
this.setState(data);
}
generateEmbedHTML() {
const srcLink = window.location.origin + this.props.slice.data.standalone_endpoint;
/* eslint max-len: 0 */
return `
<iframe
src="${srcLink}"
width="${this.state.width}"
height="${this.state.height}"
seamless frameBorder="0" scrolling="no">
</iframe>`;
}
renderPopover() {
const html = this.generateEmbedHTML();
return (
<Popover id="embed-code-popover">
<div>
<div className="row">
<div className="col-sm-10">
<textarea name="embedCode" value={html} rows="4" readOnly className="form-control input-sm"></textarea>
</div>
<div className="col-sm-2">
<CopyToClipboard
shouldShowText={false}
text={html}
copyNode={<i className="fa fa-clipboard" title="Copy to clipboard"></i>}
/>
</div>
</div>
<br />
<div className="row">
<div className="col-md-6 col-sm-12">
<div className="form-group">
<small>
<label className="control-label" htmlFor="embed-height">Height</label>
</small>
<input
className="form-control input-sm"
type="text"
defaultValue={this.state.height}
name="height"
onChange={this.handleInputChange}
/>
</div>
</div>
<div className="col-md-6 col-sm-12">
<div className="form-group">
<small>
<label className="control-label" htmlFor="embed-width">Width</label>
</small>
<input
className="form-control input-sm"
type="text"
defaultValue={this.state.width}
name="width"
onChange={this.handleInputChange}
id="embed-width"
/>
</div>
</div>
</div>
</div>
</Popover>
);
}
render() {
return (
<OverlayTrigger
trigger="click"
rootClose
placement="left"
overlay={this.renderPopover()}
>
<span className="btn btn-default btn-sm">
<i className="fa fa-code"></i>&nbsp;
</span>
</OverlayTrigger>
);
}
}
EmbedCodeButton.propTypes = propTypes;

View File

@@ -0,0 +1,45 @@
import React, { PropTypes } from 'react';
import cx from 'classnames';
import URLShortLinkButton from './URLShortLinkButton';
import EmbedCodeButton from './EmbedCodeButton';
import DisplayQueryButton from './DisplayQueryButton';
const propTypes = {
canDownload: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]).isRequired,
slice: PropTypes.object.isRequired,
};
export default function ExploreActionButtons({ canDownload, slice }) {
const exportToCSVClasses = cx('btn btn-default btn-sm', {
'disabled disabledButton': !canDownload,
});
return (
<div className="btn-group results" role="group">
<URLShortLinkButton slice={slice} />
<EmbedCodeButton slice={slice} />
<a
href={slice.data.json_endpoint}
className="btn btn-default btn-sm"
title="Export to .json"
target="_blank"
>
<i className="fa fa-file-code-o"></i> .json
</a>
<a
href={slice.data.csv_endpoint}
className={exportToCSVClasses}
title="Export to .csv format"
target="_blank"
>
<i className="fa fa-file-text-o"></i> .csv
</a>
<DisplayQueryButton slice={slice} />
</div>
);
}
ExploreActionButtons.propTypes = propTypes;

View File

@@ -0,0 +1,31 @@
import React, { PropTypes } from 'react';
import classnames from 'classnames';
const propTypes = {
canAdd: PropTypes.string.isRequired,
onQuery: PropTypes.func.isRequired,
};
export default function QueryAndSaveBtns({ canAdd, onQuery }) {
const saveClasses = classnames('btn btn-default btn-sm', {
'disabled disabledButton': canAdd !== 'True',
});
return (
<div className="btn-group query-and-save">
<button type="button" className="btn btn-primary btn-sm" onClick={onQuery}>
<i className="fa fa-bolt"></i> Query
</button>
<button
type="button"
className={saveClasses}
data-target="#save_modal"
data-toggle="modal"
>
<i className="fa fa-plus-circle"></i> Save as
</button>
</div>
);
}
QueryAndSaveBtns.propTypes = propTypes;

View File

@@ -0,0 +1,57 @@
import React, { PropTypes } from 'react';
import { Popover, OverlayTrigger } from 'react-bootstrap';
import CopyToClipboard from './../../components/CopyToClipboard';
import { getShortUrl } from '../../../utils/common';
const propTypes = {
slice: PropTypes.object.isRequired,
};
export default class URLShortLinkButton extends React.Component {
constructor(props) {
super(props);
this.state = {
shortUrl: '',
};
}
onShortUrlSuccess(data) {
this.setState({
shortUrl: data,
});
}
getCopyUrl() {
const longUrl = window.location.pathname + window.location.search;
getShortUrl(longUrl, this.onShortUrlSuccess.bind(this));
}
renderPopover() {
return (
<Popover id="shorturl-popover">
<CopyToClipboard
text={this.state.shortUrl}
copyNode={<i className="fa fa-clipboard" title="Copy to clipboard"></i>}
/>
</Popover>
);
}
render() {
return (
<OverlayTrigger
trigger="click"
rootClose
placement="left"
onEnter={this.getCopyUrl.bind(this)}
overlay={this.renderPopover()}
>
<span className="btn btn-default btn-sm">
<i className="fa fa-link"></i>&nbsp;
</span>
</OverlayTrigger>
);
}
}
URLShortLinkButton.propTypes = propTypes;

View File

@@ -0,0 +1,401 @@
// Javascript for the explorer page
// Init explorer view -> load vis dependencies -> read data (from dynamic html) -> render slice
// nb: to add a new vis, you must also add a Python fn in viz.py
//
// js
const $ = window.$ = require('jquery');
const px = require('./../modules/superset.js');
const utils = require('./../modules/utils.js');
const jQuery = window.jQuery = require('jquery'); // eslint-disable-line
import React from 'react';
import ReactDOM from 'react-dom';
import QueryAndSaveBtns from './components/QueryAndSaveBtns.jsx';
import ExploreActionButtons from './components/ExploreActionButtons.jsx';
require('jquery-ui');
$.widget.bridge('uitooltip', $.ui.tooltip); // Shutting down jq-ui tooltips
require('bootstrap');
require('./../superset-select2.js');
// css
require('../../vendor/pygments.css');
require('../../stylesheets/explore.css');
let slice;
const getPanelClass = function (fieldPrefix) {
return (fieldPrefix === 'flt' ? 'filter' : 'having') + '_panel';
};
function prepForm() {
// Assigning the right id to form elements in filters
const fixId = function ($filter, fieldPrefix, i) {
$filter.attr('id', function () {
return fieldPrefix + '_' + i;
});
['col', 'op', 'eq'].forEach(function (fieldMiddle) {
const fieldName = fieldPrefix + '_' + fieldMiddle;
$filter.find('[id^=' + fieldName + '_]')
.attr('id', function () {
return fieldName + '_' + i;
})
.attr('name', function () {
return fieldName + '_' + i;
});
});
};
['flt', 'having'].forEach(function (fieldPrefix) {
let i = 1;
$('#' + getPanelClass(fieldPrefix) + ' #filters > div').each(function () {
fixId($(this), fieldPrefix, i);
i++;
});
});
}
function query(forceUpdate, pushState) {
let force = forceUpdate;
if (force === undefined) {
force = false;
}
$('.query-and-save button').attr('disabled', 'disabled');
if (force) { // Don't hide the alert message when the page is just loaded
$('div.alert').remove();
}
$('#is_cached').hide();
prepForm();
if (pushState !== false) {
// update the url after prepForm() fix the field ids
history.pushState({}, document.title, slice.querystring());
}
slice.render(force);
}
function saveSlice() {
const action = $('input[name=rdo_save]:checked').val();
if (action === 'saveas') {
const sliceName = $('input[name=new_slice_name]').val();
if (sliceName === '') {
utils.showModal({
title: 'Error',
body: 'You must pick a name for the new slice',
});
return;
}
document.getElementById('slice_name').value = sliceName;
}
const addToDash = $('input[name=addToDash]:checked').val();
if (addToDash === 'existing' && $('#save_to_dashboard_id').val() === '') {
utils.showModal({
title: 'Error',
body: 'You must pick an existing dashboard',
});
return;
} else if (addToDash === 'new' && $('input[name=new_dashboard_name]').val() === '') {
utils.showModal({
title: 'Error',
body: 'Please enter a name for the new dashboard',
});
return;
}
$('#action').val(action);
prepForm();
$('#query').submit();
}
function initExploreView() {
function getCollapsedFieldsets() {
let collapsedFieldsets = $('#collapsedFieldsets').val();
if (collapsedFieldsets !== undefined && collapsedFieldsets !== '') {
collapsedFieldsets = collapsedFieldsets.split('||');
} else {
collapsedFieldsets = [];
}
return collapsedFieldsets;
}
function toggleFieldset(legend, animation) {
const parent = legend.parent();
const fieldset = parent.find('.legend_label').text();
const collapsedFieldsets = getCollapsedFieldsets();
let index;
if (parent.hasClass('collapsed')) {
if (animation) {
parent.find('.panel-body').slideDown();
} else {
parent.find('.panel-body').show();
}
parent.removeClass('collapsed');
parent.find('span.collapser').text('[-]');
// removing from array, js is overcomplicated
index = collapsedFieldsets.indexOf(fieldset);
if (index !== -1) {
collapsedFieldsets.splice(index, 1);
}
} else { // not collapsed
if (animation) {
parent.find('.panel-body').slideUp();
} else {
parent.find('.panel-body').hide();
}
parent.addClass('collapsed');
parent.find('span.collapser').text('[+]');
index = collapsedFieldsets.indexOf(fieldset);
if (index === -1 && fieldset !== '' && fieldset !== undefined) {
collapsedFieldsets.push(fieldset);
}
}
$('#collapsedFieldsets').val(collapsedFieldsets.join('||'));
}
px.initFavStars();
$('#viz_type').change(function () {
$('#query').submit();
});
$('#datasource_id').change(function () {
window.location = $(this).find('option:selected').attr('url');
});
const collapsedFieldsets = getCollapsedFieldsets();
for (let i = 0; i < collapsedFieldsets.length; i++) {
toggleFieldset($('legend:contains("' + collapsedFieldsets[i] + '")'), false);
}
function formatViz(viz) {
const url = `/static/assets/images/viz_thumbnails/${viz.id}.png`;
const noImg = '/static/assets/images/noimg.png';
return $(
`<img class="viz-thumb-option" src="${url}" onerror="this.src='${noImg}';">` +
`<span>${viz.text}</span>`
);
}
$('.select2').select2({
dropdownAutoWidth: true,
});
$('.select2Sortable').select2({
dropdownAutoWidth: true,
});
$('.select2-with-images').select2({
dropdownAutoWidth: true,
dropdownCssClass: 'bigdrop',
formatResult: formatViz,
});
$('.select2Sortable').select2Sortable({
bindOrder: 'sortableStop',
});
$('form').show();
$('[data-toggle="tooltip"]').tooltip({ container: 'body' });
$('.ui-helper-hidden-accessible').remove(); // jQuery-ui 1.11+ creates a div for every tooltip
function addFilter(i, fieldPrefix) {
const cp = $('#' + fieldPrefix + '0').clone();
$(cp).appendTo('#' + getPanelClass(fieldPrefix) + ' #filters');
$(cp).show();
if (i !== undefined) {
$(cp).find('#' + fieldPrefix + '_eq_0').val(px.getParam(fieldPrefix + '_eq_' + i));
$(cp).find('#' + fieldPrefix + '_op_0').val(px.getParam(fieldPrefix + '_op_' + i));
$(cp).find('#' + fieldPrefix + '_col_0').val(px.getParam(fieldPrefix + '_col_' + i));
}
$(cp).find('select').select2();
$(cp).find('.remove').click(function () {
$(this)
.parent()
.parent()
.remove();
});
}
function setFilters() {
['flt', 'having'].forEach(function (prefix) {
for (let i = 1; i < 10; i++) {
const col = px.getParam(prefix + '_col_' + i);
if (col !== '') {
addFilter(i, prefix);
}
}
});
}
setFilters();
$(window).bind('popstate', function () {
// Browser back button
const returnLocation = history.location || document.location;
// Could do something more lightweight here, but we're not optimizing
// for the use of the back button anyways
returnLocation.reload();
});
$('#filter_panel #plus').click(function () {
addFilter(undefined, 'flt');
});
$('#having_panel #plus').click(function () {
addFilter(undefined, 'having');
});
function createChoices(term, data) {
const filtered = $(data).filter(function () {
return this.text.localeCompare(term) === 0;
});
if (filtered.length === 0) {
return {
id: term,
text: term,
};
}
return {};
}
function initSelectionToValue(element, callback) {
callback({
id: element.val(),
text: element.val(),
});
}
$('.select2_freeform').each(function () {
const parent = $(this).parent();
const name = $(this).attr('name');
const l = [];
let selected = '';
for (let i = 0; i < this.options.length; i++) {
l.push({
id: this.options[i].value,
text: this.options[i].text,
});
if (this.options[i].selected) {
selected = this.options[i].value;
}
}
parent.append(
`<input class="${$(this).attr('class')}" ` +
`name="${name}" type="text" value="${selected}">`
);
$(`input[name='${name}']`).select2({
createSearchChoice: createChoices,
initSelection: initSelectionToValue,
dropdownAutoWidth: true,
multiple: false,
data: l,
});
$(this).remove();
});
function prepSaveDialog() {
const setButtonsState = function () {
const addToDash = $('input[name=addToDash]:checked').val();
if (addToDash === 'existing' || addToDash === 'new') {
$('.gotodash').removeAttr('disabled');
} else {
$('.gotodash').prop('disabled', true);
}
};
const url = '/dashboardmodelviewasync/api/read?_flt_0_owners=' + $('#userid').val();
$.get(url, function (data) {
const choices = [];
for (let i = 0; i < data.pks.length; i++) {
choices.push({ id: data.pks[i], text: data.result[i].dashboard_title });
}
$('#save_to_dashboard_id').select2({
data: choices,
dropdownAutoWidth: true,
}).on('select2-selecting', function () {
$('#addToDash_existing').prop('checked', true);
setButtonsState();
});
});
$('input[name=addToDash]').change(setButtonsState);
$("input[name='new_dashboard_name']").on('focus', function () {
$('#add_to_new_dash').prop('checked', true);
setButtonsState();
});
$("input[name='new_slice_name']").on('focus', function () {
$('#save_as_new').prop('checked', true);
setButtonsState();
});
$('#btn_modal_save').on('click', () => saveSlice());
$('#btn_modal_save_goto_dash').click(() => {
document.getElementById('goto_dash').value = 'true';
saveSlice();
});
}
prepSaveDialog();
}
function renderExploreActions() {
const exploreActionsEl = document.getElementById('js-explore-actions');
ReactDOM.render(
<ExploreActionButtons
canDownload={exploreActionsEl.getAttribute('data-can-download')}
slice={slice}
/>,
exploreActionsEl
);
}
function initComponents() {
const queryAndSaveBtnsEl = document.getElementById('js-query-and-save-btns');
ReactDOM.render(
<QueryAndSaveBtns
canAdd={queryAndSaveBtnsEl.getAttribute('data-can-add')}
onQuery={() => 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, exploreController);
// call vis render method, which issues ajax
// calls render on the slice for the first time
query(false, false);
slice.bindResizeToWindowResize();
initComponents();
});