phobos_checkpoint_ui 0.4.0 → 1.0.0.rc1

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 (66) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +5 -1
  3. data/frontend/.gitignore +4 -0
  4. data/frontend/src/actions/events-search.js +11 -11
  5. data/frontend/src/actions/events-search.spec.js +21 -21
  6. data/frontend/src/actions/failures/details.js +42 -0
  7. data/frontend/src/actions/failures/details.spec.js +71 -0
  8. data/frontend/src/actions/failures/overview.js +14 -0
  9. data/frontend/src/actions/failures/overview.spec.js +20 -0
  10. data/frontend/src/actions/failures/retry.js +58 -0
  11. data/frontend/src/actions/failures/retry.spec.js +117 -0
  12. data/frontend/src/actions/failures/search/index.js +76 -0
  13. data/frontend/src/actions/failures/search/index.spec.js +197 -0
  14. data/frontend/src/actions/index.js +24 -5
  15. data/frontend/src/actions/navigation/index.js +5 -0
  16. data/frontend/src/actions/navigation/index.spec.js +21 -0
  17. data/frontend/src/api.js +11 -0
  18. data/frontend/src/components/{event-overview/attribute.js → attribute/index.js} +0 -0
  19. data/frontend/src/components/empty-event/index.js +20 -0
  20. data/frontend/src/components/empty-event/index.spec.js +38 -0
  21. data/frontend/src/components/event-overview/index.js +1 -1
  22. data/frontend/src/components/event-retry-dialog/index.js +1 -1
  23. data/frontend/src/components/failure/error-message.js +19 -0
  24. data/frontend/src/components/failure/error-message.scss +8 -0
  25. data/frontend/src/components/failure/event.scss +3 -0
  26. data/frontend/src/components/failure/index.js +82 -0
  27. data/frontend/src/components/failure/index.spec.js +89 -0
  28. data/frontend/src/components/failure/loading.js +16 -0
  29. data/frontend/src/components/failure/overview/failure-overview.scss +28 -0
  30. data/frontend/src/components/failure/overview/index.js +60 -0
  31. data/frontend/src/components/failure/overview/index.spec.js +71 -0
  32. data/frontend/src/components/failure/overview-dialog/event-overview-dialog.scss +15 -0
  33. data/frontend/src/components/failure/overview-dialog/index.js +90 -0
  34. data/frontend/src/components/failure/retry-dialog/index.js +72 -0
  35. data/frontend/src/components/failure/style.js +16 -0
  36. data/frontend/src/components/failures-list/failures-list.scss +49 -0
  37. data/frontend/src/components/failures-list/index.js +62 -0
  38. data/frontend/src/components/failures-list/index.spec.js +59 -0
  39. data/frontend/src/components/header/index.js +36 -2
  40. data/frontend/src/components/load-more/index.js +22 -0
  41. data/frontend/src/components/load-more/index.spec.js +54 -0
  42. data/frontend/src/components/search-input/index.js +2 -5
  43. data/frontend/src/components/search-input/index.spec.js +6 -6
  44. data/frontend/src/reducers/events.js +2 -2
  45. data/frontend/src/reducers/events.spec.js +4 -4
  46. data/frontend/src/reducers/failures/details/index.js +32 -0
  47. data/frontend/src/reducers/failures/details/index.spec.js +54 -0
  48. data/frontend/src/reducers/failures/index.js +48 -0
  49. data/frontend/src/reducers/failures/index.spec.js +95 -0
  50. data/frontend/src/reducers/index.js +4 -1
  51. data/frontend/src/reducers/index.spec.js +2 -0
  52. data/frontend/src/reducers/xhr-status.js +25 -10
  53. data/frontend/src/reducers/xhr-status.spec.js +65 -15
  54. data/frontend/src/routes.js +6 -2
  55. data/frontend/src/store.js +7 -3
  56. data/frontend/src/views/{event-details.js → events/details/index.js} +0 -0
  57. data/frontend/src/views/{events-search.js → events/search/index.js} +37 -44
  58. data/frontend/src/views/{events-search.scss → events/search/index.scss} +0 -0
  59. data/frontend/src/views/{events-search.spec.js → events/search/index.spec.js} +35 -25
  60. data/frontend/src/views/failures/details/index.js +50 -0
  61. data/frontend/src/views/failures/search/index.js +113 -0
  62. data/frontend/src/views/failures/search/index.scss +24 -0
  63. data/frontend/src/views/failures/search/index.spec.js +106 -0
  64. data/lib/phobos_checkpoint_ui/version.rb +1 -1
  65. data/phobos_checkpoint_ui.gemspec +1 -1
  66. metadata +53 -14
