phobos_checkpoint_ui 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/.rspec +2 -0
- data/CHANGELOG.md +9 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +81 -0
- data/Rakefile +12 -0
- data/assets/index-52cbf3063583f3c09a4b-0.css +2 -0
- data/assets/index-52cbf3063583f3c09a4b-0.css.map +1 -0
- data/assets/index-52cbf3063583f3c09a4b-1.css +2 -0
- data/assets/index-52cbf3063583f3c09a4b-1.css.map +1 -0
- data/assets/index-52cbf3063583f3c09a4b.js +49 -0
- data/assets/index-52cbf3063583f3c09a4b.js.map +1 -0
- data/assets/index.html +12 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/circle.yml +29 -0
- data/frontend/.babelrc +3 -0
- data/frontend/.editorconfig +8 -0
- data/frontend/.eslintignore +2 -0
- data/frontend/.eslintrc +3 -0
- data/frontend/.npmignore +8 -0
- data/frontend/package.json +45 -0
- data/frontend/sagui.config.js +52 -0
- data/frontend/src/actions/event-details.js +42 -0
- data/frontend/src/actions/event-details.spec.js +71 -0
- data/frontend/src/actions/event-overview.js +11 -0
- data/frontend/src/actions/event-overview.spec.js +20 -0
- data/frontend/src/actions/event-retry.js +58 -0
- data/frontend/src/actions/event-retry.spec.js +117 -0
- data/frontend/src/actions/events-search.js +75 -0
- data/frontend/src/actions/events-search.spec.js +197 -0
- data/frontend/src/actions/flash-messages.js +16 -0
- data/frontend/src/actions/flash-messages.spec.js +23 -0
- data/frontend/src/actions/index.js +24 -0
- data/frontend/src/actions/search-input-filter.js +14 -0
- data/frontend/src/actions/search-input-filter.spec.js +20 -0
- data/frontend/src/api.js +46 -0
- data/frontend/src/components/event/error-message.js +19 -0
- data/frontend/src/components/event/error-message.scss +8 -0
- data/frontend/src/components/event/event.scss +3 -0
- data/frontend/src/components/event/index.js +77 -0
- data/frontend/src/components/event/index.spec.js +89 -0
- data/frontend/src/components/event/loading.js +16 -0
- data/frontend/src/components/event/style.js +16 -0
- data/frontend/src/components/event-overview/attribute.js +24 -0
- data/frontend/src/components/event-overview/event-overview.scss +28 -0
- data/frontend/src/components/event-overview/index.js +47 -0
- data/frontend/src/components/event-overview/index.spec.js +67 -0
- data/frontend/src/components/event-overview-dialog/event-overview-dialog.scss +15 -0
- data/frontend/src/components/event-overview-dialog/index.js +85 -0
- data/frontend/src/components/event-retry-dialog/index.js +72 -0
- data/frontend/src/components/events-list/events-list.scss +49 -0
- data/frontend/src/components/events-list/index.js +62 -0
- data/frontend/src/components/events-list/index.spec.js +59 -0
- data/frontend/src/components/flash-message/flash-message.scss +40 -0
- data/frontend/src/components/flash-message/index.js +45 -0
- data/frontend/src/components/flash-message/index.spec.js +59 -0
- data/frontend/src/components/flash-message-list/index.js +34 -0
- data/frontend/src/components/header/header.scss +29 -0
- data/frontend/src/components/header/index.js +44 -0
- data/frontend/src/components/search-input/index.js +104 -0
- data/frontend/src/components/search-input/index.spec.js +56 -0
- data/frontend/src/components/search-input/search-input.scss +7 -0
- data/frontend/src/configs.js +15 -0
- data/frontend/src/helpers.spec.js +2 -0
- data/frontend/src/index.html +12 -0
- data/frontend/src/index.js +26 -0
- data/frontend/src/index.scss +31 -0
- data/frontend/src/reducers/event-details.js +32 -0
- data/frontend/src/reducers/event-details.spec.js +54 -0
- data/frontend/src/reducers/events-filters.js +17 -0
- data/frontend/src/reducers/events-filters.spec.js +34 -0
- data/frontend/src/reducers/events.js +48 -0
- data/frontend/src/reducers/events.spec.js +95 -0
- data/frontend/src/reducers/flash-messages.js +14 -0
- data/frontend/src/reducers/flash-messages.spec.js +34 -0
- data/frontend/src/reducers/index.js +18 -0
- data/frontend/src/reducers/index.spec.js +20 -0
- data/frontend/src/reducers/xhr-status.js +75 -0
- data/frontend/src/reducers/xhr-status.spec.js +94 -0
- data/frontend/src/routes.js +20 -0
- data/frontend/src/store.js +15 -0
- data/frontend/src/views/event-details.js +50 -0
- data/frontend/src/views/events-search.js +112 -0
- data/frontend/src/views/events-search.scss +24 -0
- data/frontend/src/views/events-search.spec.js +96 -0
- data/frontend/src/views/layout.js +24 -0
- data/frontend/src/views/layout.scss +3 -0
- data/lib/phobos_checkpoint_ui/app.rb +11 -0
- data/lib/phobos_checkpoint_ui/static_app.rb +19 -0
- data/lib/phobos_checkpoint_ui/tasks.rb +20 -0
- data/lib/phobos_checkpoint_ui/version.rb +3 -0
- data/lib/phobos_checkpoint_ui.rb +10 -0
- data/phobos_checkpoint_ui.gemspec +53 -0
- data/screenshot1.png +0 -0
- data/screenshot2.png +0 -0
- metadata +267 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import React, { Component, PropTypes } from 'react'
|
|
2
|
+
import { connect } from 'react-redux'
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
changeSearchInputFilterType,
|
|
6
|
+
changeSearchInputFilterValue
|
|
7
|
+
} from 'actions/search-input-filter'
|
|
8
|
+
|
|
9
|
+
import { triggerSearch } from 'actions/events-search'
|
|
10
|
+
|
|
11
|
+
import SelectField from 'material-ui/SelectField'
|
|
12
|
+
import MenuItem from 'material-ui/MenuItem'
|
|
13
|
+
import TextField from 'material-ui/TextField'
|
|
14
|
+
import SearchSVG from 'material-ui/svg-icons/action/search'
|
|
15
|
+
import RaisedButton from 'material-ui/RaisedButton'
|
|
16
|
+
import CircularProgress from 'material-ui/CircularProgress'
|
|
17
|
+
|
|
18
|
+
import { DEFAULT_FILTER_TYPE } from 'reducers/events-filters'
|
|
19
|
+
|
|
20
|
+
export const ENTER_KEY = 13
|
|
21
|
+
const FILTER_TYPES = [
|
|
22
|
+
'entity_id',
|
|
23
|
+
'event_type',
|
|
24
|
+
'group_id',
|
|
25
|
+
'topic'
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
export class SearchInput extends Component {
|
|
29
|
+
static get propTypes () {
|
|
30
|
+
return {
|
|
31
|
+
onSearch: PropTypes.func,
|
|
32
|
+
onChangeFilterType: PropTypes.func,
|
|
33
|
+
onChangeFilterValue: PropTypes.func,
|
|
34
|
+
isFetchingEvents: PropTypes.bool,
|
|
35
|
+
|
|
36
|
+
filterType: PropTypes.string,
|
|
37
|
+
filterValue: PropTypes.string
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
render () {
|
|
42
|
+
return (
|
|
43
|
+
<div className='search-input'>
|
|
44
|
+
<SelectField
|
|
45
|
+
style={{width: '140px'}}
|
|
46
|
+
value={this.props.filterType || DEFAULT_FILTER_TYPE}
|
|
47
|
+
onChange={(e, index, value) => this.changeFilterType(value)}>
|
|
48
|
+
{
|
|
49
|
+
FILTER_TYPES.map((type) => (
|
|
50
|
+
<MenuItem
|
|
51
|
+
key={type}
|
|
52
|
+
value={type}
|
|
53
|
+
primaryText={type} />
|
|
54
|
+
))
|
|
55
|
+
}
|
|
56
|
+
</SelectField>
|
|
57
|
+
<TextField
|
|
58
|
+
name='filterValue'
|
|
59
|
+
style={{width: '100%'}}
|
|
60
|
+
onChange={(e) => this.changeFilterValue(e.target.value)}
|
|
61
|
+
onKeyDown={(e) => this.search(e)}
|
|
62
|
+
value={this.props.filterValue || ''}
|
|
63
|
+
hintText='Press ENTER to search' />
|
|
64
|
+
<RaisedButton
|
|
65
|
+
primary
|
|
66
|
+
onClick={() => this.search()}
|
|
67
|
+
style={{height: '50px', marginLeft: '20px'}}
|
|
68
|
+
icon={this.getButtonIcon()} />
|
|
69
|
+
</div>
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
changeFilterType (value) {
|
|
74
|
+
this.props.onChangeFilterType(value)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
changeFilterValue (value) {
|
|
78
|
+
this.props.onChangeFilterValue(value)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
search (e) {
|
|
82
|
+
if (e && e.keyCode === ENTER_KEY || !e) {
|
|
83
|
+
this.props.onSearch()
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
getButtonIcon () {
|
|
88
|
+
return this.props.isFetchingEvents
|
|
89
|
+
? <CircularProgress size={0.4}/>
|
|
90
|
+
: <SearchSVG color={'#fff'} style={{width: 30, height: 50}}/>
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const mapStateToProps = (state, ownProps) => (
|
|
95
|
+
Object.assign({
|
|
96
|
+
isFetchingEvents: state.xhrStatus.isFetchingEvents
|
|
97
|
+
}, ownProps)
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
export default connect(mapStateToProps, {
|
|
101
|
+
onSearch: triggerSearch,
|
|
102
|
+
onChangeFilterType: changeSearchInputFilterType,
|
|
103
|
+
onChangeFilterValue: changeSearchInputFilterValue
|
|
104
|
+
})(SearchInput)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import jasmineEnzyme from 'jasmine-enzyme'
|
|
3
|
+
import { mount } from 'enzyme'
|
|
4
|
+
import { SearchInput, ENTER_KEY } from 'components/search-input'
|
|
5
|
+
import getMuiTheme from 'material-ui/styles/getMuiTheme'
|
|
6
|
+
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'
|
|
7
|
+
import { Provider } from 'react-redux'
|
|
8
|
+
import configureMockStore from 'redux-mock-store'
|
|
9
|
+
import thunk from 'redux-thunk'
|
|
10
|
+
|
|
11
|
+
const middlewares = [ thunk ]
|
|
12
|
+
const mockStore = configureMockStore(middlewares)
|
|
13
|
+
|
|
14
|
+
const mountComponent = (store, props) => mount(
|
|
15
|
+
<Provider store={store}>
|
|
16
|
+
<MuiThemeProvider muiTheme={getMuiTheme()}>
|
|
17
|
+
<SearchInput {...props} />
|
|
18
|
+
</MuiThemeProvider>
|
|
19
|
+
</Provider>
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
describe('<SearchInput />', () => {
|
|
23
|
+
let props, component, store
|
|
24
|
+
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
jasmineEnzyme()
|
|
27
|
+
store = mockStore({})
|
|
28
|
+
|
|
29
|
+
props = {
|
|
30
|
+
onSearch: jasmine.createSpy('onSearch'),
|
|
31
|
+
onChangeFilterType: jasmine.createSpy('onChangeFilterType'),
|
|
32
|
+
onChangeFilterValue: jasmine.createSpy('onChangeFilterValue'),
|
|
33
|
+
isFetchingEvents: false,
|
|
34
|
+
|
|
35
|
+
filterType: 'entity_id',
|
|
36
|
+
filterValue: '12345'
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
component = mountComponent(store, props)
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('calls onChangeFilterValue with the new value when type is changed', () => {
|
|
43
|
+
component.find('input').simulate('change', { target: { value: 'new-value' } })
|
|
44
|
+
expect(props.onChangeFilterValue).toHaveBeenCalledWith('new-value')
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it('calls onSearch when ENTER is pressed in the textfield', () => {
|
|
48
|
+
component.find('input').simulate('keyDown', { keyCode: ENTER_KEY })
|
|
49
|
+
expect(props.onSearch).toHaveBeenCalled()
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('calls onSearch when the search button is pressed', () => {
|
|
53
|
+
component.find('button').simulate('click')
|
|
54
|
+
expect(props.onSearch).toHaveBeenCalled()
|
|
55
|
+
})
|
|
56
|
+
})
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import './index.scss'
|
|
2
|
+
|
|
3
|
+
import React from 'react'
|
|
4
|
+
import { render } from 'react-dom'
|
|
5
|
+
import { Provider } from 'react-redux'
|
|
6
|
+
import injectTapEventPlugin from 'react-tap-event-plugin'
|
|
7
|
+
injectTapEventPlugin()
|
|
8
|
+
|
|
9
|
+
import store from 'store'
|
|
10
|
+
import Routes from 'routes'
|
|
11
|
+
import { load } from 'configs'
|
|
12
|
+
|
|
13
|
+
const Root = (
|
|
14
|
+
<Provider store={store}>
|
|
15
|
+
{Routes}
|
|
16
|
+
</Provider>
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
const mountNode = document.getElementById('root')
|
|
20
|
+
|
|
21
|
+
load()
|
|
22
|
+
.then(() => render(Root, mountNode))
|
|
23
|
+
.catch(() => render(
|
|
24
|
+
<div className='load-error'>Failed to load configs</div>,
|
|
25
|
+
mountNode
|
|
26
|
+
))
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
@import url('https://fonts.googleapis.com/css?family=Roboto:300,400,500');
|
|
2
|
+
|
|
3
|
+
html,
|
|
4
|
+
body {
|
|
5
|
+
margin: 0;
|
|
6
|
+
padding: 0;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
html, body, #root {
|
|
10
|
+
height: 100%
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
body {
|
|
14
|
+
background-color: #ccc;
|
|
15
|
+
color: #d0d0d0;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.loading-boot,
|
|
19
|
+
.load-error {
|
|
20
|
+
margin: 0 auto;
|
|
21
|
+
color: black;
|
|
22
|
+
width: 100%;
|
|
23
|
+
height: 100%;
|
|
24
|
+
text-align: center;
|
|
25
|
+
font-size: 40px;
|
|
26
|
+
padding-top: 100px;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.load-error {
|
|
30
|
+
color: #ff5f5f;
|
|
31
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import {
|
|
2
|
+
EVENT_SHOW_RETRY,
|
|
3
|
+
EVENT_HIDE_RETRY,
|
|
4
|
+
EVENT_HIDE_OVERVIEW,
|
|
5
|
+
RECEIVE_EVENT_DETAILS
|
|
6
|
+
} from 'actions'
|
|
7
|
+
|
|
8
|
+
function patchEvent (state, action, params) {
|
|
9
|
+
if (state.id === action.event.id) {
|
|
10
|
+
return Object.assign(state, params)
|
|
11
|
+
}
|
|
12
|
+
return state
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export default (state = {}, action) => {
|
|
16
|
+
switch (action.type) {
|
|
17
|
+
case EVENT_SHOW_RETRY:
|
|
18
|
+
return patchEvent(state, action, { retryVisible: true })
|
|
19
|
+
|
|
20
|
+
case EVENT_HIDE_RETRY:
|
|
21
|
+
return patchEvent(state, action, { retryVisible: false })
|
|
22
|
+
|
|
23
|
+
case EVENT_HIDE_OVERVIEW:
|
|
24
|
+
return {}
|
|
25
|
+
|
|
26
|
+
case RECEIVE_EVENT_DETAILS:
|
|
27
|
+
return action.event
|
|
28
|
+
|
|
29
|
+
default:
|
|
30
|
+
return state
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import {
|
|
2
|
+
EVENT_SHOW_RETRY,
|
|
3
|
+
EVENT_HIDE_RETRY,
|
|
4
|
+
EVENT_HIDE_OVERVIEW,
|
|
5
|
+
RECEIVE_EVENT_DETAILS
|
|
6
|
+
} from 'actions'
|
|
7
|
+
|
|
8
|
+
import reducer from 'reducers/event-details'
|
|
9
|
+
|
|
10
|
+
describe('reducers/events-details', () => {
|
|
11
|
+
describe('for EVENT_SHOW_RETRY', () => {
|
|
12
|
+
it('sets retryVisible to true', () => {
|
|
13
|
+
const currentState = { id: 1 }
|
|
14
|
+
const action = { type: EVENT_SHOW_RETRY, event: { id: 1 } }
|
|
15
|
+
const expectedState = { id: 1, retryVisible: true }
|
|
16
|
+
expect(reducer(currentState, action)).toEqual(expectedState)
|
|
17
|
+
})
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
describe('for EVENT_HIDE_RETRY', () => {
|
|
21
|
+
it('sets retryVisible to false', () => {
|
|
22
|
+
const currentState = { id: 1 }
|
|
23
|
+
const action = { type: EVENT_HIDE_RETRY, event: { id: 1 } }
|
|
24
|
+
const expectedState = { id: 1, retryVisible: false }
|
|
25
|
+
expect(reducer(currentState, action)).toEqual(expectedState)
|
|
26
|
+
})
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
describe('for EVENT_HIDE_OVERVIEW', () => {
|
|
30
|
+
it('erases the state', () => {
|
|
31
|
+
const currentState = { id: 1 }
|
|
32
|
+
const action = { type: EVENT_HIDE_OVERVIEW, event: { id: 1 } }
|
|
33
|
+
const expectedState = { }
|
|
34
|
+
expect(reducer(currentState, action)).toEqual(expectedState)
|
|
35
|
+
})
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
describe('for RECEIVE_EVENT_DETAILS', () => {
|
|
39
|
+
it('replaces the state', () => {
|
|
40
|
+
const currentState = { id: 1 }
|
|
41
|
+
const action = { type: RECEIVE_EVENT_DETAILS, event: { id: 2 } }
|
|
42
|
+
const expectedState = { id: 2 }
|
|
43
|
+
expect(reducer(currentState, action)).toEqual(expectedState)
|
|
44
|
+
})
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
describe('for default', () => {
|
|
48
|
+
it('returns the currentState', () => {
|
|
49
|
+
const currentState = { current: true }
|
|
50
|
+
const action = { type: 'another' }
|
|
51
|
+
expect(reducer(currentState, action)).toEqual(currentState)
|
|
52
|
+
})
|
|
53
|
+
})
|
|
54
|
+
})
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { SEARCH_INPUT_CHANGE_FILTER_TYPE, SEARCH_INPUT_CHANGE_FILTER_VALUE } from 'actions'
|
|
2
|
+
|
|
3
|
+
export const DEFAULT_FILTER_TYPE = 'entity_id'
|
|
4
|
+
const initialState = { type: DEFAULT_FILTER_TYPE }
|
|
5
|
+
|
|
6
|
+
export default (state = initialState, action) => {
|
|
7
|
+
switch (action.type) {
|
|
8
|
+
case SEARCH_INPUT_CHANGE_FILTER_TYPE:
|
|
9
|
+
return Object.assign({}, state, { type: action.filterType })
|
|
10
|
+
|
|
11
|
+
case SEARCH_INPUT_CHANGE_FILTER_VALUE:
|
|
12
|
+
return Object.assign({}, state, { value: action.filterValue })
|
|
13
|
+
|
|
14
|
+
default:
|
|
15
|
+
return state
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SEARCH_INPUT_CHANGE_FILTER_TYPE,
|
|
3
|
+
SEARCH_INPUT_CHANGE_FILTER_VALUE
|
|
4
|
+
} from 'actions'
|
|
5
|
+
|
|
6
|
+
import reducer from 'reducers/events-filters'
|
|
7
|
+
|
|
8
|
+
describe('reducers/events-filters', () => {
|
|
9
|
+
describe('for SEARCH_INPUT_CHANGE_FILTER_TYPE', () => {
|
|
10
|
+
it('sets type', () => {
|
|
11
|
+
const currentState = { type: 'foo' }
|
|
12
|
+
const action = { type: SEARCH_INPUT_CHANGE_FILTER_TYPE, filterType: 'bar' }
|
|
13
|
+
const expectedState = { type: 'bar' }
|
|
14
|
+
expect(reducer(currentState, action)).toEqual(expectedState)
|
|
15
|
+
})
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
describe('for SEARCH_INPUT_CHANGE_FILTER_VALUE', () => {
|
|
19
|
+
it('sets type', () => {
|
|
20
|
+
const currentState = { value: 'foo' }
|
|
21
|
+
const action = { type: SEARCH_INPUT_CHANGE_FILTER_VALUE, filterValue: 'bar' }
|
|
22
|
+
const expectedState = { value: 'bar' }
|
|
23
|
+
expect(reducer(currentState, action)).toEqual(expectedState)
|
|
24
|
+
})
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
describe('for default', () => {
|
|
28
|
+
it('returns the currentState', () => {
|
|
29
|
+
const currentState = { current: true }
|
|
30
|
+
const action = { type: 'another' }
|
|
31
|
+
expect(reducer(currentState, action)).toEqual(currentState)
|
|
32
|
+
})
|
|
33
|
+
})
|
|
34
|
+
})
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import {
|
|
2
|
+
EVENT_SHOW_OVERVIEW,
|
|
3
|
+
EVENT_HIDE_OVERVIEW,
|
|
4
|
+
RECEIVE_SEARCH_RESULTS,
|
|
5
|
+
EVENT_SHOW_RETRY,
|
|
6
|
+
EVENT_HIDE_RETRY,
|
|
7
|
+
RECEIVE_EVENT_RETRY,
|
|
8
|
+
REQUEST_EVENT_RETRY_FAILED
|
|
9
|
+
} from 'actions'
|
|
10
|
+
|
|
11
|
+
function patchEvent (state, action, params) {
|
|
12
|
+
return state.map((event) => {
|
|
13
|
+
if (event.id === action.event.id) {
|
|
14
|
+
return Object.assign({}, event, params)
|
|
15
|
+
}
|
|
16
|
+
return event
|
|
17
|
+
})
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export default (state = [], action) => {
|
|
21
|
+
switch (action.type) {
|
|
22
|
+
case EVENT_SHOW_OVERVIEW:
|
|
23
|
+
return patchEvent(state, action, { overviewVisible: true })
|
|
24
|
+
|
|
25
|
+
case EVENT_HIDE_OVERVIEW:
|
|
26
|
+
return patchEvent(state, action, { overviewVisible: false, error: null })
|
|
27
|
+
|
|
28
|
+
case RECEIVE_SEARCH_RESULTS:
|
|
29
|
+
return action.offset <= 0
|
|
30
|
+
? action.events
|
|
31
|
+
: state.concat(action.events)
|
|
32
|
+
|
|
33
|
+
case EVENT_SHOW_RETRY:
|
|
34
|
+
return patchEvent(state, action, { retryVisible: true })
|
|
35
|
+
|
|
36
|
+
case EVENT_HIDE_RETRY:
|
|
37
|
+
return patchEvent(state, action, { retryVisible: false, error: null })
|
|
38
|
+
|
|
39
|
+
case RECEIVE_EVENT_RETRY:
|
|
40
|
+
return patchEvent(state, action, { acknowledged: action.acknowledged, error: null })
|
|
41
|
+
|
|
42
|
+
case REQUEST_EVENT_RETRY_FAILED:
|
|
43
|
+
return patchEvent(state, action, { error: action.error })
|
|
44
|
+
|
|
45
|
+
default:
|
|
46
|
+
return state
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import {
|
|
2
|
+
EVENT_SHOW_OVERVIEW,
|
|
3
|
+
EVENT_HIDE_OVERVIEW,
|
|
4
|
+
RECEIVE_SEARCH_RESULTS,
|
|
5
|
+
EVENT_SHOW_RETRY,
|
|
6
|
+
EVENT_HIDE_RETRY,
|
|
7
|
+
RECEIVE_EVENT_RETRY,
|
|
8
|
+
REQUEST_EVENT_RETRY_FAILED
|
|
9
|
+
} from 'actions'
|
|
10
|
+
|
|
11
|
+
import reducer from 'reducers/events'
|
|
12
|
+
|
|
13
|
+
describe('reducers/events', () => {
|
|
14
|
+
describe('for EVENT_SHOW_OVERVIEW', () => {
|
|
15
|
+
it('sets overviewVisible for the specific event to true', () => {
|
|
16
|
+
const currentState = [{ id: 1 }, { id: 2 }, { id: 3 }]
|
|
17
|
+
const action = { type: EVENT_SHOW_OVERVIEW, event: { id: 2 } }
|
|
18
|
+
const expectedState = [{ id: 1 }, { id: 2, overviewVisible: true }, { id: 3 }]
|
|
19
|
+
expect(reducer(currentState, action)).toEqual(expectedState)
|
|
20
|
+
})
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
describe('for EVENT_HIDE_OVERVIEW', () => {
|
|
24
|
+
it('sets overviewVisible to false and erase errors for a specific', () => {
|
|
25
|
+
const currentState = [{ id: 1 }, { id: 2, overviewVisible: true }, { id: 3 }]
|
|
26
|
+
const action = { type: EVENT_HIDE_OVERVIEW, event: { id: 2 } }
|
|
27
|
+
const expectedState = [{ id: 1 }, { id: 2, overviewVisible: false, error: null }, { id: 3 }]
|
|
28
|
+
expect(reducer(currentState, action)).toEqual(expectedState)
|
|
29
|
+
})
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
describe('for RECEIVE_SEARCH_RESULTS', () => {
|
|
33
|
+
describe('when offset less or equal to 0', () => {
|
|
34
|
+
it('overrides events with action events', () => {
|
|
35
|
+
const currentState = [{ id: 1 }, { id: 2 }, { id: 3 }]
|
|
36
|
+
const action = { type: RECEIVE_SEARCH_RESULTS, offset: 0, events: [{ id: 4 }] }
|
|
37
|
+
const expectedState = [{ id: 4 }]
|
|
38
|
+
expect(reducer(currentState, action)).toEqual(expectedState)
|
|
39
|
+
})
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
describe('when offset is greater than 0', () => {
|
|
43
|
+
it('add the new events to state', () => {
|
|
44
|
+
const currentState = [{ id: 1 }, { id: 2 }, { id: 3 }]
|
|
45
|
+
const action = { type: RECEIVE_SEARCH_RESULTS, offset: 1, events: [{ id: 4 }] }
|
|
46
|
+
const expectedState = [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }]
|
|
47
|
+
expect(reducer(currentState, action)).toEqual(expectedState)
|
|
48
|
+
})
|
|
49
|
+
})
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
describe('for EVENT_SHOW_RETRY', () => {
|
|
53
|
+
it('sets retryVisible for the specific event to true', () => {
|
|
54
|
+
const currentState = [{ id: 1 }, { id: 2 }, { id: 3 }]
|
|
55
|
+
const action = { type: EVENT_SHOW_RETRY, event: { id: 2 } }
|
|
56
|
+
const expectedState = [{ id: 1 }, { id: 2, retryVisible: true }, { id: 3 }]
|
|
57
|
+
expect(reducer(currentState, action)).toEqual(expectedState)
|
|
58
|
+
})
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
describe('for EVENT_HIDE_RETRY', () => {
|
|
62
|
+
it('sets retryVisible to false and erase errors for a specific', () => {
|
|
63
|
+
const currentState = [{ id: 1 }, { id: 2, retryVisible: true }, { id: 3 }]
|
|
64
|
+
const action = { type: EVENT_HIDE_RETRY, event: { id: 2 } }
|
|
65
|
+
const expectedState = [{ id: 1 }, { id: 2, retryVisible: false, error: null }, { id: 3 }]
|
|
66
|
+
expect(reducer(currentState, action)).toEqual(expectedState)
|
|
67
|
+
})
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
describe('for RECEIVE_EVENT_RETRY', () => {
|
|
71
|
+
it('sets acknowledged and erase errors for a specific', () => {
|
|
72
|
+
const currentState = [{ id: 1 }, { id: 2 }, { id: 3 }]
|
|
73
|
+
const action = { type: RECEIVE_EVENT_RETRY, event: { id: 2 }, acknowledged: true }
|
|
74
|
+
const expectedState = [{ id: 1 }, { id: 2, acknowledged: true, error: null }, { id: 3 }]
|
|
75
|
+
expect(reducer(currentState, action)).toEqual(expectedState)
|
|
76
|
+
})
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
describe('for REQUEST_EVENT_RETRY_FAILED', () => {
|
|
80
|
+
it('sets error for a specific', () => {
|
|
81
|
+
const currentState = [{ id: 1 }, { id: 2 }, { id: 3 }]
|
|
82
|
+
const action = { type: REQUEST_EVENT_RETRY_FAILED, event: { id: 2 }, error: 'some error' }
|
|
83
|
+
const expectedState = [{ id: 1 }, { id: 2, error: 'some error' }, { id: 3 }]
|
|
84
|
+
expect(reducer(currentState, action)).toEqual(expectedState)
|
|
85
|
+
})
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
describe('for default', () => {
|
|
89
|
+
it('returns the currentState', () => {
|
|
90
|
+
const currentState = { current: true }
|
|
91
|
+
const action = { type: 'another' }
|
|
92
|
+
expect(reducer(currentState, action)).toEqual(currentState)
|
|
93
|
+
})
|
|
94
|
+
})
|
|
95
|
+
})
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { ADD_FLASH_MESSAGE, DELETE_FLASH_MESSAGE } from 'actions'
|
|
2
|
+
|
|
3
|
+
export default (state = [], action) => {
|
|
4
|
+
switch (action.type) {
|
|
5
|
+
case ADD_FLASH_MESSAGE:
|
|
6
|
+
return [...state, action.message]
|
|
7
|
+
|
|
8
|
+
case DELETE_FLASH_MESSAGE:
|
|
9
|
+
return state.filter((message) => message.id !== action.id)
|
|
10
|
+
|
|
11
|
+
default:
|
|
12
|
+
return state
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ADD_FLASH_MESSAGE,
|
|
3
|
+
DELETE_FLASH_MESSAGE
|
|
4
|
+
} from 'actions'
|
|
5
|
+
|
|
6
|
+
import reducer from 'reducers/flash-messages'
|
|
7
|
+
|
|
8
|
+
describe('reducers/flash-messages', () => {
|
|
9
|
+
describe('for ADD_FLASH_MESSAGE', () => {
|
|
10
|
+
it('adds a new message', () => {
|
|
11
|
+
const currentState = []
|
|
12
|
+
const action = { type: ADD_FLASH_MESSAGE, message: { id: 1, type: 'success' } }
|
|
13
|
+
const expectedState = [action.message]
|
|
14
|
+
expect(reducer(currentState, action)).toEqual(expectedState)
|
|
15
|
+
})
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
describe('for DELETE_FLASH_MESSAGE', () => {
|
|
19
|
+
it('deletes the message by id', () => {
|
|
20
|
+
const currentState = [{ id: 1, type: 'success' }, { id: 2, type: 'error' }]
|
|
21
|
+
const action = { type: DELETE_FLASH_MESSAGE, id: 2 }
|
|
22
|
+
const expectedState = [{ id: 1, type: 'success' }]
|
|
23
|
+
expect(reducer(currentState, action)).toEqual(expectedState)
|
|
24
|
+
})
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
describe('for default', () => {
|
|
28
|
+
it('returns the currentState', () => {
|
|
29
|
+
const currentState = { current: true }
|
|
30
|
+
const action = { type: 'another' }
|
|
31
|
+
expect(reducer(currentState, action)).toEqual(currentState)
|
|
32
|
+
})
|
|
33
|
+
})
|
|
34
|
+
})
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { combineReducers } from 'redux'
|
|
2
|
+
import { routerReducer } from 'react-router-redux'
|
|
3
|
+
|
|
4
|
+
import events from 'reducers/events'
|
|
5
|
+
import eventDetails from 'reducers/event-details'
|
|
6
|
+
import eventsFilters from 'reducers/events-filters'
|
|
7
|
+
import xhrStatus from 'reducers/xhr-status'
|
|
8
|
+
import flashMessages from 'reducers/flash-messages'
|
|
9
|
+
|
|
10
|
+
export default combineReducers({
|
|
11
|
+
xhrStatus,
|
|
12
|
+
flashMessages,
|
|
13
|
+
eventsFilters,
|
|
14
|
+
eventDetails,
|
|
15
|
+
events,
|
|
16
|
+
|
|
17
|
+
routing: routerReducer
|
|
18
|
+
})
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import reducer from 'reducers'
|
|
2
|
+
|
|
3
|
+
describe('reducers', () => {
|
|
4
|
+
it('without actions returns the initial state', () => {
|
|
5
|
+
expect(reducer(undefined, {})).toEqual({
|
|
6
|
+
xhrStatus: {
|
|
7
|
+
isFetchingEvents: false,
|
|
8
|
+
isRetryingEvent: false,
|
|
9
|
+
isFetchingEventDetails: false,
|
|
10
|
+
currentEventsOffset: 0,
|
|
11
|
+
lastEventsLoadSize: 0
|
|
12
|
+
},
|
|
13
|
+
flashMessages: [],
|
|
14
|
+
eventsFilters: { type: 'entity_id' },
|
|
15
|
+
eventDetails: {},
|
|
16
|
+
events: [],
|
|
17
|
+
routing: { locationBeforeTransitions: null }
|
|
18
|
+
})
|
|
19
|
+
})
|
|
20
|
+
})
|