diff --git a/src/components/captor/EditCaptorsPopup.js b/src/components/captor/EditCaptorsPopup.js index dcfaa5c..567b339 100644 --- a/src/components/captor/EditCaptorsPopup.js +++ b/src/components/captor/EditCaptorsPopup.js @@ -5,6 +5,7 @@ import * as FormHelper from '../generic/FormHelper' import { connect } from 'react-redux' import { removeCaptor } from '../../actions' import {captorKeyToView} from '../../domain/Captor' +import {selectedData} from '../../state/data' const mapStateToProps = state => { @@ -13,7 +14,7 @@ const mapStateToProps = state => { return { watchIndex, captors, - timeline: state.data.timeline, + timeline: selectedData(state).timeline, } } diff --git a/src/components/datalist/FilterLikeThisView.js b/src/components/datalist/FilterLikeThisView.js index 481b11b..2f7e6b9 100644 --- a/src/components/datalist/FilterLikeThisView.js +++ b/src/components/datalist/FilterLikeThisView.js @@ -6,9 +6,11 @@ import FilterLikethisForm from '../captor/FilterLikeThisForm' import DataList from './DataList' +import {selectedData} from '../../state/data' + const mapStateToProps = state => { return { - filterLikeThis: state.data.timeline.filterLikeThis, + filterLikeThis: selectedData(state).timeline.filterLikeThis, } } diff --git a/src/components/datalist/HitsTimeline.js b/src/components/datalist/HitsTimeline.js index 7e9703b..3adc37f 100644 --- a/src/components/datalist/HitsTimeline.js +++ b/src/components/datalist/HitsTimeline.js @@ -9,12 +9,14 @@ import EditCaptorsButton from '../captor/EditCaptorsButton' import {viewToCaptorKey} from '../../domain/Captor' import FilterLikeThisView from './FilterLikeThisView' import DataList from './DataList' +import {selectedData} from '../../state/data' const mapStateToProps = (state) => { let watchIndex = state.view.watchIndex + let data = selectedData(state, true) return { - timeline: state.data.timeline, - hitIds: state.data.hits.ids, + timeline: data.timeline, + hitIds: data.hits.ids, view: state.view.key, viewProps: state.view, error: state.fetchStatus.error, diff --git a/src/components/title/DocumentTitleContainer.js b/src/components/title/DocumentTitleContainer.js index c20d0a1..3c5c25c 100644 --- a/src/components/title/DocumentTitleContainer.js +++ b/src/components/title/DocumentTitleContainer.js @@ -1,11 +1,12 @@ import { connect } from 'react-redux' import DocumentTitle from './DocumentTitle' import _ from 'lodash' +import {selectedData} from '../../state/data' const mapStateToProps = state => { let titleArray = [] - let pendingCount = (state.data.timeline.pending || []).length + let pendingCount = (selectedData(state, true).timeline.pending || []).length if (pendingCount > 0) { titleArray.push('' + pendingCount) } diff --git a/src/reducers/captorPredicatesUpdater.js b/src/reducers/captorPredicatesUpdater.js deleted file mode 100644 index 82f0653..0000000 --- a/src/reducers/captorPredicatesUpdater.js +++ /dev/null @@ -1,33 +0,0 @@ -import update from 'immutability-helper'; -import _ from 'lodash' -import {captorToPredicate} from '../domain/Captor' - -export default function captorPredicatesUpdater(state, action) { - switch (action.type) { - case 'ON_INIT': - case 'ADD_CAPTOR': - case 'REMOVE_CAPTOR': - case 'SELECT_WATCH': - return doCopy(state) - default: - return state - } -} - -function doCopy(state) { - let watchIndex = state.view.watchIndex - if (undefined === watchIndex) { - return state - } - let watchSelected = state.config.watches[watchIndex] - let captorPredicates = _.flatMap(_.get(watchSelected, 'captors'), c => { - try { - return [captorToPredicate(c)] - } catch (e) { - console.error('error making predicate from captor', c, e) - return [] - } - } - ) - return update(state, {data: {captorPredicates: {$set: captorPredicates}}} ) -} \ No newline at end of file diff --git a/src/reducers/captorPredicatesUpdater.test.js b/src/reducers/captorPredicatesUpdater.test.js deleted file mode 100644 index 32dd058..0000000 --- a/src/reducers/captorPredicatesUpdater.test.js +++ /dev/null @@ -1,31 +0,0 @@ -import captorPredicatesUpdater from './captorPredicatesUpdater' -import {messageMatchesRegexCaptor} from '../domain/Captor' -import {captureConsoleError} from '../testutil/funmock' - -test("captorPredicatesUpdater should simply skip failing (regex) predicates for now", () => { - - let captors = [ - messageMatchesRegexCaptor('bad', 'a('), - messageMatchesRegexCaptor('good', 'a(b)'), - ] - - let state = { - config: { - watches: [ - {captors} - ] - }, - data: {}, - view: { - watchIndex: 0 - } - } - - let errorsReported = captureConsoleError(() => { - let hackedActually = captorPredicatesUpdater(state, {type: 'ON_INIT'}); - - expect(hackedActually.data.captorPredicates.length) - .toEqual(1) - }) - expect(errorsReported.length).toEqual(1) -}) diff --git a/src/reducers/data.js b/src/reducers/data.js index 7e012f2..221ea04 100644 --- a/src/reducers/data.js +++ b/src/reducers/data.js @@ -2,7 +2,7 @@ import _ from 'lodash' import LogHit from '../domain/LogHit' import update from 'immutability-helper'; import * as constant from '../constant' -import {matchPredicates, captorKeyToView} from '../domain/Captor' +import {matchPredicates, captorKeyToView, captorToPredicate} from '../domain/Captor' export const emptyState = { hits: { @@ -12,24 +12,29 @@ export const emptyState = { }, timeline: {}, acked: {}, // id -> true - captorPredicates: [], //hackishly copied here upon config update. see captorPredicatesUpdater draftFilter: null, // editing filter } -const data = (state = emptyState, action) => { +const data = (state = emptyState, action, filters) => { switch (action.type) { case 'RESET_DATA': //reset all - return { ...emptyState, - captorPredicates: state.captorPredicates - } + return emptyState case 'NEW_IDS_ARRIVED' : { + let captorPredicates = _.flatMap(filters, c => { + try { + return [captorToPredicate(c)] + } catch (e) { + console.error('error making predicate from captor', c, e) + return [] + } + }) return update(state, { hits: { newIds: { $set: [] }, }, - timeline: {$set: reprocessTimeline(state)}, + timeline: {$set: reprocessTimeline({...state, captorPredicates})}, }) } case 'INCOMING_HITS': diff --git a/src/reducers/data.newhitstimeline.test.js b/src/reducers/data.newhitstimeline.test.js index 10da079..c0f1cdf 100644 --- a/src/reducers/data.newhitstimeline.test.js +++ b/src/reducers/data.newhitstimeline.test.js @@ -1,5 +1,4 @@ -import dataReducer, { - emptyState, +import { reprocessTimeline } from './data' import LogHit from '../domain/LogHit' @@ -9,7 +8,6 @@ import { messageExtractor, captorToPredicate } from '../domain/Captor' -import * as constant from '../constant' const testConfig = { timeField: 'timestamp', diff --git a/src/reducers/data.reducer.test.js b/src/reducers/data.reducer.test.js index c8db336..3e562f9 100644 --- a/src/reducers/data.reducer.test.js +++ b/src/reducers/data.reducer.test.js @@ -1,32 +1,17 @@ import dataReducer, { emptyState } from './data' -import _ from 'lodash' -import LogHit from '../domain/LogHit' -import {messageContainsCaptor, captorToPredicate, messageMatchesRegexCaptor, messageExtractor, keepPending } from '../domain/Captor' -import {captureConsoleError} from '../testutil/funmock' -const testConfig = { - timeField: 'timestamp', - messageField: 'message' -} - -const toLogHit = (h) => LogHit(h, testConfig) - -const captorForMessage = (key, messageSub) => captorToPredicate(messageContainsCaptor(key, messageSub)) describe('data reducer', () => { it('intial state is empty state', () => { expect(dataReducer(undefined, {})) .toEqual(emptyState) }) - it('resets back to empty state, except for the captors hack', () => { + it('resets back to empty state', () => { expect(dataReducer({ data: { hits: [1, 2, 3] }, captorPredicates: ['a', 'b', 'c'] }, { type: 'RESET_DATA' })) - .toEqual({ - ...emptyState, - captorPredicates: ['a', 'b', 'c'], - }) + .toEqual(emptyState) }) it('shall ack all', () => { diff --git a/src/reducers/index.js b/src/reducers/index.js index 62e3456..0af7764 100644 --- a/src/reducers/index.js +++ b/src/reducers/index.js @@ -1,24 +1,28 @@ -import { combineReducers} from 'redux' -import data from './data' +import watches from './watches' import synctimes from './synctimes' import config from './config' import versions from './versions' import fetchStatus from './fetchStatus' -import captorPredicatesUpdater from './captorPredicatesUpdater' import view from './view' +import _ from 'lodash' -const combinedReducers = combineReducers({ - data, +const combinedReducers = combineReducersWithState({ + view, + watches, fetchStatus, synctimes, config, versions, - view, }) function kibanatorApp(state, action) { - const intermediateState = combinedReducers(state, action); - return captorPredicatesUpdater(intermediateState, action) + return combinedReducers(state, action); +} + +function combineReducersWithState(combination) { + return (state, action) => { + return _.mapValues(combination, (reducer, key)=> reducer(state[key], action, state)) + } } export default kibanatorApp \ No newline at end of file diff --git a/src/reducers/watches.js b/src/reducers/watches.js new file mode 100644 index 0000000..8d589fe --- /dev/null +++ b/src/reducers/watches.js @@ -0,0 +1,39 @@ +import data from './data' +import _ from 'lodash' + +const startingState = { + data: {} +} + +export default function watches (state = startingState, action, fullState) { + + if (action.type === 'RESET_DATA') { + return startingState + } + + //delegate the rest to the specific data reducer if watchIndex present + let watchIndex = _.get(action, 'payload.watchIndex') + + if (undefined === watchIndex) { + watchIndex = _.get(fullState, 'view.watchIndex') + } + + if (undefined === watchIndex) { + return startingState + } + + watchIndex = "" + watchIndex //stringify index + + let filters = _.get(fullState.config.watches[watchIndex], 'captors') + + let updatedData = data(state.data[watchIndex], action, filters) + + if (undefined === updatedData) { + console.warn('unexpectedly undefined data for watch index', watchIndex) + } + + return {...state, data: { + ...state.data, + [watchIndex]: updatedData + }} +} \ No newline at end of file diff --git a/src/state/data.js b/src/state/data.js new file mode 100644 index 0000000..386b17a --- /dev/null +++ b/src/state/data.js @@ -0,0 +1,15 @@ +import {emptyState} from '../reducers/data' +export function selectedData(state, fallbackEmptyState) { + const watchIndex = state.view.watchIndex + if (undefined !== watchIndex) { + let theData = state.watches.data[watchIndex + ""] + if (undefined !== theData) { + return theData + } + } + if (fallbackEmptyState) { + return emptyState + } else { + return undefined + } +} \ No newline at end of file diff --git a/src/store.js b/src/store.js index decc328..9e43fca 100644 --- a/src/store.js +++ b/src/store.js @@ -6,6 +6,7 @@ import { import thunkMiddleware from 'redux-thunk' import {readConfigFromLocalStore, writeConfigToLocalStore} from './reducers/config' //import { createLogger } from 'redux-logger' +import {selectedData} from './state/data' import kibanatorApp from './reducers' @@ -35,7 +36,12 @@ const onNewHitsArrivedMiddleware = store => { } let state = store.getState(); - const newIds = state.data.hits.newIds + let data = selectedData(state, false) + if (data === undefined) { + console.warn('no selected data to propagate incoming hits') + return r + } + const newIds = data.hits.newIds if (newIds && newIds.length > 0) { store.dispatch({