@@ -0,0 +1,60 @@
1
+ import React, { Component, PropTypes } from 'react'
2
+ import moment from 'moment'
3
+ import JSONPretty from 'react-json-pretty'
4
+ import 'react-json-pretty/src/JSONPretty.monikai.css'
5
+
6
+ import Attribute from 'components/attribute'
7
+
8
+ const EVENT_TIME_FORMAT = 'MMMM Do YYYY, h:mm:ss a'
9
+
10
+ export function formatEventTime (eventTime) {
11
+ if (!eventTime) return null
12
+ const eventTimeDate = new Date(eventTime)
13
+ return moment(eventTimeDate).format(EVENT_TIME_FORMAT)
14
+ }
15
+
16
+ export default class extends Component {
17
+ static get propTypes () {
18
+ return {
19
+ id: PropTypes.number,
20
+ created_at: PropTypes.string,
21
+ topic: PropTypes.string,
22
+ group_id: PropTypes.string,
23
+ entity_id: PropTypes.string,
24
+ event_time: PropTypes.string,
25
+ event_type: PropTypes.string,
26
+ event_version: PropTypes.string,
27
+ checksum: PropTypes.string,
28
+ payload: PropTypes.object,
29
+ metadata: PropTypes.object,
30
+ error_class: PropTypes.string,
31
+ error_message: PropTypes.string,
32
+ error_backtrace: PropTypes.array
33
+ }
34
+ }
35
+
36
+ render () {
37
+ return (
38
+ <div className='failure-overview'>
39
+ <Attribute label='Group ID' value={this.props.group_id} />
40
+ <Attribute label='Topic' value={this.props.topic} />
41
+ <Attribute label='Entity ID' value={this.props.entity_id} />
42
+ <Attribute label='Event Type' value={this.props.event_type} />
43
+ <Attribute label='Event Time' value={formatEventTime(this.props.event_time)} />
44
+ <Attribute label='Event Version' value={this.props.event_version} />
45
+ <Attribute label='Checksum' value={this.props.checksum} />
46
+ <Attribute label='Payload'>
47
+ <JSONPretty className='json-pretty' json={this.props.payload} />
48
+ </Attribute>
49
+ <Attribute label='Metadata'>
50
+ <JSONPretty className='json-pretty' json={this.props.metadata} />
51
+ </Attribute>
52
+ <Attribute label='Error Class' value={this.props.error_class} />
53
+ <Attribute label='Error Message' value={this.props.error_class} />
54
+ <Attribute label='Backtrace'>
55
+ <JSONPretty className='json-pretty' json={this.props.error_backtrace} />
56
+ </Attribute>
57
+ </div>
58
+ )
59
+ }
60
+ }
@@ -0,0 +1,71 @@
1
+ import React from 'react'
2
+ import jasmineEnzyme from 'jasmine-enzyme'
3
+ import { mount } from 'enzyme'
4
+ import FailureOverview, { formatEventTime } from 'components/failure/overview'
5
+ import getMuiTheme from 'material-ui/styles/getMuiTheme'
6
+ import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'
7
+
8
+ const mountComponent = (props) => mount(
9
+ <MuiThemeProvider muiTheme={getMuiTheme()}>
10
+ <FailureOverview {...props} />
11
+ </MuiThemeProvider>
12
+ )
13
+
14
+ describe('<FailureOverview />', () => {
15
+ let props, component
16
+
17
+ beforeEach(() => {
18
+ jasmineEnzyme()
19
+
20
+ props = {
21
+ id: 1,
22
+ topic: 'phobos.test',
23
+ group_id: 'phobos-checkpoint-consumer',
24
+ entity_id: 'a5dbd02d-bc40-6d15-b993-83a4825d94e6',
25
+ event_time: '2016-09-23T21:00:40.515Z',
26
+ event_type: 'order-placed',
27
+ event_version: 'v1',
28
+ checksum: '188773471ec0f898fd81d272760a027f',
29
+ payload: { data: { name: 'phobos' } },
30
+ metadata: { meta: { version: 'foo' } },
31
+ error_class: 'FooError',
32
+ error_message: 'Expected "foo" to equal "bar"',
33
+ error_backtrace: ['Line 1: foo', 'Line 2: baz']
34
+ }
35
+
36
+ component = mountComponent(props)
37
+ })
38
+
39
+ it('displays topic', () => {
40
+ expect(component.text()).toMatch(props.topic)
41
+ })
42
+
43
+ it('displays group_id', () => {
44
+ expect(component.text()).toMatch(props.group_id)
45
+ })
46
+
47
+ it('displays entity_id', () => {
48
+ expect(component.text()).toMatch(props.entity_id)
49
+ })
50
+
51
+ it('displays event_time formatted', () => {
52
+ expect(component.text()).toMatch(formatEventTime(props.event_time))
53
+ })
54
+
55
+ it('displays event_type', () => {
56
+ expect(component.text()).toMatch(props.event_type)
57
+ })
58
+
59
+ it('displays event_version', () => {
60
+ expect(component.text()).toMatch(props.event_version)
61
+ })
62
+
63
+ it('displays checksum', () => {
64
+ expect(component.text()).toMatch(props.checksum)
65
+ })
66
+
67
+ it('displays payload', () => {
68
+ const payloadFormatted = JSON.stringify(props.payload, null, ' ')
69
+ expect(component.text()).toMatch(payloadFormatted)
70
+ })
71
+ })
@@ -0,0 +1,15 @@
1
+ .failure-overview-dialog {
2
+ .dialog-title {
3
+ text-decoration: none;
4
+ color: rgba(0, 0, 0, 0.87);
5
+
6
+ &:active,
7
+ &:visited {
8
+ color: inherit;
9
+ }
10
+
11
+ &:hover {
12
+ color: #666;
13
+ }
14
+ }
15
+ }
@@ -0,0 +1,90 @@
1
+ import React, { Component, PropTypes } from 'react'
2
+ import { connect } from 'react-redux'
3
+ import { Link } from 'react-router'
4
+
5
+ import { hideFailureOverview } from 'actions/failures/overview'
6
+ import { showFailureRetry } from 'actions/failures/retry'
7
+
8
+ import Dialog from 'material-ui/Dialog'
9
+ import RaisedButton from 'material-ui/RaisedButton'
10
+ import FailureOverview from 'components/failure/overview'
11
+
12
+ class OverviewDialog extends Component {
13
+ static get propTypes () {
14
+ return {
15
+ onHideOverview: PropTypes.func,
16
+ onShowRetry: PropTypes.func,
17
+ failure: PropTypes.shape({
18
+ id: PropTypes.number,
19
+ created_at: PropTypes.string,
20
+ topic: PropTypes.string,
21
+ group_id: PropTypes.string,
22
+ entity_id: PropTypes.string,
23
+ event_time: PropTypes.string,
24
+ event_type: PropTypes.string,
25
+ event_version: PropTypes.string,
26
+ checksum: PropTypes.string,
27
+ payload: PropTypes.object,
28
+ metadata: PropTypes.object,
29
+ error_class: PropTypes.string,
30
+ error_message: PropTypes.string,
31
+ error_backtrace: PropTypes.array,
32
+
33
+ overviewVisible: PropTypes.bool
34
+ })
35
+ }
36
+ }
37
+
38
+ static get defaultProps () {
39
+ return {
40
+ failure: {}
41
+ }
42
+ }
43
+
44
+ render () {
45
+ return (
46
+ <Dialog
47
+ modal={false}
48
+ autoScrollBodyContent
49
+ className='failures/overview-dialog'
50
+ title={this.dialogTitle()}
51
+ open={!!this.props.failure.overviewVisible}
52
+ onRequestClose={() => this.hideOverview()}
53
+ contentStyle={{maxWidth: '1024px'}}
54
+ bodyStyle={{maxWidth: '1024px'}}
55
+ actions={[
56
+ <RaisedButton
57
+ secondary
58
+ label='Retry'
59
+ onClick={() => this.showRetry()}/>
60
+ ]}>
61
+ <FailureOverview {...this.props.failure} />
62
+ </Dialog>
63
+ )
64
+ }
65
+
66
+ hideOverview () {
67
+ this.props.onHideOverview(this.props.failure)
68
+ }
69
+
70
+ showRetry () {
71
+ this.props.onShowRetry(this.props.failure)
72
+ }
73
+
74
+ dialogTitle () {
75
+ return (
76
+ <h3>
77
+ <Link className='dialog-title' to={`/failures/${this.props.failure.id}`}>
78
+ {`#${this.props.failure.id}`}
79
+ </Link>
80
+ </h3>
81
+ )
82
+ }
83
+ }
84
+
85
+ export default connect(
86
+ (state, ownProps) => ownProps, {
87
+ onHideOverview: hideFailureOverview,
88
+ onShowRetry: showFailureRetry
89
+ }
90
+ )(OverviewDialog)
@@ -0,0 +1,72 @@
1
+ import React, { Component, PropTypes } from 'react'
2
+ import { connect } from 'react-redux'
3
+
4
+ import ErrorMessage from 'components/event/error-message'
5
+ import Loading from 'components/event/loading'
6
+
7
+ import { hideFailureRetry, performFailureRetry } from 'actions/failures/retry'
8
+
9
+ import Dialog from 'material-ui/Dialog'
10
+ import RaisedButton from 'material-ui/RaisedButton'
11
+
12
+ class FailureRetryDialog extends Component {
13
+ static get propTypes () {
14
+ return {
15
+ onHideRetry: PropTypes.func.isRequired,
16
+ onPerformRetry: PropTypes.func.isRequired,
17
+ isRetryingEvent: PropTypes.bool,
18
+ failure: PropTypes.shape({
19
+ id: PropTypes.number,
20
+ retryVisible: PropTypes.bool
21
+ }).isRequired
22
+ }
23
+ }
24
+
25
+ static get defaultProps () {
26
+ return {
27
+ event: {}
28
+ }
29
+ }
30
+
31
+ render () {
32
+ return (
33
+ <Dialog
34
+ modal={!!this.props.isRetryingEvent}
35
+ title='Are you sure?'
36
+ open={!!this.props.failure.retryVisible}
37
+ bodyStyle={{maxWidth: '300px'}}
38
+ contentStyle={{maxWidth: '300px'}}
39
+ onRequestClose={() => this.hide()}
40
+ actions={[
41
+ <RaisedButton
42
+ primary
43
+ label='Retry'
44
+ onClick={() => this.performRetry()}/>
45
+ ]}>
46
+ <div style={{textAlign: 'center'}}>
47
+ <Loading visible={this.props.isRetryingEvent}/>
48
+ <ErrorMessage message={this.props.failure.error}/>
49
+ </div>
50
+ </Dialog>
51
+ )
52
+ }
53
+
54
+ hide () {
55
+ this.props.onHideRetry(this.props.failure)
56
+ }
57
+
58
+ performRetry () {
59
+ this.props.onPerformRetry(this.props.failure)
60
+ }
61
+ }
62
+
63
+ const mapStateToProps = (state, ownProps) => (
64
+ Object.assign({
65
+ isRetryingEvent: state.xhrStatus.isRetryingEvent
66
+ }, ownProps)
67
+ )
68
+
69
+ export default connect(mapStateToProps, {
70
+ onHideRetry: hideFailureRetry,
71
+ onPerformRetry: performFailureRetry
72
+ })(FailureRetryDialog)
@@ -0,0 +1,16 @@
1
+ export default {
2
+ card: {
3
+ width: '490px',
4
+ overflow: 'hidden',
5
+ backgroundColor: '#fff'
6
+ },
7
+ cardHeader: {
8
+ title: {color: '#000', fontWeight: 'lighter'},
9
+ subtitle: {color: '#000'}
10
+ },
11
+ cardTitle: {
12
+ color: '#000',
13
+ fontSize: '38px',
14
+ fontWeight: 'lighter'
15
+ }
16
+ }
@@ -0,0 +1,49 @@
1
+ .failures-list {
2
+ display: flex;
3
+ flex-wrap: wrap;
4
+ flex-direction: column;
5
+ align-items: inherit;
6
+ position: relative;
7
+
8
+ .timeline {
9
+ padding-left: 40px;
10
+
11
+ &:before {
12
+ content: "";
13
+ position: absolute;
14
+ top: 0;
15
+ bottom: 0;
16
+ left: 15px;
17
+ display: block;
18
+ width: 2px;
19
+ background-color: #cdcdcd;
20
+ }
21
+
22
+ .dot {
23
+ border: 2px solid #868686;
24
+ border-radius: 50%;
25
+ width: 10px;
26
+ height: 10px;
27
+ display: inline-block;
28
+ margin: 0 10px 0 9px;
29
+ background-color: #e4e4e4;
30
+ z-index: 1;
31
+ }
32
+
33
+ .day-header {
34
+ display: inline-flex;
35
+ align-items: center;
36
+ color: #333;
37
+ font-family: Roboto;
38
+ margin: 10px 0 10px -40px;
39
+
40
+ &:first-child {
41
+ margin-top: 20px;
42
+ }
43
+ }
44
+
45
+ & > .event {
46
+ margin: 10px 10px;
47
+ }
48
+ }
49
+ }
@@ -0,0 +1,62 @@
1
+ import React, { Component, PropTypes } from 'react'
2
+ import moment from 'moment'
3
+ import Failure from 'components/failure'
4
+
5
+ const SECTION_DATE_FORMAT = 'MMM DD, YYYY'
6
+ const EVENT_TIME_FORMAT = 'YYYY-MM-DD'
7
+
8
+ export default class extends Component {
9
+ static get propTypes () {
10
+ return {
11
+ failures: PropTypes.array
12
+ }
13
+ }
14
+
15
+ render () {
16
+ return (
17
+ <div className='failures-list'>
18
+ <div className='timeline'>
19
+ {this.renderTimeline()}
20
+ </div>
21
+ </div>
22
+ )
23
+ }
24
+
25
+ renderTimeline () {
26
+ let day
27
+ let timeline = []
28
+
29
+ this.props.failures.forEach((failure) => {
30
+ const eventDay = formatEventDate(failure.event_time)
31
+ if (day !== eventDay) {
32
+ day = eventDay
33
+ timeline.push(this.renderDayHeader(day))
34
+ }
35
+
36
+ timeline.push(
37
+ <Failure key={`failure-${failure.id}`} failure={failure}/>
38
+ )
39
+ })
40
+
41
+ return timeline
42
+ }
43
+
44
+ renderDayHeader (day) {
45
+ return (
46
+ <div key={`day-header-${day}`} className='day-header'>
47
+ <span className='dot' />
48
+ {formatSectionDate(day)}
49
+ </div>
50
+ )
51
+ }
52
+ }
53
+
54
+ function formatEventDate (eventdate) {
55
+ if (!eventdate) return null
56
+ return moment(new Date(eventdate)).format(EVENT_TIME_FORMAT)
57
+ }
58
+
59
+ function formatSectionDate (date) {
60
+ if (!date) return null
61
+ return moment(new Date(date)).format(SECTION_DATE_FORMAT)
62
+ }
@@ -0,0 +1,59 @@
1
+ import React from 'react'
2
+ import jasmineEnzyme from 'jasmine-enzyme'
3
+ import { mount } from 'enzyme'
4
+ import EventsList from 'components/events-list'
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
+ <EventsList {...props} />
18
+ </MuiThemeProvider>
19
+ </Provider>
20
+ )
21
+
22
+ describe('<EventsList />', () => {
23
+ let store, props, component, event
24
+
25
+ beforeEach(() => {
26
+ jasmineEnzyme()
27
+
28
+ store = mockStore({
29
+ xhrStatus: {
30
+ isRetryingEvent: false
31
+ }
32
+ })
33
+
34
+ event = {
35
+ id: 1,
36
+ topic: 'phobos.test',
37
+ group_id: 'phobos-checkpoint-consumer',
38
+ entity_id: 'a5dbd02d-bc40-6d15-b993-83a4825d94e6',
39
+ event_time: '2016-09-23T21:00:40.515Z',
40
+ event_type: 'order-placed',
41
+ event_version: 'v1',
42
+ checksum: '188773471ec0f898fd81d272760a027f',
43
+ payload: { data: { name: 'phobos' } }
44
+ }
45
+
46
+ props = {
47
+ events: [
48
+ event,
49
+ Object.assign({}, event, { id: 2 })
50
+ ]
51
+ }
52
+
53
+ component = mountComponent(store, props)
54
+ })
55
+
56
+ it('renders the list of events', () => {
57
+ expect(component.find('.event').length).toEqual(2)
58
+ })
59
+ })
@@ -1,9 +1,18 @@
1
1
  import React, { Component } from 'react'
