Skip to content

Commit 7e0a3c4

Browse files
committed
Try to fetch remote guide if not found locally
1 parent b569e99 commit 7e0a3c4

File tree

8 files changed

+109
-31
lines changed

8 files changed

+109
-31
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@
4646
"afterEach",
4747
"neo",
4848
"FileReader",
49-
"Blob"
49+
"Blob",
50+
"fetch"
5051
]
5152
},
5253
"repository": {},

src/browser/modules/Stream/PlayFrame.jsx

Lines changed: 69 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -18,35 +18,80 @@
1818
* along with this program. If not, see <http://www.gnu.org/licenses/>.
1919
*/
2020

21+
import { Component } from 'preact'
22+
import { withBus } from 'preact-suber'
23+
import { connect } from 'preact-redux'
24+
import { getAutoResolveRemoteGuidesUrl } from 'shared/modules/settings/settingsDuck'
25+
import { fetchRemoteGuideAction } from 'shared/modules/commands/commandsDuck'
26+
2127
import Guides from '../Guides/Guides'
2228
import * as html from '../Guides/html'
2329
import FrameTemplate from './FrameTemplate'
30+
import ErrorsView from './Views/ErrorsView'
2431

25-
const PlayFrame = ({frame}) => {
26-
let guide = 'Play guide not specified'
27-
if (frame.result) {
28-
guide = <Guides withDirectives html={frame.result} />
29-
} else {
30-
const guideName = frame.cmd.replace(':play', '').replace(/\s|-/g, '').trim() || 'start'
31-
if (guideName !== '') {
32-
const content = html[guideName]
33-
if (content !== undefined) {
34-
guide = <Guides withDirectives html={content} />
35-
} else {
36-
if (frame.error && frame.error.error) {
37-
guide = frame.error.error
38-
} else {
39-
guide = <Guides withDirectives html={html['unfound']} />
32+
export class PlayFrame extends Component {
33+
constructor (props) {
34+
super(props)
35+
this.state = {
36+
guide: null
37+
}
38+
}
39+
componentDidMount () {
40+
if (this.props.frame.result) { // Found remote guide
41+
this.setState({ guide: <Guides withDirectives html={this.props.frame.result} /> })
42+
return
43+
}
44+
if (this.props.frame.response && this.props.frame.error && this.props.frame.error.error) { // Not found remotely (or other error)
45+
if (this.props.frame.response.status === 404) return this.setState({ guide: <Guides withDirectives html={html['unfound']} /> })
46+
return this.setState({
47+
guide: (
48+
<ErrorsView
49+
error={{
50+
message: 'Error: The remote server responded with the following error: ' + this.props.frame.response.status,
51+
code: 'Remote guide error'
52+
}}
53+
/>)
54+
})
55+
}
56+
if (this.props.frame.error && this.props.frame.error.error) { // Some other error. Whitelist error etc.
57+
return this.setState({ guide: <ErrorsView error={{
58+
message: this.props.frame.error.error,
59+
code: 'Remote guide error'
60+
}} /> })
61+
}
62+
const guideName = this.props.frame.cmd.replace(':play', '').replace(/\s|-/g, '').trim() || 'start'
63+
if (typeof html[guideName] !== 'undefined') { // Found it locally
64+
this.setState({ guide: <Guides withDirectives html={html[guideName]} /> })
65+
return
66+
}
67+
// Not found remotely or locally
68+
// Try to find it remotely by name
69+
if (this.props.bus) {
70+
const action = fetchRemoteGuideAction(this.props.remoteUrl + guideName)
71+
this.props.bus.self(action.type, action, (res) => {
72+
if (!res.success) { // No luck
73+
return this.setState({ guide: <Guides withDirectives html={html['unfound']} /> })
4074
}
41-
}
75+
this.setState({ guide: <Guides withDirectives html={res.result} /> })
76+
})
77+
} else { // No bus. Give up
78+
return this.setState({ guide: <Guides withDirectives html={html['unfound']} /> })
4279
}
4380
}
44-
return (
45-
<FrameTemplate
46-
className='playFrame'
47-
header={frame}
48-
contents={guide}
49-
/>
50-
)
81+
render () {
82+
return (
83+
<FrameTemplate
84+
className='playFrame'
85+
header={this.props.frame}
86+
contents={this.state.guide}
87+
/>
88+
)
89+
}
90+
}
91+
92+
const mapStateToProps = (state) => {
93+
return {
94+
remoteUrl: getAutoResolveRemoteGuidesUrl(state)
95+
}
5196
}
52-
export default PlayFrame
97+
export default withBus(connect(mapStateToProps)(PlayFrame))

src/shared/modules/commands/commandsDuck.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,13 @@
1818
* along with this program. If not, see <http://www.gnu.org/licenses/>.
1919
*/
2020

21+
import Rx from 'rxjs'
2122
import { getInterpreter, isNamedInterpreter, cleanCommand } from 'services/commandUtils'
2223
import { hydrate } from 'services/duckUtils'
2324
import helper from 'services/commandInterpreterHelper'
2425
import { addHistory } from '../history/historyDuck'
2526
import { getCmdChar, getMaxHistory } from '../settings/settingsDuck'
27+
import { fetchRemoteGuide } from './helpers/play'
2628
import { CONNECTION_SUCCESS } from '../connections/connectionsDuck'
2729
import { UPDATE_SETTINGS, getAvailableSettings, fetchMetaData } from '../dbMeta/dbMetaDuck'
2830
import { USER_CLEAR } from 'shared/modules/app/appDuck'
@@ -36,6 +38,7 @@ export const SHOW_ERROR_MESSAGE = NAME + '/SHOW_ERROR_MESSAGE'
3638
export const CYPHER = NAME + '/CYPHER'
3739
export const CYPHER_SUCCEEDED = NAME + '/CYPHER_SUCCEEDED'
3840
export const CYPHER_FAILED = NAME + '/CYPHER_FAILED'
41+
export const FETCH_REMOTE_GUIDE = NAME + '/FETCH_REMOTE_GUIDE'
3942

4043
const initialState = {
4144
lastCommandWasUnknown: false
@@ -91,6 +94,7 @@ export const showErrorMessage = (errorMessage) => ({
9194
export const cypher = (query) => ({ type: CYPHER, query })
9295
export const successfulCypher = (query) => ({ type: CYPHER_SUCCEEDED, query })
9396
export const unsuccessfulCypher = (query) => ({ type: CYPHER_FAILED, query })
97+
export const fetchRemoteGuideAction = (url) => ({ type: FETCH_REMOTE_GUIDE, url })
9498

9599
// Epics
96100
export const handleCommandsEpic = (action$, store) =>
@@ -138,3 +142,12 @@ export const postConnectCmdEpic = (some$, store) =>
138142
}
139143
})
140144
.mapTo({ type: 'NOOP' })
145+
146+
export const fetchRemoteGuideEpic = (some$, store) =>
147+
some$.ofType(FETCH_REMOTE_GUIDE)
148+
.mergeMap((action) => {
149+
if (!action.$$responseChannel || !action.url) return Rx.Observable.of({ type: 'NOOP' })
150+
return fetchRemoteGuide(action.url)
151+
.then((r) => ({type: action.$$responseChannel, success: true, result: r}))
152+
.catch((e) => ({type: action.$$responseChannel, success: false, error: e}))
153+
})

src/shared/modules/commands/helpers/play.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ export const fetchRemoteGuide = (url) => {
3131
if (!hostIsAllowed(url, whitelist)) {
3232
throw new Error('Hostname is not allowed according to server whitelist')
3333
}
34-
return remote.get(url).then((r) => cleanHtml(r))
34+
return remote.get(url).then((r) => {
35+
return cleanHtml(r)
36+
})
3537
})
3638
}

src/shared/modules/settings/settingsDuck.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ export const getMaxNeighbours = (state) => state[NAME].maxNeighbours || initialS
3838
export const getMaxRows = (state) => state[NAME].maxRows || initialState.maxRows
3939
export const getInitialNodeDisplay = (state) => state[NAME].initialNodeDisplay || initialState.initialNodeDisplay
4040
export const shouldReportUdc = (state) => state[NAME].shouldReportUdc !== false
41+
export const getAutoResolveRemoteGuidesUrl = (state) => state[NAME].autoResolveRemoteGuidesUrl || initialState.autoResolveRemoteGuidesUrl
4142

4243
const browserSyncConfig = {
4344
authWindowUrl: 'https://auth.neo4j.com/indexNewBrowser.html',
@@ -61,7 +62,8 @@ const initialState = {
6162
showSampleScripts: true,
6263
browserSyncDebugServer: null,
6364
maxRows: 1000,
64-
shouldReportUdc: true
65+
shouldReportUdc: true,
66+
autoResolveRemoteGuidesUrl: 'https://guides.neo4j.com/'
6567
}
6668

6769
export default function settings (state = initialState, action) {

src/shared/rootEpic.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
*/
2020

2121
import { combineEpics } from 'redux-observable'
22-
import { handleCommandsEpic, postConnectCmdEpic } from './modules/commands/commandsDuck'
22+
import { handleCommandsEpic, postConnectCmdEpic, fetchRemoteGuideEpic } from './modules/commands/commandsDuck'
2323
import { checkSettingsForRoutingDriver, connectEpic, disconnectEpic, startupConnectEpic, disconnectSuccessEpic, startupConnectionSuccessEpic, startupConnectionFailEpic, detectActiveConnectionChangeEpic, connectionLostEpic } from './modules/connections/connectionsDuck'
2424
import { dbMetaEpic, clearMetaOnDisconnectEpic } from './modules/dbMeta/dbMetaDuck'
2525
import { cancelRequestEpic } from './modules/requests/requestsDuck'
@@ -35,6 +35,7 @@ import { bootEpic, incrementEventEpic, udcStartupEpic, trackSyncLogoutEpic, trac
3535
export default combineEpics(
3636
handleCommandsEpic,
3737
postConnectCmdEpic,
38+
fetchRemoteGuideEpic,
3839
connectionLostEpic,
3940
checkSettingsForRoutingDriver,
4041
connectEpic,

src/shared/services/commandInterpreterHelper.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ const availableCommands = [{
121121
.then((r) => {
122122
put(frames.add({...action, type: 'play-remote', result: r}))
123123
}).catch((e) => {
124-
put(frames.add({...action, type: 'play-remote', error: CouldNotFetchRemoteGuideError(e.name + ': ' + e.message)}))
124+
put(frames.add({...action, type: 'play-remote', response: (e.response || null), error: CouldNotFetchRemoteGuideError(e.name + ': ' + e.message)}))
125125
})
126126
}
127127
}, {

src/shared/services/remote.js

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
* along with this program. If not, see <http://www.gnu.org/licenses/>.
1919
*/
2020

21-
import fetch from 'isomorphic-fetch'
21+
/* global fetch */
22+
import 'isomorphic-fetch'
2223

2324
function request (method, url, data = null) {
2425
return fetch(url, {
@@ -30,12 +31,15 @@ function request (method, url, data = null) {
3031
},
3132
body: data
3233
})
34+
.then(checkStatus)
3335
}
3436

3537
function get (url) {
3638
return fetch(url, {
3739
method: 'get'
38-
}).then(function (response) {
40+
})
41+
.then(checkStatus)
42+
.then(function (response) {
3943
return response.text()
4044
})
4145
}
@@ -53,6 +57,16 @@ function getJSON (url) {
5357
})
5458
}
5559

60+
function checkStatus (response) {
61+
if (response.status >= 200 && response.status < 300) {
62+
return response
63+
} else {
64+
var error = new Error(response.status + ' ' + response.statusText)
65+
error.response = response
66+
throw error
67+
}
68+
}
69+
5670
export default {
5771
get,
5872
getJSON,

0 commit comments

Comments
 (0)