phobos_checkpoint_ui 0.4.0 → 1.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
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)