Skip to content

Commit

Permalink
Feature: Adding new exploration UI
Browse files Browse the repository at this point in the history
  • Loading branch information
Fabian committed Sep 12, 2017
1 parent 147c12d commit 121cb27
Show file tree
Hide file tree
Showing 59 changed files with 3,360 additions and 41 deletions.
12 changes: 12 additions & 0 deletions superset/assets/backendSync.json
Original file line number Diff line number Diff line change
Expand Up @@ -1909,6 +1909,18 @@
"default": "linear",
"description": "Line interpolation as defined by d3.js"
},
"overlays": {
"type": "SelectControl",
"multi": true,
"label": "Overlays",
"default": []
},
"offset_overlays": {
"type": "CheckboxControl",
"label": "Auto Offset Overlays",
"default": false,
"description": "Auto offset overlay to match the time frame of the current config."
},
"pie_label_type": {
"type": "SelectControl",
"label": "Label Type",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import cx from 'classnames';
import URLShortLinkButton from './URLShortLinkButton';
import EmbedCodeButton from './EmbedCodeButton';
import DisplayQueryButton from './DisplayQueryButton';
import { getSwivelUrl } from '../exploreUtils';

const propTypes = {
canDownload: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]).isRequired,
Expand All @@ -21,6 +22,15 @@ export default function ExploreActionButtons({
if (slice) {
return (
<div className="btn-group results" role="group">
<a
href={getSwivelUrl(slice.formData)}
className="btn btn-default btn-sm"
title="Open in Swivel"
target="_blank"
rel="noopener noreferrer"
>
Swivel
</a>
<URLShortLinkButton slice={slice} />

<EmbedCodeButton slice={slice} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export default class SelectControl extends React.PureComponent {
let optionValue = opt ? opt[this.props.valueKey] : null;
// if multi, return options values as an array
if (this.props.multi) {
optionValue = opt ? opt.map(o => o[this.props.valueKey]) : null;
optionValue = opt ? opt.map(o => o[this.props.valueKey]) : null;
}
this.props.onChange(optionValue);
}
Expand Down
13 changes: 13 additions & 0 deletions superset/assets/javascripts/explore/exploreUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,16 @@ export function getExploreUrl(form_data, endpointType = 'base', force = false,
uri = uri.search(search).directory(directory);
return uri.toString();
}

export function getSwivelUrl(form_data) {
if (!form_data.datasource) {
return null;
}

const uri = URI(window.location.search);

// Building the query
return uri.pathname('/swivel')
.search({ form_data: JSON.stringify(form_data) })
.toString();
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,4 @@ export default function exploreReducer(state = {}, action) {
}
return state;
}

5 changes: 5 additions & 0 deletions superset/assets/javascripts/swivel/ColumnTypes.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export default {
TIMESTAMP: 'TIMESTAMP',
NUMERIC: 'NUMERIC',
STRING: 'NVARCHAR',
};
4 changes: 4 additions & 0 deletions superset/assets/javascripts/swivel/ContainerTypes.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export default {
SPLIT: 'SPLIT',
FILTER: 'FILTER',
};
5 changes: 5 additions & 0 deletions superset/assets/javascripts/swivel/FilterTypes.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export default {
INTERVAL: 'INTERVAL',
SELECT: 'SELECT',
UNBOUND: 'UNBOUND',
};
3 changes: 3 additions & 0 deletions superset/assets/javascripts/swivel/ItemTypes.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default {
DIMENSION: 'dimension',
};
42 changes: 42 additions & 0 deletions superset/assets/javascripts/swivel/actions/globalActions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
export const RESET = 'RESET';
export const CLEAR_HISTORY = 'CLEAR_HISTORY';
export function reset(clearHistory) {
if (clearHistory) {
return dispatch =>
// We need the sandwich to make sure there is enough space in the
// local storage to RESET
Promise.resolve(dispatch({ type: CLEAR_HISTORY }))
.then(() => dispatch({ type: RESET }))
.then(() => dispatch({ type: CLEAR_HISTORY }));
}
return { type: RESET };
}

// This controls weather a query should be run
export const SET_RUN = 'SET_RUN';
export function setRun(run) {
return { type: SET_RUN, run };
}

// This controls if a query should automatically run if the query settings change
export const SET_AUTO_RUN = 'SET_AUTO_RUN';
export function setAutoRun(autoRun) {
return { type: SET_AUTO_RUN, autoRun };
}

// This indicates if a query is currently running
export const SET_IS_RUNNING = 'SET_IS_RUNNING';
export function setIsRunning(isRunning) {
return { type: SET_IS_RUNNING, isRunning };
}

export const SET_ERROR = 'SET_ERROR';
export function setError(error) {
return { type: SET_ERROR, error };
}

export const UPDATE_FORM_DATA = 'UPDATE_FORM_DATA';
export const IMPORT_FORM_DATA = 'IMPORT_FORM_DATA';
export function importFormData(formData, refData) {
return { type: IMPORT_FORM_DATA, formData, refData };
}
98 changes: 98 additions & 0 deletions superset/assets/javascripts/swivel/actions/querySettingsActions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { convertQuerySettingsToFormData } from '../formDataUtils/convertToFormData';

import { fetchDatasources, fetchDatasourceMetadata } from './refDataActions';
import { UPDATE_FORM_DATA, setError, setAutoRun, importFormData } from './globalActions';
import { runQuery } from './vizDataActions';

export const SET_DEFAULTS = 'SET_DEFAULTS';
export function setDefaults(refData) {
return { type: SET_DEFAULTS, refData };
}

export const SET_DATASOURCE = 'SET_DATASOURCE';
export function setDatasource(uid, init = true) {
return (dispatch, getState) => {
if (getState().settings.future.length === 0 &&
getState().settings.present.query.datasource === uid &&
getState().refData.columns.length) {
return Promise.resolve();
}
return dispatch(fetchDatasourceMetadata(uid))
.then(() => dispatch({ type: SET_DATASOURCE, uid }))
.then(() => init ? dispatch(setDefaults(getState().refData)) : Promise.resolve());
};
}

export const BOOTSTRAP = 'BOOTSTRAP';
export function bootstrap(formData) {
return (dispatch, getState) =>
dispatch(fetchDatasources()).then(() => {
const datasource = getState().settings.present.query.datasource;
if (formData.datasource) {
return Promise.resolve(dispatch(setAutoRun(false)))
.then(() => dispatch(setDatasource(formData.datasource, false)))
.then(() => dispatch(importFormData(formData, getState().refData)))
.then(() => dispatch(setAutoRun(true)));
} else if (datasource) {
return dispatch(setDatasource(datasource, false));
}
return Promise.resolve();
});
}

export function updateFormDataAndRunQuery(settings) {
return (dispatch) => {
const formData = convertQuerySettingsToFormData(settings);
return Promise.resolve(
dispatch({ type: UPDATE_FORM_DATA, formData, wipeData: true }))
.then(() => dispatch(setError(formData.error)))
.then(() => dispatch(runQuery()));
};
}

export const TOGGLE_METRIC = 'TOGGLE_METRIC';
export function toggleMetric(metric) {
return { type: TOGGLE_METRIC, metric };
}

export const ADD_FILTER = 'ADD_FILTER';
export function addFilter(filter) {
return { type: ADD_FILTER, filter };
}

export const CONFIGURE_FILTER = 'CONFIGURE_FILTER';
export function configureFilter(filter) {
return { type: CONFIGURE_FILTER, filter };
}

export const REMOVE_FILTER = 'REMOVE_FILTER';
export function removeFilter(filter) {
return { type: REMOVE_FILTER, filter };
}

export const ADD_SPLIT = 'ADD_SPLIT';
export function addSplit(split) {
return { type: ADD_SPLIT, split };
}

export const CONFIGURE_SPLIT = 'CONFIGURE_SPLIT';
export function configureSplit(split) {
return { type: CONFIGURE_SPLIT, split };
}

export const REMOVE_SPLIT = 'REMOVE_SPLIT';
export function removeSplit(split) {
return { type: REMOVE_SPLIT, split };
}

export const CHANGE_INTERVAL = 'CHANGE_INTERVAL';
export function changeInterval(intervalStart, intervalEnd) {
return { type: CHANGE_INTERVAL, intervalStart, intervalEnd };
}

// TODO need to move those to the vizSettings
export const SET_VIZTYPE = 'SET_VIZTYPE';
export function setVizType(vizType) {
return { type: SET_VIZTYPE, vizType };
}

80 changes: 80 additions & 0 deletions superset/assets/javascripts/swivel/actions/refDataActions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import ColumnTypes from '../ColumnTypes';

export const SET_COLUMNS = 'SET_COLUMNS';
export function setColumns(columns) {
return { type: SET_COLUMNS, columns };
}

export const SET_DATASOURCES = 'SET_DATASOURCES';
export function setDatasources(datasources) {
return { type: SET_DATASOURCES, datasources };
}

export const SET_METRICS = 'SET_METRICS';
export function setMetrics(metrics) {
return { type: SET_METRICS, metrics };
}

export const SET_TIME_GRAINS = 'SET_TIME_GRAINS';
export function setTimeGrains(timeGrains) {
return { type: SET_TIME_GRAINS, timeGrains };
}

export function fetchDatasources() {
return dispatch => fetch('/superset/datasources/', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
credentials: 'same-origin',
}).then(response => response.json()).then((data) => {
const datasources = data.map(x => ({
uid: x.uid,
name: x.name,
type: x.type,
id: x.id }));
return datasources;
}).then(datasources => dispatch(setDatasources(datasources)));
}

export function fetchDatasourceMetadata(uid) {
return function (dispatch) {
if (!uid) {
return Promise.resolve();
}
const url = `/swivel/fetch_datasource_metadata?uid=${uid}`;
return fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
credentials: 'same-origin',
}).then(response => response.json()).then((data) => {
const columns = data.columns
.filter(x => x.groupable ||
x.filterable ||
(x.type !== ColumnTypes.NUMERIC)).map(x => ({
name: x.name,
id: x.id,
columnType: x.type,
groupable: x.groupable,
}));
const metrics = data.metrics.map(x => ({ name: x.name, id: x.id }));
const timeGrains = data.time_grains;

// Todo: this is hacky should be done better
if (uid.endsWith('druid')) {
const timeColumn = columns.find(x =>
x.id.toLowerCase() === '__time');
timeColumn.columnType = ColumnTypes.TIMESTAMP;
timeColumn.groupable = false;
}

return Promise.all([
dispatch(setColumns(columns)),
dispatch(setMetrics(metrics)),
dispatch(setTimeGrains(timeGrains)),
]);
});
};
}
52 changes: 52 additions & 0 deletions superset/assets/javascripts/swivel/actions/vizDataActions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { setError, setRun, setIsRunning } from './globalActions';

export const SET_DATA = 'SET_DATA';
export function setData(data) {
return { type: SET_DATA, data };
}

export const SET_OUTDATED = 'SET_OUTDATED';
export function setOutdated(outdated) {
return { type: SET_OUTDATED, outdated };
}

export const RESET_DATA = 'RESET_DATA';
export function resetData() {
return { type: RESET_DATA };
}

// TODO switch this to jQuery and make it abortable
export function runQuery() {
return (dispatch, getState) => {
const payload = getState().vizData.formData;
if (!getState().controls.error) {
const datasource = getState().settings.present.query.datasource;
const [dsId, dsType] = datasource.split('__');
const url = `${window.location.origin}/superset/explore_json/${dsType}/${dsId}`;
const token = $('input#csrf_token').val();
return Promise.resolve(dispatch(setIsRunning(true)))
.then(() => fetch(url, {
method: 'POST',
credentials: 'same-origin',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
Cache: 'no-cache',
'X-CSRFToken': token,
},
body: payload.toJson() })
.then(response => response.json()).then(data => dispatch(setData(data.data)))
.then(() => Promise.all([
dispatch(setRun(false)),
dispatch(setOutdated(false)),
dispatch(setIsRunning(false))]))
.catch(function (res) {
return Promise.all([
dispatch(setData()),
dispatch(setError(res)),
dispatch(setIsRunning(false))]);
}));
}
return Promise.resolve();
};
}
23 changes: 23 additions & 0 deletions superset/assets/javascripts/swivel/actions/vizSettingsActions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { convertVizSettingsToFormData } from '../formDataUtils/convertToFormData';
import { UPDATE_FORM_DATA } from './globalActions';

export const TOGGLE_SHOW_LEGEND = 'TOGGLE_SHOW_LEGEND';
export function toggleShowLegend() {
return { type: TOGGLE_SHOW_LEGEND };
}

export const TOGGLE_RICH_TOOLTIP = 'TOGGLE_RICH_TOOLTIP';
export function toggleRichTooltip() {
return { type: TOGGLE_RICH_TOOLTIP };
}

export function updateFormData(vizSettings) {
const formData = convertVizSettingsToFormData(vizSettings);
return { type: UPDATE_FORM_DATA, formData };
}

export const TOGGLE_SEPARATE_CHARTS = 'TOGGLE_SEPARATE_CHARTS';
export function toggleSeparateCharts() {
return { type: TOGGLE_SEPARATE_CHARTS };
}

Loading

0 comments on commit 121cb27

Please sign in to comment.