phobos_checkpoint_ui 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/.rspec +2 -0
- data/CHANGELOG.md +9 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +81 -0
- data/Rakefile +12 -0
- data/assets/index-52cbf3063583f3c09a4b-0.css +2 -0
- data/assets/index-52cbf3063583f3c09a4b-0.css.map +1 -0
- data/assets/index-52cbf3063583f3c09a4b-1.css +2 -0
- data/assets/index-52cbf3063583f3c09a4b-1.css.map +1 -0
- data/assets/index-52cbf3063583f3c09a4b.js +49 -0
- data/assets/index-52cbf3063583f3c09a4b.js.map +1 -0
- data/assets/index.html +12 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/circle.yml +29 -0
- data/frontend/.babelrc +3 -0
- data/frontend/.editorconfig +8 -0
- data/frontend/.eslintignore +2 -0
- data/frontend/.eslintrc +3 -0
- data/frontend/.npmignore +8 -0
- data/frontend/package.json +45 -0
- data/frontend/sagui.config.js +52 -0
- data/frontend/src/actions/event-details.js +42 -0
- data/frontend/src/actions/event-details.spec.js +71 -0
- data/frontend/src/actions/event-overview.js +11 -0
- data/frontend/src/actions/event-overview.spec.js +20 -0
- data/frontend/src/actions/event-retry.js +58 -0
- data/frontend/src/actions/event-retry.spec.js +117 -0
- data/frontend/src/actions/events-search.js +75 -0
- data/frontend/src/actions/events-search.spec.js +197 -0
- data/frontend/src/actions/flash-messages.js +16 -0
- data/frontend/src/actions/flash-messages.spec.js +23 -0
- data/frontend/src/actions/index.js +24 -0
- data/frontend/src/actions/search-input-filter.js +14 -0
- data/frontend/src/actions/search-input-filter.spec.js +20 -0
- data/frontend/src/api.js +46 -0
- data/frontend/src/components/event/error-message.js +19 -0
- data/frontend/src/components/event/error-message.scss +8 -0
- data/frontend/src/components/event/event.scss +3 -0
- data/frontend/src/components/event/index.js +77 -0
- data/frontend/src/components/event/index.spec.js +89 -0
- data/frontend/src/components/event/loading.js +16 -0
- data/frontend/src/components/event/style.js +16 -0
- data/frontend/src/components/event-overview/attribute.js +24 -0
- data/frontend/src/components/event-overview/event-overview.scss +28 -0
- data/frontend/src/components/event-overview/index.js +47 -0
- data/frontend/src/components/event-overview/index.spec.js +67 -0
- data/frontend/src/components/event-overview-dialog/event-overview-dialog.scss +15 -0
- data/frontend/src/components/event-overview-dialog/index.js +85 -0
- data/frontend/src/components/event-retry-dialog/index.js +72 -0
- data/frontend/src/components/events-list/events-list.scss +49 -0
- data/frontend/src/components/events-list/index.js +62 -0
- data/frontend/src/components/events-list/index.spec.js +59 -0
- data/frontend/src/components/flash-message/flash-message.scss +40 -0
- data/frontend/src/components/flash-message/index.js +45 -0
- data/frontend/src/components/flash-message/index.spec.js +59 -0
- data/frontend/src/components/flash-message-list/index.js +34 -0
- data/frontend/src/components/header/header.scss +29 -0
- data/frontend/src/components/header/index.js +44 -0
- data/frontend/src/components/search-input/index.js +104 -0
- data/frontend/src/components/search-input/index.spec.js +56 -0
- data/frontend/src/components/search-input/search-input.scss +7 -0
- data/frontend/src/configs.js +15 -0
- data/frontend/src/helpers.spec.js +2 -0
- data/frontend/src/index.html +12 -0
- data/frontend/src/index.js +26 -0
- data/frontend/src/index.scss +31 -0
- data/frontend/src/reducers/event-details.js +32 -0
- data/frontend/src/reducers/event-details.spec.js +54 -0
- data/frontend/src/reducers/events-filters.js +17 -0
- data/frontend/src/reducers/events-filters.spec.js +34 -0
- data/frontend/src/reducers/events.js +48 -0
- data/frontend/src/reducers/events.spec.js +95 -0
- data/frontend/src/reducers/flash-messages.js +14 -0
- data/frontend/src/reducers/flash-messages.spec.js +34 -0
- data/frontend/src/reducers/index.js +18 -0
- data/frontend/src/reducers/index.spec.js +20 -0
- data/frontend/src/reducers/xhr-status.js +75 -0
- data/frontend/src/reducers/xhr-status.spec.js +94 -0
- data/frontend/src/routes.js +20 -0
- data/frontend/src/store.js +15 -0
- data/frontend/src/views/event-details.js +50 -0
- data/frontend/src/views/events-search.js +112 -0
- data/frontend/src/views/events-search.scss +24 -0
- data/frontend/src/views/events-search.spec.js +96 -0
- data/frontend/src/views/layout.js +24 -0
- data/frontend/src/views/layout.scss +3 -0
- data/lib/phobos_checkpoint_ui/app.rb +11 -0
- data/lib/phobos_checkpoint_ui/static_app.rb +19 -0
- data/lib/phobos_checkpoint_ui/tasks.rb +20 -0
- data/lib/phobos_checkpoint_ui/version.rb +3 -0
- data/lib/phobos_checkpoint_ui.rb +10 -0
- data/phobos_checkpoint_ui.gemspec +53 -0
- data/screenshot1.png +0 -0
- data/screenshot2.png +0 -0
- metadata +267 -0
@@ -0,0 +1,75 @@
|
|
1
|
+
import {
|
2
|
+
REQUEST_SEARCH_RESULTS,
|
3
|
+
RECEIVE_SEARCH_RESULTS,
|
4
|
+
REQUEST_SEARCH_RESULTS_FAILED,
|
5
|
+
LOAD_MORE_SEARCH_RESULTS,
|
6
|
+
TRIGGER_SEARCH,
|
7
|
+
REQUEST_EVENT_RETRY,
|
8
|
+
RECEIVE_EVENT_RETRY,
|
9
|
+
REQUEST_EVENT_RETRY_FAILED,
|
10
|
+
REQUEST_EVENT_DETAILS,
|
11
|
+
RECEIVE_EVENT_DETAILS
|
12
|
+
} from 'actions'
|
13
|
+
|
14
|
+
const initialState = {
|
15
|
+
isFetchingEvents: false,
|
16
|
+
isRetryingEvent: false,
|
17
|
+
isFetchingEventDetails: false,
|
18
|
+
currentEventsOffset: 0,
|
19
|
+
lastEventsLoadSize: 0
|
20
|
+
}
|
21
|
+
|
22
|
+
export default (state = initialState, action) => {
|
23
|
+
switch (action.type) {
|
24
|
+
case TRIGGER_SEARCH:
|
25
|
+
return Object.assign({}, state, {
|
26
|
+
currentEventsOffset: 0,
|
27
|
+
lastEventsLoadSize: 0
|
28
|
+
})
|
29
|
+
|
30
|
+
case REQUEST_SEARCH_RESULTS:
|
31
|
+
return Object.assign({}, state, {
|
32
|
+
isFetchingEvents: true
|
33
|
+
})
|
34
|
+
|
35
|
+
case RECEIVE_SEARCH_RESULTS:
|
36
|
+
return Object.assign({}, state, {
|
37
|
+
isFetchingEvents: false,
|
38
|
+
lastEventsLoadSize: action.events.length
|
39
|
+
})
|
40
|
+
|
41
|
+
case REQUEST_SEARCH_RESULTS_FAILED:
|
42
|
+
return Object.assign({}, state, {
|
43
|
+
isFetchingEvents: false
|
44
|
+
})
|
45
|
+
|
46
|
+
case LOAD_MORE_SEARCH_RESULTS:
|
47
|
+
return Object.assign({}, state, {
|
48
|
+
currentEventsOffset: action.offset
|
49
|
+
})
|
50
|
+
|
51
|
+
case REQUEST_EVENT_RETRY:
|
52
|
+
return Object.assign({}, state, {
|
53
|
+
isRetryingEvent: true
|
54
|
+
})
|
55
|
+
|
56
|
+
case RECEIVE_EVENT_RETRY:
|
57
|
+
case REQUEST_EVENT_RETRY_FAILED:
|
58
|
+
return Object.assign({}, state, {
|
59
|
+
isRetryingEvent: false
|
60
|
+
})
|
61
|
+
|
62
|
+
case REQUEST_EVENT_DETAILS:
|
63
|
+
return Object.assign({}, state, {
|
64
|
+
isFetchingEventDetails: true
|
65
|
+
})
|
66
|
+
|
67
|
+
case RECEIVE_EVENT_DETAILS:
|
68
|
+
return Object.assign({}, state, {
|
69
|
+
isFetchingEventDetails: false
|
70
|
+
})
|
71
|
+
|
72
|
+
default:
|
73
|
+
return state
|
74
|
+
}
|
75
|
+
}
|
@@ -0,0 +1,94 @@
|
|
1
|
+
import {
|
2
|
+
TRIGGER_SEARCH,
|
3
|
+
REQUEST_SEARCH_RESULTS,
|
4
|
+
RECEIVE_SEARCH_RESULTS,
|
5
|
+
REQUEST_SEARCH_RESULTS_FAILED,
|
6
|
+
LOAD_MORE_SEARCH_RESULTS,
|
7
|
+
REQUEST_EVENT_RETRY,
|
8
|
+
RECEIVE_EVENT_RETRY,
|
9
|
+
REQUEST_EVENT_RETRY_FAILED
|
10
|
+
} from 'actions'
|
11
|
+
|
12
|
+
import reducer from 'reducers/xhr-status'
|
13
|
+
|
14
|
+
describe('reducers/xhr-status', () => {
|
15
|
+
describe('for TRIGGER_SEARCH', () => {
|
16
|
+
it('resets currentEventsOffset and lastEventsLoadSize', () => {
|
17
|
+
const currentState = { currentEventsOffset: 5, lastEventsLoadSize: 10 }
|
18
|
+
const action = { type: TRIGGER_SEARCH }
|
19
|
+
const expectedState = { currentEventsOffset: 0, lastEventsLoadSize: 0 }
|
20
|
+
expect(reducer(currentState, action)).toEqual(expectedState)
|
21
|
+
})
|
22
|
+
})
|
23
|
+
|
24
|
+
describe('for REQUEST_SEARCH_RESULTS', () => {
|
25
|
+
it('enables isFetchingEvents', () => {
|
26
|
+
const currentState = { isFetchingEvents: false }
|
27
|
+
const action = { type: REQUEST_SEARCH_RESULTS }
|
28
|
+
const expectedState = { isFetchingEvents: true }
|
29
|
+
expect(reducer(currentState, action)).toEqual(expectedState)
|
30
|
+
})
|
31
|
+
})
|
32
|
+
|
33
|
+
describe('for RECEIVE_SEARCH_RESULTS', () => {
|
34
|
+
it('disables isFetchingEvents and keep the load size', () => {
|
35
|
+
const currentState = { isFetchingEvents: true }
|
36
|
+
const action = { type: RECEIVE_SEARCH_RESULTS, events: ['A', 'B', 'C'] }
|
37
|
+
const expectedState = { isFetchingEvents: false, lastEventsLoadSize: 3 }
|
38
|
+
expect(reducer(currentState, action)).toEqual(expectedState)
|
39
|
+
})
|
40
|
+
})
|
41
|
+
|
42
|
+
describe('for REQUEST_SEARCH_RESULTS_FAILED', () => {
|
43
|
+
it('disables isFetchingEvents', () => {
|
44
|
+
const currentState = { isFetchingEvents: true }
|
45
|
+
const action = { type: REQUEST_SEARCH_RESULTS_FAILED }
|
46
|
+
const expectedState = { isFetchingEvents: false }
|
47
|
+
expect(reducer(currentState, action)).toEqual(expectedState)
|
48
|
+
})
|
49
|
+
})
|
50
|
+
|
51
|
+
describe('for LOAD_MORE_SEARCH_RESULTS', () => {
|
52
|
+
it('updates currentEventsOffset', () => {
|
53
|
+
const currentState = { currentEventsOffset: 0 }
|
54
|
+
const action = { type: LOAD_MORE_SEARCH_RESULTS, offset: 3 }
|
55
|
+
const expectedState = { currentEventsOffset: 3 }
|
56
|
+
expect(reducer(currentState, action)).toEqual(expectedState)
|
57
|
+
})
|
58
|
+
})
|
59
|
+
|
60
|
+
describe('for REQUEST_EVENT_RETRY', () => {
|
61
|
+
it('enables isRetryingEvent', () => {
|
62
|
+
const currentState = { isRetryingEvent: false }
|
63
|
+
const action = { type: REQUEST_EVENT_RETRY }
|
64
|
+
const expectedState = { isRetryingEvent: true }
|
65
|
+
expect(reducer(currentState, action)).toEqual(expectedState)
|
66
|
+
})
|
67
|
+
})
|
68
|
+
|
69
|
+
describe('for RECEIVE_EVENT_RETRY', () => {
|
70
|
+
it('disables isRetryingEvent', () => {
|
71
|
+
const currentState = { isRetryingEvent: true }
|
72
|
+
const action = { type: RECEIVE_EVENT_RETRY }
|
73
|
+
const expectedState = { isRetryingEvent: false }
|
74
|
+
expect(reducer(currentState, action)).toEqual(expectedState)
|
75
|
+
})
|
76
|
+
})
|
77
|
+
|
78
|
+
describe('for REQUEST_EVENT_RETRY_FAILED', () => {
|
79
|
+
it('disables isRetryingEvent', () => {
|
80
|
+
const currentState = { isRetryingEvent: true }
|
81
|
+
const action = { type: REQUEST_EVENT_RETRY_FAILED }
|
82
|
+
const expectedState = { isRetryingEvent: false }
|
83
|
+
expect(reducer(currentState, action)).toEqual(expectedState)
|
84
|
+
})
|
85
|
+
})
|
86
|
+
|
87
|
+
describe('for default', () => {
|
88
|
+
it('returns the currentState', () => {
|
89
|
+
const currentState = { current: true }
|
90
|
+
const action = { type: 'another' }
|
91
|
+
expect(reducer(currentState, action)).toEqual(currentState)
|
92
|
+
})
|
93
|
+
})
|
94
|
+
})
|
@@ -0,0 +1,20 @@
|
|
1
|
+
import React from 'react'
|
2
|
+
import { Router, Route, IndexRedirect, browserHistory } from 'react-router'
|
3
|
+
import { syncHistoryWithStore } from 'react-router-redux'
|
4
|
+
|
5
|
+
import store from 'store'
|
6
|
+
import Layout from 'views/layout'
|
7
|
+
import EventsSearch from 'views/events-search'
|
8
|
+
import EventDetails from 'views/event-details'
|
9
|
+
|
10
|
+
export const history = syncHistoryWithStore(browserHistory, store)
|
11
|
+
|
12
|
+
export default (
|
13
|
+
<Router history={history}>
|
14
|
+
<Route path='/' component={Layout}>
|
15
|
+
<IndexRedirect to='/events' />
|
16
|
+
<Route path='/events' component={EventsSearch} />
|
17
|
+
<Route path='/events/:id' component={EventDetails} />
|
18
|
+
</Route>
|
19
|
+
</Router>
|
20
|
+
)
|
@@ -0,0 +1,15 @@
|
|
1
|
+
import { createStore, applyMiddleware, compose } from 'redux'
|
2
|
+
import thunkMiddleware from 'redux-thunk'
|
3
|
+
|
4
|
+
import reducer from 'reducers'
|
5
|
+
|
6
|
+
const store = createStore(
|
7
|
+
reducer,
|
8
|
+
{}, // initial state
|
9
|
+
compose(
|
10
|
+
applyMiddleware(thunkMiddleware), // for async actions
|
11
|
+
window.devToolsExtension ? window.devToolsExtension() : (f) => f
|
12
|
+
)
|
13
|
+
)
|
14
|
+
|
15
|
+
export default store
|
@@ -0,0 +1,50 @@
|
|
1
|
+
import React, { Component, PropTypes } from 'react'
|
2
|
+
import { connect } from 'react-redux'
|
3
|
+
import { history } from 'routes'
|
4
|
+
|
5
|
+
import { fetchEventDetails } from 'actions/event-details'
|
6
|
+
import EventOverviewDialog from 'components/event-overview-dialog'
|
7
|
+
import EventRetryDialog from 'components/event-retry-dialog'
|
8
|
+
|
9
|
+
class EventDetails extends Component {
|
10
|
+
static get propTypes () {
|
11
|
+
return {
|
12
|
+
fetchEventDetails: PropTypes.func.isRequired
|
13
|
+
}
|
14
|
+
}
|
15
|
+
|
16
|
+
componentDidMount () {
|
17
|
+
this.props.params.id &&
|
18
|
+
this.props.fetchEventDetails({ id: this.props.params.id })
|
19
|
+
}
|
20
|
+
|
21
|
+
componentDidUpdate (prevProps) {
|
22
|
+
if (this.isDialogClosed(prevProps)) {
|
23
|
+
history.push('/events')
|
24
|
+
}
|
25
|
+
}
|
26
|
+
|
27
|
+
render () {
|
28
|
+
const details = this.props.eventDetails
|
29
|
+
const visible = !!details.id
|
30
|
+
const event = Object.assign({ overviewVisible: visible }, details)
|
31
|
+
|
32
|
+
return (
|
33
|
+
<div className='event-details'>
|
34
|
+
<EventOverviewDialog event={event} />
|
35
|
+
<EventRetryDialog event={event} />
|
36
|
+
</div>
|
37
|
+
)
|
38
|
+
}
|
39
|
+
|
40
|
+
isDialogClosed (prevProps) {
|
41
|
+
return prevProps.eventDetails.id !== undefined &&
|
42
|
+
this.props.eventDetails.id === undefined
|
43
|
+
}
|
44
|
+
}
|
45
|
+
|
46
|
+
export default connect(
|
47
|
+
(state) => state, {
|
48
|
+
fetchEventDetails
|
49
|
+
}
|
50
|
+
)(EventDetails)
|
@@ -0,0 +1,112 @@
|
|
1
|
+
import React, { Component, PropTypes } from 'react'
|
2
|
+
import { connect } from 'react-redux'
|
3
|
+
import { EVENTS_SEARCH_LIMIT } from 'api'
|
4
|
+
|
5
|
+
import EventsList from 'components/events-list'
|
6
|
+
import SearchInput from 'components/search-input'
|
7
|
+
import CircularProgress from 'material-ui/CircularProgress'
|
8
|
+
import RaisedButton from 'material-ui/RaisedButton'
|
9
|
+
|
10
|
+
import { fetchSearchResults, loadMoreSearchResults } from 'actions/events-search'
|
11
|
+
import { changeSearchInputFilterType, changeSearchInputFilterValue } from 'actions/search-input-filter'
|
12
|
+
import { showEventOverview } from 'actions/event-overview'
|
13
|
+
|
14
|
+
export class EventsSearch extends Component {
|
15
|
+
static get propTypes () {
|
16
|
+
return {
|
17
|
+
fetchSearchResults: PropTypes.func.isRequired,
|
18
|
+
loadMoreSearchResults: PropTypes.func.isRequired,
|
19
|
+
changeSearchInputFilterType: PropTypes.func.isRequired,
|
20
|
+
changeSearchInputFilterValue: PropTypes.func.isRequired,
|
21
|
+
showEventOverview: PropTypes.func.isRequired
|
22
|
+
}
|
23
|
+
}
|
24
|
+
|
25
|
+
componentDidMount () {
|
26
|
+
const { type, value } = this.props.location.query
|
27
|
+
if (type && value) {
|
28
|
+
this.props.changeSearchInputFilterType(type)
|
29
|
+
this.props.changeSearchInputFilterValue(value)
|
30
|
+
}
|
31
|
+
|
32
|
+
this.props.events.length === 0 &&
|
33
|
+
this.props.fetchSearchResults({ offset: 0 })
|
34
|
+
}
|
35
|
+
|
36
|
+
render () {
|
37
|
+
const { events } = this.props
|
38
|
+
const { type, value } = this.props.eventsFilters
|
39
|
+
|
40
|
+
return (
|
41
|
+
<div className='events-search'>
|
42
|
+
<SearchInput filterType={type} filterValue={value}/>
|
43
|
+
<div>
|
44
|
+
<EventsList events={events} />
|
45
|
+
<LoadMore {...this.props} />
|
46
|
+
<EmptyEvent
|
47
|
+
events={events}
|
48
|
+
isFetchingEvents={this.props.xhrStatus.isFetchingEvents}/>
|
49
|
+
</div>
|
50
|
+
{
|
51
|
+
this.isFetchingFirstPage() &&
|
52
|
+
<div className='page-loader'>
|
53
|
+
<CircularProgress />
|
54
|
+
</div>
|
55
|
+
}
|
56
|
+
</div>
|
57
|
+
)
|
58
|
+
}
|
59
|
+
|
60
|
+
isFetchingFirstPage () {
|
61
|
+
return this.props.xhrStatus.isFetchingEvents &&
|
62
|
+
this.props.events.length === 0
|
63
|
+
}
|
64
|
+
}
|
65
|
+
|
66
|
+
class LoadMore extends Component {
|
67
|
+
static get propTypes () {
|
68
|
+
return {
|
69
|
+
loadMoreSearchResults: PropTypes.func.isRequired,
|
70
|
+
xhrStatus: PropTypes.shape({ lastEventsLoadSize: PropTypes.number }).isRequired
|
71
|
+
}
|
72
|
+
}
|
73
|
+
|
74
|
+
render () {
|
75
|
+
return (
|
76
|
+
this.props.xhrStatus.lastEventsLoadSize === EVENTS_SEARCH_LIMIT &&
|
77
|
+
<RaisedButton
|
78
|
+
label='Load more'
|
79
|
+
onClick={() => this.props.loadMoreSearchResults()}
|
80
|
+
style={{margin: '10px'}}/>
|
81
|
+
)
|
82
|
+
}
|
83
|
+
}
|
84
|
+
|
85
|
+
class EmptyEvent extends Component {
|
86
|
+
static get propTypes () {
|
87
|
+
return {
|
88
|
+
events: PropTypes.array.isRequired,
|
89
|
+
isFetchingEvents: PropTypes.bool.isRequired
|
90
|
+
}
|
91
|
+
}
|
92
|
+
|
93
|
+
render () {
|
94
|
+
return (
|
95
|
+
this.props.events.length === 0 &&
|
96
|
+
!this.props.isFetchingEvents &&
|
97
|
+
<div className='empty-event'>
|
98
|
+
No events found
|
99
|
+
</div>
|
100
|
+
)
|
101
|
+
}
|
102
|
+
}
|
103
|
+
|
104
|
+
export default connect(
|
105
|
+
(state) => state, {
|
106
|
+
fetchSearchResults,
|
107
|
+
loadMoreSearchResults,
|
108
|
+
changeSearchInputFilterType,
|
109
|
+
changeSearchInputFilterValue,
|
110
|
+
showEventOverview
|
111
|
+
}
|
112
|
+
)(EventsSearch)
|
@@ -0,0 +1,24 @@
|
|
1
|
+
.events-search {
|
2
|
+
max-width: 1020px;
|
3
|
+
min-height: 100%;
|
4
|
+
|
5
|
+
background-color: #e4e4e4;
|
6
|
+
margin: 0 auto;
|
7
|
+
padding: 20px 0;
|
8
|
+
|
9
|
+
.page-loader {
|
10
|
+
text-align: center;
|
11
|
+
padding: 50px 0;
|
12
|
+
}
|
13
|
+
|
14
|
+
.empty-event {
|
15
|
+
margin: 50px 15px 0;
|
16
|
+
font-size: 30px;
|
17
|
+
font-weight: lighter;
|
18
|
+
font-family: Roboto;
|
19
|
+
}
|
20
|
+
|
21
|
+
& > .event {
|
22
|
+
margin: 10px 10px;
|
23
|
+
}
|
24
|
+
}
|
@@ -0,0 +1,96 @@
|
|
1
|
+
import React from 'react'
|
2
|
+
import jasmineEnzyme from 'jasmine-enzyme'
|
3
|
+
import { mount } from 'enzyme'
|
4
|
+
import { EventsSearch } from 'views/events-search'
|
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
|
+
<EventsSearch {...props} />
|
18
|
+
</MuiThemeProvider>
|
19
|
+
</Provider>
|
20
|
+
)
|
21
|
+
|
22
|
+
describe('view <EventsSearch />', () => {
|
23
|
+
let props, component, store
|
24
|
+
|
25
|
+
beforeEach(() => {
|
26
|
+
jasmineEnzyme()
|
27
|
+
store = mockStore({
|
28
|
+
xhrStatus: {
|
29
|
+
isRetryingEvent: false
|
30
|
+
}
|
31
|
+
})
|
32
|
+
|
33
|
+
props = {
|
34
|
+
fetchSearchResults: jasmine.createSpy('fetchSearchResults'),
|
35
|
+
loadMoreSearchResults: jasmine.createSpy('loadMoreSearchResults'),
|
36
|
+
changeSearchInputFilterType: jasmine.createSpy('changeSearchInputFilterType'),
|
37
|
+
changeSearchInputFilterValue: jasmine.createSpy('changeSearchInputFilterValue'),
|
38
|
+
showEventOverview: jasmine.createSpy('showEventOverview'),
|
39
|
+
|
40
|
+
xhrStatus: { isFetchingEvents: false, lastEventsLoadSize: 0 },
|
41
|
+
location: { query: {} },
|
42
|
+
eventsFilters: {},
|
43
|
+
events: [{ id: 1 }, { id: 2 }]
|
44
|
+
}
|
45
|
+
|
46
|
+
component = mountComponent(store, props)
|
47
|
+
})
|
48
|
+
|
49
|
+
it('renders <SearchInput />', () => {
|
50
|
+
expect(component.find('.search-input').length).toEqual(1)
|
51
|
+
})
|
52
|
+
|
53
|
+
it('renders <EventsList />', () => {
|
54
|
+
expect(component.find('.events-list').length).toEqual(1)
|
55
|
+
})
|
56
|
+
|
57
|
+
it('does not render <EventsSearch.EmptyEvent />', () => {
|
58
|
+
expect(component.find('.empty-event').length).toEqual(0)
|
59
|
+
})
|
60
|
+
|
61
|
+
describe('when it initializes with filter type and value', () => {
|
62
|
+
beforeEach(() => {
|
63
|
+
Object.assign(props, {
|
64
|
+
location: { query: {
|
65
|
+
type: 'entity_id',
|
66
|
+
value: '12345'
|
67
|
+
}}
|
68
|
+
})
|
69
|
+
|
70
|
+
component = mountComponent(store, props)
|
71
|
+
})
|
72
|
+
|
73
|
+
it('calls changeSearchInputFilterType with type', () => {
|
74
|
+
expect(props.changeSearchInputFilterType).toHaveBeenCalledWith('entity_id')
|
75
|
+
})
|
76
|
+
|
77
|
+
it('calls changeSearchInputFilterValue with value', () => {
|
78
|
+
expect(props.changeSearchInputFilterValue).toHaveBeenCalledWith('12345')
|
79
|
+
})
|
80
|
+
})
|
81
|
+
|
82
|
+
describe('when events is empty', () => {
|
83
|
+
beforeEach(() => {
|
84
|
+
Object.assign(props, { events: [] })
|
85
|
+
component = mountComponent(store, props)
|
86
|
+
})
|
87
|
+
|
88
|
+
it('calls fetchSearchResults with offset 0', () => {
|
89
|
+
expect(props.fetchSearchResults).toHaveBeenCalledWith({ offset: 0 })
|
90
|
+
})
|
91
|
+
|
92
|
+
it('renders <EventsSearch.EmptyEvent />', () => {
|
93
|
+
expect(component.find('.empty-event').length).toEqual(1)
|
94
|
+
})
|
95
|
+
})
|
96
|
+
})
|
@@ -0,0 +1,24 @@
|
|
1
|
+
import React, { Component } from 'react'
|
2
|
+
|
3
|
+
import lightBaseTheme from 'material-ui/styles/baseThemes/lightBaseTheme'
|
4
|
+
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'
|
5
|
+
import getMuiTheme from 'material-ui/styles/getMuiTheme'
|
6
|
+
|
7
|
+
import Header from 'components/header'
|
8
|
+
import FlashMessageList from 'components/flash-message-list'
|
9
|
+
|
10
|
+
const theme = getMuiTheme(lightBaseTheme)
|
11
|
+
|
12
|
+
export default class extends Component {
|
13
|
+
render () {
|
14
|
+
return (
|
15
|
+
<MuiThemeProvider muiTheme={theme}>
|
16
|
+
<div className='layout'>
|
17
|
+
<Header />
|
18
|
+
<FlashMessageList />
|
19
|
+
{React.cloneElement(this.props.children)}
|
20
|
+
</div>
|
21
|
+
</MuiThemeProvider>
|
22
|
+
)
|
23
|
+
}
|
24
|
+
}
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module PhobosCheckpointUI
|
2
|
+
class StaticApp < Sinatra::Base
|
3
|
+
class << self
|
4
|
+
attr_accessor :configs
|
5
|
+
end
|
6
|
+
|
7
|
+
set :logging, nil
|
8
|
+
set :public_folder, -> { File.join(Dir.pwd, 'public') }
|
9
|
+
|
10
|
+
get '/configs' do
|
11
|
+
content_type :json
|
12
|
+
self.class.configs.to_json
|
13
|
+
end
|
14
|
+
|
15
|
+
get '/*' do
|
16
|
+
send_file File.join(settings.public_folder, 'index.html')
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
module PhobosCheckpointUI
|
5
|
+
module Tasks
|
6
|
+
extend Rake::DSL if defined? Rake::DSL
|
7
|
+
|
8
|
+
namespace :phobos_checkpoint_ui do
|
9
|
+
desc 'Copy Assets to ./public folder. It creates the folder if it doesn\'t exist'
|
10
|
+
task :copy_assets do
|
11
|
+
folder = File.join(Dir.pwd, 'public')
|
12
|
+
assets = File.expand_path(File.join(File.dirname(__FILE__), '../../assets'))
|
13
|
+
FileUtils.rm_rf(folder)
|
14
|
+
FileUtils.mkdir_p(folder)
|
15
|
+
FileUtils.cp_r(assets, File.join(folder, 'assets'))
|
16
|
+
FileUtils.mv(File.join(folder, 'assets/index.html'), File.join(folder, 'index.html'))
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'phobos_checkpoint_ui/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'phobos_checkpoint_ui'
|
8
|
+
spec.version = PhobosCheckpointUI::VERSION
|
9
|
+
spec.authors = [
|
10
|
+
'Túlio Ornelas',
|
11
|
+
'Mathias Klippinge',
|
12
|
+
'Sergey Evstifeev',
|
13
|
+
'Thiago R. Colucci',
|
14
|
+
'Martin Svalin',
|
15
|
+
'Francisco Juan'
|
16
|
+
]
|
17
|
+
spec.email = [
|
18
|
+
'ornelas.tulio@gmail.com',
|
19
|
+
'mathias.klippinge@gmail.com',
|
20
|
+
'sergey.evstifeev@gmail.com',
|
21
|
+
'ticolucci@gmail.com',
|
22
|
+
'martin@lite.nu',
|
23
|
+
'francisco.juan@gmail.com'
|
24
|
+
]
|
25
|
+
|
26
|
+
spec.summary = 'Phobos Checkpoint UI is a GUI for phobos checkpoint API'
|
27
|
+
spec.description = 'Phobos Checkpoint UI is a GUI for phobos checkpoint API, it is compatible with https://github.com/klarna/phobos_db_checkpoint'
|
28
|
+
spec.homepage = 'https://github.com/klarna/phobos_checkpoint_ui'
|
29
|
+
spec.license = 'MIT'
|
30
|
+
|
31
|
+
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
32
|
+
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
33
|
+
if spec.respond_to?(:metadata)
|
34
|
+
spec.metadata['allowed_push_host'] = 'https://rubygems.org'
|
35
|
+
else
|
36
|
+
raise 'RubyGems 2.0 or newer is required to protect against public gem pushes.'
|
37
|
+
end
|
38
|
+
|
39
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
40
|
+
spec.bindir = 'bin'
|
41
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
42
|
+
spec.require_paths = ['lib']
|
43
|
+
|
44
|
+
spec.add_development_dependency 'bundler', '~> 1.12'
|
45
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
46
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
47
|
+
spec.add_development_dependency 'pry-byebug'
|
48
|
+
spec.add_development_dependency 'rack-test'
|
49
|
+
spec.add_development_dependency 'rspec_junit_formatter', '0.2.2'
|
50
|
+
|
51
|
+
spec.add_dependency 'rake'
|
52
|
+
spec.add_dependency 'sinatra'
|
53
|
+
end
|
data/screenshot1.png
ADDED
Binary file
|
data/screenshot2.png
ADDED
Binary file
|