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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -1
- data/frontend/.gitignore +4 -0
- data/frontend/src/actions/events-search.js +11 -11
- data/frontend/src/actions/events-search.spec.js +21 -21
- data/frontend/src/actions/failures/details.js +42 -0
- data/frontend/src/actions/failures/details.spec.js +71 -0
- data/frontend/src/actions/failures/overview.js +14 -0
- data/frontend/src/actions/failures/overview.spec.js +20 -0
- data/frontend/src/actions/failures/retry.js +58 -0
- data/frontend/src/actions/failures/retry.spec.js +117 -0
- data/frontend/src/actions/failures/search/index.js +76 -0
- data/frontend/src/actions/failures/search/index.spec.js +197 -0
- data/frontend/src/actions/index.js +24 -5
- data/frontend/src/actions/navigation/index.js +5 -0
- data/frontend/src/actions/navigation/index.spec.js +21 -0
- data/frontend/src/api.js +11 -0
- data/frontend/src/components/{event-overview/attribute.js → attribute/index.js} +0 -0
- data/frontend/src/components/empty-event/index.js +20 -0
- data/frontend/src/components/empty-event/index.spec.js +38 -0
- data/frontend/src/components/event-overview/index.js +1 -1
- data/frontend/src/components/event-retry-dialog/index.js +1 -1
- data/frontend/src/components/failure/error-message.js +19 -0
- data/frontend/src/components/failure/error-message.scss +8 -0
- data/frontend/src/components/failure/event.scss +3 -0
- data/frontend/src/components/failure/index.js +82 -0
- data/frontend/src/components/failure/index.spec.js +89 -0
- data/frontend/src/components/failure/loading.js +16 -0
- data/frontend/src/components/failure/overview/failure-overview.scss +28 -0
- data/frontend/src/components/failure/overview/index.js +60 -0
- data/frontend/src/components/failure/overview/index.spec.js +71 -0
- data/frontend/src/components/failure/overview-dialog/event-overview-dialog.scss +15 -0
- data/frontend/src/components/failure/overview-dialog/index.js +90 -0
- data/frontend/src/components/failure/retry-dialog/index.js +72 -0
- data/frontend/src/components/failure/style.js +16 -0
- data/frontend/src/components/failures-list/failures-list.scss +49 -0
- data/frontend/src/components/failures-list/index.js +62 -0
- data/frontend/src/components/failures-list/index.spec.js +59 -0
- data/frontend/src/components/header/index.js +36 -2
- data/frontend/src/components/load-more/index.js +22 -0
- data/frontend/src/components/load-more/index.spec.js +54 -0
- data/frontend/src/components/search-input/index.js +2 -5
- data/frontend/src/components/search-input/index.spec.js +6 -6
- data/frontend/src/reducers/events.js +2 -2
- data/frontend/src/reducers/events.spec.js +4 -4
- data/frontend/src/reducers/failures/details/index.js +32 -0
- data/frontend/src/reducers/failures/details/index.spec.js +54 -0
- data/frontend/src/reducers/failures/index.js +48 -0
- data/frontend/src/reducers/failures/index.spec.js +95 -0
- data/frontend/src/reducers/index.js +4 -1
- data/frontend/src/reducers/index.spec.js +2 -0
- data/frontend/src/reducers/xhr-status.js +25 -10
- data/frontend/src/reducers/xhr-status.spec.js +65 -15
- data/frontend/src/routes.js +6 -2
- data/frontend/src/store.js +7 -3
- data/frontend/src/views/{event-details.js → events/details/index.js} +0 -0
- data/frontend/src/views/{events-search.js → events/search/index.js} +37 -44
- data/frontend/src/views/{events-search.scss → events/search/index.scss} +0 -0
- data/frontend/src/views/{events-search.spec.js → events/search/index.spec.js} +35 -25
- data/frontend/src/views/failures/details/index.js +50 -0
- data/frontend/src/views/failures/search/index.js +113 -0
- data/frontend/src/views/failures/search/index.scss +24 -0
- data/frontend/src/views/failures/search/index.spec.js +106 -0
- data/lib/phobos_checkpoint_ui/version.rb +1 -1
- data/phobos_checkpoint_ui.gemspec +1 -1
- 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,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
|
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.
|
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)
|