phobos_checkpoint_ui 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|