From a9f6cd153cfd0657d24841936e82b732a45eaace Mon Sep 17 00:00:00 2001 From: Vera Liu Date: Wed, 14 Sep 2016 11:21:19 -0700 Subject: [PATCH 1/3] Created store and reducers --- .../assets/javascripts/explorev2/actions.js | 71 ++++++++++ .../components/ExploreViewContainer.jsx | 2 +- .../assets/javascripts/explorev2/index.jsx | 23 +++- .../assets/javascripts/explorev2/reducers.js | 127 ++++++++++++++++++ 4 files changed, 219 insertions(+), 4 deletions(-) create mode 100644 caravel/assets/javascripts/explorev2/actions.js create mode 100644 caravel/assets/javascripts/explorev2/reducers.js diff --git a/caravel/assets/javascripts/explorev2/actions.js b/caravel/assets/javascripts/explorev2/actions.js new file mode 100644 index 000000000000..52aeeb278b0a --- /dev/null +++ b/caravel/assets/javascripts/explorev2/actions.js @@ -0,0 +1,71 @@ +export const SET_DATASOURCE = 'SET_DATASOURCE'; +export const SET_VIZTYPE = 'SET_VIZTYPE'; +export const SET_TIME_FILTER = 'SET_TIME_FILTER'; +export const SET_GROUPBY = 'SET_GROUPBY'; +export const ADD_COLUMN = 'ADD_COLUMN'; +export const REMOVE_COLUMN = 'REMOVE_COLUMN'; +export const ADD_ORDERING = 'ADD_ORDERING'; +export const REMOVE_ORDERING = 'REMOVE_ORDERING'; +export const SET_TIME_STAMP = 'SET_TIME_STAMP'; +export const SET_ROW_LIMIT = 'SET_ROW_LIMIT'; +export const TOGGLE_SEARCHBOX = 'TOGGLE_SEARCHBOX'; +export const SET_SQL = 'SET_SQL'; +export const ADD_FILTER = 'ADD_FILTER'; +export const SET_FILTER = 'SET_FILTER'; +export const REMOVE_FILTER = 'REMOVE_FILTER'; + +export function setDatasource(datasourceId) { + return { type: SET_DATASOURCE, datasourceId }; +} + +export function setVizType(vizType) { + return { type: SET_VIZTYPE, vizType }; +} + +export function setTimeFilter(timeFilter) { + return { type: SET_TIME_FILTER, timeFilter }; +} + +export function setGroupBy(groupBy) { + return { type: SET_GROUPBY, groupBy }; +} + +export function addColumn(column) { + return { type: ADD_COLUMN, column }; +} + +export function removeColumn(column) { + return { type: REMOVE_COLUMN, column }; +} + +export function addOrdering(ordering) { + return { type: ADD_ORDERING, ordering }; +} + +export function removeOrdering(ordering) { + return { type: REMOVE_ORDERING, ordering }; +} + +export function setTimeStamp(timeStampFormat) { + return { type: SET_TIME_STAMP, timeStampFormat }; +} + +export function setRowLimit(rowLimit) { + return { type: SET_ROW_LIMIT, rowLimit }; +} + +export function toggleSearchBox(searchBox) { + return { type: TOGGLE_SEARCHBOX, searchBox }; +} + +export function setSQL(sql) { + return { type: SET_SQL, sql }; +} + +export function addFilter(filter) { + return { type: ADD_FILTER, filter }; +} + +export function removeFilter(filter) { + return { type: REMOVE_FILTER, filter }; +} diff --git a/caravel/assets/javascripts/explorev2/components/ExploreViewContainer.jsx b/caravel/assets/javascripts/explorev2/components/ExploreViewContainer.jsx index 8d7e4f26cffa..0369655743f8 100644 --- a/caravel/assets/javascripts/explorev2/components/ExploreViewContainer.jsx +++ b/caravel/assets/javascripts/explorev2/components/ExploreViewContainer.jsx @@ -11,7 +11,7 @@ export default class ExploreViewContainer extends React.Component {
{ console.log('clicked query') }} + onQuery={() => { console.log('clicked query'); }} />

