[js linting] use airbnb eslint settings (#796)

* add airbnb eslint settings and lint all the code

* fix linting erros
This commit is contained in:
Alanna Scott 2016-07-27 16:57:05 -07:00 committed by GitHub
parent f43e5f18d5
commit 1101de5ae4
20 changed files with 1006 additions and 1132 deletions

View File

@ -1,3 +1,6 @@
node_modules/*
vendor/*
javascripts/dist/*
visualizations/*
stylesheets/*
spec/*

View File

@ -1,234 +1,12 @@
{
"root": true,
"globals": {
"Symbol": false,
"Map": false,
"Set": false,
"Reflect": false,
},
"env": {
"es6": false,
"browser": true,
"node": true,
},
"parserOptions": {
"ecmaVersion": 5,
"sourceType": "module"
},
"extends": "airbnb",
"rules": {
"array-bracket-spacing": [2, "never", {
"singleValue": false,
"objectsInArrays": false,
"arraysInArrays": false
}],
"array-callback-return": [2],
"block-spacing": [2, "always"],
"brace-style": [2, "1tbs", { "allowSingleLine": true }],
"callback-return": [2, ["callback"]],
"camelcase": [0],
"comma-dangle": [2, "always-multiline"],
"comma-spacing": [2],
"comma-style": [2, "last"],
"curly": [2, "all"],
"eqeqeq": 2,
"func-names": [0],
"id-length": [2, { "min": 1, "max": 25, "properties": "never" }],
"key-spacing": [2, { "beforeColon": false, "afterColon": true }],
"keyword-spacing": [2, {
"before": true,
"after": true,
"overrides": {
"return": { "after": true },
"throw": { "after": true },
"case": { "after": true }
}
}],
"linebreak-style": [2, "unix"],
"lines-around-comment": [2, {
"beforeBlockComment": false,
"afterBlockComment": false,
"beforeLineComment": false,
"allowBlockStart": true,
"allowBlockEnd": true
}],
"max-depth": [2, 5],
"max-len": [0, 80, 4],
"max-nested-callbacks": [1, 3],
"max-params": [1, 4],
"new-parens": [2],
"newline-after-var": [0],
"no-bitwise": [0],
"no-cond-assign": [2],
"no-console": [1, { allow: ["warn", "error"] }],
"no-const-assign": [2],
"no-constant-condition": [2],
"no-control-regex": [2],
"no-debugger": [2],
"no-delete-var": [2],
"no-dupe-args": [2],
"no-dupe-class-members": [2],
"no-dupe-keys": [2],
"no-duplicate-case": [2],
"no-else-return": [0],
"no-empty": [2],
"no-eq-null": [0],
"no-eval": [2],
"no-ex-assign": [2],
"no-extend-native": [2],
"no-extra-bind": [2],
"no-extra-boolean-cast": [2],
"no-extra-label": [2],
"no-extra-parens": [0], // needed for clearer #math eg (a - b) / c
"no-extra-semi": [2],
"no-fallthrough": [2],
"no-floating-decimal": [2],
"no-func-assign": [2],
"no-implied-eval": [2],
"no-implicit-coercion": [2, {
"boolean": false,
"number": true,
"string": true
}],
"no-implicit-globals": [2],
"no-inline-comments": [0],
"no-invalid-regexp": [2],
"no-irregular-whitespace": [2],
"no-iterator": [2],
"no-label-var": [2],
"no-labels": [2, { "allowLoop": false, "allowSwitch": false }],
"no-lone-blocks": [2],
"no-lonely-if": [2],
"no-loop-func": [2],
"no-magic-numbers": [0], // doesn't work well with vis cosmetic constant
"no-mixed-requires": [1, false],
"no-mixed-spaces-and-tabs": [2, false],
"no-multi-spaces": [2, {
"exceptions": {
"ImportDeclaration": true,
"Property": true,
"VariableDeclarator": true
}
}],
"no-multi-str": [2],
"no-multiple-empty-lines": [2, { "max": 1, "maxEOF": 1 }],
"no-native-reassign": [2],
"no-negated-condition": [2],
"no-negated-in-lhs": [2],
"no-nested-ternary": [0],
"no-new": [2],
"no-new-func": [2],
"no-new-object": [2],
"no-new-require": [0],
"no-new-symbol": [2],
"no-new-wrappers": [2],
"no-obj-calls": [2],
"no-octal": [2],
"no-octal-escape": [2],
"no-path-concat": [0],
"no-process-env": [0],
"no-process-exit": [2],
"no-proto": [2],
"no-redeclare": [2],
"no-regex-spaces": [2],
"no-restricted-modules": [0],
"no-restricted-imports": [0],
"no-restricted-syntax": [2,
"DebuggerStatement",
"LabeledStatement",
"WithStatement"
],
"no-return-assign": [2, "always"],
"no-script-url": [2],
"no-self-assign": [2],
"no-self-compare": [0],
"no-sequences": [2],
"no-shadow-restricted-names": [2],
"no-spaced-func": [2],
"no-sparse-arrays": [2],
"no-sync": [0],
"no-ternary": [0],
"no-this-before-super": [2],
"no-throw-literal": [2],
"no-trailing-spaces": [2, { "skipBlankLines": false }],
"no-undef": [2, { "typeof": true }],
"no-undef-init": [2],
"no-undefined": [0],
"no-underscore-dangle": [0], // __data__ sometimes
"no-unexpected-multiline": [2],
"no-unmodified-loop-condition": [2],
"no-unneeded-ternary": [2],
"no-unreachable": [2],
"no-unused-expressions": [2],
"no-unused-labels": [2],
"no-unused-vars": [2, {
"vars": "all",
"args": "none", // (d, i) pattern d3 func makes difficult to enforce
"varsIgnorePattern": "jQuery"
}],
"no-use-before-define": [0],
"no-useless-call": [2],
"no-useless-concat": [2],
"no-useless-constructor": [2],
"no-void": [0],
"no-warning-comments": [0, { "terms": ["todo", "fixme", "xxx"], "location": "start" }],
"no-with": [2],
"no-whitespace-before-property": [2],
"object-curly-spacing": [2, "always"],
"object-shorthand": [2, "never"],
"one-var": [0],
"one-var-declaration-per-line": [2, "initializations"],
"operator-assignment": [0, "always"],
"padded-blocks": [0],
"prefer-arrow-callback": [0],
"prefer-const": [0],
"prefer-reflect": [0],
"prefer-rest-params": [0],
"prefer-spread": [0],
"prefer-template": [0],
"quote-props": [2, "as-needed", { "keywords": true }],
"radix": [2],
"require-yield": [2],
"semi": [2],
"semi-spacing": [2, { "before": false, "after": true }],
"sort-vars": [0],
"sort-imports": [0],
"space-before-function-paren": [2, { "anonymous": "always", "named": "never" }],
"space-before-blocks": [2, { "functions": "always", "keywords": "always" }],
"space-in-brackets": [0, "never", {
"singleValue": true,
"arraysInArrays": false,
"arraysInObjects": false,
"objectsInArrays": true,
"objectsInObjects": true,
"propertyName": false
}],
},
// Temporarily not enforced
"new-cap": [2], // @TODO more tricky for the moment
"newline-per-chained-call": [2, { "ignoreChainWithDepth": 6 }],
"no-param-reassign": [0], // turn on once default args supported
"no-shadow": [2, { // @TODO more tricky for the moment with eg 'data'
"builtinGlobals": false,
"hoist": "functions",
"allow": ["i", "d"]
}],
"space-in-parens": [2, "never"],
"space-infix-ops": [2],
"space-unary-ops": [2, { "words": true, "nonwords": false }],
"spaced-comment": [2, "always", { "markers": ["!"] }],
"spaced-line-comment": [0, "always"],
"strict": [2, "global"],
"template-curly-spacing": [2, "never"],
"use-isnan": [2],
"valid-jsdoc": [0],
"valid-typeof": [2],
"vars-on-top": [0],
"wrap-iife": [2],
"wrap-regex": [2],
"yield-star-spacing": [2, { "before": false, "after": true }],
"yoda": [2, "never", { "exceptRange": true, "onlyEquality": false }]
"prefer-template": 0,
"new-cap": 0,
"no-restricted-syntax": 0,
"guard-for-in": 0,
"prefer-arrow-callback": 0,
"func-names": 0,
"react/jsx-no-bind": 0,
}
}

View File

@ -1,11 +1,10 @@
var $ = require('jquery');
var utils = require('./modules/utils');
const $ = require('jquery');
const utils = require('./modules/utils');
$(document).ready(function () {
$(':checkbox[data-checkbox-api-prefix]').change(function () {
var $this = $(this);
var prefix = $this.data('checkbox-api-prefix');
var id = $this.attr('id');
utils.toggleCheckbox(prefix, "#" + id);
});
$(':checkbox[data-checkbox-api-prefix]').change(function () {
const $this = $(this);
const prefix = $this.data('checkbox-api-prefix');
const id = $this.attr('id');
utils.toggleCheckbox(prefix, '#' + id);
});
});

View File

@ -1,39 +1,61 @@
var $ = window.$ = require('jquery');
var jQuery = window.jQuery = $;
var px = require('../modules/caravel.js');
var d3 = require('d3');
var urlLib = require('url');
var showModal = require('../modules/utils.js').showModal;
const $ = window.$ = require('jquery');
const jQuery = window.jQuery = $;
const px = require('../modules/caravel.js');
const d3 = require('d3');
const urlLib = require('url');
const showModal = require('../modules/utils.js').showModal;
import React from 'react';
import { render } from 'react-dom';
import SliceAdder from './components/SliceAdder.jsx';
import GridLayout from './components/GridLayout.jsx';
var ace = require('brace');
const ace = require('brace');
require('bootstrap');
require('brace/mode/css');
require('brace/theme/crimson_editor');
require('./main.css');
require('../caravel-select2.js');
// Injects the passed css string into a style sheet with the specified className
// If a stylesheet doesn't exist with the passed className, one will be injected into <head>
function injectCss(className, css) {
const head = document.head || document.getElementsByTagName('head')[0];
let style = document.querySelector('.' + className);
var Dashboard = function (dashboardData) {
var dashboard = $.extend(dashboardData, {
if (!style) {
if (className.split(' ').length > 1) {
throw new Error('This method only supports selections with a single class name.');
}
style = document.createElement('style');
style.className = className;
style.type = 'text/css';
head.appendChild(style);
}
if (style.styleSheet) {
style.styleSheet.cssText = css;
} else {
style.innerHTML = css;
}
}
function dashboardContainer(dashboardData) {
let dashboard = $.extend(dashboardData, {
filters: {},
init: function () {
init() {
this.initDashboardView();
this.firstLoad = true;
px.initFavStars();
var sliceObjects = [],
dash = this;
dashboard.slices.forEach(function (data) {
const sliceObjects = [];
const dash = this;
dashboard.slices.forEach((data) => {
if (data.error) {
var html = '<div class="alert alert-danger">' + data.error + '</div>';
$('#slice_' + data.slice_id).find('.token').html(html);
const html = '<div class="alert alert-danger">' + data.error + '</div>';
$('#slice_' + data.sliceId).find('.token').html(html);
} else {
var slice = px.Slice(data, dash);
$('#slice_' + data.slice_id).find('a.refresh').click(function () {
const slice = px.Slice(data, dash);
$('#slice_' + data.sliceId).find('a.refresh').click(() => {
slice.render(true);
});
sliceObjects.push(slice);
@ -45,81 +67,81 @@ var Dashboard = function (dashboardData) {
this.startPeriodicRender(0);
this.bindResizeToWindowResize();
},
loadPreSelectFilters: function () {
loadPreSelectFilters() {
try {
var filters = JSON.parse(px.getParam("preselect_filters") || "{}");
for (var slice_id in filters) {
for (var col in filters[slice_id]) {
this.setFilter(slice_id, col, filters[slice_id][col], false, false);
const filters = JSON.parse(px.getParam('preselect_filters') || '{}');
for (const sliceId in filters) {
for (const col in filters[sliceId]) {
this.setFilter(sliceId, col, filters[sliceId][col], false, false);
}
}
} catch (e) {
console.error(e);
// console.error(e);
}
},
setFilter: function (slice_id, col, vals, refresh) {
this.addFilter(slice_id, col, vals, false, refresh);
setFilter(sliceId, col, vals, refresh) {
this.addFilter(sliceId, col, vals, false, refresh);
},
addFilter: function (slice_id, col, vals, merge = true, refresh = true) {
if (!(slice_id in this.filters)) {
this.filters[slice_id] = {};
addFilter(sliceId, col, vals, merge = true, refresh = true) {
if (!(sliceId in this.filters)) {
this.filters[sliceId] = {};
}
if (!(col in this.filters[slice_id]) || !merge) {
this.filters[slice_id][col] = vals;
if (!(col in this.filters[sliceId]) || !merge) {
this.filters[sliceId][col] = vals;
} else {
this.filters[slice_id][col] = d3.merge([this.filters[slice_id][col], vals]);
this.filters[sliceId][col] = d3.merge([this.filters[sliceId][col], vals]);
}
if (refresh) {
this.refreshExcept(slice_id);
this.refreshExcept(sliceId);
}
this.updateFilterParamsInUrl();
},
readFilters: function () {
readFilters() {
// Returns a list of human readable active filters
return JSON.stringify(this.filters, null, 4);
},
updateFilterParamsInUrl: function () {
var urlObj = urlLib.parse(location.href, true);
updateFilterParamsInUrl() {
const urlObj = urlLib.parse(location.href, true);
urlObj.query = urlObj.query || {};
urlObj.query.preselect_filters = this.readFilters();
urlObj.search = null;
history.pushState(urlObj.query, window.title, urlLib.format(urlObj));
},
bindResizeToWindowResize: function () {
var resizeTimer;
var dash = this;
$(window).on('resize', function (e) {
bindResizeToWindowResize() {
let resizeTimer;
const dash = this;
$(window).on('resize', () => {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(function () {
dash.slices.forEach(function (slice) {
resizeTimer = setTimeout(() => {
dash.slices.forEach((slice) => {
slice.resize();
});
}, 500);
});
},
stopPeriodicRender: function () {
stopPeriodicRender() {
if (this.refreshTimer) {
clearTimeout(this.refreshTimer);
this.refreshTimer = null;
}
},
startPeriodicRender: function (interval) {
startPeriodicRender(interval) {
this.stopPeriodicRender();
var dash = this;
var maxRandomDelay = Math.min(interval * 0.2, 5000);
var refreshAll = function () {
const dash = this;
const maxRandomDelay = Math.min(interval * 0.2, 5000);
const refreshAll = function () {
dash.slices.forEach(function (slice) {
var force = !dash.firstLoad;
const force = !dash.firstLoad;
setTimeout(function () {
slice.render(force);
},
//Randomize to prevent all widgets refreshing at the same time
// Randomize to prevent all widgets refreshing at the same time
maxRandomDelay * Math.random());
});
dash.firstLoad = false;
};
var fetchAndRender = function () {
const fetchAndRender = function () {
refreshAll();
if (interval > 0) {
dash.refreshTimer = setTimeout(function () {
@ -129,122 +151,130 @@ var Dashboard = function (dashboardData) {
};
fetchAndRender();
},
refreshExcept: function (slice_id) {
var immune = this.metadata.filter_immune_slices || [];
refreshExcept(sliceId) {
const immune = this.metadata.filter_immune_slices || [];
this.slices.forEach(function (slice) {
if (slice.data.slice_id !== slice_id && immune.indexOf(slice.data.slice_id) === -1) {
if (slice.data.sliceId !== sliceId && immune.indexOf(slice.data.sliceId) === -1) {
slice.render();
}
});
},
clearFilters: function (slice_id) {
delete this.filters[slice_id];
this.refreshExcept(slice_id);
clearFilters(sliceId) {
delete this.filters[sliceId];
this.refreshExcept(sliceId);
this.updateFilterParamsInUrl();
},
removeFilter: function (slice_id, col, vals) {
if (slice_id in this.filters) {
if (col in this.filters[slice_id]) {
var a = [];
this.filters[slice_id][col].forEach(function (v) {
removeFilter(sliceId, col, vals) {
if (sliceId in this.filters) {
if (col in this.filters[sliceId]) {
const a = [];
this.filters[sliceId][col].forEach(function (v) {
if (vals.indexOf(v) < 0) {
a.push(v);
}
});
this.filters[slice_id][col] = a;
this.filters[sliceId][col] = a;
}
}
this.refreshExcept(slice_id);
this.refreshExcept(sliceId);
this.updateFilterParamsInUrl();
},
getSlice: function (slice_id) {
slice_id = parseInt(slice_id, 10);
for (var i=0; i < this.slices.length; i++) {
if (this.slices[i].data.slice_id === slice_id) {
return this.slices[i];
getSlice(sliceId) {
const id = parseInt(sliceId, 10);
let i = 0;
let slice = null;
while (i < this.slices.length) {
// when the slice is found, assign to slice and break;
if (this.slices[i].data.slice_id === id) {
slice = this.slices[i];
break;
}
i++;
}
return slice;
},
showAddSlice: function () {
var slicesOnDashMap = {};
this.reactGridLayout.serialize().forEach(function (position) {
showAddSlice() {
const slicesOnDashMap = {};
const layoutPositions = this.reactGridLayout.serialize();
layoutPositions.forEach((position) => {
slicesOnDashMap[position.slice_id] = true;
}, this);
});
render(
<SliceAdder dashboard={dashboard} slicesOnDashMap={slicesOnDashMap} caravel={px} />,
document.getElementById("add-slice-container")
document.getElementById('add-slice-container')
);
},
getAjaxErrorMsg: function (error) {
var respJSON = error.responseJSON;
getAjaxErrorMsg(error) {
const respJSON = error.responseJSON;
return (respJSON && respJSON.message) ? respJSON.message :
error.responseText;
},
addSlicesToDashboard: function (sliceIds) {
addSlicesToDashboard(sliceIds) {
const getAjaxErrorMsg = this.getAjaxErrorMsg;
$.ajax({
type: "POST",
type: 'POST',
url: '/caravel/add_slices/' + dashboard.id + '/',
data: {
data: JSON.stringify({ slice_ids: sliceIds })
data: JSON.stringify({ slice_ids: sliceIds }),
},
success: function () {
success() {
// Refresh page to allow for slices to re-render
window.location.reload();
},
error: function (error) {
var errorMsg = this.getAjaxErrorMsg(error);
error(error) {
const errorMsg = getAjaxErrorMsg(error);
showModal({
title: "Error",
body: "Sorry, there was an error adding slices to this dashboard: </ br>" + errorMsg
title: 'Error',
body: 'Sorry, there was an error adding slices to this dashboard: </ br>' + errorMsg,
});
}.bind(this)
},
});
},
saveDashboard: function () {
var expandedSlices = {};
$.each($(".slice_info"), function (i, d) {
var widget = $(this).parents('.widget');
var sliceDescription = widget.find('.slice_description');
if (sliceDescription.is(":visible")) {
saveDashboard() {
const expandedSlices = {};
$.each($('.slice_info'), function () {
const widget = $(this).parents('.widget');
const sliceDescription = widget.find('.slice_description');
if (sliceDescription.is(':visible')) {
expandedSlices[$(widget).attr('data-slice-id')] = true;
}
});
var data = {
positions: this.reactGridLayout.serialize(),
const positions = this.reactGridLayout.serialize();
const data = {
positions,
css: this.editor.getValue(),
expanded_slices: expandedSlices
expanded_slices: expandedSlices,
};
$.ajax({
type: "POST",
type: 'POST',
url: '/caravel/save_dash/' + dashboard.id + '/',
data: {
data: JSON.stringify(data)
data: JSON.stringify(data),
},
success: function () {
success() {
showModal({
title: "Success",
body: "This dashboard was saved successfully."
title: 'Success',
body: 'This dashboard was saved successfully.',
});
},
error: function (error) {
var errorMsg = this.getAjaxErrorMsg(error);
error(error) {
const errorMsg = this.getAjaxErrorMsg(error);
showModal({
title: "Error",
body: "Sorry, there was an error saving this dashboard: </ br>" + errorMsg
title: 'Error',
body: 'Sorry, there was an error saving this dashboard: </ br>' + errorMsg,
});
}
},
});
},
initDashboardView: function () {
initDashboardView() {
this.posDict = {};
this.position_json.forEach(function (position) {
this.posDict[position.slice_id] = position;
}, this);
this.reactGridLayout = render(
<GridLayout slices={this.slices} posDict={this.posDict} dashboard={dashboard}/>,
document.getElementById("grid-container")
<GridLayout slices={this.slices} posDict={this.posDict} dashboard={dashboard} />,
document.getElementById('grid-container')
);
this.curUserId = $('.dashboard').data('user');
@ -260,101 +290,77 @@ var Dashboard = function (dashboardData) {
$(this).find('.chart-controls').fadeOut(300);
}
);
$("div.grid-container").css('visibility', 'visible');
$("#savedash").click(this.saveDashboard.bind(this));
$("#add-slice").click(this.showAddSlice.bind(this));
$('div.grid-container').css('visibility', 'visible');
$('#savedash').click(this.saveDashboard.bind(this));
$('#add-slice').click(this.showAddSlice.bind(this));
var editor = ace.edit("dash_css");
const editor = ace.edit('dash_css');
this.editor = editor;
editor.$blockScrolling = Infinity;
editor.setTheme("ace/theme/crimson_editor");
editor.setTheme('ace/theme/crimson_editor');
editor.setOptions({
minLines: 16,
maxLines: Infinity,
useWorker: false
useWorker: false,
});
editor.getSession().setMode("ace/mode/css");
editor.getSession().setMode('ace/mode/css');
$(".select2").select2({
dropdownAutoWidth: true
$('.select2').select2({
dropdownAutoWidth: true,
});
$("#css_template").on("change", function () {
var css = $(this).find('option:selected').data('css');
$('#css_template').on('change', function () {
const css = $(this).find('option:selected').data('css');
editor.setValue(css);
$('#dash_css').val(css);
injectCss("dashboard-template", css);
injectCss('dashboard-template', css);
});
$('#filters').click(function () {
$('#filters').click(() => {
showModal({
title: "<span class='fa fa-info-circle'></span> Current Global Filters",
body: "The following global filters are currently applied:<br/>" + dashboard.readFilters()
title: '<span class="fa fa-info-circle"></span> Current Global Filters',
body: 'The following global filters are currently applied:<br/>' +
dashboard.readFilters(),
});
});
$("#refresh_dash_interval").on("change", function () {
var interval = $(this).find('option:selected').val() * 1000;
$('#refresh_dash_interval').on('change', function () {
const interval = $(this).find('option:selected').val() * 1000;
dashboard.startPeriodicRender(interval);
});
$('#refresh_dash').click(function () {
dashboard.slices.forEach(function (slice) {
$('#refresh_dash').click(() => {
dashboard.slices.forEach((slice) => {
slice.render(true);
});
});
$("div.widget").click(function (e) {
var $this = $(this);
var $target = $(e.target);
$('div.widget').click(function (e) {
const $this = $(this);
const $target = $(e.target);
if ($target.hasClass("slice_info")) {
$this.find(".slice_description").slideToggle(0, function () {
if ($target.hasClass('slice_info')) {
$this.find('.slice_description').slideToggle(0, function () {
$this.find('.refresh').click();
});
} else if ($target.hasClass("controls-toggle")) {
$this.find(".chart-controls").toggle();
} else if ($target.hasClass('controls-toggle')) {
$this.find('.chart-controls').toggle();
}
});
editor.on("change", function () {
var css = editor.getValue();
editor.on('change', function () {
const css = editor.getValue();
$('#dash_css').val(css);
injectCss("dashboard-template", css);
injectCss('dashboard-template', css);
});
var css = $('.dashboard').data('css');
injectCss("dashboard-template", css);
// Injects the passed css string into a style sheet with the specified className
// If a stylesheet doesn't exist with the passed className, one will be injected into <head>
function injectCss(className, css) {
var head = document.head || document.getElementsByTagName('head')[0];
var style = document.querySelector('.' + className);
if (!style) {
if (className.split(' ').length > 1) {
throw new Error("This method only supports selections with a single class name.");
}
style = document.createElement('style');
style.className = className;
style.type = 'text/css';
head.appendChild(style);
}
if (style.styleSheet) {
style.styleSheet.cssText = css;
} else {
style.innerHTML = css;
}
}
}
const css = $('.dashboard').data('css');
injectCss('dashboard-template', css);
},
});
dashboard.init();
return dashboard;
};
}
$(document).ready(function () {
Dashboard($('.dashboard').data('dashboard'));
$(document).ready(() => {
dashboardContainer($('.dashboard').data('dashboard'));
$('[data-toggle="tooltip"]').tooltip({ container: 'body' });
});

View File

@ -1,108 +1,69 @@
import $ from 'jquery';
import React, { PropTypes } from 'react';
import { Responsive, WidthProvider } from 'react-grid-layout';
const ResponsiveReactGridLayout = WidthProvider(Responsive);
import SliceCell from './SliceCell';
require('../../../node_modules/react-grid-layout/css/styles.css');
require('../../../node_modules/react-resizable/css/styles.css');
const sliceCellPropTypes = {
slice: PropTypes.object.isRequired,
removeSlice: PropTypes.func.isRequired,
expandedSlices: PropTypes.object
};
const gridLayoutPropTypes = {
const propTypes = {
dashboard: PropTypes.object.isRequired,
slices: PropTypes.arrayOf(PropTypes.object).isRequired,
posDict: PropTypes.object.isRequired
posDict: PropTypes.object.isRequired,
};
class SliceCell extends React.Component {
render() {
const slice = this.props.slice,
createMarkup = function () {
return { __html: slice.description_markeddown };
};
return (
<div>
<div className="chart-header">
<div className="row">
<div className="col-md-12 text-center header">
{slice.slice_name}
</div>
<div className="col-md-12 chart-controls">
<div className="pull-left">
<a title="Move chart" data-toggle="tooltip">
<i className="fa fa-arrows drag"/>
</a>
<a className="refresh" title="Force refresh data" data-toggle="tooltip">
<i className="fa fa-repeat"/>
</a>
</div>
<div className="pull-right">
{slice.description ?
<a title="Toggle chart description">
<i className="fa fa-info-circle slice_info" title={slice.description} data-toggle="tooltip"/>
</a>
: ""}
<a href={slice.edit_url} title="Edit chart" data-toggle="tooltip">
<i className="fa fa-pencil"/>
</a>
<a href={slice.slice_url} title="Explore chart" data-toggle="tooltip">
<i className="fa fa-share"/>
</a>
<a className="remove-chart" title="Remove chart from dashboard" data-toggle="tooltip">
<i className="fa fa-close" onClick={this.props.removeSlice.bind(null, slice.slice_id)}/>
</a>
</div>
</div>
</div>
</div>
<div
className="slice_description bs-callout bs-callout-default"
style={this.props.expandedSlices && this.props.expandedSlices[String(slice.slice_id)] ? {} : { display: "none" }}
dangerouslySetInnerHTML={createMarkup()}>
</div>
<div className="row chart-container">
<input type="hidden" value="false"/>
<div id={slice.token} className="token col-md-12">
<img src="/static/assets/images/loading.gif" className="loading" alt="loading"/>
<div className="slice_container" id={slice.token + "_con"}></div>
</div>
</div>
</div>
);
}
}
class GridLayout extends React.Component {
componentWillMount() {
const layout = [];
this.props.slices.forEach((slice, index) => {
let pos = this.props.posDict[slice.slice_id];
if (!pos) {
pos = {
col: (index * 4 + 1) % 12,
row: Math.floor((index) / 3) * 4,
size_x: 4,
size_y: 4,
};
}
layout.push({
i: String(slice.slice_id),
x: pos.col - 1,
y: pos.row,
w: pos.size_x,
minW: 2,
h: pos.size_y,
});
});
this.setState({
layout,
slices: this.props.slices,
});
}
onResizeStop(layout, oldItem, newItem) {
const newSlice = this.props.dashboard.getSlice(newItem.i);
if (oldItem.w !== newItem.w || oldItem.h !== newItem.h) {
this.setState({ layout }, () => { newSlice.resize(); });
}
}
onDragStop(layout) {
this.setState({ layout });
}
removeSlice(sliceId) {
$('[data-toggle="tooltip"]').tooltip("hide");
$('[data-toggle=tooltip]').tooltip('hide');
this.setState({
layout: this.state.layout.filter(function (reactPos) {
return reactPos.i !== String(sliceId);
}),
slices: this.state.slices.filter(function (slice) {
return slice.slice_id !== sliceId;
})
});
}
onResizeStop(layout, oldItem, newItem) {
if (oldItem.w !== newItem.w || oldItem.h !== newItem.h) {
this.setState({
layout: layout
}, function () {
this.props.dashboard.getSlice(newItem.i).resize();
});
}
}
onDragStop(layout) {
this.setState({
layout: layout
}),
});
}
@ -113,41 +74,11 @@ class GridLayout extends React.Component {
col: reactPos.x + 1,
row: reactPos.y,
size_x: reactPos.w,
size_y: reactPos.h
size_y: reactPos.h,
};
});
}
componentWillMount() {
var layout = [];
this.props.slices.forEach(function (slice, index) {
var pos = this.props.posDict[slice.slice_id];
if (!pos) {
pos = {
col: (index * 4 + 1) % 12,
row: Math.floor((index) / 3) * 4,
size_x: 4,
size_y: 4
};
}
layout.push({
i: String(slice.slice_id),
x: pos.col - 1,
y: pos.row,
w: pos.size_x,
minW: 2,
h: pos.size_y
});
}, this);
this.setState({
layout: layout,
slices: this.props.slices
});
}
render() {
return (
<ResponsiveReactGridLayout
@ -157,30 +88,35 @@ class GridLayout extends React.Component {
onDragStop={this.onDragStop.bind(this)}
cols={{ lg: 12, md: 12, sm: 10, xs: 8, xxs: 6 }}
rowHeight={100}
autoSize={true}
autoSize
margin={[20, 20]}
useCSSTransforms={false}
draggableHandle=".drag">
{this.state.slices.map((slice) => {
return (
<div
id={'slice_' + slice.slice_id}
key={slice.slice_id}
data-slice-id={slice.slice_id}
className={"widget " + slice.viz_name}>
<SliceCell
slice={slice}
removeSlice={this.removeSlice.bind(this)}
expandedSlices={this.props.dashboard.metadata.expanded_slices}/>
</div>
);
})}
draggableHandle=".drag"
>
{
/* eslint arrow-body-style: 0 */
this.state.slices.map((slice) => {
return (
<div
id={'slice_' + slice.slice_id}
key={slice.slice_id}
data-slice-id={slice.slice_id}
className={`widget ${slice.viz_name}`}
>
<SliceCell
slice={slice}
removeSlice={(sliceId) => this.removeSlice(sliceId)}
expandedSlices={this.props.dashboard.metadata.expanded_slices}
/>
</div>
);
})
}
</ResponsiveReactGridLayout>
);
}
}
SliceCell.propTypes = sliceCellPropTypes;
GridLayout.propTypes = gridLayoutPropTypes;
GridLayout.propTypes = propTypes;
export default GridLayout;

