Skip to content

Commit

Permalink
feat(sync): Let the clients set layers UUID
Browse files Browse the repository at this point in the history
This make it possible to synchronize datalayers before their creation on
the server, allowing at the same time to solve issues related to them
not being saved (e.g. duplication of geometries)

We use the DataLayer._referenceVersion to track if a DataLayer has been
saved on the server.

At the same time, we also changed:
- Umap.options.umap_id => Umap.id
- DataLayer.umap_id => Datalayer.id
  • Loading branch information
almet authored and yohanboniface committed Nov 15, 2024
1 parent 8c2a0ec commit d003de6
Show file tree
Hide file tree
Showing 19 changed files with 285 additions and 115 deletions.
18 changes: 18 additions & 0 deletions umap/migrations/0023_alter_datalayer_uuid.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 5.1.2 on 2024-11-15 11:03

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('umap', '0022_add_team'),
]

operations = [
migrations.AlterField(
model_name='datalayer',
name='uuid',
field=models.UUIDField(editable=False, primary_key=True, serialize=False, unique=True),
),
]
21 changes: 14 additions & 7 deletions umap/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ def preview_settings(self):
"hash": False,
"scrollWheelZoom": False,
"noControl": True,
"umap_id": self.pk,
"id": self.pk,
"schema": self.extra_schema,
"slideshow": {},
}
Expand Down Expand Up @@ -444,9 +444,7 @@ class DataLayer(NamedModel):
(COLLABORATORS, _("Editors and team only")),
(OWNER, _("Owner only")),
)
uuid = models.UUIDField(
unique=True, primary_key=True, default=uuid.uuid4, editable=False
)
uuid = models.UUIDField(unique=True, primary_key=True, editable=False)
old_id = models.IntegerField(null=True, blank=True)
map = models.ForeignKey(Map, on_delete=models.CASCADE)
description = models.TextField(blank=True, null=True, verbose_name=_("description"))
Expand Down Expand Up @@ -530,7 +528,7 @@ def metadata(self, request=None):
def clone(self, map_inst=None):
new = self.__class__.objects.get(pk=self.pk)
new._state.adding = True
new.pk = None
new.pk = uuid.uuid4()
if map_inst:
new.map = map_inst
new.geojson = File(new.geojson.file.file)
Expand All @@ -543,11 +541,20 @@ def is_valid_version(self, name):
valid_prefixes.append(name.startswith("%s_" % self.old_id))
return any(valid_prefixes) and name.endswith(".geojson")

def extract_version_number(self, path):
version = path.split(".")[0]
if "_" in version:
return version.split("_")[-1]
return version

@property
def reference_version(self):
return self.extract_version_number(self.geojson.path)

def version_metadata(self, name):
els = name.split(".")[0].split("_")
return {
"name": name,
"at": els[1],
"at": self.extract_version_number(name),
"size": self.geojson.storage.size(self.get_version_path(name)),
}

Expand Down
2 changes: 1 addition & 1 deletion umap/static/umap/js/modules/data/features.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ class Feature {
subject: 'feature',
metadata: {
id: this.id,
layerId: this.datalayer?.umap_id || null,
layerId: this.datalayer.id,
featureType: this.getClassName(),
},
}
Expand Down
97 changes: 55 additions & 42 deletions umap/static/umap/js/modules/data/layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ const LAYER_MAP = LAYER_TYPES.reduce((acc, klass) => {
}, {})

