Compare commits

..

9 commits

Author SHA1 Message Date
Yohan Boniface
a442e56cbc fixup: use correct delete function for DataLayer
Some checks are pending
Test & Docs / tests (postgresql, 3.10) (push) Waiting to run
Test & Docs / tests (postgresql, 3.12) (push) Waiting to run
Test & Docs / lint (push) Waiting to run
Test & Docs / docs (push) Waiting to run
2025-01-31 17:28:09 +01:00
Yohan Boniface
718cc919f8
fix: do not try to backup an undefined geojson (#2468)
This occurs when a remote data returns an invalid geojson.
2025-01-31 17:18:43 +01:00
Yohan Boniface
aa25398a62
feat: display a more descriptive alert on invalid geojson error (#2466) 2025-01-31 17:16:44 +01:00
Yohan Boniface
18ff0de872
fix: clear previous alert timeout when opening a new alert (#2467)
Otherwise, when an infinite alert replace an alert with a finite
duration, this first alert timeout will close the second alert.
2025-01-31 17:16:27 +01:00
Yohan Boniface
1d6e74e1d2
fix: only allow to set a map as sync when it is already saved (#2465) 2025-01-31 17:10:22 +01:00
Yohan Boniface
37ecea0799 feat: display a more descriptive alert on invalid geojson error
Co-authored-by: David Larlet <david@larlet.fr>
2025-01-31 17:08:35 +01:00
Yohan Boniface
e38e0f162c fix: do not try to backup an undefined geojson
This occurs when a remote data returns an invalid geojson.

Co-authored-by: David Larlet <david@larlet.fr>
2025-01-31 16:47:17 +01:00
Yohan Boniface
bcd69ccb47 fix: clear previous alert timeout when opening a new alert
Otherwise, when an infinite alert replace an alert with a finite duration,
this first alert timeout will close the second alert.

Co-authored-by: David Larlet <david@larlet.fr>
2025-01-31 16:42:38 +01:00
Yohan Boniface
2162aaf930 fix: only allow to set a map as sync when it is already saved 2025-01-31 15:45:55 +01:00
7 changed files with 57 additions and 17 deletions

View file

@ -57,8 +57,11 @@ class uMapAlert extends uMapElement {
this.container.dataset.duration = duration
this.element.textContent = message
this.setAttribute('open', 'open')
if (this._timeoutId) {
clearTimeout(this._timeoutId)
}
if (Number.isFinite(duration)) {
setTimeout(() => {
this._timeoutId = setTimeout(() => {
this._hide()
}, duration)
}

View file

@ -249,6 +249,7 @@ export class DataLayer extends ServerStored {
}
fromGeoJSON(geojson, sync = true) {
if (!geojson) return []
const features = this.addData(geojson, sync)
this._geojson = geojson
this.onDataLoaded()
@ -282,8 +283,10 @@ export class DataLayer extends ServerStored {
}
backupData() {
if (this._geojson) {
this._geojson_bk = Utils.CopyJSON(this._geojson)
}
}
reindex() {
const features = Object.values(this._features)
@ -319,7 +322,9 @@ export class DataLayer extends ServerStored {
if (!this.isRemoteLayer()) return
if (!this.hasDynamicData() && this.hasDataLoaded() && !force) return
if (!this.isVisible()) return
let url = this._umap.renderUrl(this.options.remoteData.url)
// Keep non proxied url for later use in Alert.
const remoteUrl = this._umap.renderUrl(this.options.remoteData.url)
let url = remoteUrl
if (this.options.remoteData.proxy) {
url = this._umap.proxyUrl(url, this.options.remoteData.ttl)
}
@ -328,6 +333,14 @@ export class DataLayer extends ServerStored {
return this._umap.formatter
.parse(raw, this.options.remoteData.format)
.then((geojson) => this.fromGeoJSON(geojson))
.catch((error) => {
Alert.error(
translate('Cannot parse remote data for layer "{layer}" with url "{url}"', {
layer: this.getName(),
url: remoteUrl,
})
)
})
})
}
@ -455,7 +468,7 @@ export class DataLayer extends ServerStored {
// otherwise the layer becomes uneditable.
return this.makeFeatures(geojson, sync)
} catch (err) {
console.log('Error with DataLayer', this.id)
console.debug('Error with DataLayer', this.id)
console.error(err)
return []
}
@ -502,7 +515,7 @@ export class DataLayer extends ServerStored {
feature = new Polygon(this._umap, this, geojson, id)
break
default:
console.log(geojson)
console.debug(geojson)
Alert.error(
translate('Skipping unknown geometry.type: {type}', {
type: geometry.type || 'undefined',
@ -524,6 +537,9 @@ export class DataLayer extends ServerStored {
if (data?.length) this.isDirty = true
return data
})
.catch((error) => {
Alert.error(translate('Import failed: invalid data'))
})
}
readFile(f) {

View file

@ -1,5 +1,6 @@
/* Uses globals for: csv2geojson, osmtogeojson (not available as ESM) */
import { translate } from './i18n.js'
import { uMapAlert as Alert } from '../components/alerts/alert.js'
export const EXPORT_FORMATS = {
geojson: {
@ -58,11 +59,7 @@ export class Formatter {
}
async fromGeoJSON(str) {
try {
return JSON.parse(str)
} catch (err) {
U.Alert.error(`Invalid JSON file: ${err}`)
}
}
async fromOSM(str) {
@ -106,8 +103,8 @@ export class Formatter {
message: err[0].message,
})
}
U.Alert.error(message, 10000)
console.error(err)
Alert.error(message, 10000)
console.debug(err)
}
if (result?.features.length) {
callback(result)
@ -127,7 +124,7 @@ export class Formatter {
const doc = new DOMParser().parseFromString(x, 'text/xml')
const errorNode = doc.querySelector('parsererror')
if (errorNode) {
U.Alert.error(translate('Cannot parse data'))
Alert.error(translate('Cannot parse data'))
}
return doc
}

View file

@ -305,7 +305,7 @@ export default class Importer extends Utils.WithTemplate {
this.onSuccess()
} catch (e) {
this.onError(translate('Invalid umap data'))
console.error(e)
console.debug(e)
return false
}
}

View file

@ -1111,7 +1111,7 @@ export default class Umap extends ServerStored {
this._editOverlay(container)
this._editBounds(container)
this._editSlideshow(container)
if (this.properties.websocketEnabled) {
if (this.properties.websocketEnabled && this.id) {
this._editSync(container)
}
this._advancedActions(container)
@ -1450,7 +1450,7 @@ export default class Umap extends ServerStored {
removeDataLayers() {
this.eachDataLayerReverse((datalayer) => {
datalayer._delete()
datalayer.del()
})
}

View file

@ -71,7 +71,6 @@ def test_umap_import_from_file(live_server, tilelayer, page):
expect(nonloaded).to_have_count(1)
@pytest.mark.skip
def test_umap_import_from_textarea(live_server, tilelayer, page, settings):
settings.UMAP_ALLOW_ANONYMOUS = True
page.goto(f"{live_server.url}/map/new/")
@ -123,6 +122,28 @@ def test_import_geojson_from_textarea(tilelayer, live_server, page):
expect(paths).to_have_count(3)
def test_import_invalid_data(tilelayer, live_server, page):
uncaught_errors = []
page.on("pageerror", lambda exc: uncaught_errors.append(exc))
page.goto(f"{live_server.url}/map/new/")
page.get_by_title("Open browser").click()
layers = page.locator(".umap-browser .datalayer")
markers = page.locator(".leaflet-marker-icon")
paths = page.locator("path")
expect(markers).to_have_count(0)
expect(paths).to_have_count(0)
expect(layers).to_have_count(0)
button = page.get_by_title("Import data")
expect(button).to_be_visible()
button.click()
textarea = page.locator(".umap-upload textarea")
textarea.fill("invalid data")
for format in ["geojson", "csv", "gpx", "kml", "georss", "osm", "umap"]:
page.locator('select[name="format"]').select_option(format)
page.get_by_role("button", name="Import data", exact=True).click()
assert not uncaught_errors, f"Error with format {format}"
def test_import_kml_from_textarea(tilelayer, live_server, page):
page.goto(f"{live_server.url}/map/new/")
page.get_by_title("Open browser").click()

View file

@ -503,9 +503,12 @@ def test_create_and_sync_map(new_page, asgi_live_server, tilelayer, login, user)
# Create a syncable map with peerA
peerA = login(user, prefix="Page A")
peerA.goto(f"{asgi_live_server.url}/en/map/new/")
peerA.get_by_role("link", name="Map advanced properties").click()
expect(peerA.get_by_text("Real-time collaboration", exact=True)).to_be_hidden()
with peerA.expect_response(re.compile("./map/create/.*")):
peerA.get_by_role("button", name="Save Draft").click()
peerA.get_by_role("link", name="Map advanced properties").click()
expect(peerA.get_by_text("Real-time collaboration", exact=True)).to_be_visible()
peerA.get_by_text("Real-time collaboration", exact=True).click()
peerA.get_by_text("Enable real-time").click()
peerA.get_by_role("link", name="Update permissions and editors").click()