View File

@ -4,37 +4,37 @@ const propTypes = {
modalId: PropTypes.string.isRequired,
title: PropTypes.string,
modalContent: PropTypes.node,
customButtons: PropTypes.node
customButton: PropTypes.node,
};
class Modal extends React.Component {
render() {
return (
<div className="modal fade" id={this.props.modalId} role="dialog">
<div className="modal-dialog" role="document">
<div className="modal-content">
<div className="modal-header">
<button type="button" className="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<h4 className="modal-title">{this.props.title}</h4>
</div>
<div className="modal-body">
{this.props.modalContent}
</div>
<div className="modal-footer">
<button type="button"
className="btn btn-default"
data-dismiss="modal">
Cancel
</button>
{this.props.customButtons}
</div>
</div>
function Modal({ modalId, title, modalContent, customButton }) {
return (
<div className="modal fade" id={modalId} role="dialog">
<div className="modal-dialog" role="document">
<div className="modal-content">
<div className="modal-header">
<button type="button" className="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<h4 className="modal-title">{title}</h4>
</div>
<div className="modal-body">
{modalContent}
</div>
<div className="modal-footer">
<button
type="button"
className="btn btn-default"
data-dismiss="modal"
>
Cancel
</button>
{customButton}
</div>
</div>
</div>
);
}
</div>
);
}
Modal.propTypes = propTypes;

View File

@ -1,3 +1,4 @@
import $ from 'jquery';
import React, { PropTypes } from 'react';
import update from 'immutability-helper';
import { BootstrapTable, TableHeaderColumn } from 'react-bootstrap-table';
@ -6,15 +7,14 @@ require('../../../node_modules/react-bootstrap-table/css/react-bootstrap-table.c
const propTypes = {
dashboard: PropTypes.object.isRequired,
caravel: PropTypes.object.isRequired
caravel: PropTypes.object.isRequired,
};
class SliceAdder extends React.Component {
constructor(props) {
super(props);
this.state = {
slices: []
slices: [],
};
this.addSlices = this.addSlices.bind(this);
@ -22,46 +22,47 @@ class SliceAdder extends React.Component {
this.toggleAllSlices = this.toggleAllSlices.bind(this);
this.slicesLoaded = false;
this.selectRowProp = {
mode: "checkbox",
mode: 'checkbox',
clickToSelect: true,
onSelect: this.toggleSlice,
onSelectAll: this.toggleAllSlices
onSelectAll: this.toggleAllSlices,
};
this.options = {
defaultSortOrder: "desc",
defaultSortName: "modified",
sizePerPage: 10
defaultSortOrder: 'desc',
defaultSortName: 'modified',
sizePerPage: 10,
};
}
componentDidMount() {
var uri = "/sliceaddview/api/read?_flt_0_created_by=" + this.props.dashboard.curUserId;
const uri = '/sliceaddview/api/read?_flt_0_created_by=' + this.props.dashboard.curUserId;
this.slicesRequest = $.ajax({
url: uri,
type: 'GET',
success: function (response) {
this.slicesLoaded = true;
// Prepare slice data for table
let slices = response.result;
slices.forEach(function (slice) {
slice.id = slice.data.slice_id;
slice.sliceName = slice.data.slice_name;
slice.vizType = slice.viz_type;
slice.modified = slice.modified;
const slices = response.result.map(function (slice) {
return {
id: slice.data.slice_id,
sliceName: slice.data.slice_name,
vizType: slice.viz_type,
modified: slice.modified,
data: slice.data,
};
});
this.setState({
slices: slices,
selectionMap: {}
slices,
selectionMap: {},
});
}.bind(this),
error: function (error) {
this.errored = true;
this.setState({
errorMsg: this.props.dashboard.getAjaxErrorMsg(error)
errorMsg: this.props.dashboard.getAjaxErrorMsg(error),
});
}.bind(this)
}.bind(this),
});
}
@ -70,13 +71,12 @@ class SliceAdder extends React.Component {
}
addSlices() {
var slices = this.state.slices.filter(function (slice) {
const slices = this.state.slices.filter(function (slice) {
return this.state.selectionMap[slice.id];
}, this);
slices.forEach(function (slice) {
var sliceObj = this.props.caravel.Slice(slice.data, this.props.dashboard);
$("#slice_" + slice.data.slice_id).find('a.refresh').click(function () {
const sliceObj = this.props.caravel.Slice(slice.data, this.props.dashboard);
$('#slice_' + slice.data.slice_id).find('a.refresh').click(function () {
sliceObj.render(true);
});
this.props.dashboard.slices.push(sliceObj);
@ -89,23 +89,23 @@ class SliceAdder extends React.Component {
this.setState({
selectionMap: update(this.state.selectionMap, {
[slice.id]: {
$set: !this.state.selectionMap[slice.id]
}
})
$set: !this.state.selectionMap[slice.id],
},
}),
});
}
toggleAllSlices(value) {
let updatePayload = {};
const updatePayload = {};
this.state.slices.forEach(function (slice) {
updatePayload[slice.id] = {
$set: value
$set: value,
};
}, this);
this.setState({
selectionMap: update(this.state.selectionMap, updatePayload)
selectionMap: update(this.state.selectionMap, updatePayload),
});
}
@ -134,13 +134,15 @@ class SliceAdder extends React.Component {
}, this);
const modalContent = (
<div>
<img src="/static/assets/images/loading.gif"
className={"loading " + (hideLoad ? "hidden" : "")}
alt={hideLoad ? "" : "loading"}/>
<div className={this.errored ? "" : "hidden"}>
<img
src="/static/assets/images/loading.gif"
className={'loading ' + (hideLoad ? 'hidden' : '')}
alt={hideLoad ? '' : 'loading'}
/>
<div className={this.errored ? '' : 'hidden'}>
{this.state.errorMsg}
</div>
<div className={this.slicesLoaded ? "" : "hidden"}>
<div className={this.slicesLoaded ? '' : 'hidden'}>
<BootstrapTable
ref="table"
data={this.state.slices}
@ -149,37 +151,52 @@ class SliceAdder extends React.Component {
hover
search
pagination
height="auto">
<TableHeaderColumn dataField="sliceName" isKey={true} dataSort={true}>Name</TableHeaderColumn>
<TableHeaderColumn dataField="vizType" dataSort={true}>Viz</TableHeaderColumn>
height="auto"
>
<TableHeaderColumn
dataField="sliceName"
isKey
dataSort
>
Name
</TableHeaderColumn>
<TableHeaderColumn
dataField="vizType"
dataSort
>
Viz
</TableHeaderColumn>
<TableHeaderColumn
dataField="modified"
dataSort={true}
dataSort
sortFunc={this.modifiedDateComparator}
// Will cause react-bootstrap-table to interpret the HTML returned
dataFormat={modified => modified}>
dataFormat={modified => modified}
>
Modified
</TableHeaderColumn>
</BootstrapTable>
</div>
</div>
);
const customButtons = [
<button key={0}
type="button"
className="btn btn-default"
data-dismiss="modal"
onClick={this.addSlices}
disabled={!enableAddSlice}>
Add Slices
const customButton = (
<button
type="button"
className="btn btn-default"
data-dismiss="modal"
onClick={this.addSlices}
disabled={!enableAddSlice}
>
Add Slices
</button>
];
);
return (
<Modal modalId='add_slice_modal'
modalContent={modalContent}
title='Add New Slices'
customButtons={customButtons}
<Modal
modalId="add_slice_modal"
modalContent={modalContent}
title="Add New Slices"
customButton={customButton}
/>
);
}

View File

@ -0,0 +1,88 @@
import React, { PropTypes } from 'react';
const propTypes = {
slice: PropTypes.object.isRequired,
removeSlice: PropTypes.func.isRequired,
expandedSlices: PropTypes.object,
};
function SliceCell({ expandedSlices, removeSlice, slice }) {
return (
<div>
<div className="chart-header">
<div className="row">
<div className="col-md-12 text-center header">
{slice.slice_name}
</div>
<div className="col-md-12 chart-controls">
<div className="pull-left">
<a title="Move chart" data-toggle="tooltip">
<i className="fa fa-arrows drag" />
</a>
<a className="refresh" title="Force refresh data" data-toggle="tooltip">
<i className="fa fa-repeat" />
</a>
</div>
<div className="pull-right">
{slice.description &&
<a title="Toggle chart description">
<i
className="fa fa-info-circle slice_info"
title={slice.description}
data-toggle="tooltip"
/>
</a>
}
<a
href={slice.edit_url}
title="Edit chart"
data-toggle="tooltip"
>
<i className="fa fa-pencil" />
</a>
<a href={slice.slice_url} title="Explore chart" data-toggle="tooltip">
<i className="fa fa-share" />
</a>
<a
className="remove-chart"
title="Remove chart from dashboard"
data-toggle="tooltip"
>
<i
className="fa fa-close"
onClick={() => { removeSlice(slice.slice_id); }}
/>
</a>
</div>
</div>
</div>
</div>
<div
className="slice_description bs-callout bs-callout-default"
style={
expandedSlices &&
expandedSlices[String(slice.slice_id)] ? {} : { display: 'none' }
}
dangerouslySetInnerHTML={{ __html: slice.description_markeddown }}
>
</div>
<div className="row chart-container">
<input type="hidden" value="false" />
<div id={slice.token} className="token col-md-12">
<img
src="/static/assets/images/loading.gif"
className="loading"
alt="loading"
/>
<div className="slice_container" id={slice.token + '_con'}></div>
</div>
</div>
</div>
);
}
SliceCell.propTypes = propTypes;
export default SliceCell;

View File

@ -16,10 +16,11 @@ export default function QueryAndSaveBtns({ canAdd, onQuery }) {
<button type="button" className="btn btn-primary" onClick={onQuery}>
<i className="fa fa-bolt"></i>Query
</button>
<button type="button"
className={saveClasses}
data-target="#save_modal"
data-toggle="modal"
<button
type="button"
className={saveClasses}
data-target="#save_modal"
data-toggle="modal"
>
<i className="fa fa-plus-circle"></i>Save as
</button>

View File

@ -28,31 +28,31 @@ require('../../node_modules/bootstrap-toggle/css/bootstrap-toggle.min.css');
var slice;
var getPanelClass = function (fieldPrefix) {
return (fieldPrefix === "flt" ? "filter" : "having") + "_panel";
return (fieldPrefix === 'flt' ? 'filter' : 'having') + '_panel';
};
function prepForm() {
// Assigning the right id to form elements in filters
var fixId = function ($filter, fieldPrefix, i) {
$filter.attr("id", function () {
return fieldPrefix + "_" + i;
$filter.attr('id', function () {
return fieldPrefix + '_' + i;
});
["col", "op", "eq"].forEach(function (fieldMiddle) {
var fieldName = fieldPrefix + "_" + fieldMiddle;
$filter.find("[id^='" + fieldName + "_']")
.attr("id", function () {
return fieldName + "_" + i;
['col', 'op', 'eq'].forEach(function (fieldMiddle) {
var fieldName = fieldPrefix + '_' + fieldMiddle;
$filter.find('[id^=' + fieldName + '_]')
.attr('id', function () {
return fieldName + '_' + i;
})
.attr("name", function () {
return fieldName + "_" + i;
.attr('name', function () {
return fieldName + '_' + i;
});
});
};
["flt", "having"].forEach(function (fieldPrefix) {
['flt', 'having'].forEach(function (fieldPrefix) {
var i = 1;
$("#" + getPanelClass(fieldPrefix) + " #filters > div").each(function () {
$('#' + getPanelClass(fieldPrefix) + ' #filters > div').each(function () {
fixId($(this), fieldPrefix, i);
i++;
});
@ -79,11 +79,10 @@ function query(force, pushState) {
}
function initExploreView() {
function get_collapsed_fieldsets() {
var collapsed_fieldsets = $("#collapsed_fieldsets").val();
var collapsed_fieldsets = $('#collapsed_fieldsets').val();
if (collapsed_fieldsets !== undefined && collapsed_fieldsets !== "") {
if (collapsed_fieldsets !== undefined && collapsed_fieldsets !== '') {
collapsed_fieldsets = collapsed_fieldsets.split('||');
} else {
collapsed_fieldsets = [];
@ -93,18 +92,18 @@ function initExploreView() {
function toggle_fieldset(legend, animation) {
var parent = legend.parent();
var fieldset = parent.find(".legend_label").text();
var fieldset = parent.find('.legend_label').text();
var collapsed_fieldsets = get_collapsed_fieldsets();
var index;
if (parent.hasClass("collapsed")) {
if (parent.hasClass('collapsed')) {
if (animation) {
parent.find(".panel-body").slideDown();
parent.find('.panel-body').slideDown();
} else {
parent.find(".panel-body").show();
parent.find('.panel-body').show();
}
parent.removeClass("collapsed");
parent.find("span.collapser").text("[-]");
parent.removeClass('collapsed');
parent.find('span.collapser').text('[-]');
// removing from array, js is overcomplicated
index = collapsed_fieldsets.indexOf(fieldset);
@ -113,20 +112,20 @@ function initExploreView() {
}
} else { // not collapsed
if (animation) {
parent.find(".panel-body").slideUp();
parent.find('.panel-body').slideUp();
} else {
parent.find(".panel-body").hide();
parent.find('.panel-body').hide();
}
parent.addClass("collapsed");
parent.find("span.collapser").text("[+]");
parent.addClass('collapsed');
parent.find('span.collapser').text('[+]');
index = collapsed_fieldsets.indexOf(fieldset);
if (index === -1 && fieldset !== "" && fieldset !== undefined) {
if (index === -1 && fieldset !== '' && fieldset !== undefined) {
collapsed_fieldsets.push(fieldset);
}
}
$("#collapsed_fieldsets").val(collapsed_fieldsets.join("||"));
$('#collapsed_fieldsets').val(collapsed_fieldsets.join('||'));
}
px.initFavStars();
@ -137,7 +136,7 @@ function initExploreView() {
});
function copyURLToClipboard(url) {
var textArea = document.createElement("textarea");
var textArea = document.createElement('textarea');
textArea.style.position = 'fixed';
textArea.style.left = '-1000px';
textArea.value = url;
@ -148,10 +147,10 @@ function initExploreView() {
try {
var successful = document.execCommand('copy');
if (!successful) {
throw new Error("Not successful");
throw new Error('Not successful');
}
} catch (err) {
window.alert("Sorry, your browser does not support copying. Use Ctrl / Cmd + C!");
window.alert('Sorry, your browser does not support copying. Use Ctrl / Cmd + C!');
}
document.body.removeChild(textArea);
return successful;
@ -159,12 +158,12 @@ function initExploreView() {
$('#shortner').click(function () {
$.ajax({
type: "POST",
type: 'POST',
url: '/r/shortner/',
data: {
data: '/' + window.location.pathname + slice.querystring()
data: '/' + window.location.pathname + slice.querystring(),
},
success: function (data) {
success(data) {
var close = '<a style="cursor: pointer;"><i class="fa fa-close" id="close_shortner"></i></a>';
var copy = '<a style="cursor: pointer;"><i class="fa fa-clipboard" title="Copy to clipboard" id="copy_url"></i></a>';
var spaces = '&nbsp;&nbsp;&nbsp;';
@ -175,14 +174,14 @@ function initExploreView() {
content: popover,
placement: 'left',
html: true,
trigger: 'manual'
trigger: 'manual',
})
.popover('show');
$('#copy_url').tooltip().click(function () {
var success = copyURLToClipboard(data);
if (success) {
$(this).attr("data-original-title", "Copied!").tooltip('fixTitle').tooltip('show');
$(this).attr('data-original-title', 'Copied!').tooltip('fixTitle').tooltip('show');
window.setTimeout(destroyPopover, 1200);
}
});
@ -192,13 +191,13 @@ function initExploreView() {
$shortner.popover('destroy');
}
},
error: function (error) {
error(error) {
showModal({
title: "Error",
body: "Sorry, an error occurred during this operation:<br/>" + error
title: 'Error',
body: 'Sorry, an error occurred during this operation:<br/>' + error,
});
console.warn("Short URL error", error);
}
console.warn('Short URL error', error);
},
});
});
@ -219,13 +218,13 @@ function initExploreView() {
title: 'embed html',
placement: 'left',
html: true,
trigger: 'manual'
trigger: 'manual',
})
.popover('show');
$('#copy_embed').tooltip().click(function () {
var success = copyURLToClipboard(dataToCopy);
if (success) {
$(this).attr("data-original-title", "Copied!").tooltip('fixTitle').tooltip('show');
$(this).attr('data-original-title', 'Copied!').tooltip('fixTitle').tooltip('show');
window.setTimeout(destroyPopover, 1200);
}
});
@ -250,17 +249,17 @@ function initExploreView() {
function generateEmbedHTML() {
var width = $standalone_width.val();
var height = $standalone_height.val();
dataToCopy = '<iframe src="' + src_link + '" width="' + width + '" height="' + height +'"';
dataToCopy = '<iframe src="' + src_link + '" width="' + width + '" height="' + height + '"';
dataToCopy = dataToCopy + ' seamless frameBorder="0" scrolling="no"></iframe>';
$standalone_text.val(dataToCopy);
}
});
$("#viz_type").change(function () {
$("#query").submit();
$('#viz_type').change(function () {
$('#query').submit();
});
$("#datasource_id").change(function () {
$('#datasource_id').change(function () {
var url = $(this).find('option:selected').attr('url');
window.location = url;
});
@ -278,28 +277,28 @@ function initExploreView() {
);
}
$(".select2").select2({
dropdownAutoWidth: true
});
$(".select2Sortable").select2({
dropdownAutoWidth: true
});
$(".select2-with-images").select2({
$('.select2').select2({
dropdownAutoWidth: true,
dropdownCssClass: "bigdrop",
formatResult: formatViz
});
$(".select2Sortable").select2Sortable({
bindOrder: 'sortableStop'
$('.select2Sortable').select2({
dropdownAutoWidth: true,
});
$("form").show();
$('.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
$('.ui-helper-hidden-accessible').remove(); // jQuery-ui 1.11+ creates a div for every tooltip
function set_filters() {
["flt", "having"].forEach(function (prefix) {
['flt', 'having'].forEach(function (prefix) {
for (var i = 1; i < 10; i++) {
var col = px.getParam(prefix + "_col_" + i);
var col = px.getParam(prefix + '_col_' + i);
if (col !== '') {
add_filter(i, prefix);
}
@ -309,13 +308,13 @@ function initExploreView() {
set_filters();
function add_filter(i, fieldPrefix) {
var cp = $("#"+fieldPrefix+"0").clone();
$(cp).appendTo("#" + getPanelClass(fieldPrefix) + " #filters");
var 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('#' + 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 () {
@ -323,7 +322,7 @@ function initExploreView() {
});
}
$(window).bind("popstate", function (event) {
$(window).bind('popstate', function (event) {
// Browser back button
var returnLocation = history.location || document.location;
// Could do something more lightweight here, but we're not optimizing
@ -331,11 +330,11 @@ function initExploreView() {
returnLocation.reload();
});
$("#filter_panel #plus").click(function () {
add_filter(undefined, "flt");
$('#filter_panel #plus').click(function () {
add_filter(undefined, 'flt');
});
$("#having_panel #plus").click(function () {
add_filter(undefined, "having");
$('#having_panel #plus').click(function () {
add_filter(undefined, 'having');
});
const queryAndSaveBtnsEl = document.getElementById('js-query-and-save-btns');
@ -354,7 +353,7 @@ function initExploreView() {
if (filtered.length === 0) {
return {
id: term,
text: term
text: term,
};
}
}
@ -362,11 +361,11 @@ function initExploreView() {
function initSelectionToValue(element, callback) {
callback({
id: element.val(),
text: element.val()
text: element.val(),
});
}
$(".select2_freeform").each(function () {
$('.select2_freeform').each(function () {
var parent = $(this).parent();
var name = $(this).attr('name');
var l = [];
@ -374,7 +373,7 @@ function initExploreView() {
for (var i = 0; i < this.options.length; i++) {
l.push({
id: this.options[i].value,
text: this.options[i].text
text: this.options[i].text,
});
if (this.options[i].selected) {
selected = this.options[i].value;
@ -388,14 +387,14 @@ function initExploreView() {
initSelection: initSelectionToValue,
dropdownAutoWidth: true,
multiple: false,
data: l
data: l,
});
$(this).remove();
});
function prepSaveDialog() {
var setButtonsState = function () {
var add_to_dash = $("input[name=add_to_dash]:checked").val();
var add_to_dash = $('input[name=add_to_dash]:checked').val();
if (add_to_dash === 'existing' || add_to_dash === 'new') {
$('.gotodash').removeAttr('disabled');
} else {
@ -406,25 +405,25 @@ function initExploreView() {
url += '?_flt_0_owners=' + $('#userid').val();
$.get(url, function (data) {
var choices = [];
for (var i=0; i< data.pks.length; i++) {
for (var 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 () {
$("#add_to_dash_existing").prop("checked", true);
dropdownAutoWidth: true,
}).on('select2-selecting', function () {
$('#add_to_dash_existing').prop('checked', true);
setButtonsState();
});
});
$("input[name=add_to_dash]").change(setButtonsState);
$('input[name=add_to_dash]').change(setButtonsState);
$("input[name='new_dashboard_name']").on('focus', function () {
$("#add_to_new_dash").prop("checked", true);
$('#add_to_new_dash').prop('checked', true);
setButtonsState();
});
$("input[name='new_slice_name']").on('focus', function () {
$("#save_as_new").prop("checked", true);
$('#save_as_new').prop('checked', true);
setButtonsState();
});
@ -444,30 +443,30 @@ function saveSlice() {
var slice_name = $('input[name=new_slice_name]').val();
if (slice_name === '') {
showModal({
title: "Error",
body: "You must pick a name for the new slice"
title: 'Error',
body: 'You must pick a name for the new slice',
});
return;
}
document.getElementById("slice_name").value = slice_name;
document.getElementById('slice_name').value = slice_name;
}
var add_to_dash = $('input[name=add_to_dash]:checked').val();
if (add_to_dash === 'existing' && $('#save_to_dashboard_id').val() === '') {
showModal({
title: "Error",
body: "You must pick an existing dashboard"
title: 'Error',
body: 'You must pick an existing dashboard',
});
return;
} else if (add_to_dash === 'new' && $('input[name=new_dashboard_name]').val() === '') {
showModal({
title: "Error",
body: "Please enter a name for the new dashboard"
title: 'Error',
body: 'Please enter a name for the new dashboard',
});
return;
}
$('#action').val(action);
prepForm();
$("#query").submit();
$('#query').submit();
}
$(document).ready(function () {
@ -489,9 +488,9 @@ $(document).ready(function () {
// make checkbox inputs display as toggles
$(':checkbox')
.addClass('pull-right')
.attr("data-onstyle", "default")
.attr('data-onstyle', 'default')
.bootstrapToggle({
size: 'mini'
size: 'mini',
});
$('div.toggle').addClass('pull-right');

View File

@ -1,18 +1,14 @@
var $ = require('jquery');
var jQuery = $;
import React from 'react';
import { render } from 'react-dom';
import { Jumbotron } from 'react-bootstrap';
class App extends React.Component {
render () {
return (
<Jumbotron>
<h1>Caravel</h1>
<p>Extensible visualization tool for exploring data from any database.</p>
</Jumbotron>
);
}
function App() {
return (
<Jumbotron>
<h1>Caravel</h1>
<p>Extensible visualization tool for exploring data from any database.</p>
</Jumbotron>
);
}
render(<App />, document.getElementById('app'));

View File

@ -1,11 +1,10 @@
var $ = require('jquery');
var jQuery = $;
var d3 = require('d3');
var Mustache = require('mustache');
var utils = require('./utils');
import $ from 'jquery';
const d3 = require('d3');
const Mustache = require('mustache');
const utils = require('./utils');
// vis sources
var sourceMap = {
/* eslint camel-case: 0 */
const sourceMap = {
area: 'nvd3_vis.js',
bar: 'nvd3_vis.js',
bubble: 'nvd3_vis.js',
@ -34,185 +33,247 @@ var sourceMap = {
horizon: 'horizon.js',
mapbox: 'mapbox.jsx',
};
var color = function () {
const color = function () {
// Color related utility functions go in this object
var bnbColors = [
//rausch hackb kazan babu lima beach barol
'#ff5a5f', '#7b0051', '#007A87', '#00d1c1', '#8ce071', '#ffb400', '#b4a76c',
'#ff8083', '#cc0086', '#00a1b3', '#00ffeb', '#bbedab', '#ffd266', '#cbc29a',
'#ff3339', '#ff1ab1', '#005c66', '#00b3a5', '#55d12e', '#b37e00', '#988b4e',
const bnbColors = [
'#ff5a5f', // rausch
'#7b0051', // hackb
'#007A87', // kazan
'#00d1c1', // babu
'#8ce071', // lima
'#ffb400', // beach
'#b4a76c', // barol
'#ff8083',
'#cc0086',
'#00a1b3',
'#00ffeb',
'#bbedab',
'#ffd266',
'#cbc29a',
'#ff3339',
'#ff1ab1',
'#005c66',
'#00b3a5',
'#55d12e',
'#b37e00',
'#988b4e',
];
var spectrums = {
blue_white_yellow: ['#00d1c1', 'white', '#ffb400'],
fire: ['white', 'yellow', 'red', 'black'],
white_black: ['white', 'black'],
black_white: ['black', 'white'],
const spectrums = {
blue_white_yellow: [
'#00d1c1',
'white',
'#ffb400',
],
fire: [
'white',
'yellow',
'red',
'black',
],
white_black: [
'white',
'black',
],
black_white: [
'black',
'white',
],
};
var colorBnb = function () {
const colorBnb = function () {
// Color factory
var seen = {};
const seen = {};
return function (s) {
if (!s) { return; }
s = String(s);
// next line is for caravel series that should have the same color
s = s.replace('---', '');
if (seen[s] === undefined) {
seen[s] = Object.keys(seen).length;
if (!s) {
return;
}
return this.bnbColors[seen[s] % this.bnbColors.length];
let stringifyS = String(s);
// next line is for caravel series that should have the same color
stringifyS = stringifyS.replace('---', '');
if (seen[stringifyS] === undefined) {
seen[stringifyS] = Object.keys(seen).length;
}
/* eslint consistent-return: 0 */
return this.bnbColors[seen[stringifyS] % this.bnbColors.length];
};
};
var colorScalerFactory = function (colors, data, accessor) {
const colorScalerFactory = function (colors, data, accessor) {
// Returns a linear scaler our of an array of color
if (!Array.isArray(colors)) {
/* eslint no-param-reassign: 0 */
colors = spectrums[colors];
}
var ext = [0, 1];
let ext = [
0,
1,
];
if (data !== undefined) {
ext = d3.extent(data, accessor);
}
var points = [];
var chunkSize = (ext[1] - ext[0]) / colors.length;
$.each(colors, function (i, c) {
const points = [];
const chunkSize = (ext[1] - ext[0]) / colors.length;
$.each(colors, function (i) {
points.push(i * chunkSize);
});
return d3.scale.linear().domain(points).range(colors);
};
return {
bnbColors: bnbColors,
bnbColors,
category21: colorBnb(),
colorScalerFactory: colorScalerFactory,
colorScalerFactory,
};
};
var px = (function () {
var visualizations = {};
var slice;
/* eslint wrap-iife: 0*/
const px = function () {
const visualizations = {};
let slice;
function getParam(name) {
name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
results = regex.exec(location.search);
return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');
const regex = new RegExp('[\\?&]' + name + '=([^&#]*)');
const results = regex.exec(location.search);
return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' '));
}
function UTC(dttm) {
return new Date(dttm.getUTCFullYear(), dttm.getUTCMonth(), dttm.getUTCDate(), dttm.getUTCHours(), dttm.getUTCMinutes(), dttm.getUTCSeconds());
return new Date(
dttm.getUTCFullYear(),
dttm.getUTCMonth(),
dttm.getUTCDate(),
dttm.getUTCHours(),
dttm.getUTCMinutes(),
dttm.getUTCSeconds()
);
}
var tickMultiFormat = d3.time.format.multi([
[".%L", function (d) {
return d.getMilliseconds();
}], // If there are millisections, show only them
[":%S", function (d) {
return d.getSeconds();
}], // If there are seconds, show only them
["%a %b %d, %I:%M %p", function (d) {
return d.getMinutes() !== 0;
}], // If there are non-zero minutes, show Date, Hour:Minute [AM/PM]
["%a %b %d, %I %p", function (d) {
return d.getHours() !== 0;
}], // If there are hours that are multiples of 3, show date and AM/PM
["%a %b %d", function (d) {
return d.getDate() !== 1;
}], // If not the first of the month, do "month day, year."
["%B %Y", function (d) {
return d.getMonth() !== 0 && d.getDate() === 1;
}], // If the first of the month, do "month day, year."
["%Y", function (d) {
return true;
}], // fall back on month, year
const tickMultiFormat = d3.time.format.multi([
[
'.%L',
function (d) {
return d.getMilliseconds();
},
],
// If there are millisections, show only them
[
':%S',
function (d) {
return d.getSeconds();
},
],
// If there are seconds, show only them
[
'%a %b %d, %I:%M %p',
function (d) {
return d.getMinutes() !== 0;
},
],
// If there are non-zero minutes, show Date, Hour:Minute [AM/PM]
[
'%a %b %d, %I %p',
function (d) {
return d.getHours() !== 0;
},
],
// If there are hours that are multiples of 3, show date and AM/PM
[
'%a %b %d',
function (d) {
return d.getDate() !== 1;
},
],
// If not the first of the month, do "month day, year."
[
'%B %Y',
function (d) {
return d.getMonth() !== 0 && d.getDate() === 1;
},
],
// If the first of the month, do "month day, year."
[
'%Y',
function () {
return true;
},
], // fall back on month, year
]);
function formatDate(dttm) {
var d = UTC(new Date(dttm));
//d = new Date(d.getTime() - 1 * 60 * 60 * 1000);
const d = UTC(new Date(dttm));
// d = new Date(d.getTime() - 1 * 60 * 60 * 1000);
return tickMultiFormat(d);
}
function timeFormatFactory(d3timeFormat) {
var f = d3.time.format(d3timeFormat);
const f = d3.time.format(d3timeFormat);
return function (dttm) {
var d = UTC(new Date(dttm));
const d = UTC(new Date(dttm));
return f(d);
};
}
function initFavStars() {
var baseUrl = '/caravel/favstar/';
// Init star behavihor for favorite
function show() {
if ($(this).hasClass('selected')) {
$(this).html('<i class="fa fa-star"></i>');
} else {
$(this).html('<i class="fa fa-star-o"></i>');
}
const baseUrl = '/caravel/favstar/';
// Init star behavihor for favorite
function show() {
if ($(this).hasClass('selected')) {
$(this).html('<i class="fa fa-star"></i>');
} else {
$(this).html('<i class="fa fa-star-o"></i>');
}
$('.favstar')
.attr('title', 'Click to favorite/unfavorite')
.each(show)
.each(function () {
var url = baseUrl + $(this).attr("class_name");
var 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');
var url = baseUrl + $(this).attr("class_name");
url += '/' + $(this).attr("obj_id") + '/';
if ($(this).hasClass('selected')) {
url += 'select/';
} else {
url += 'unselect/';
}
$('.favstar')
.attr('title', 'Click to favorite/unfavorite')
.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);
})
.tooltip();
});
})
.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();
}
var Slice = function (data, dashboard) {
var timer;
var token = $('#' + data.token);
var container_id = data.token + '_con';
var selector = '#' + container_id;
var container = $(selector);
var slice_id = data.slice_id;
var dttm = 0;
var stopwatch = function () {
const Slice = function (data, dashboard) {
let timer;
const token = $('#' + data.token);
const containerId = data.token + '_con';
const selector = '#' + containerId;
const container = $(selector);
const sliceId = data.sliceId;
let dttm = 0;
const stopwatch = function () {
dttm += 10;
var num = dttm / 1000;
$('#timer').text(num.toFixed(2) + " sec");
const num = dttm / 1000;
$('#timer').text(num.toFixed(2) + ' sec');
};
var qrystr = '';
var always = function (data) {
//Private f, runs after done and error
let qrystr = '';
const always = function () {
// Private f, runs after done and error
clearInterval(timer);
$('#timer').removeClass('btn-warning');
};
slice = {
data: data,
container: container,
container_id: container_id,
selector: selector,
querystring: function (params) {
data,
container,
containerId,
selector,
querystring(params) {
params = params || {};
var parser = document.createElement('a');
const parser = document.createElement('a');
parser.href = data.json_endpoint;
if (dashboard !== undefined) {
var flts = params.extraFilters === false ?
'' : encodeURIComponent(JSON.stringify(dashboard.filters));
qrystr = parser.search + "&extra_filters=" + flts;
const flts =
params.extraFilters === false ? '' :
encodeURIComponent(JSON.stringify(dashboard.filters));
qrystr = parser.search + '&extra_filters=' + flts;
} else if ($('#query').length === 0) {
qrystr = parser.search;
} else {
@ -220,69 +281,66 @@ var px = (function () {
}
return qrystr;
},
getWidgetHeader: function () {
return this.container.parents("div.widget").find(".chart-header");
getWidgetHeader() {
return this.container.parents('div.widget').find('.chart-header');
},
render_template: function (s) {
var context = {
render_template(s) {
const context = {
width: this.width,
height: this.height,
};
return Mustache.render(s, context);
},
jsonEndpoint: function (params) {
jsonEndpoint(params) {
params = params || {};
var parser = document.createElement('a');
const parser = document.createElement('a');
parser.href = data.json_endpoint;
var endpoint = parser.pathname + this.querystring({
extraFilters: params.extraFilters,
});
endpoint += "&json=true";
endpoint += "&force=" + this.force;
let endpoint = parser.pathname + this.querystring({ extraFilters: params.extraFilters });
endpoint += '&json=true';
endpoint += '&force=' + this.force;
return endpoint;
},
d3format: function (col, number) {
d3format(col, number) {
// uses the utils memoized d3format function and formats based on
// column level defined preferences
var format = this.data.column_formats[col];
const format = this.data.column_formats[col];
return utils.d3format(format, number);
},
done: function (data) {
/* eslint no-shadow: 0 */
done(data) {
clearInterval(timer);
token.find("img.loading").hide();
token.find('img.loading').hide();
container.show();
var cachedSelector = null;
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 to force-refresh')
.attr('title',
'Served from data cached at ' + data.cached_dttm + '. Click to force-refresh')
.show()
.tooltip('fixTitle');
} else {
cachedSelector.hide();
}
} else {
var refresh = this.getWidgetHeader().find('.refresh');
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');
.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');
.removeClass('danger')
.attr('title', 'Click to force-refresh')
.tooltip('fixTitle');
}
}
if (data !== undefined) {
$("#query_container").html(data.query);
$('#query_container').html(data.query);
}
$('#timer').removeClass('btn-warning');
$('#timer').addClass('btn-success');
@ -297,26 +355,26 @@ var px = (function () {
$('.query-and-save button').removeAttr('disabled');
always(data);
},
getErrorMsg: function (xhr) {
if (xhr.statusText === "timeout") {
return "The request timed out";
getErrorMsg(xhr) {
if (xhr.statusText === 'timeout') {
return 'The request timed out';
}
var msg = "";
let msg = '';
if (!xhr.responseText) {
var status = xhr.status;
msg += "An unknown error occurred. (Status: " + status + ")";
const status = xhr.status;
msg += 'An unknown error occurred. (Status: ' + status + ')';
if (status === 0) {
// This may happen when the worker in gunicorn times out
msg += " Maybe the request timed out?";
msg += ' Maybe the request timed out?';
}
}
return msg;
},
error: function (msg, xhr) {
token.find("img.loading").hide();
var err = msg ? ('<div class="alert alert-danger">' + msg + '</div>') : "";
error(msg, xhr) {
token.find('img.loading').hide();
let err = msg ? '<div class="alert alert-danger">' + msg + '</div>' : '';
if (xhr) {
var extendedMsg = this.getErrorMsg(xhr);
const extendedMsg = this.getErrorMsg(xhr);
if (extendedMsg) {
err += '<div class="alert alert-danger">' + extendedMsg + '</div>';
}
@ -329,35 +387,35 @@ var px = (function () {
$('.query-and-save button').removeAttr('disabled');
always(data);
},
width: function () {
width() {
return token.width();
},
height: function () {
var others = 0;
var widget = container.parents('.widget');
var slice_description = widget.find('.slice_description');
if (slice_description.is(":visible")) {
others += widget.find('.slice_description').height() + 25;
height() {
let others = 0;
const widget = container.parents('.widget');
const sliceDescription = widget.find('.sliceDescription');
if (sliceDescription.is(':visible')) {
others += widget.find('.sliceDescription').height() + 25;
}
others += widget.find('.chart-header').height();
return widget.height() - others - 10;
},
bindResizeToWindowResize: function () {
var resizeTimer;
var slice = this;
$(window).on('resize', function (e) {
bindResizeToWindowResize() {
let resizeTimer;
const slice = this;
$(window).on('resize', function () {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(function () {
slice.resize();
}, 500);
});
},
render: function (force) {
render(force) {
if (force === undefined) {
force = false;
}
this.force = force;
token.find("img.loading").show();
token.find('img.loading').show();
container.css('height', this.height());
dttm = 0;
timer = setInterval(stopwatch, 10);
@ -365,67 +423,65 @@ var px = (function () {
$('#timer').addClass('btn-warning');
this.viz.render();
},
resize: function () {
token.find("img.loading").show();
resize() {
token.find('img.loading').show();
container.css('height', this.height());
this.viz.render();
this.viz.resize();
},
addFilter: function (col, vals) {
addFilter(col, vals) {
if (dashboard !== undefined) {
dashboard.addFilter(slice_id, col, vals);
dashboard.addFilter(sliceId, col, vals);
}
},
setFilter: function (col, vals) {
setFilter(col, vals) {
if (dashboard !== undefined) {
dashboard.setFilter(slice_id, col, vals);
dashboard.setFilter(sliceId, col, vals);
}
},
getFilters: function (col, vals) {
getFilters() {
if (dashboard !== undefined) {
return dashboard.filters[slice_id];
return dashboard.filters[sliceId];
}
return false;
},
clearFilter() {
if (dashboard !== undefined) {
dashboard.clearFilter(sliceId);
}
},
clearFilter: function () {
removeFilter(col, vals) {
if (dashboard !== undefined) {
delete dashboard.clearFilter(slice_id);
}
},
removeFilter: function (col, vals) {
if (dashboard !== undefined) {
delete dashboard.removeFilter(slice_id, col, vals);
dashboard.removeFilter(sliceId, col, vals);
}
},
};
var visType = data.form_data.viz_type;
const visType = data.form_data.viz_type;
px.registerViz(visType);
slice.viz = visualizations[data.form_data.viz_type](slice);
return slice;
};
function registerViz(name) {
var visSource = sourceMap[name];
const visSource = sourceMap[name];
if (visSource) {
var visFactory = require('../../visualizations/' + visSource);
/* eslint global-require: 0 */
const visFactory = require('../../visualizations/' + visSource);
if (typeof visFactory === 'function') {
visualizations[name] = visFactory;
}
} else {
throw new Error("require(" + name + ") failed.");
throw new Error('require(' + name + ') failed.');
}
}
// Export public functions
return {
registerViz: registerViz,
Slice: Slice,
formatDate: formatDate,
timeFormatFactory: timeFormatFactory,
color: color(),
getParam: getParam,
initFavStars: initFavStars,
formatDate,
getParam,
initFavStars,
registerViz,
Slice,
timeFormatFactory,
};
})();
}();
module.exports = px;

View File

@ -1,56 +1,51 @@
var $ = require('jquery');
var d3 = require('d3');
const $ = require('jquery');
const d3 = require('d3');
/*
Utility function that takes a d3 svg:text selection and a max width, and splits the
text's text across multiple tspan lines such that any given line does not exceed max width
If text does not span multiple lines AND adjustedY is passed, will set the text to the passed val
If text does not span multiple lines AND adjustedY is passed,
will set the text to the passed val
*/
function wrapSvgText(text, width, adjustedY) {
var lineHeight = 1; // ems
const lineHeight = 1;
// ems
text.each(function () {
var text = d3.select(this),
words = text.text().split(/\s+/),
word,
line = [],
lineNumber = 0,
x = text.attr("x"),
y = text.attr("y"),
dy = parseFloat(text.attr("dy")),
tspan = text.text(null)
.append("tspan")
.attr("x", x)
.attr("y", y)
.attr("dy", dy + "em");
const d3Text = d3.select(this);
const words = d3Text.text().split(/\s+/);
let word;
let line = [];
let lineNumber = 0;
const x = d3Text.attr('x');
const y = d3Text.attr('y');
const dy = parseFloat(d3Text.attr('dy'));
let tspan =
d3Text.text(null).append('tspan').attr('x', x)
.attr('y', y)
.attr('dy', dy + 'em');
var didWrap = false;
for (var i = 0; i < words.length; i++) {
let didWrap = false;
for (let i = 0; i < words.length; i++) {
word = words[i];
line.push(word);
tspan.text(line.join(" "));
tspan.text(line.join(' '));
if (tspan.node().getComputedTextLength() > width) {
line.pop(); // remove word that pushes over the limit
tspan.text(line.join(" "));
line.pop();
// remove word that pushes over the limit
tspan.text(line.join(' '));
line = [word];
tspan = text.append("tspan")
.attr("x", x)
.attr("y", y)
.attr("dy", ++lineNumber * lineHeight + dy + "em")
.text(word);
tspan =
d3Text.append('tspan').attr('x', x).attr('y', y)
.attr('dy', ++lineNumber * lineHeight + dy + 'em')
.text(word);
didWrap = true;
}
}
if (!didWrap && typeof adjustedY !== "undefined") {
tspan.attr("y", adjustedY);
if (!didWrap && typeof adjustedY !== 'undefined') {
tspan.attr('y', adjustedY);
}
});
}
/**
* Sets the body and title content of a modal, and shows it. Assumes HTML for modal exists and that
* it handles closing (i.e., works with bootstrap)
@ -65,47 +60,41 @@ function wrapSvgText(text, width, adjustedY) {
* }
*/
function showModal(options) {
options.modalSelector = options.modalSelector || ".misc-modal";
options.titleSelector = options.titleSelector || ".misc-modal .modal-title";
options.bodySelector = options.bodySelector || ".misc-modal .modal-body";
$(options.titleSelector).html(options.title || "");
$(options.bodySelector).html(options.body || "");
$(options.modalSelector).modal("show");
/* eslint no-param-reassign: 0 */
options.modalSelector = options.modalSelector || '.misc-modal';
options.titleSelector = options.titleSelector || '.misc-modal .modal-title';
options.bodySelector = options.bodySelector || '.misc-modal .modal-body';
$(options.titleSelector).html(options.title || '');
$(options.bodySelector).html(options.body || '');
$(options.modalSelector).modal('show');
}
var showApiMessage = function (resp) {
var template = '<div class="alert"> ' +
'<button type="button" class="close" ' +
'data-dismiss="alert">×</button> </div>';
var severity = resp.severity || 'info';
$(template)
.addClass('alert-' + severity)
.append(resp.message)
.appendTo($('#alert-container'));
const showApiMessage = function (resp) {
const template =
'<div class="alert"> ' +
'<button type="button" class="close" ' +
'data-dismiss="alert">\xD7</button> </div>';
const severity = resp.severity || 'info';
$(template).addClass('alert-' + severity)
.append(resp.message)
.appendTo($('#alert-container'));
};
var toggleCheckbox = function (apiUrlPrefix, selector) {
var apiUrl = apiUrlPrefix + $(selector)[0].checked;
$.get(apiUrl).fail(function (xhr, textStatus, errorThrown) {
var resp = xhr.responseJSON;
const toggleCheckbox = function (apiUrlPrefix, selector) {
const apiUrl = apiUrlPrefix + $(selector)[0].checked;
$.get(apiUrl).fail(function (xhr) {
const resp = xhr.responseJSON;
if (resp && resp.message) {
showApiMessage(resp);
}
});
};
/**
* Fix the height of the table body of a DataTable with scrollY set
*/
var fixDataTableBodyHeight = function ($tableDom, height) {
var headHeight = $tableDom.find('.dataTables_scrollHead').height();
$tableDom.find('.dataTables_scrollBody')
.css('max-height', height - headHeight);
const fixDataTableBodyHeight = function ($tableDom, height) {
const headHeight = $tableDom.find('.dataTables_scrollHead').height();
$tableDom.find('.dataTables_scrollBody').css('max-height', height - headHeight);
};
var formatters = {};
const formatters = {};
function d3format(format, number) {
// Formats a number and memoizes formatters to be reused
format = format || '.3s';
@ -114,11 +103,10 @@ function d3format(format, number) {
}
return formatters[format](number);
}
module.exports = {
wrapSvgText: wrapSvgText,
showModal: showModal,
toggleCheckbox: toggleCheckbox,
fixDataTableBodyHeight: fixDataTableBodyHeight,
d3format: d3format,
d3format,
fixDataTableBodyHeight,
showModal,
toggleCheckbox,
wrapSvgText,
};

View File

@ -1,93 +1,80 @@
var $ = window.$ = require('jquery');
var jQuery = window.jQuery = $;
var showModal = require('./modules/utils.js').showModal;
const $ = window.$ = require('jquery');
const showModal = require('./modules/utils.js').showModal;
require('./caravel-select2.js');
require('datatables.net-bs');
require('../node_modules/datatables-bootstrap3-plugin/media/css/datatables-bootstrap3.css');
require('bootstrap');
var ace = require('brace');
const ace = require('brace');
require('brace/mode/sql');
require('brace/theme/crimson_editor');
require('../stylesheets/sql.css');
$(document).ready(function () {
function getParam(name) {
name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
results = regex.exec(location.search);
return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
/* eslint no-param-reassign: 0 */
name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');
const regex = new RegExp('[\\?&]' + name + '=([^&#]*)');
const results = regex.exec(location.search);
return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' '));
}
function initSqlEditorView() {
var database_id = $('#database_id').val();
var editor = ace.edit("sql");
const databaseId = $('#databaseId').val();
const editor = ace.edit('sql');
editor.$blockScrolling = Infinity;
editor.getSession().setUseWrapMode(true);
$('#sql').hide();
editor.setTheme("ace/theme/crimson_editor");
editor.setTheme('ace/theme/crimson_editor');
editor.setOptions({
minLines: 16,
maxLines: Infinity,
});
editor.getSession().setMode("ace/mode/sql");
editor.getSession().setMode('ace/mode/sql');
editor.focus();
$("select").select2({
dropdownAutoWidth: true,
});
$('select').select2({ dropdownAutoWidth: true });
function showTableMetadata() {
$(".metadata").load(
'/caravel/table/' + database_id + '/' + $("#dbtable").val() + '/');
$('.metadata').load('/caravel/table/' + databaseId + '/' + $('#dbtable').val() + '/');
}
$("#dbtable").on("change", showTableMetadata);
$('#dbtable').on('change', showTableMetadata);
showTableMetadata();
$("#create_view").click(function () {
$('#create_view').click(function () {
showModal({
title: "Error",
body: "Sorry, this feature is not yet implemented",
title: 'Error',
body: 'Sorry, this feature is not yet implemented',
});
});
$(".sqlcontent").show();
$('.sqlcontent').show();
function selectStarOnClick() {
$.ajax('/caravel/select_star/' + database_id + '/' + $("#dbtable").val() + '/')
.done(function (msg) {
const url = '/caravel/select_star/' + databaseId + '/' + $('#dbtable').val() + '/';
$.ajax(url).done(function (msg) {
editor.setValue(msg);
});
}
$("#select_star").click(selectStarOnClick);
$('#select_star').click(selectStarOnClick);
editor.setValue(getParam('sql'));
$(window).bind("popstate", function (event) {
$(window).bind('popstate', function () {
// Could do something more lightweight here, but we're not optimizing
// for the use of the back button anyways
editor.setValue(getParam('sql'));
$("#run").click();
$('#run').click();
});
$("#run").click(function () {
$('#run').click(function () {
$('#results').hide(0);
$('#loading').show(0);
history.pushState({}, document.title, '?sql=' + encodeURIComponent(editor.getValue()));
$.ajax({
type: "POST",
type: 'POST',
url: '/caravel/runsql/',
data: {
data: JSON.stringify({
database_id: $('#database_id').val(),
databaseId: $('#databaseId').val(),
sql: editor.getSession().getValue(),
}),
},
success: function (data) {
success(data) {
$('#loading').hide(0);
$('#results').show(0);
$('#results').html(data);
$('table.sql_results').DataTable({
retrieve: true,
paging: false,
@ -95,7 +82,7 @@ $(document).ready(function () {
aaSorting: [],
});
},
error: function (err, err2) {
error(err) {
$('#loading').hide(0);
$('#results').show(0);
$('#results').html(err.responseText);

View File

@ -1,13 +1,13 @@
var $ = window.$ = require('jquery');
var jQuery = window.jQuery = $;
var px = require('./modules/caravel.js');
const $ = window.$ = require('jquery');
/* eslint no-unused-vars: 0 */
const jQuery = window.jQuery = $;
const px = require('./modules/caravel.js');
require('bootstrap');
$(document).ready(function () {
var slice;
var data = $('.slice').data('slice');
slice = px.Slice(data);
const data = $('.slice').data('slice');
const slice = px.Slice(data);
slice.render();
slice.bindResizeToWindowResize();
});

View File

@ -1,32 +1,30 @@
var $ = window.$ = require('jquery');
var jQuery = window.jQuery = $;
const $ = window.$ = require('jquery');
/* eslint no-unused-vars: 0 */
const jQuery = window.jQuery = $;
require('../stylesheets/welcome.css');
require('bootstrap');
require('datatables.net-bs');
require('../node_modules/datatables-bootstrap3-plugin/media/css/datatables-bootstrap3.css');
require('../node_modules/cal-heatmap/cal-heatmap.css');
var d3 = require('d3');
var CalHeatMap = require('cal-heatmap');
const d3 = require('d3');
const CalHeatMap = require('cal-heatmap');
function modelViewTable(selector, modelView, orderCol, order) {
// Builds a dataTable from a flask appbuilder api endpoint
var url = '/' + modelView.toLowerCase() + '/api/read';
let url = '/' + modelView.toLowerCase() + '/api/read';
url += '?_oc_' + modelView + '=' + orderCol;
url += '&_od_' + modelView +'=' + order;
url += '&_od_' + modelView + '=' + order;
$.getJSON(url, function (data) {
var tableData = jQuery.map(data.result, function (el, i) {
var row = $.map(data.list_columns, function (col, i) {
return el[col];
});
return [row];
const tableData = $.map(data.result, function (el) {
const row = $.map(data.list_columns, function (col) {
return el[col];
});
return [row];
});
var cols = jQuery.map(data.list_columns, function (col, i) {
const cols = $.map(data.list_columns, function (col) {
return { sTitle: data.label_columns[col] };
});
var panel = $(selector).parents('.panel');
panel.find("img.loading").remove();
const panel = $(selector).parents('.panel');
panel.find('img.loading').remove();
$(selector).DataTable({
aaData: tableData,
aoColumns: cols,
@ -37,32 +35,28 @@ function modelViewTable(selector, modelView, orderCol, order) {
searching: true,
bInfo: false,
});
// Hack to move the searchbox in the right spot
var search = panel.find(".dataTables_filter input");
const search = panel.find('.dataTables_filter input');
search.addClass('form-control').detach();
search.appendTo(panel.find(".search"));
search.appendTo(panel.find('.search'));
panel.find('.dataTables_filter').remove();
// Hack to display the page navigator properly
panel.find('.col-sm-5').remove();
var nav = panel.find('.col-sm-7');
const nav = panel.find('.col-sm-7');
nav.removeClass('col-sm-7');
nav.addClass('col-sm-12');
$(selector).slideDown();
$('[data-toggle="tooltip"]').tooltip({ container: 'body' });
});
}
$(document).ready(function () {
d3.json('/caravel/activity_per_day', function (json) {
var ext = d3.extent(d3.values(json));
var cal = new CalHeatMap();
var range = 10;
var legendBounds = [];
var step = (ext[1] - ext[0]) / (range - 1);
for (var i = 0; i< range; i++) {
const ext = d3.extent(d3.values(json));
const cal = new CalHeatMap();
const range = 10;
const legendBounds = [];
const step = (ext[1] - ext[0]) / (range - 1);
for (let i = 0; i < range; i++) {
legendBounds.push(i * step + ext[0]);
}
cal.init({
@ -70,10 +64,14 @@ $(document).ready(function () {
range: 13,
data: json,
legend: legendBounds,
legendColors: ['#D6E685', '#1E6823'], // Based on github's colors
domain: "month",
subDomain: "day",
itemName: "action",
legendColors: [
'#D6E685',
'#1E6823',
],
// Based on github's colors
domain: 'month',
subDomain: 'day',
itemName: 'action',
tooltip: true,
});
});

View File

@ -7,7 +7,7 @@
"test": "spec"
},
"scripts": {
"test": "mocha --compilers js:babel-core/register --required spec/helpers/browser.js spec/**/*_spec.*",
"test": "npm run lint && mocha --compilers js:babel-core/register --required spec/helpers/browser.js spec/**/*_spec.*",
"dev": "webpack -d --watch --colors",
"prod": "webpack -p --colors",
"lint": "npm run --silent lint:js",
@ -81,7 +81,11 @@
"chai": "^3.5.0",
"css-loader": "^0.23.1",
"enzyme": "^2.0.0",
"eslint": "^2.2.0",
"eslint": "^2.13.1",
"eslint-config-airbnb": "^9.0.1",
"eslint-plugin-import": "^1.11.1",
"eslint-plugin-jsx-a11y": "^2.0.1",
"eslint-plugin-react": "^5.2.2",
"exports-loader": "^0.6.3",
"file-loader": "^0.8.5",
"imports-loader": "^0.6.5",

View File

@ -2,9 +2,9 @@
require('babel-register')();
var jsdom = require('jsdom').jsdom;
const jsdom = require('jsdom').jsdom;
var exposedProperties = ['window', 'navigator', 'document'];
const exposedProperties = ['window', 'navigator', 'document'];
global.document = jsdom('');
global.window = document.defaultView;
@ -16,7 +16,7 @@ Object.keys(document.defaultView).forEach((property) => {
});
global.navigator = {
serAgent: 'node.js',
userAgent: 'node.js',
};
documentRef = document;

View File

@ -1,3 +1,4 @@
/* eslint global-require: 0 */
const d3 = window.d3 || require('d3');
export const EARTH_CIRCUMFERENCE_KM = 40075.16;
@ -23,5 +24,5 @@ export function isNumeric(num) {
export function rgbLuminance(r, g, b) {
// Formula: https://en.wikipedia.org/wiki/Relative_luminance
return LUMINANCE_RED_WEIGHT*r + LUMINANCE_GREEN_WEIGHT*g + LUMINANCE_BLUE_WEIGHT*b;
return (LUMINANCE_RED_WEIGHT * r) + (LUMINANCE_GREEN_WEIGHT * g) + (LUMINANCE_BLUE_WEIGHT * b);
}

View File

@ -1,7 +1,10 @@
const path = require('path');
const APP_DIR = path.resolve(__dirname, './'); // input
const BUILD_DIR = path.resolve(__dirname, './javascripts/dist'); // output
// input dir
const APP_DIR = path.resolve(__dirname, './');
// output dir
const BUILD_DIR = path.resolve(__dirname, './javascripts/dist');
const config = {
entry: {
@ -18,10 +21,12 @@ const config = {
filename: '[name].entry.js',
},
resolve: {
extensions: ['', '.js', '.jsx'],
alias: {
webworkify: 'webworkify-webpack',
},
extensions: [
'',
'.js',
'.jsx',
],
alias: { webworkify: 'webworkify-webpack' },
},
module: {
loaders: [
@ -30,40 +35,53 @@ const config = {
exclude: APP_DIR + '/node_modules',
loader: 'babel',
query: {
presets: ['airbnb', 'es2015', 'react'],
presets: [
'airbnb',
'es2015',
'react',
],
},
},
/* for react-map-gl overlays */
{
test: /\.react\.js$/,
include: APP_DIR + '/node_modules/react-map-gl/src/overlays',
loader: 'babel',
},
/* for require('*.css') */
{
test: /\.css$/,
include: APP_DIR,
loader: "style-loader!css-loader",
loader: 'style-loader!css-loader',
},
/* for css linking images */
{ test: /\.png$/, loader: "url-loader?limit=100000" },
{ test: /\.jpg$/, loader: "file-loader" },
{ test: /\.gif$/, loader: "file-loader" },
{
test: /\.png$/,
loader: 'url-loader?limit=100000',
},
{
test: /\.jpg$/,
loader: 'file-loader',
},
{
test: /\.gif$/,
loader: 'file-loader',
},
/* for font-awesome */
{ test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/, loader: "url-loader?limit=10000&minetype=application/font-woff" },
{ test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/, loader: "file-loader" },
{
test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
loader: 'url-loader?limit=10000&minetype=application/font-woff',
},
{
test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
loader: 'file-loader',
},
/* for require('*.less') */
{
test: /\.less$/,
include: APP_DIR,
loader: "style!css!less",
loader: 'style!css!less',
},
/* for mapbox */
{
test: /\.json$/,
@ -88,5 +106,4 @@ const config = {
},
plugins: [],
};
module.exports = config;