export class DataLayer extends ServerStored {
constructor(umap, leafletMap, data) {
constructor(umap, leafletMap, data = {}) {
super()
this._umap = umap
this.sync = umap.sync_engine.proxy(this)
Expand All @@ -51,10 +51,7 @@ export class DataLayer extends ServerStored {

this._leafletMap = leafletMap
this.parentPane = this._leafletMap.getPane('overlayPane')
this.pane = this._leafletMap.createPane(
`datalayer${stamp(this)}`,
this.parentPane
)
this.pane = this._leafletMap.createPane(`datalayer${stamp(this)}`, this.parentPane)
this.pane.dataset.id = stamp(this)
// FIXME: should be on layer
this.renderer = L.svg({ pane: this.pane })
Expand All @@ -66,7 +63,13 @@ export class DataLayer extends ServerStored {
}

this._isDeleted = false
this.setUmapId(data.id)
this._referenceVersion = data._referenceVersion
// Do not save
delete data._referenceVersion
console.log(data)
data.id ??= crypto.randomUUID()
console.log(data)

this.setOptions(data)

if (!Utils.isObject(this.options.remoteData)) {
Expand All @@ -84,7 +87,8 @@ export class DataLayer extends ServerStored {
this.backupOptions()
this.connectToMap()
this.permissions = new DataLayerPermissions(this._umap, this)
if (!this.umap_id) {

if (!this.createdOnServer) {
if (this.showAtLoad()) this.show()
this.isDirty = true
}
Expand All @@ -95,6 +99,14 @@ export class DataLayer extends ServerStored {
if (this.isVisible()) this.propagateShow()
}

get id() {
return this.options.id
}

get createdOnServer() {
return Boolean(this._referenceVersion)
}

onDirty(status) {
if (status) {
// A layer can be made dirty by indirect action (like dragging layers)
Expand All @@ -121,9 +133,7 @@ export class DataLayer extends ServerStored {
getSyncMetadata() {
return {
subject: 'datalayer',
metadata: {
id: this.umap_id || null,
},
metadata: { id: this.id },
}
}

Expand Down Expand Up @@ -160,7 +170,7 @@ export class DataLayer extends ServerStored {
autoLoaded() {
if (!this._umap.datalayersFromQueryString) return this.options.displayOnLoad
const datalayerIds = this._umap.datalayersFromQueryString
let loadMe = datalayerIds.includes(this.umap_id.toString())
let loadMe = datalayerIds.includes(this.id.toString())
if (this.options.old_id) {
loadMe = loadMe || datalayerIds.includes(this.options.old_id.toString())
}
Expand Down Expand Up @@ -215,13 +225,13 @@ export class DataLayer extends ServerStored {
}

async fetchData() {
if (!this.umap_id) return
if (!this.createdOnServer) return
if (this._loading) return
this._loading = true
const [geojson, response, error] = await this._umap.server.get(this._dataUrl())
if (!error) {
this._reference_version = response.headers.get('X-Datalayer-Version')
// FIXME: for now this property is set dynamically from backend
this.setReferenceVersion({ response, sync: false })
// FIXME: for now the _umap_options property is set dynamically from backend
// And thus it's not in the geojson file in the server
// So do not let all options to be reset
// Fix is a proper migration so all datalayers settings are
Expand Down Expand Up @@ -257,6 +267,7 @@ export class DataLayer extends ServerStored {

async fromUmapGeoJSON(geojson) {
if (geojson._storage) geojson._umap_options = geojson._storage // Retrocompat
geojson._umap_options.id = this.id
if (geojson._umap_options) this.setOptions(geojson._umap_options)
if (this.isRemoteLayer()) await this.fetchRemoteData()
else this.fromGeoJSON(geojson, false)
Expand Down Expand Up @@ -313,18 +324,13 @@ export class DataLayer extends ServerStored {
}

isLoaded() {
return !this.umap_id || this._loaded
return !this.createdOnServer || this._loaded
}

hasDataLoaded() {
return this._dataloaded
}

setUmapId(id) {
// Datalayer is null when listening creation form
if (!this.umap_id && id) this.umap_id = id
}

backupOptions() {
this._backupOptions = Utils.CopyJSON(this.options)
}
Expand Down Expand Up @@ -357,8 +363,8 @@ export class DataLayer extends ServerStored {

_dataUrl() {
let url = this._umap.urls.get('datalayer_view', {
pk: this.umap_id,
map_id: this._umap.properties.umap_id,
pk: this.id,
map_id: this._umap.id,
})

// No browser cache for owners/editors.
Expand Down Expand Up @@ -437,7 +443,7 @@ export class DataLayer extends ServerStored {
// otherwise the layer becomes uneditable.
this.makeFeatures(geojson, sync)
} catch (err) {
console.log('Error with DataLayer', this.umap_id)
console.log('Error with DataLayer', this.id)
console.error(err)
}
}
Expand Down Expand Up @@ -524,22 +530,22 @@ export class DataLayer extends ServerStored {

getDeleteUrl() {
return this._umap.urls.get('datalayer_delete', {
pk: this.umap_id,
map_id: this._umap.properties.umap_id,
pk: this.id,
map_id: this._umap.id,
})
}

getVersionsUrl() {
return this._umap.urls.get('datalayer_versions', {
pk: this.umap_id,
map_id: this._umap.properties.umap_id,
pk: this.id,
map_id: this._umap.id,
})
}

getVersionUrl(name) {
return this._umap.urls.get('datalayer_version', {
pk: this.umap_id,
map_id: this._umap.properties.umap_id,
pk: this.id,
map_id: this._umap.id,
name: name,
})
}
Expand Down Expand Up @@ -579,7 +585,8 @@ export class DataLayer extends ServerStored {
}

reset() {
if (!this.umap_id) this.erase()
if (!this.createdOnServer) this.erase()

this.resetOptions()
this.parentPane.appendChild(this.pane)
if (this._leaflet_events_bk && !this._leaflet_events) {
Expand Down Expand Up @@ -809,7 +816,7 @@ export class DataLayer extends ServerStored {
},
this
)
if (this.umap_id) {
if (this.createdOnServer) {
const filename = `${Utils.slugify(this.options.name)}.geojson`
const download = Utils.loadTemplate(`
<a class="button" href="${this._dataUrl()}" download="${filename}">
Expand Down Expand Up @@ -1034,6 +1041,11 @@ export class DataLayer extends ServerStored {
return this.isReadOnly() || this.isRemoteLayer()
}

setReferenceVersion({ response, sync }) {
this._referenceVersion = response.headers.get('X-Datalayer-Version')
this.sync.update('_referenceVersion', this._referenceVersion)
}

async save() {
if (this.isDeleted) return await this.saveDelete()
if (!this.isLoaded()) {
Expand All @@ -1048,14 +1060,15 @@ export class DataLayer extends ServerStored {
// Filename support is shaky, don't do it for now.
const blob = new Blob([JSON.stringify(geojson)], { type: 'application/json' })
formData.append('geojson', blob)
const saveUrl = this._umap.urls.get('datalayer_save', {
map_id: this._umap.properties.umap_id,
pk: this.umap_id,
const saveURL = this._umap.urls.get('datalayer_save', {
map_id: this._umap.id,
pk: this.id,
created: this.createdOnServer,
})
const headers = this._reference_version
? { 'X-Datalayer-Reference': this._reference_version }
const headers = this._referenceVersion
? { 'X-Datalayer-Reference': this._referenceVersion }
: {}
const status = await this._trySave(saveUrl, headers, formData)
const status = await this._trySave(saveURL, headers, formData)
this._geojson = geojson
return status
}
Expand Down Expand Up @@ -1090,11 +1103,11 @@ export class DataLayer extends ServerStored {
this.fromGeoJSON(data.geojson)
delete data.geojson
}
this._reference_version = response.headers.get('X-Datalayer-Version')
this.sync.update('_reference_version', this._reference_version)

this.setUmapId(data.id)
delete data.id
this.updateOptions(data)

this.setReferenceVersion({ response, sync: true })

this.backupOptions()
this.backupData()
this.connectToMap()
Expand All @@ -1105,7 +1118,7 @@ export class DataLayer extends ServerStored {
}

async saveDelete() {
if (this.umap_id) {
if (this.createdOnServer) {
await this._umap.server.post(this.getDeleteUrl())
}
delete this._umap.datalayers[stamp(this)]
Expand Down
10 changes: 5 additions & 5 deletions umap/static/umap/js/modules/permissions.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ export class MapPermissions extends ServerStored {

edit() {
if (this._umap.properties.editMode !== 'advanced') return
if (!this._umap.properties.umap_id) {
if (!this._umap.id) {
Alert.info(translate('Please save the map first'))
return
}
Expand Down Expand Up @@ -199,13 +199,13 @@ export class MapPermissions extends ServerStored {

getUrl() {
return this._umap.urls.get('map_update_permissions', {
map_id: this._umap.properties.umap_id,
map_id: this._umap.id,
})
}

getAttachUrl() {
return this._umap.urls.get('map_attach_owner', {
map_id: this._umap.properties.umap_id,
map_id: this._umap.id,
})
}

Expand Down Expand Up @@ -262,8 +262,8 @@ export class DataLayerPermissions extends ServerStored {

getUrl() {
return this._umap.urls.get('datalayer_permissions', {
map_id: this._umap.properties.umap_id,
pk: this.datalayer.umap_id,
map_id: this._umap.id,
pk: this.datalayer.id,
})
}

Expand Down
5 changes: 5 additions & 0 deletions umap/static/umap/js/modules/schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -563,4 +563,9 @@ export const SCHEMA = {
type: Object,
impacts: ['data'],
},

_referenceVersion: {
type: Number,
impacts: ['data'],
},
}
6 changes: 3 additions & 3 deletions umap/static/umap/js/modules/share.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export default class Share {
translate('All data and settings of the map')
)
const downloadUrl = this._umap.urls.get('map_download', {
map_id: this._umap.properties.umap_id,
map_id: this._umap.id,
})
const link = Utils.loadTemplate(`
<div>
Expand Down Expand Up @@ -205,8 +205,8 @@ class IframeExporter {
}
if (this.options.keepCurrentDatalayers) {
this._umap.eachDataLayer((datalayer) => {
if (datalayer.isVisible() && datalayer.umap_id) {
datalayers.push(datalayer.umap_id)
if (datalayer.isVisible() && datalayer.createdOnServer) {
datalayers.push(datalayer.id)
}
})
this.queryString.datalayers = datalayers.join(',')
Expand Down
Loading

0 comments on commit d003de6

Please sign in to comment.