chore: ServerRequest returns an explicit error if any

This commit is contained in:
Yohan Boniface 2024-01-24 13:27:33 +01:00
parent c50be398ab
commit b947134fa2
6 changed files with 172 additions and 125 deletions

View file

@ -17,15 +17,15 @@ const BaseRequest = Evented.extend({
} catch (error) { } catch (error) {
this._onError(error) this._onError(error)
this.fire('dataload', { id: id }) this.fire('dataload', { id: id })
return return null
} }
if (!response.ok) { if (!response.ok) {
this.onNok(response.status, await response.text()) this.fire('dataload', { id: id })
return this.onNok(response.status, response)
} }
// TODO // TODO
// - error handling // - error handling
// - UI connection / events // - UI connection / events
// - preflight mode in CORS ?
this.fire('dataload', { id: id }) this.fire('dataload', { id: id })
return response return response
@ -44,7 +44,9 @@ const BaseRequest = Evented.extend({
this.onError(error) this.onError(error)
}, },
onError: function (error) {}, onError: function (error) {},
onNok: function (status) {}, onNok: function (status, reponse) {
return response
},
}) })
export const Request = BaseRequest.extend({ export const Request = BaseRequest.extend({
@ -55,8 +57,9 @@ export const Request = BaseRequest.extend({
console.error(error) console.error(error)
this.ui.alert({ content: L._('Problem in the response'), level: 'error' }) this.ui.alert({ content: L._('Problem in the response'), level: 'error' })
}, },
onNok: function (status, message) { onNok: function (status, response) {
this.onError(message) this.onError(message)
return response
}, },
}) })
@ -81,26 +84,30 @@ export const ServerRequest = Request.extend({
headers['X-CSRFToken'] = token headers['X-CSRFToken'] = token
} }
const response = await Request.prototype.post.call(this, uri, headers, data) const response = await Request.prototype.post.call(this, uri, headers, data)
return await this._handle_json_response(response) return await this._as_json(response)
}, },
get: async function (uri, headers) { get: async function (uri, headers) {
const response = await Request.prototype.get.call(this, uri, headers) const response = await Request.prototype.get.call(this, uri, headers)
return await this._handle_json_response(response) return await this._as_json(response)
}, },
_handle_json_response: async function (response) { _as_json: async function (response) {
if (!response) return [{}, null, new Error("Undefined error")]
try { try {
const data = await response.json() const data = await response.json()
this._handle_server_instructions(data) if (this._handle_server_instructions(data) !== false) {
return [data, response] return [{}, null]
}
return [data, response, null]
} catch (error) { } catch (error) {
this._onError(error) this._onError(error)
return [{}, null, error]
} }
}, },
_handle_server_instructions: function (data) { _handle_server_instructions: function (data) {
// In some case, the response contains instructions // Generic cases, let's deal with them once
if (data.redirect) { if (data.redirect) {
const newPath = data.redirect const newPath = data.redirect
if (window.location.pathname == newPath) { if (window.location.pathname == newPath) {
@ -122,6 +129,9 @@ export const ServerRequest = Request.extend({
this.fire('login') this.fire('login')
win.close() win.close()
} }
} else {
// Nothing to do, we can let the response proceed
return false
} }
}, },

View file

@ -55,10 +55,16 @@ L.U.DataLayerPermissions = L.Class.extend({
if (!this.isDirty) return this.datalayer.map.continueSaving() if (!this.isDirty) return this.datalayer.map.continueSaving()
const formData = new FormData() const formData = new FormData()
formData.append('edit_status', this.options.edit_status) formData.append('edit_status', this.options.edit_status)
await this.datalayer.map.server.post(this.getUrl(), {}, formData) const [data, response, error] = await this.datalayer.map.server.post(
this.commit() this.getUrl(),
this.isDirty = false {},
this.datalayer.map.continueSaving() formData
)
if (!error) {
this.commit()
this.isDirty = false
this.datalayer.map.continueSaving()
}
}, },
commit: function () { commit: function () {

View file

@ -695,11 +695,13 @@ L.FormBuilder.IconUrl = L.FormBuilder.BlurInput.extend({
if (this.pictogram_list) { if (this.pictogram_list) {
this.buildSymbolsList() this.buildSymbolsList()
} else { } else {
const [{ pictogram_list }, response] = await this.builder.map.server.get( const [{ pictogram_list }, response, error] = await this.builder.map.server.get(
this.builder.map.options.urls.pictogram_list_json this.builder.map.options.urls.pictogram_list_json
) )
this.pictogram_list = pictogram_list if (!error) {
this.buildSymbolsList() this.pictogram_list = pictogram_list
this.buildSymbolsList()
}
} }
}, },

View file

@ -34,7 +34,7 @@ L.Map.mergeOptions({
// we cannot rely on this because of the y is overriden by Leaflet // we cannot rely on this because of the y is overriden by Leaflet
// See https://github.com/Leaflet/Leaflet/pull/9201 // See https://github.com/Leaflet/Leaflet/pull/9201
// And let's remove this -y when this PR is merged and released. // And let's remove this -y when this PR is merged and released.
demoTileInfos: { s: 'a', z: 9, x: 265, y: 181, '-y': 181, r: '' }, demoTileInfos: { 's': 'a', 'z': 9, 'x': 265, 'y': 181, '-y': 181, 'r': '' },
licences: [], licences: [],
licence: '', licence: '',
enableMarkerDraw: true, enableMarkerDraw: true,
@ -838,7 +838,10 @@ L.U.Map.include({
self.isDirty = true self.isDirty = true
} }
if (this._controls.tilelayersChooser) if (this._controls.tilelayersChooser)
this._controls.tilelayersChooser.openSwitcher({ callback: callback, className: 'dark' }) this._controls.tilelayersChooser.openSwitcher({
callback: callback,
className: 'dark',
})
}, },
manageDatalayers: function () { manageDatalayers: function () {
@ -1097,59 +1100,61 @@ L.U.Map.include({
formData.append('center', JSON.stringify(this.geometry())) formData.append('center', JSON.stringify(this.geometry()))
formData.append('settings', JSON.stringify(geojson)) formData.append('settings', JSON.stringify(geojson))
const uri = this.urls.get('map_save', { map_id: this.options.umap_id }) const uri = this.urls.get('map_save', { map_id: this.options.umap_id })
const [data, response] = await this.server.post(uri, {}, formData) const [data, response, error] = await this.server.post(uri, {}, formData)
let duration = 3000, if (!error) {
alert = { content: L._('Map has been saved!'), level: 'info' } let duration = 3000,
if (!this.options.umap_id) { alert = { content: L._('Map has been saved!'), level: 'info' }
alert.content = L._('Congratulations, your map has been created!') if (!this.options.umap_id) {
this.options.umap_id = data.id alert.content = L._('Congratulations, your map has been created!')
this.permissions.setOptions(data.permissions) this.options.umap_id = data.id
this.permissions.commit() this.permissions.setOptions(data.permissions)
if ( this.permissions.commit()
data.permissions && if (
data.permissions.anonymous_edit_url && data.permissions &&
this.options.urls.map_send_edit_link data.permissions.anonymous_edit_url &&
) { this.options.urls.map_send_edit_link
alert.duration = Infinity ) {
alert.content = alert.duration = Infinity
L._( alert.content =
'Your map has been created! As you are not logged in, here is your secret link to edit the map, please keep it safe:' L._(
) + `<br>${data.permissions.anonymous_edit_url}` 'Your map has been created! As you are not logged in, here is your secret link to edit the map, please keep it safe:'
) + `<br>${data.permissions.anonymous_edit_url}`
alert.actions = [ alert.actions = [
{ {
label: L._('Send me the link'), label: L._('Send me the link'),
input: L._('Email'), input: L._('Email'),
callback: this.sendEditLink, callback: this.sendEditLink,
callbackContext: this, callbackContext: this,
},
{
label: L._('Copy link'),
callback: () => {
L.Util.copyToClipboard(data.permissions.anonymous_edit_url)
this.ui.alert({
content: L._('Secret edit link copied to clipboard!'),
level: 'info',
})
}, },
callbackContext: this, {
}, label: L._('Copy link'),
] callback: () => {
L.Util.copyToClipboard(data.permissions.anonymous_edit_url)
this.ui.alert({
content: L._('Secret edit link copied to clipboard!'),
level: 'info',
})
},
callbackContext: this,
},
]
}
} else if (!this.permissions.isDirty) {
// Do not override local changes to permissions,
// but update in case some other editors changed them in the meantime.
this.permissions.setOptions(data.permissions)
this.permissions.commit()
} }
} else if (!this.permissions.isDirty) { // Update URL in case the name has changed.
// Do not override local changes to permissions, if (history && history.pushState)
// but update in case some other editors changed them in the meantime. history.pushState({}, this.options.name, data.url)
this.permissions.setOptions(data.permissions) else window.location = data.url
this.permissions.commit() alert.content = data.info || alert.content
this.once('saved', () => this.ui.alert(alert))
this.ui.closePanel()
this.permissions.save()
} }
// Update URL in case the name has changed.
if (history && history.pushState)
history.pushState({}, this.options.name, data.url)
else window.location = data.url
alert.content = data.info || alert.content
this.once('saved', () => this.ui.alert(alert))
this.ui.closePanel()
this.permissions.save()
}, },
save: function () { save: function () {

View file

@ -674,22 +674,24 @@ L.U.DataLayer = L.Evented.extend({
if (!this.umap_id) return if (!this.umap_id) return
if (this._loading) return if (this._loading) return
this._loading = true this._loading = true
const [geojson, response] = await this.map.server.get(this._dataUrl()) const [geojson, response, error] = await this.map.server.get(this._dataUrl())
this._last_modified = response.headers['Last-Modified'] if (!error) {
// FIXME: for now this property is set dynamically from backend this._last_modified = response.headers['Last-Modified']
// And thus it's not in the geojson file in the server // FIXME: for now this property is set dynamically from backend
// So do not let all options to be reset // And thus it's not in the geojson file in the server
// Fix is a proper migration so all datalayers settings are // So do not let all options to be reset
// in DB, and we remove it from geojson flat files. // Fix is a proper migration so all datalayers settings are
if (geojson._umap_options) { // in DB, and we remove it from geojson flat files.
geojson._umap_options.editMode = this.options.editMode if (geojson._umap_options) {
geojson._umap_options.editMode = this.options.editMode
}
// In case of maps pre 1.0 still around
if (geojson._storage) geojson._storage.editMode = this.options.editMode
this.fromUmapGeoJSON(geojson)
this.backupOptions()
this.fire('loaded')
this._loading = false
} }
// In case of maps pre 1.0 still around
if (geojson._storage) geojson._storage.editMode = this.options.editMode
this.fromUmapGeoJSON(geojson)
this.backupOptions()
this.fire('loaded')
this._loading = false
}, },
fromGeoJSON: function (geojson) { fromGeoJSON: function (geojson) {
@ -749,8 +751,10 @@ L.U.DataLayer = L.Evented.extend({
url = this.map.proxyUrl(url, this.options.remoteData.ttl) url = this.map.proxyUrl(url, this.options.remoteData.ttl)
const response = await this.map.request.get(url) const response = await this.map.request.get(url)
this.clear() this.clear()
this.rawToGeoJSON(await response.text(), this.options.remoteData.format, (geojson) => this.rawToGeoJSON(
this.fromGeoJSON(geojson) await response.text(),
this.options.remoteData.format,
(geojson) => this.fromGeoJSON(geojson)
) )
}, },
@ -1391,8 +1395,10 @@ L.U.DataLayer = L.Evented.extend({
const versionsContainer = L.DomUtil.createFieldset(container, L._('Versions'), { const versionsContainer = L.DomUtil.createFieldset(container, L._('Versions'), {
callback: async function () { callback: async function () {
const [{versions}, response] = await this.map.server.get(this.getVersionsUrl()) const [{ versions }, response, error] = await this.map.server.get(
versions.forEach(appendVersion) this.getVersionsUrl()
)
if (!error) versions.forEach(appendVersion)
}, },
context: this, context: this,
}) })
@ -1401,13 +1407,17 @@ L.U.DataLayer = L.Evented.extend({
restore: async function (version) { restore: async function (version) {
if (!this.map.editEnabled) return if (!this.map.editEnabled) return
if (!confirm(L._('Are you sure you want to restore this version?'))) return if (!confirm(L._('Are you sure you want to restore this version?'))) return
const [geojson, response] = await this.map.server.get(this.getVersionUrl(version)) const [geojson, response, error] = await this.map.server.get(
if (geojson._storage) geojson._umap_options = geojson._storage // Retrocompat. this.getVersionUrl(version)
if (geojson._umap_options) this.setOptions(geojson._umap_options) )
this.empty() if (!error) {
if (this.isRemoteLayer()) this.fetchRemoteData() if (geojson._storage) geojson._umap_options = geojson._storage // Retrocompat.
else this.addData(geojson) if (geojson._umap_options) this.setOptions(geojson._umap_options)
this.isDirty = true this.empty()
if (this.isRemoteLayer()) this.fetchRemoteData()
else this.addData(geojson)
this.isDirty = true
}
}, },
featuresToGeoJSON: function () { featuresToGeoJSON: function () {
@ -1544,27 +1554,33 @@ L.U.DataLayer = L.Evented.extend({
map_id: this.map.options.umap_id, map_id: this.map.options.umap_id,
pk: this.umap_id, pk: this.umap_id,
}) })
const headers = this._last_modified const headers = this._last_modified
? { 'If-Unmodified-Since': this._last_modified } ? { 'If-Unmodified-Since': this._last_modified }
: {} : {}
const [data, response] = await this.map.server.post(saveUrl, headers, formData) const [data, response, error] = await this.map.server.post(
// Response contains geojson only if save has conflicted and conflicts have saveUrl,
// been resolved. So we need to reload to get extra data (saved from someone else) headers,
if (data.geojson) { formData
this.clear() )
this.fromGeoJSON(data.geojson) if (!error) {
delete data.geojson // Response contains geojson only if save has conflicted and conflicts have
// been resolved. So we need to reload to get extra data (saved from someone else)
if (data.geojson) {
this.clear()
this.fromGeoJSON(data.geojson)
delete data.geojson
}
this._geojson = geojson
this._last_modified = response.headers['Last-Modified']
this.setUmapId(data.id)
this.updateOptions(data)
this.backupOptions()
this.connectToMap()
this._loaded = true
this.redraw() // Needed for reordering features
this.isDirty = false
this.permissions.save()
} }
this._geojson = geojson
this._last_modified = response.headers['Last-Modified']
this.setUmapId(data.id)
this.updateOptions(data)
this.backupOptions()
this.connectToMap()
this._loaded = true
this.redraw() // Needed for reordering features
this.isDirty = false
this.permissions.save()
}, },
saveDelete: async function () { saveDelete: async function () {

View file

@ -132,13 +132,15 @@ L.U.MapPermissions = L.Class.extend({
}, },
attach: async function () { attach: async function () {
await this.map.server.post(this.getAttachUrl()) const [data, response, error] = await this.map.server.post(this.getAttachUrl())
this.options.owner = this.map.options.user if (!error) {
this.map.ui.alert({ this.options.owner = this.map.options.user
content: L._('Map has been attached to your account'), this.map.ui.alert({
level: 'info', content: L._('Map has been attached to your account'),
}) level: 'info',
this.map.ui.closePanel() })
this.map.ui.closePanel()
}
}, },
save: async function () { save: async function () {
@ -155,11 +157,17 @@ L.U.MapPermissions = L.Class.extend({
formData.append('owner', this.options.owner && this.options.owner.id) formData.append('owner', this.options.owner && this.options.owner.id)
formData.append('share_status', this.options.share_status) formData.append('share_status', this.options.share_status)
} }
await this.map.server.post(this.getUrl(), {}, formData) const [data, response, error] = await this.map.server.post(
this.commit() this.getUrl(),
this.isDirty = false {},
this.map.continueSaving() formData
this.map.fire('postsync') )
if (!error) {
this.commit()
this.isDirty = false
this.map.continueSaving()
this.map.fire('postsync')
}
}, },
getUrl: function () { getUrl: function () {