2
+ import { connect } from 'react-redux'
2
3
  import configs from 'configs'
3
4
  import AppBar from 'material-ui/AppBar'
4
5
  import Chip from 'material-ui/Chip'
5
6
  import { Link } from 'react-router'
6
7
 
8
+ import IconButton from 'material-ui/IconButton'
9
+ import IconMenu from 'material-ui/IconMenu'
10
+ import MenuItem from 'material-ui/MenuItem'
11
+ import MoreVertIcon from 'material-ui/svg-icons/navigation/more-vert'
12
+ import ContentSend from 'material-ui/svg-icons/content/send'
13
+ import ActionAssignment from 'material-ui/svg-icons/action/assignment'
14
+ import { navigateTo } from 'actions/navigation'
15
+
7
16
  const DEFAULT_TITLE = 'Phobos Checkpoint'
8
17
 
9
18
  const style = {
@@ -20,14 +29,24 @@ const style = {
20
29
  }
21
30
  }
22
31
 
23
- export default class extends Component {
32
+ export class Header extends Component {
24
33
  render () {
25
34
  return (
26
35
  <AppBar
27
36
  title={this.logo()}
28
37
  showMenuIconButton={false}
29
38
  style={style.bar}
30
- titleStyle={style.title}/>
39
+ titleStyle={style.title}
40
+ iconElementRight={
41
+ <IconMenu
42
+ iconButtonElement={<IconButton><MoreVertIcon /></IconButton>}
43
+ targetOrigin={{horizontal: 'right', vertical: 'top'}}
44
+ anchorOrigin={{horizontal: 'right', vertical: 'top'}}>
45
+ <MenuItem primaryText='Events' leftIcon={<ContentSend />} onTouchTap={() => this.props.navigateTo('/events')} />
46
+ <MenuItem primaryText='Errors' leftIcon={<ActionAssignment />} onTouchTap={() => this.props.navigateTo('/failures')} />
47
+ </IconMenu>
48
+ }
49
+ />
31
50
  )
32
51
  }
33
52
 
@@ -42,3 +61,18 @@ export default class extends Component {
42
61
  )
43
62
  }
44
63
  }
