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.
Files changed (99) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +15 -0
  3. data/.rspec +2 -0
  4. data/CHANGELOG.md +9 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +81 -0
  8. data/Rakefile +12 -0
  9. data/assets/index-52cbf3063583f3c09a4b-0.css +2 -0
  10. data/assets/index-52cbf3063583f3c09a4b-0.css.map +1 -0
  11. data/assets/index-52cbf3063583f3c09a4b-1.css +2 -0
  12. data/assets/index-52cbf3063583f3c09a4b-1.css.map +1 -0
  13. data/assets/index-52cbf3063583f3c09a4b.js +49 -0
  14. data/assets/index-52cbf3063583f3c09a4b.js.map +1 -0
  15. data/assets/index.html +12 -0
  16. data/bin/console +14 -0
  17. data/bin/setup +8 -0
  18. data/circle.yml +29 -0
  19. data/frontend/.babelrc +3 -0
  20. data/frontend/.editorconfig +8 -0
  21. data/frontend/.eslintignore +2 -0
  22. data/frontend/.eslintrc +3 -0
  23. data/frontend/.npmignore +8 -0
  24. data/frontend/package.json +45 -0
  25. data/frontend/sagui.config.js +52 -0
  26. data/frontend/src/actions/event-details.js +42 -0
  27. data/frontend/src/actions/event-details.spec.js +71 -0
  28. data/frontend/src/actions/event-overview.js +11 -0
  29. data/frontend/src/actions/event-overview.spec.js +20 -0
  30. data/frontend/src/actions/event-retry.js +58 -0
  31. data/frontend/src/actions/event-retry.spec.js +117 -0
  32. data/frontend/src/actions/events-search.js +75 -0
  33. data/frontend/src/actions/events-search.spec.js +197 -0
  34. data/frontend/src/actions/flash-messages.js +16 -0
  35. data/frontend/src/actions/flash-messages.spec.js +23 -0
  36. data/frontend/src/actions/index.js +24 -0
  37. data/frontend/src/actions/search-input-filter.js +14 -0
  38. data/frontend/src/actions/search-input-filter.spec.js +20 -0
  39. data/frontend/src/api.js +46 -0
  40. data/frontend/src/components/event/error-message.js +19 -0
  41. data/frontend/src/components/event/error-message.scss +8 -0
  42. data/frontend/src/components/event/event.scss +3 -0
  43. data/frontend/src/components/event/index.js +77 -0
  44. data/frontend/src/components/event/index.spec.js +89 -0
  45. data/frontend/src/components/event/loading.js +16 -0
  46. data/frontend/src/components/event/style.js +16 -0
  47. data/frontend/src/components/event-overview/attribute.js +24 -0
  48. data/frontend/src/components/event-overview/event-overview.scss +28 -0
  49. data/frontend/src/components/event-overview/index.js +47 -0
  50. data/frontend/src/components/event-overview/index.spec.js +67 -0
  51. data/frontend/src/components/event-overview-dialog/event-overview-dialog.scss +15 -0
  52. data/frontend/src/components/event-overview-dialog/index.js +85 -0
  53. data/frontend/src/components/event-retry-dialog/index.js +72 -0
  54. data/frontend/src/components/events-list/events-list.scss +49 -0
  55. data/frontend/src/components/events-list/index.js +62 -0
  56. data/frontend/src/components/events-list/index.spec.js +59 -0
  57. data/frontend/src/components/flash-message/flash-message.scss +40 -0
  58. data/frontend/src/components/flash-message/index.js +45 -0
  59. data/frontend/src/components/flash-message/index.spec.js +59 -0
  60. data/frontend/src/components/flash-message-list/index.js +34 -0
  61. data/frontend/src/components/header/header.scss +29 -0
  62. data/frontend/src/components/header/index.js +44 -0
  63. data/frontend/src/components/search-input/index.js +104 -0
  64. data/frontend/src/components/search-input/index.spec.js +56 -0
  65. data/frontend/src/components/search-input/search-input.scss +7 -0
  66. data/frontend/src/configs.js +15 -0
  67. data/frontend/src/helpers.spec.js +2 -0
  68. data/frontend/src/index.html +12 -0
  69. data/frontend/src/index.js +26 -0
  70. data/frontend/src/index.scss +31 -0
  71. data/frontend/src/reducers/event-details.js +32 -0
  72. data/frontend/src/reducers/event-details.spec.js +54 -0
  73. data/frontend/src/reducers/events-filters.js +17 -0
  74. data/frontend/src/reducers/events-filters.spec.js +34 -0
  75. data/frontend/src/reducers/events.js +48 -0
  76. data/frontend/src/reducers/events.spec.js +95 -0
  77. data/frontend/src/reducers/flash-messages.js +14 -0
  78. data/frontend/src/reducers/flash-messages.spec.js +34 -0
  79. data/frontend/src/reducers/index.js +18 -0
  80. data/frontend/src/reducers/index.spec.js +20 -0
  81. data/frontend/src/reducers/xhr-status.js +75 -0
  82. data/frontend/src/reducers/xhr-status.spec.js +94 -0
  83. data/frontend/src/routes.js +20 -0
  84. data/frontend/src/store.js +15 -0
  85. data/frontend/src/views/event-details.js +50 -0
  86. data/frontend/src/views/events-search.js +112 -0
  87. data/frontend/src/views/events-search.scss +24 -0
  88. data/frontend/src/views/events-search.spec.js +96 -0
  89. data/frontend/src/views/layout.js +24 -0
  90. data/frontend/src/views/layout.scss +3 -0
  91. data/lib/phobos_checkpoint_ui/app.rb +11 -0
  92. data/lib/phobos_checkpoint_ui/static_app.rb +19 -0
  93. data/lib/phobos_checkpoint_ui/tasks.rb +20 -0
  94. data/lib/phobos_checkpoint_ui/version.rb +3 -0
  95. data/lib/phobos_checkpoint_ui.rb +10 -0
  96. data/phobos_checkpoint_ui.gemspec +53 -0
  97. data/screenshot1.png +0 -0
  98. data/screenshot2.png +0 -0
  99. 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,7 @@
1
+ .search-input {
2
+ display: flex;
3
+ align-items: center;
4
+ background-color: #fff;
5
+ padding: 5px 30px;
6
+ margin: 0 10px;
7
+ }
@@ -0,0 +1,15 @@
1
+ import API from 'api'
2
+
3
+ let config
4
+
5
+ export function load () {
6
+ return API.Config
7
+ .load()
8
+ .then((response) => {
9
+ config = response.data
10
+ })
11
+ }
12
+
13
+ export default function get () {
14
+ return Object.assign({}, config)
15
+ }
@@ -0,0 +1,2 @@
1
+ import injectTapEventPlugin from 'react-tap-event-plugin'
2
+ injectTapEventPlugin()
@@ -0,0 +1,12 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>PhobosCheckpoint</title>
5
+ <meta charset="utf-8" />
6
+ </head>
7
+ <body>
8
+ <div id="root">
9
+ <div class='loading-boot'>Loading...</div>
10
+ </div>
11
+ </body>
12
+ </html>
@@ -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
+ })