import shortid from 'shortid'; import * as actions from './actions'; import { now } from '../modules/dates'; import { addToObject, alterInObject, alterInArr, removeFromArr, getFromArr, addToArr } from '../reduxUtils'; export function getInitialState(defaultDbId) { const defaultQueryEditor = { id: shortid.generate(), title: 'Untitled Query', sql: 'SELECT *\nFROM\nWHERE', selectedText: null, latestQueryId: null, autorun: false, dbId: defaultDbId, }; return { alerts: [], queries: {}, databases: {}, queryEditors: [defaultQueryEditor], tabHistory: [defaultQueryEditor.id], tables: [], queriesLastUpdate: 0, activeSouthPaneTab: 'Results', }; } 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); }, [actions.CLONE_QUERY_TO_NEW_TAB]() { const progenitor = state.queryEditors.find(qe => qe.id === state.tabHistory[state.tabHistory.length - 1]); const qe = { id: shortid.generate(), title: `Copy of ${progenitor.title}`, dbId: (action.query.dbId) ? action.query.dbId : null, schema: (action.query.schema) ? action.query.schema : null, autorun: true, sql: action.query.sql, }; return sqlLabReducer(state, actions.addQueryEditor(qe)); }, [actions.REMOVE_QUERY_EDITOR]() { let newState = removeFromArr(state, 'queryEditors', action.queryEditor); // List of remaining queryEditor ids const qeIds = newState.queryEditors.map(qe => qe.id); const queries = {}; Object.keys(state.queries).forEach((k) => { const query = state.queries[k]; if (qeIds.indexOf(query.sqlEditorId) > -1) { queries[k] = query; } }); let tabHistory = state.tabHistory.slice(); tabHistory = tabHistory.filter(id => qeIds.indexOf(id) > -1); newState = Object.assign({}, newState, { tabHistory, queries }); return newState; }, [actions.REMOVE_QUERY]() { const newQueries = Object.assign({}, state.queries); delete newQueries[action.query.id]; return Object.assign({}, state, { queries: newQueries }); }, [actions.RESET_STATE]() { return Object.assign({}, getInitialState()); }, [actions.MERGE_TABLE]() { const at = Object.assign({}, action.table); let existingTable; state.tables.forEach((t) => { if ( t.dbId === at.dbId && t.queryEditorId === at.queryEditorId && t.schema === at.schema && t.name === at.name) { existingTable = t; } }); if (existingTable) { if (action.query) { at.dataPreviewQueryId = action.query.id; } return alterInArr(state, 'tables', existingTable, at); } at.id = shortid.generate(); // for new table, associate Id of query for data preview at.dataPreviewQueryId = null; let newState = addToArr(state, 'tables', at); if (action.query) { newState = alterInArr(newState, 'tables', at, { dataPreviewQueryId: action.query.id }); } return newState; }, [actions.EXPAND_TABLE]() { return alterInArr(state, 'tables', action.table, { expanded: true }); }, [actions.REMOVE_DATA_PREVIEW]() { const queries = Object.assign({}, state.queries); delete queries[action.table.dataPreviewQueryId]; const newState = alterInArr(state, 'tables', action.table, { dataPreviewQueryId: null }); return Object.assign( {}, newState, { queries }); }, [actions.CHANGE_DATA_PREVIEW_ID]() { const queries = Object.assign({}, state.queries); delete queries[action.oldQueryId]; const newTables = []; state.tables.forEach((t) => { if (t.dataPreviewQueryId === action.oldQueryId) { newTables.push(Object.assign({}, t, { dataPreviewQueryId: action.newQuery.id })); } else { newTables.push(t); } }); return Object.assign( {}, state, { queries, tables: newTables, activeSouthPaneTab: action.newQuery.id }); }, [actions.COLLAPSE_TABLE]() { return alterInArr(state, 'tables', action.table, { expanded: false }); }, [actions.REMOVE_TABLE]() { return removeFromArr(state, 'tables', action.table); }, [actions.START_QUERY]() { let newState = Object.assign({}, state); if (action.query.sqlEditorId) { const qe = getFromArr(state.queryEditors, action.query.sqlEditorId); if (qe.latestQueryId && state.queries[qe.latestQueryId]) { const newResults = Object.assign( {}, state.queries[qe.latestQueryId].results, { data: [], query: null }); const q = Object.assign({}, state.queries[qe.latestQueryId], { results: newResults }); const queries = Object.assign({}, state.queries, { [q.id]: q }); newState = Object.assign({}, state, { queries }); } } else { newState.activeSouthPaneTab = action.query.id; } newState = addToObject(newState, 'queries', action.query); const sqlEditor = { id: action.query.sqlEditorId }; return alterInArr(newState, 'queryEditors', sqlEditor, { latestQueryId: action.query.id }); }, [actions.STOP_QUERY]() { return alterInObject(state, 'queries', action.query, { state: 'stopped', results: [] }); }, [actions.CLEAR_QUERY_RESULTS]() { const newResults = Object.assign({}, action.query.results); newResults.data = []; return alterInObject(state, 'queries', action.query, { results: newResults, cached: true }); }, [actions.REQUEST_QUERY_RESULTS]() { return alterInObject(state, 'queries', action.query, { state: 'fetching' }); }, [actions.QUERY_SUCCESS]() { if (action.query.state === 'stopped') { return state; } let rows; if (action.results.data) { rows = action.results.data.length; } const alts = { endDttm: now(), progress: 100, results: action.results, rows, state: 'success', errorMessage: null, cached: false, }; return alterInObject(state, 'queries', action.query, alts); }, [actions.QUERY_FAILED]() { if (action.query.state === 'stopped') { return state; } const alts = { state: 'failed', errorMessage: action.msg, endDttm: now() }; return alterInObject(state, 'queries', action.query, alts); }, [actions.SET_ACTIVE_QUERY_EDITOR]() { const qeIds = state.queryEditors.map(qe => qe.id); if (qeIds.indexOf(action.queryEditor.id) > -1) { const tabHistory = state.tabHistory.slice(); tabHistory.push(action.queryEditor.id); return Object.assign({}, state, { tabHistory }); } return state; }, [actions.SET_ACTIVE_SOUTHPANE_TAB]() { return Object.assign({}, state, { activeSouthPaneTab: action.tabId }); }, [actions.QUERY_EDITOR_SETDB]() { return alterInArr(state, 'queryEditors', action.queryEditor, { dbId: action.dbId }); }, [actions.QUERY_EDITOR_SET_SCHEMA]() { return alterInArr(state, 'queryEditors', action.queryEditor, { schema: action.schema }); }, [actions.QUERY_EDITOR_SET_TITLE]() { return alterInArr(state, 'queryEditors', action.queryEditor, { title: action.title }); }, [actions.QUERY_EDITOR_SET_SQL]() { return alterInArr(state, 'queryEditors', action.queryEditor, { sql: action.sql }); }, [actions.QUERY_EDITOR_SET_SELECTED_TEXT]() { return alterInArr(state, 'queryEditors', action.queryEditor, { selectedText: action.sql }); }, [actions.QUERY_EDITOR_SET_AUTORUN]() { return alterInArr(state, 'queryEditors', action.queryEditor, { autorun: action.autorun }); }, [actions.ADD_ALERT]() { return addToArr(state, 'alerts', action.alert); }, [actions.SET_DATABASES]() { const databases = {}; action.databases.forEach((db) => { databases[db.id] = db; }); return Object.assign({}, state, { databases }); }, [actions.REMOVE_ALERT]() { return removeFromArr(state, 'alerts', action.alert); }, [actions.REFRESH_QUERIES]() { let newQueries = Object.assign({}, state.queries); // Fetch the updates to the queries present in the store. let change = false; let queriesLastUpdate = state.queriesLastUpdate; for (const id in action.alteredQueries) { const changedQuery = action.alteredQueries[id]; if (!state.queries.hasOwnProperty(id) || state.queries[id].state !== 'stopped') { if (changedQuery.changedOn > queriesLastUpdate) { queriesLastUpdate = changedQuery.changedOn; } newQueries[id] = Object.assign({}, state.queries[id], changedQuery); change = true; } } if (!change) { newQueries = state.queries; } return Object.assign({}, state, { queries: newQueries, queriesLastUpdate }); }, [actions.CREATE_DATASOURCE_STARTED]() { return Object.assign({}, state, { isDatasourceLoading: true, errorMessage: null, }); }, [actions.CREATE_DATASOURCE_SUCCESS]() { return Object.assign({}, state, { isDatasourceLoading: false, errorMessage: null, datasource: action.datasource, }); }, [actions.CREATE_DATASOURCE_FAILED]() { return Object.assign({}, state, { isDatasourceLoading: false, errorMessage: action.err, }); }, }; if (action.type in actionHandlers) { return actionHandlers[action.type](); } return state; };