64
+
65
+ export const mapStateToProps = (state, ownProps) => {
66
+ return {
67
+ ...state
68
+ }
69
+ }
70
+
71
+ const actionsToConnect = {
72
+ navigateTo
73
+ }
74
+
75
+ export default connect(
76
+ mapStateToProps,
77
+ actionsToConnect
78
+ )(Header)
@@ -0,0 +1,22 @@
1
+ import React, { Component, PropTypes } from 'react'
2
+ import { EVENTS_SEARCH_LIMIT } from 'api'
3
+ import RaisedButton from 'material-ui/RaisedButton'
4
+
5
+ export default class LoadMore extends Component {
6
+ static get propTypes () {
7
+ return {
8
+ loadMoreSearchResults: PropTypes.func.isRequired,
9
+ xhrStatus: PropTypes.shape({ lastEventsLoadSize: PropTypes.number }).isRequired
10
+ }
11
+ }
12
+
13
+ render () {
14
+ return (
15
+ this.props.xhrStatus.lastEventsLoadSize === EVENTS_SEARCH_LIMIT &&
16
+ <RaisedButton
17
+ label='Load more'
18
+ onClick={() => this.props.loadMoreSearchResults()}
19
+ style={{margin: '10px'}}/>
20
+ )
21
+ }
22
+ }
@@ -0,0 +1,54 @@
1
+ import React from 'react'
2
+ import jasmineEnzyme from 'jasmine-enzyme'
3
+ import { shallow } from 'enzyme'
4
+
5
+ import LoadMore from 'components/load-more'
6
+ import { EVENTS_SEARCH_LIMIT } from 'api'
7
+
8
+ import RaisedButton from 'material-ui/RaisedButton'
9
+
10
+ describe('<LoadMore />', () => {
11
+ let props, wrapper
12
+
13
+ beforeEach(() => {
14
+ jasmineEnzyme()
15
+ props = {
16
+ loadMoreSearchResults: jasmine.createSpy('loadMoreSearchResults'),
17
+ xhrStatus: {
18
+ lastEventsLoadSize: 1
19
+ }
20
+ }
21
+ })
22
+
23
+ describe('when events loaded equals event search limit', () => {
24
+ beforeEach(() => {
25
+ props = {
26
+ ...props,
27
+ xhrStatus: {
28
+ lastEventsLoadSize: EVENTS_SEARCH_LIMIT
29
+ }
30
+ }
31
+ wrapper = shallow(<LoadMore {...props} />)
32
+ })
33
+
34
+ it('renders the load more button', () => {
35
+ expect(wrapper.find(RaisedButton).length).toEqual(1)
36
+ })
37
+ })
38
+
39
+ describe('when events loaded are less than event search limit', () => {
40
+ beforeEach(() => {
41
+ props = {
42
+ ...props,
43
+ xhrStatus: {
44
+ lastEventsLoadSize: EVENTS_SEARCH_LIMIT - 1
45
+ }
46
+ }
47
+ wrapper = shallow(<LoadMore {...props} />)
48
+ })
49
+
50
+ it('does not render the load more button', () => {
51
+ expect(wrapper.find(RaisedButton).length).toEqual(0)
52
+ })
53
+ })
54
+ })
@@ -6,8 +6,6 @@ import {
6
6
  changeSearchInputFilterValue
7
7
  } from 'actions/search-input-filter'