diff --git a/caravel/assets/javascripts/explorev2/index.jsx b/caravel/assets/javascripts/explorev2/index.jsx index 2ca33b10be5e..bc67ced3a0fb 100644 --- a/caravel/assets/javascripts/explorev2/index.jsx +++ b/caravel/assets/javascripts/explorev2/index.jsx @@ -2,12 +2,29 @@ import React from 'react'; import ReactDOM from 'react-dom'; import ExploreViewContainer from './components/ExploreViewContainer'; +import { compose, createStore } from 'redux'; +import { Provider } from 'react-redux'; + const exploreViewContainer = document.getElementById('js-explore-view-container'); const bootstrapData = exploreViewContainer.getAttribute('data-bootstrap'); + +import { initialState, exploreReducer } from './reducers'; +import persistState from 'redux-localstorage'; + +let enhancer = compose(persistState()); +if (process.env.NODE_ENV === 'dev') { + enhancer = compose( + persistState(), window.devToolsExtension && window.devToolsExtension() + ); +} +let store = createStore(exploreReducer, initialState, enhancer); + ReactDOM.render( - , + + + , exploreViewContainer ); diff --git a/caravel/assets/javascripts/explorev2/reducers.js b/caravel/assets/javascripts/explorev2/reducers.js new file mode 100644 index 000000000000..052a1894f1ee --- /dev/null +++ b/caravel/assets/javascripts/explorev2/reducers.js @@ -0,0 +1,127 @@ +import shortid from 'shortid'; +import * as actions from './actions'; + +const defaultTimeFilter = { + timeColumn: null, + timeGrain: null, + since: null, + until: null, +}; + +const defaultGroupBy = { + groupByColumn: [], + metrics: [], +}; + +const defaultSql = { + where: '', + having: '', +}; + +const defaultFilter = { + id: shortid.generate(), + eq: null, + op: 'in', + col: null, +}; + +export const initialState = { + datasourceId: null, + vizType: null, + timeFilter: defaultTimeFilter, + groupBy: defaultGroupBy, + columns: [], + orderings: [], + timeStampFormat: null, + rowLimit: null, + searchBox: false, + SQL: defaultSql, + filters: [defaultFilter], +}; + +function addToArr(state, arrKey, obj) { + const newState = {}; + newState[arrKey] = [...state[arrKey], obj]; + return Object.assign({}, state, newState); +} + +function removeFromArr(state, arrKey, obj) { + const newArr = []; + state[arrKey].forEach((arrItem) => { + if (!(obj === arrItem)) { + newArr.push(arrItem); + } + }); + return Object.assign({}, state, { [arrKey]: newArr }); +} + +function addToObjArr(state, arrKey, obj) { + const newObj = Object.assign({}, obj); + if (!newObj.id) { + newObj.id = shortid.generate(); + } + const newState = {}; + newState[arrKey] = [...state[arrKey], newObj]; + return Object.assign({}, state, newState); +} + +function removeFromObjArr(state, arrKey, obj, idKey = 'id') { + const newArr = []; + state[arrKey].forEach((arrItem) => { + if (!(obj[idKey] === arrItem[idKey])) { + newArr.push(arrItem); + } + }); + return Object.assign({}, state, { [arrKey]: newArr }); +} + +export const exploreReducer = function (state, action) { + const actionHandlers = { + [actions.SET_DATASOURCE]() { + return Object.assign({}, state, { datasourceId: action.datasourceId }); + }, + [actions.SET_VIZTYPE]() { + return Object.assign({}, state, { vizType: action.vizType }); + }, + [actions.SET_TIMEFILTER]() { + return Object.assign({}, state, { timeFilter: action.timeFilter }); + }, + [actions.SET_GROUPBY]() { + return Object.assign({}, state, { groupBy: action.groupBy }); + }, + [actions.ADD_COLUMN]() { + return addToArr(state, 'columns', action.column); + }, + [actions.REMOVE_COLUMN]() { + return removeFromArr(state, 'columns', action.column); + }, + [actions.ADD_ORDERING]() { + return addToArr(state, 'orderings', action.ordering); + }, + [actions.REMOVE_ORDERING]() { + return removeFromArr(state, 'orderings', action.ordering); + }, + [actions.SET_TIME_STAMP]() { + return Object.assign({}, state, { timeStampFormat: action.timeStampFormat }); + }, + [actions.SET_ROW_LIMIT]() { + return Object.assign({}, state, { rowLimit: action.rowLimit }); + }, + [actions.TOGGLE_SEARCHBOX]() { + return Object.assign({}, state, { searchBox: action.searchBox }); + }, + [actions.SET_SQL]() { + return Object.assign({}, state, { SQL: action.sql }); + }, + [actions.ADD_FILTER]() { + return addToObjArr(state, 'filters', action.filter); + }, + [actions.REMOVE_FILTER]() { + return removeFromObjArr(state, 'filters', action.filter); + }, + }; + if (action.type in actionHandlers) { + return actionHandlers[action.type](); + } + return state; +}; From ad318eef016009a2934a562192fe64cb25cf5258 Mon Sep 17 00:00:00 2001 From: Vera Liu Date: Thu, 15 Sep 2016 10:25:27 -0700 Subject: [PATCH 2/3] Added spec --- caravel/assets/javascripts/SqlLab/reducers.js | 95 ++++--------- .../{actions.js => actions/exploreActions.js} | 0 .../explorev2/components/ChartContainer.jsx | 17 ++- .../components/ControlPanelsContainer.jsx | 17 ++- .../components/ExploreViewContainer.jsx | 38 +++--- .../assets/javascripts/explorev2/index.jsx | 4 +- .../assets/javascripts/explorev2/reducers.js | 127 ------------------ .../explorev2/reducers/exploreReducer.js | 65 +++++++++ .../javascripts/explorev2/stores/store.js | 30 +++++ caravel/assets/package.json | 2 +- .../explore/components/actions_spec.js | 121 +++++++++++++++++ caravel/assets/utils/reducerUtils.js | 53 ++++++++ 12 files changed, 331 insertions(+), 238 deletions(-) rename caravel/assets/javascripts/explorev2/{actions.js => actions/exploreActions.js} (100%) delete mode 100644 caravel/assets/javascripts/explorev2/reducers.js create mode 100644 caravel/assets/javascripts/explorev2/reducers/exploreReducer.js create mode 100644 caravel/assets/javascripts/explorev2/stores/store.js create mode 100644 caravel/assets/spec/javascripts/explore/components/actions_spec.js create mode 100644 caravel/assets/utils/reducerUtils.js diff --git a/caravel/assets/javascripts/SqlLab/reducers.js b/caravel/assets/javascripts/SqlLab/reducers.js index dcdeac9c4742..61ec52d3b1ca 100644 --- a/caravel/assets/javascripts/SqlLab/reducers.js +++ b/caravel/assets/javascripts/SqlLab/reducers.js @@ -1,6 +1,7 @@ import shortid from 'shortid'; import * as actions from './actions'; import { now } from '../modules/dates'; +import * as utils from '../../utils/reducerUtils'; const defaultQueryEditor = { id: shortid.generate(), @@ -23,68 +24,16 @@ export const initialState = { queriesLastUpdate: 0, }; -function addToObject(state, arrKey, obj) { - const newObject = Object.assign({}, state[arrKey]); - const copiedObject = Object.assign({}, obj); - - if (!copiedObject.id) { - copiedObject.id = shortid.generate(); - } - newObject[copiedObject.id] = copiedObject; - return Object.assign({}, state, { [arrKey]: newObject }); -} - -function alterInObject(state, arrKey, obj, alterations) { - const newObject = Object.assign({}, state[arrKey]); - newObject[obj.id] = (Object.assign({}, newObject[obj.id], alterations)); - return Object.assign({}, state, { [arrKey]: newObject }); -} - -function alterInArr(state, arrKey, obj, alterations) { - // Finds an item in an array in the state and replaces it with a - // new object with an altered property - const idKey = 'id'; - const newArr = []; - state[arrKey].forEach((arrItem) => { - if (obj[idKey] === arrItem[idKey]) { - newArr.push(Object.assign({}, arrItem, alterations)); - } else { - newArr.push(arrItem); - } - }); - return Object.assign({}, state, { [arrKey]: newArr }); -} - -function removeFromArr(state, arrKey, obj, idKey = 'id') { - const newArr = []; - state[arrKey].forEach((arrItem) => { - if (!(obj[idKey] === arrItem[idKey])) { - newArr.push(arrItem); - } - }); - return Object.assign({}, state, { [arrKey]: newArr }); -} - -function addToArr(state, arrKey, obj) { - const newObj = Object.assign({}, obj); - if (!newObj.id) { - newObj.id = shortid.generate(); - } - const newState = {}; - newState[arrKey] = [...state[arrKey], newObj]; - return Object.assign({}, state, newState); -} - export const sqlLabReducer = function (state, action) { const actionHandlers = { [actions.ADD_QUERY_EDITOR]() { const tabHistory = state.tabHistory.slice(); tabHistory.push(action.queryEditor.id); const newState = Object.assign({}, state, { tabHistory }); - return addToArr(newState, 'queryEditors', action.queryEditor); + return utils.addToArr(newState, 'queryEditors', action.queryEditor); }, [actions.REMOVE_QUERY_EDITOR]() { - let newState = removeFromArr(state, 'queryEditors', action.queryEditor); + let newState = utils.removeFromArr(state, 'queryEditors', action.queryEditor); // List of remaining queryEditor ids const qeIds = newState.queryEditors.map((qe) => qe.id); let th = state.tabHistory.slice(); @@ -101,24 +50,25 @@ export const sqlLabReducer = function (state, action) { return Object.assign({}, initialState); }, [actions.ADD_TABLE]() { - return addToArr(state, 'tables', action.table); + return utils.addToArr(state, 'tables', action.table); }, [actions.EXPAND_TABLE]() { - return alterInArr(state, 'tables', action.table, { expanded: true }); + return utils.alterInArr(state, 'tables', action.table, { expanded: true }); }, [actions.COLLAPSE_TABLE]() { - return alterInArr(state, 'tables', action.table, { expanded: false }); + return utils.alterInArr(state, 'tables', action.table, { expanded: false }); }, [actions.REMOVE_TABLE]() { - return removeFromArr(state, 'tables', action.table); + return utils.removeFromArr(state, 'tables', action.table); }, [actions.START_QUERY]() { - const newState = addToObject(state, 'queries', action.query); + const newState = utils.addToObject(state, 'queries', action.query); const sqlEditor = { id: action.query.sqlEditorId }; - return alterInArr(newState, 'queryEditors', sqlEditor, { latestQueryId: action.query.id }); + return utils.alterInArr( + newState, 'queryEditors', sqlEditor, { latestQueryId: action.query.id }); }, [actions.STOP_QUERY]() { - return alterInObject(state, 'queries', action.query, { state: 'stopped' }); + return utils.alterInObject(state, 'queries', action.query, { state: 'stopped' }); }, [actions.QUERY_SUCCESS]() { const alts = { @@ -128,11 +78,11 @@ export const sqlLabReducer = function (state, action) { progress: 100, endDttm: now(), }; - return alterInObject(state, 'queries', action.query, alts); + return utils.alterInObject(state, 'queries', action.query, alts); }, [actions.QUERY_FAILED]() { const alts = { state: 'failed', errorMessage: action.msg, endDttm: now() }; - return alterInObject(state, 'queries', action.query, alts); + return utils.alterInObject(state, 'queries', action.query, alts); }, [actions.SET_ACTIVE_QUERY_EDITOR]() { const qeIds = state.queryEditors.map((qe) => qe.id); @@ -144,28 +94,29 @@ export const sqlLabReducer = function (state, action) { return state; }, [actions.QUERY_EDITOR_SETDB]() { - return alterInArr(state, 'queryEditors', action.queryEditor, { dbId: action.dbId }); + return utils.alterInArr(state, 'queryEditors', action.queryEditor, { dbId: action.dbId }); }, [actions.QUERY_EDITOR_SET_SCHEMA]() { - return alterInArr(state, 'queryEditors', action.queryEditor, { schema: action.schema }); + return utils.alterInArr(state, 'queryEditors', action.queryEditor, { schema: action.schema }); }, [actions.QUERY_EDITOR_SET_TITLE]() { - return alterInArr(state, 'queryEditors', action.queryEditor, { title: action.title }); + return utils.alterInArr(state, 'queryEditors', action.queryEditor, { title: action.title }); }, [actions.QUERY_EDITOR_SET_SQL]() { - return alterInArr(state, 'queryEditors', action.queryEditor, { sql: action.sql }); + return utils.alterInArr(state, 'queryEditors', action.queryEditor, { sql: action.sql }); }, [actions.QUERY_EDITOR_SET_AUTORUN]() { - return alterInArr(state, 'queryEditors', action.queryEditor, { autorun: action.autorun }); + return utils.alterInArr( + state, 'queryEditors', action.queryEditor, { autorun: action.autorun }); }, [actions.ADD_WORKSPACE_QUERY]() { - return addToArr(state, 'workspaceQueries', action.query); + return utils.addToArr(state, 'workspaceQueries', action.query); }, [actions.REMOVE_WORKSPACE_QUERY]() { - return removeFromArr(state, 'workspaceQueries', action.query); + return utils.removeFromArr(state, 'workspaceQueries', action.query); }, [actions.ADD_ALERT]() { - return addToArr(state, 'alerts', action.alert); + return utils.addToArr(state, 'alerts', action.alert); }, [actions.SET_DATABASES]() { const databases = {}; @@ -175,7 +126,7 @@ export const sqlLabReducer = function (state, action) { return Object.assign({}, state, { databases }); }, [actions.REMOVE_ALERT]() { - return removeFromArr(state, 'alerts', action.alert); + return utils.removeFromArr(state, 'alerts', action.alert); }, [actions.SET_NETWORK_STATUS]() { if (state.networkOn !== action.networkOn) { diff --git a/caravel/assets/javascripts/explorev2/actions.js b/caravel/assets/javascripts/explorev2/actions/exploreActions.js similarity index 100% rename from caravel/assets/javascripts/explorev2/actions.js rename to caravel/assets/javascripts/explorev2/actions/exploreActions.js diff --git a/caravel/assets/javascripts/explorev2/components/ChartContainer.jsx b/caravel/assets/javascripts/explorev2/components/ChartContainer.jsx index 9915264dc7a5..3666e923a201 100644 --- a/caravel/assets/javascripts/explorev2/components/ChartContainer.jsx +++ b/caravel/assets/javascripts/explorev2/components/ChartContainer.jsx @@ -1,12 +1,11 @@ import React from 'react'; import { Panel } from 'react-bootstrap'; -export default class ChartContainer extends React.Component { - render() { - return ( - - chart goes here - - ); - } -} +const ChartContainer = function () { + return ( + + chart goes here + + ); +}; +export default ChartContainer; diff --git a/caravel/assets/javascripts/explorev2/components/ControlPanelsContainer.jsx b/caravel/assets/javascripts/explorev2/components/ControlPanelsContainer.jsx index b0e7efa2627b..20b8b34075a8 100644 --- a/caravel/assets/javascripts/explorev2/components/ControlPanelsContainer.jsx +++ b/caravel/assets/javascripts/explorev2/components/ControlPanelsContainer.jsx @@ -1,12 +1,11 @@ import React from 'react'; import { Panel } from 'react-bootstrap'; -export default class ControlPanelsContainer extends React.Component { - render() { - return ( - - control panels here - - ); - } -} +const ControlPanelsContainer = function () { + return ( + + control panels here + + ); +}; +export default ControlPanelsContainer; diff --git a/caravel/assets/javascripts/explorev2/components/ExploreViewContainer.jsx b/caravel/assets/javascripts/explorev2/components/ExploreViewContainer.jsx index 0369655743f8..f6b6b52faae3 100644 --- a/caravel/assets/javascripts/explorev2/components/ExploreViewContainer.jsx +++ b/caravel/assets/javascripts/explorev2/components/ExploreViewContainer.jsx @@ -3,24 +3,24 @@ import ChartContainer from './ChartContainer'; import ControlPanelsContainer from './ControlPanelsContainer'; import QueryAndSaveButtons from './QueryAndSaveButtons'; -export default class ExploreViewContainer extends React.Component { - render() { - return ( -
-
-
- { console.log('clicked query'); }} - /> -

- -
-
- -
+const ExploreViewContainer = function () { + return ( +
+
+
+ { console.log('clicked query'); }} + /> +

+ +
+
+
- ); - } -} +
+ ); +}; + +export default ExploreViewContainer; diff --git a/caravel/assets/javascripts/explorev2/index.jsx b/caravel/assets/javascripts/explorev2/index.jsx index bc67ced3a0fb..8974b6a2ed59 100644 --- a/caravel/assets/javascripts/explorev2/index.jsx +++ b/caravel/assets/javascripts/explorev2/index.jsx @@ -5,11 +5,13 @@ import ExploreViewContainer from './components/ExploreViewContainer'; import { compose, createStore } from 'redux'; import { Provider } from 'react-redux'; +import { initialState } from './stores/store'; + const exploreViewContainer = document.getElementById('js-explore-view-container'); const bootstrapData = exploreViewContainer.getAttribute('data-bootstrap'); -import { initialState, exploreReducer } from './reducers'; +import { exploreReducer } from './reducers/exploreReducer'; import persistState from 'redux-localstorage'; let enhancer = compose(persistState()); diff --git a/caravel/assets/javascripts/explorev2/reducers.js b/caravel/assets/javascripts/explorev2/reducers.js deleted file mode 100644 index 052a1894f1ee..000000000000 --- a/caravel/assets/javascripts/explorev2/reducers.js +++ /dev/null @@ -1,127 +0,0 @@ -import shortid from 'shortid'; -import * as actions from './actions'; - -const defaultTimeFilter = { - timeColumn: null, - timeGrain: null, - since: null, - until: null, -}; - -const defaultGroupBy = { - groupByColumn: [], - metrics: [], -}; - -const defaultSql = { - where: '', - having: '', -}; - -const defaultFilter = { - id: shortid.generate(), - eq: null, - op: 'in', - col: null, -}; - -export const initialState = { - datasourceId: null, - vizType: null, - timeFilter: defaultTimeFilter, - groupBy: defaultGroupBy, - columns: [], - orderings: [], - timeStampFormat: null, - rowLimit: null, - searchBox: false, - SQL: defaultSql, - filters: [defaultFilter], -}; - -function addToArr(state, arrKey, obj) { - const newState = {}; - newState[arrKey] = [...state[arrKey], obj]; - return Object.assign({}, state, newState); -} - -function removeFromArr(state, arrKey, obj) { - const newArr = []; - state[arrKey].forEach((arrItem) => { - if (!(obj === arrItem)) { - newArr.push(arrItem); - } - }); - return Object.assign({}, state, { [arrKey]: newArr }); -} - -function addToObjArr(state, arrKey, obj) { - const newObj = Object.assign({}, obj); - if (!newObj.id) { - newObj.id = shortid.generate(); - } - const newState = {}; - newState[arrKey] = [...state[arrKey], newObj]; - return Object.assign({}, state, newState); -} - -function removeFromObjArr(state, arrKey, obj, idKey = 'id') { - const newArr = []; - state[arrKey].forEach((arrItem) => { - if (!(obj[idKey] === arrItem[idKey])) { - newArr.push(arrItem); - } - }); - return Object.assign({}, state, { [arrKey]: newArr }); -} - -export const exploreReducer = function (state, action) { - const actionHandlers = { - [actions.SET_DATASOURCE]() { - return Object.assign({}, state, { datasourceId: action.datasourceId }); - }, - [actions.SET_VIZTYPE]() { - return Object.assign({}, state, { vizType: action.vizType }); - }, - [actions.SET_TIMEFILTER]() { - return Object.assign({}, state, { timeFilter: action.timeFilter }); - }, - [actions.SET_GROUPBY]() { - return Object.assign({}, state, { groupBy: action.groupBy }); - }, - [actions.ADD_COLUMN]() { - return addToArr(state, 'columns', action.column); - }, - [actions.REMOVE_COLUMN]() { - return removeFromArr(state, 'columns', action.column); - }, - [actions.ADD_ORDERING]() { - return addToArr(state, 'orderings', action.ordering); - }, - [actions.REMOVE_ORDERING]() { - return removeFromArr(state, 'orderings', action.ordering); - }, - [actions.SET_TIME_STAMP]() { - return Object.assign({}, state, { timeStampFormat: action.timeStampFormat }); - }, - [actions.SET_ROW_LIMIT]() { - return Object.assign({}, state, { rowLimit: action.rowLimit }); - }, - [actions.TOGGLE_SEARCHBOX]() { - return Object.assign({}, state, { searchBox: action.searchBox }); - }, - [actions.SET_SQL]() { - return Object.assign({}, state, { SQL: action.sql }); - }, - [actions.ADD_FILTER]() { - return addToObjArr(state, 'filters', action.filter); - }, - [actions.REMOVE_FILTER]() { - return removeFromObjArr(state, 'filters', action.filter); - }, - }; - if (action.type in actionHandlers) { - return actionHandlers[action.type](); - } - return state; -}; diff --git a/caravel/assets/javascripts/explorev2/reducers/exploreReducer.js b/caravel/assets/javascripts/explorev2/reducers/exploreReducer.js new file mode 100644 index 000000000000..a7bdf0cf62ec --- /dev/null +++ b/caravel/assets/javascripts/explorev2/reducers/exploreReducer.js @@ -0,0 +1,65 @@ +import * as actions from '../actions/exploreActions'; +import * as utils from '../../../utils/reducerUtils'; + +export const exploreReducer = function (state, action) { + const actionHandlers = { + [actions.SET_DATASOURCE]() { + return Object.assign({}, state, { datasourceId: action.datasourceId }); + }, + [actions.SET_VIZTYPE]() { + return Object.assign({}, state, { vizType: action.vizType }); + }, + [actions.SET_TIME_FILTER]() { + return Object.assign({}, state, { timeFilter: action.timeFilter }); + }, + [actions.SET_GROUPBY]() { + return Object.assign({}, state, { groupBy: action.groupBy }); + }, + [actions.ADD_COLUMN]() { + return Object.assign({}, state, { columns: [...state.columns, action.column] }); + }, + [actions.REMOVE_COLUMN]() { + const newColumns = []; + state.columns.forEach((c) => { + if (c !== action.column) { + newColumns.push(c); + } + }); + return Object.assign({}, state, { columns: newColumns }); + }, + [actions.ADD_ORDERING]() { + return Object.assign({}, state, { orderings: [...state.orderings, action.ordering] }); + }, + [actions.REMOVE_ORDERING]() { + const newOrderings = []; + state.orderings.forEach((o) => { + if (o !== action.ordering) { + newOrderings.push(o); + } + }); + return Object.assign({}, state, { orderings: newOrderings }); + }, + [actions.SET_TIME_STAMP]() { + return Object.assign({}, state, { timeStampFormat: action.timeStampFormat }); + }, + [actions.SET_ROW_LIMIT]() { + return Object.assign({}, state, { rowLimit: action.rowLimit }); + }, + [actions.TOGGLE_SEARCHBOX]() { + return Object.assign({}, state, { searchBox: action.searchBox }); + }, + [actions.SET_SQL]() { + return Object.assign({}, state, { SQL: action.sql }); + }, + [actions.ADD_FILTER]() { + return utils.addToArr(state, 'filters', action.filter); + }, + [actions.REMOVE_FILTER]() { + return utils.removeFromArr(state, 'filters', action.filter); + }, + }; + if (action.type in actionHandlers) { + return actionHandlers[action.type](); + } + return state; +}; diff --git a/caravel/assets/javascripts/explorev2/stores/store.js b/caravel/assets/javascripts/explorev2/stores/store.js new file mode 100644 index 000000000000..bf16f4fbb5f5 --- /dev/null +++ b/caravel/assets/javascripts/explorev2/stores/store.js @@ -0,0 +1,30 @@ +const defaultTimeFilter = { + timeColumn: null, + timeGrain: null, + since: null, + until: null, +}; + +const defaultGroupBy = { + groupByColumn: [], + metrics: [], +}; + +const defaultSql = { + where: '', + having: '', +}; + +export const initialState = { + datasourceId: null, + vizType: null, + timeFilter: defaultTimeFilter, + groupBy: defaultGroupBy, + columns: [], + orderings: [], + timeStampFormat: null, + rowLimit: null, + searchBox: false, + SQL: defaultSql, + filters: [], +}; diff --git a/caravel/assets/package.json b/caravel/assets/package.json index 321def76482a..ceea55544588 100644 --- a/caravel/assets/package.json +++ b/caravel/assets/package.json @@ -101,9 +101,9 @@ "eslint-plugin-jsx-a11y": "^2.0.1", "eslint-plugin-react": "^5.2.2", "exports-loader": "^0.6.3", - "istanbul": "^1.0.0-alpha", "file-loader": "^0.8.5", "imports-loader": "^0.6.5", + "istanbul": "^1.0.0-alpha", "jsdom": "^8.0.1", "json-loader": "^0.5.4", "less": "^2.6.1", diff --git a/caravel/assets/spec/javascripts/explore/components/actions_spec.js b/caravel/assets/spec/javascripts/explore/components/actions_spec.js new file mode 100644 index 000000000000..eebfbb144142 --- /dev/null +++ b/caravel/assets/spec/javascripts/explore/components/actions_spec.js @@ -0,0 +1,121 @@ +import { expect } from 'chai'; +import shortid from 'shortid'; +import * as actions from '../../../../javascripts/explorev2/actions/exploreActions'; +import { initialState } from '../../../../javascripts/explorev2/stores/store'; +import { exploreReducer } from '../../../../javascripts/explorev2/reducers/exploreReducer'; + +describe('reducers', () => { + + it('should return new state with datasource id', () => { + const newState = exploreReducer(initialState, actions.setDatasource(1)); + expect(newState.datasourceId).to.equal(1); + }); + + it('should return new state with viz type', () => { + const newState = exploreReducer(initialState, actions.setVizType('bar')); + expect(newState.vizType).to.equal('bar'); + }); + + it('should return new state with time filter', () => { + const newTimeFilter = { + timeColumn: 1, + timeGrain: 1, + since: 1, + until: 2, + }; + const newState = exploreReducer(initialState, actions.setTimeFilter(newTimeFilter)); + expect(newState.timeFilter).to.deep.equal(newTimeFilter); + }); + + it('should return new state with group by', () => { + const newGroupBy = { + groupByColumn: ['col1'], + metrics: ['sum_value'], + }; + const newState = exploreReducer(initialState, actions.setGroupBy(newGroupBy)); + expect(newState.groupBy).to.deep.equal(newGroupBy); + }); + + it('should return new state with added column', () => { + const newColumn = 'col'; + const newState = exploreReducer(initialState, actions.addColumn(newColumn)); + expect(newState.columns).to.deep.equal([newColumn]); + }); + + it('should return new state with removed column', () => { + const testState = { initialState, columns: ['col1', 'col2'] }; + const remColumn = 'col1'; + const newState = exploreReducer(testState, actions.removeColumn(remColumn)); + expect(newState.columns).to.deep.equal(['col2']); + }); + + it('should return new state with added ordering', () => { + const newOrdering = 'ord'; + const newState = exploreReducer(initialState, actions.addOrdering(newOrdering)); + expect(newState.orderings).to.deep.equal(['ord']); + }); + + it('should return new state with removed ordering', () => { + const testState = { initialState, orderings: ['ord1', 'ord2'] }; + const remOrdering = 'ord1'; + const newState = exploreReducer(testState, actions.removeOrdering(remOrdering)); + expect(newState.orderings).to.deep.equal(['ord2']); + }); + + it('should return new state with time stamp', () => { + const newState = exploreReducer(initialState, actions.setTimeStamp(1)); + expect(newState.timeStampFormat).to.equal(1); + }); + + it('should return new state with row limit', () => { + const newState = exploreReducer(initialState, actions.setRowLimit(10)); + expect(newState.rowLimit).to.equal(10); + }); + + it('should return new state with search box toggled', () => { + const newState = exploreReducer(initialState, actions.toggleSearchBox(true)); + expect(newState.searchBox).to.equal(true); + }); + + it('should return new state with new sql', () => { + const newSql = { + where: 'where clause', + having: 'having clause', + }; + const newState = exploreReducer(initialState, actions.setSQL(newSql)); + expect(newState.SQL).to.equal(newSql); + }); + + it('should return new state with added filter', () => { + const newFilter = { + id: shortid.generate(), + eq: 'value', + op: 'in', + col: 'vals', + }; + const newState = exploreReducer(initialState, actions.addFilter(newFilter)); + expect(newState.filters).to.deep.equal([newFilter]); + }); + + it('should return new state with removed filter', () => { + const filter1 = { + id: shortid.generate(), + eq: 'value', + op: 'in', + col: 'vals1', + }; + const filter2 = { + id: shortid.generate(), + eq: 'value', + op: 'not in', + col: 'vals2', + }; + const testState = { + initialState, + filters: [filter1, filter2], + }; + const newState = exploreReducer(testState, actions.removeFilter(filter1)); + expect(newState.filters).to.deep.equal([filter2]); + }); + +}); diff --git a/caravel/assets/utils/reducerUtils.js b/caravel/assets/utils/reducerUtils.js new file mode 100644 index 000000000000..e233c2413b9a --- /dev/null +++ b/caravel/assets/utils/reducerUtils.js @@ -0,0 +1,53 @@ +import shortid from 'shortid'; + +export function addToObject(state, arrKey, obj) { + const newObject = Object.assign({}, state[arrKey]); + const copiedObject = Object.assign({}, obj); + + if (!copiedObject.id) { + copiedObject.id = shortid.generate(); + } + newObject[copiedObject.id] = copiedObject; + return Object.assign({}, state, { [arrKey]: newObject }); +} + +export function alterInObject(state, arrKey, obj, alterations) { + const newObject = Object.assign({}, state[arrKey]); + newObject[obj.id] = (Object.assign({}, newObject[obj.id], alterations)); + return Object.assign({}, state, { [arrKey]: newObject }); +} + +export function alterInArr(state, arrKey, obj, alterations) { + // Finds an item in an array in the state and replaces it with a + // new object with an altered property + const idKey = 'id'; + const newArr = []; + state[arrKey].forEach((arrItem) => { + if (obj[idKey] === arrItem[idKey]) { + newArr.push(Object.assign({}, arrItem, alterations)); + } else { + newArr.push(arrItem); + } + }); + return Object.assign({}, state, { [arrKey]: newArr }); +} + +export function removeFromArr(state, arrKey, obj, idKey = 'id') { + const newArr = []; + state[arrKey].forEach((arrItem) => { + if (!(obj[idKey] === arrItem[idKey])) { + newArr.push(arrItem); + } + }); + return Object.assign({}, state, { [arrKey]: newArr }); +} + +export function addToArr(state, arrKey, obj) { + const newObj = Object.assign({}, obj); + if (!newObj.id) { + newObj.id = shortid.generate(); + } + const newState = {}; + newState[arrKey] = [...state[arrKey], newObj]; + return Object.assign({}, state, newState); +} From 352ae516ccc76114a73984862994ae2674ee02f9 Mon Sep 17 00:00:00 2001 From: Vera Liu Date: Wed, 21 Sep 2016 09:59:23 -0700 Subject: [PATCH 3/3] Modifications based on comments --- caravel/assets/javascripts/SqlLab/index.jsx | 10 +---- caravel/assets/javascripts/SqlLab/reducers.js | 44 ++++++++++--------- .../assets/javascripts/explorev2/index.jsx | 17 +++---- .../explorev2/reducers/exploreReducer.js | 6 +-- .../javascripts/explorev2/stores/store.js | 2 + caravel/assets/utils/common.js | 13 ++++++ 6 files changed, 50 insertions(+), 42 deletions(-) diff --git a/caravel/assets/javascripts/SqlLab/index.jsx b/caravel/assets/javascripts/SqlLab/index.jsx index 6c9bd25e8e81..e0a7fa18c0c2 100644 --- a/caravel/assets/javascripts/SqlLab/index.jsx +++ b/caravel/assets/javascripts/SqlLab/index.jsx @@ -10,20 +10,14 @@ import TabbedSqlEditors from './components/TabbedSqlEditors'; import QueryAutoRefresh from './components/QueryAutoRefresh'; import Alerts from './components/Alerts'; -import { bindActionCreators, compose, createStore } from 'redux'; +import { bindActionCreators, createStore } from 'redux'; import { connect, Provider } from 'react-redux'; import { initialState, sqlLabReducer } from './reducers'; -import persistState from 'redux-localstorage'; +import { enhancer } from '../../utils/common'; require('./main.css'); -let enhancer = compose(persistState()); -if (process.env.NODE_ENV === 'dev') { - enhancer = compose( - persistState(), window.devToolsExtension && window.devToolsExtension() - ); -} let store = createStore(sqlLabReducer, initialState, enhancer); // jquery hack to highlight the navbar menu diff --git a/caravel/assets/javascripts/SqlLab/reducers.js b/caravel/assets/javascripts/SqlLab/reducers.js index 61ec52d3b1ca..5dce1877a4f0 100644 --- a/caravel/assets/javascripts/SqlLab/reducers.js +++ b/caravel/assets/javascripts/SqlLab/reducers.js @@ -1,7 +1,9 @@ import shortid from 'shortid'; import * as actions from './actions'; import { now } from '../modules/dates'; -import * as utils from '../../utils/reducerUtils'; +import + { addToArr, alterInArr, removeFromArr, addToObject, alterInObject } + from '../../utils/reducerUtils'; const defaultQueryEditor = { id: shortid.generate(), @@ -30,10 +32,10 @@ export const sqlLabReducer = function (state, action) { const tabHistory = state.tabHistory.slice(); tabHistory.push(action.queryEditor.id); const newState = Object.assign({}, state, { tabHistory }); - return utils.addToArr(newState, 'queryEditors', action.queryEditor); + return addToArr(newState, 'queryEditors', action.queryEditor); }, [actions.REMOVE_QUERY_EDITOR]() { - let newState = utils.removeFromArr(state, 'queryEditors', action.queryEditor); + let newState = removeFromArr(state, 'queryEditors', action.queryEditor); // List of remaining queryEditor ids const qeIds = newState.queryEditors.map((qe) => qe.id); let th = state.tabHistory.slice(); @@ -50,25 +52,25 @@ export const sqlLabReducer = function (state, action) { return Object.assign({}, initialState); }, [actions.ADD_TABLE]() { - return utils.addToArr(state, 'tables', action.table); + return addToArr(state, 'tables', action.table); }, [actions.EXPAND_TABLE]() { - return utils.alterInArr(state, 'tables', action.table, { expanded: true }); + return alterInArr(state, 'tables', action.table, { expanded: true }); }, [actions.COLLAPSE_TABLE]() { - return utils.alterInArr(state, 'tables', action.table, { expanded: false }); + return alterInArr(state, 'tables', action.table, { expanded: false }); }, [actions.REMOVE_TABLE]() { - return utils.removeFromArr(state, 'tables', action.table); + return removeFromArr(state, 'tables', action.table); }, [actions.START_QUERY]() { - const newState = utils.addToObject(state, 'queries', action.query); + const newState = addToObject(state, 'queries', action.query); const sqlEditor = { id: action.query.sqlEditorId }; - return utils.alterInArr( + return alterInArr( newState, 'queryEditors', sqlEditor, { latestQueryId: action.query.id }); }, [actions.STOP_QUERY]() { - return utils.alterInObject(state, 'queries', action.query, { state: 'stopped' }); + return alterInObject(state, 'queries', action.query, { state: 'stopped' }); }, [actions.QUERY_SUCCESS]() { const alts = { @@ -78,11 +80,11 @@ export const sqlLabReducer = function (state, action) { progress: 100, endDttm: now(), }; - return utils.alterInObject(state, 'queries', action.query, alts); + return alterInObject(state, 'queries', action.query, alts); }, [actions.QUERY_FAILED]() { const alts = { state: 'failed', errorMessage: action.msg, endDttm: now() }; - return utils.alterInObject(state, 'queries', action.query, alts); + return alterInObject(state, 'queries', action.query, alts); }, [actions.SET_ACTIVE_QUERY_EDITOR]() { const qeIds = state.queryEditors.map((qe) => qe.id); @@ -94,29 +96,29 @@ export const sqlLabReducer = function (state, action) { return state; }, [actions.QUERY_EDITOR_SETDB]() { - return utils.alterInArr(state, 'queryEditors', action.queryEditor, { dbId: action.dbId }); + return alterInArr(state, 'queryEditors', action.queryEditor, { dbId: action.dbId }); }, [actions.QUERY_EDITOR_SET_SCHEMA]() { - return utils.alterInArr(state, 'queryEditors', action.queryEditor, { schema: action.schema }); + return alterInArr(state, 'queryEditors', action.queryEditor, { schema: action.schema }); }, [actions.QUERY_EDITOR_SET_TITLE]() { - return utils.alterInArr(state, 'queryEditors', action.queryEditor, { title: action.title }); + return alterInArr(state, 'queryEditors', action.queryEditor, { title: action.title }); }, [actions.QUERY_EDITOR_SET_SQL]() { - return utils.alterInArr(state, 'queryEditors', action.queryEditor, { sql: action.sql }); + return alterInArr(state, 'queryEditors', action.queryEditor, { sql: action.sql }); }, [actions.QUERY_EDITOR_SET_AUTORUN]() { - return utils.alterInArr( + return alterInArr( state, 'queryEditors', action.queryEditor, { autorun: action.autorun }); }, [actions.ADD_WORKSPACE_QUERY]() { - return utils.addToArr(state, 'workspaceQueries', action.query); + return addToArr(state, 'workspaceQueries', action.query); }, [actions.REMOVE_WORKSPACE_QUERY]() { - return utils.removeFromArr(state, 'workspaceQueries', action.query); + return removeFromArr(state, 'workspaceQueries', action.query); }, [actions.ADD_ALERT]() { - return utils.addToArr(state, 'alerts', action.alert); + return addToArr(state, 'alerts', action.alert); }, [actions.SET_DATABASES]() { const databases = {}; @@ -126,7 +128,7 @@ export const sqlLabReducer = function (state, action) { return Object.assign({}, state, { databases }); }, [actions.REMOVE_ALERT]() { - return utils.removeFromArr(state, 'alerts', action.alert); + return removeFromArr(state, 'alerts', action.alert); }, [actions.SET_NETWORK_STATUS]() { if (state.networkOn !== action.networkOn) { diff --git a/caravel/assets/javascripts/explorev2/index.jsx b/caravel/assets/javascripts/explorev2/index.jsx index 8974b6a2ed59..53e8a64035f4 100644 --- a/caravel/assets/javascripts/explorev2/index.jsx +++ b/caravel/assets/javascripts/explorev2/index.jsx @@ -2,25 +2,22 @@ import React from 'react'; import ReactDOM from 'react-dom'; import ExploreViewContainer from './components/ExploreViewContainer'; -import { compose, createStore } from 'redux'; +import { createStore } from 'redux'; import { Provider } from 'react-redux'; +import { enhancer } from '../../utils/common'; import { initialState } from './stores/store'; const exploreViewContainer = document.getElementById('js-explore-view-container'); const bootstrapData = exploreViewContainer.getAttribute('data-bootstrap'); - import { exploreReducer } from './reducers/exploreReducer'; -import persistState from 'redux-localstorage'; -let enhancer = compose(persistState()); -if (process.env.NODE_ENV === 'dev') { - enhancer = compose( - persistState(), window.devToolsExtension && window.devToolsExtension() - ); -} -let store = createStore(exploreReducer, initialState, enhancer); +const bootstrappedState = Object.assign(initialState, { + datasources: bootstrapData.datasources, + viz: bootstrapData.viz, +}); +const store = createStore(exploreReducer, bootstrappedState, enhancer); ReactDOM.render( diff --git a/caravel/assets/javascripts/explorev2/reducers/exploreReducer.js b/caravel/assets/javascripts/explorev2/reducers/exploreReducer.js index a7bdf0cf62ec..e7c0de6d49a8 100644 --- a/caravel/assets/javascripts/explorev2/reducers/exploreReducer.js +++ b/caravel/assets/javascripts/explorev2/reducers/exploreReducer.js @@ -1,5 +1,5 @@ import * as actions from '../actions/exploreActions'; -import * as utils from '../../../utils/reducerUtils'; +import { addToArr, removeFromArr } from '../../../utils/reducerUtils'; export const exploreReducer = function (state, action) { const actionHandlers = { @@ -52,10 +52,10 @@ export const exploreReducer = function (state, action) { return Object.assign({}, state, { SQL: action.sql }); }, [actions.ADD_FILTER]() { - return utils.addToArr(state, 'filters', action.filter); + return addToArr(state, 'filters', action.filter); }, [actions.REMOVE_FILTER]() { - return utils.removeFromArr(state, 'filters', action.filter); + return removeFromArr(state, 'filters', action.filter); }, }; if (action.type in actionHandlers) { diff --git a/caravel/assets/javascripts/explorev2/stores/store.js b/caravel/assets/javascripts/explorev2/stores/store.js index bf16f4fbb5f5..5dcfd2d387a9 100644 --- a/caravel/assets/javascripts/explorev2/stores/store.js +++ b/caravel/assets/javascripts/explorev2/stores/store.js @@ -16,7 +16,9 @@ const defaultSql = { }; export const initialState = { + datasources: null, datasourceId: null, + viz: null, vizType: null, timeFilter: defaultTimeFilter, groupBy: defaultGroupBy, diff --git a/caravel/assets/utils/common.js b/caravel/assets/utils/common.js index a6aa001e41d8..5e98b996bfb5 100644 --- a/caravel/assets/utils/common.js +++ b/caravel/assets/utils/common.js @@ -1,4 +1,7 @@ /* eslint global-require: 0 */ +import persistState from 'redux-localstorage'; +import { compose } from 'redux'; + const d3 = window.d3 || require('d3'); export const EARTH_CIRCUMFERENCE_KM = 40075.16; @@ -26,3 +29,13 @@ 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); } + +export function getDevEnhancer() { + let enhancer = compose(persistState()); + if (process.env.NODE_ENV === 'dev') { + enhancer = compose( + persistState(), window.devToolsExtension && window.devToolsExtension() + ); + } + return enhancer; +}