8
8
 
9
- import { triggerSearch } from 'actions/events-search'
10
-
11
9
  import SelectField from 'material-ui/SelectField'
12
10
  import MenuItem from 'material-ui/MenuItem'
13
11
  import TextField from 'material-ui/TextField'
@@ -28,11 +26,11 @@ const FILTER_TYPES = [
28
26
  export class SearchInput extends Component {
29
27
  static get propTypes () {
30
28
  return {
31
- onSearch: PropTypes.func,
32
29
  onChangeFilterType: PropTypes.func,
33
30
  onChangeFilterValue: PropTypes.func,
34
31
  isFetchingEvents: PropTypes.bool,
35
32
 
33
+ triggerSearch: PropTypes.func.isRequired,
36
34
  filterType: PropTypes.string,
37
35
  filterValue: PropTypes.string
38
36
  }
@@ -80,7 +78,7 @@ export class SearchInput extends Component {
80
78
 
81
79
  search (e) {
82
80
  if (e && e.keyCode === ENTER_KEY || !e) {
83
- this.props.onSearch()
81
+ this.props.triggerSearch()
84
82
  }
85
83
  }
86
84
 
@@ -98,7 +96,6 @@ const mapStateToProps = (state, ownProps) => (
98
96
  )
99
97
 
100
98
  export default connect(mapStateToProps, {
101
- onSearch: triggerSearch,
102
99
  onChangeFilterType: changeSearchInputFilterType,
103
100
  onChangeFilterValue: changeSearchInputFilterValue
104
101
  })(SearchInput)