forked from MapComplete/MapComplete
Better error handling, see #2009
This commit is contained in:
parent
0db81f7b68
commit
c91d691a36
2 changed files with 95 additions and 62 deletions
|
@ -55,7 +55,7 @@ export class Changes {
|
||||||
featureSwitches?: FeatureSwitchState
|
featureSwitches?: FeatureSwitchState
|
||||||
},
|
},
|
||||||
leftRightSensitive: boolean = false,
|
leftRightSensitive: boolean = false,
|
||||||
reportError?: (string: string | Error) => void
|
reportError?: (string: string | Error) => void,
|
||||||
) {
|
) {
|
||||||
this._leftRightSensitive = leftRightSensitive
|
this._leftRightSensitive = leftRightSensitive
|
||||||
// We keep track of all changes just as well
|
// We keep track of all changes just as well
|
||||||
|
@ -70,7 +70,7 @@ export class Changes {
|
||||||
state.osmConnection,
|
state.osmConnection,
|
||||||
state.featurePropertiesStore,
|
state.featurePropertiesStore,
|
||||||
this,
|
this,
|
||||||
(e) => this._reportError(e)
|
(e) => this._reportError(e),
|
||||||
)
|
)
|
||||||
this.historicalUserLocations = state.historicalUserLocations
|
this.historicalUserLocations = state.historicalUserLocations
|
||||||
|
|
||||||
|
@ -84,7 +84,7 @@ export class Changes {
|
||||||
modifiedObjects: OsmObject[]
|
modifiedObjects: OsmObject[]
|
||||||
newObjects: OsmObject[]
|
newObjects: OsmObject[]
|
||||||
deletedObjects: OsmObject[]
|
deletedObjects: OsmObject[]
|
||||||
}
|
},
|
||||||
): string {
|
): string {
|
||||||
const changedElements = allChanges.modifiedObjects ?? []
|
const changedElements = allChanges.modifiedObjects ?? []
|
||||||
const newElements = allChanges.newObjects ?? []
|
const newElements = allChanges.newObjects ?? []
|
||||||
|
@ -173,7 +173,7 @@ export class Changes {
|
||||||
docs: "The identifier of the used background layer, this will probably be an identifier from the [editor layer index](https://github.com/osmlab/editor-layer-index)",
|
docs: "The identifier of the used background layer, this will probably be an identifier from the [editor layer index](https://github.com/osmlab/editor-layer-index)",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"default"
|
"default",
|
||||||
),
|
),
|
||||||
...addSource(ChangeTagAction.metatags, "ChangeTag"),
|
...addSource(ChangeTagAction.metatags, "ChangeTag"),
|
||||||
...addSource(ChangeLocationAction.metatags, "ChangeLocation"),
|
...addSource(ChangeLocationAction.metatags, "ChangeLocation"),
|
||||||
|
@ -201,7 +201,7 @@ export class Changes {
|
||||||
: "",
|
: "",
|
||||||
]),
|
]),
|
||||||
source,
|
source,
|
||||||
])
|
]),
|
||||||
),
|
),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
@ -250,7 +250,7 @@ export class Changes {
|
||||||
const changeDescriptions = await action.Perform(this)
|
const changeDescriptions = await action.Perform(this)
|
||||||
changeDescriptions[0].meta.distanceToObject = this.calculateDistanceToChanges(
|
changeDescriptions[0].meta.distanceToObject = this.calculateDistanceToChanges(
|
||||||
action,
|
action,
|
||||||
changeDescriptions
|
changeDescriptions,
|
||||||
)
|
)
|
||||||
this.applyChanges(changeDescriptions)
|
this.applyChanges(changeDescriptions)
|
||||||
}
|
}
|
||||||
|
@ -264,7 +264,7 @@ export class Changes {
|
||||||
|
|
||||||
public CreateChangesetObjects(
|
public CreateChangesetObjects(
|
||||||
changes: ChangeDescription[],
|
changes: ChangeDescription[],
|
||||||
downloadedOsmObjects: OsmObject[]
|
downloadedOsmObjects: OsmObject[],
|
||||||
): {
|
): {
|
||||||
newObjects: OsmObject[]
|
newObjects: OsmObject[]
|
||||||
modifiedObjects: OsmObject[]
|
modifiedObjects: OsmObject[]
|
||||||
|
@ -316,6 +316,8 @@ export class Changes {
|
||||||
r.members = change.changes["members"]
|
r.members = change.changes["members"]
|
||||||
osmObj = r
|
osmObj = r
|
||||||
break
|
break
|
||||||
|
default:
|
||||||
|
throw "Got an invalid change.type: " + change.type
|
||||||
}
|
}
|
||||||
if (osmObj === undefined) {
|
if (osmObj === undefined) {
|
||||||
throw "Hmm? This is a bug"
|
throw "Hmm? This is a bug"
|
||||||
|
@ -424,14 +426,14 @@ export class Changes {
|
||||||
result.modifiedObjects.length,
|
result.modifiedObjects.length,
|
||||||
"modified;",
|
"modified;",
|
||||||
result.deletedObjects,
|
result.deletedObjects,
|
||||||
"deleted"
|
"deleted",
|
||||||
)
|
)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
private calculateDistanceToChanges(
|
private calculateDistanceToChanges(
|
||||||
change: OsmChangeAction,
|
change: OsmChangeAction,
|
||||||
changeDescriptions: ChangeDescription[]
|
changeDescriptions: ChangeDescription[],
|
||||||
) {
|
) {
|
||||||
const locations = this.historicalUserLocations?.features?.data
|
const locations = this.historicalUserLocations?.features?.data
|
||||||
if (locations === undefined) {
|
if (locations === undefined) {
|
||||||
|
@ -451,7 +453,7 @@ export class Changes {
|
||||||
.filter((feat) => feat.geometry.type === "Point")
|
.filter((feat) => feat.geometry.type === "Point")
|
||||||
.filter((feat) => {
|
.filter((feat) => {
|
||||||
const visitTime = new Date(
|
const visitTime = new Date(
|
||||||
(<GeoLocationPointProperties>(<any>feat.properties)).date
|
(<GeoLocationPointProperties>(<any>feat.properties)).date,
|
||||||
)
|
)
|
||||||
// In seconds
|
// In seconds
|
||||||
const diff = (now.getTime() - visitTime.getTime()) / 1000
|
const diff = (now.getTime() - visitTime.getTime()) / 1000
|
||||||
|
@ -498,42 +500,59 @@ export class Changes {
|
||||||
...recentLocationPoints.map((gpsPoint) => {
|
...recentLocationPoints.map((gpsPoint) => {
|
||||||
const otherCoor = GeoOperations.centerpointCoordinates(gpsPoint)
|
const otherCoor = GeoOperations.centerpointCoordinates(gpsPoint)
|
||||||
return GeoOperations.distanceBetween(coor, otherCoor)
|
return GeoOperations.distanceBetween(coor, otherCoor)
|
||||||
})
|
}),
|
||||||
)
|
),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Upload the selected changes to OSM.
|
* Gets a single, fresh version of the requested osmObject with some error handling
|
||||||
* Returns 'true' if successful and if they can be removed
|
*/
|
||||||
|
private async getOsmObject(id: string, downloader: OsmObjectDownloader) {
|
||||||
|
try {
|
||||||
|
try {
|
||||||
|
|
||||||
|
// Important: we do **not** cache this request, we _always_ need a fresh version!
|
||||||
|
const osmObj = await downloader.DownloadObjectAsync(id, 0)
|
||||||
|
return { id, osmObj }
|
||||||
|
} catch (e) {
|
||||||
|
const msg = "Could not download OSM-object" +
|
||||||
|
id +
|
||||||
|
" trying again before dropping it from the changes (" +
|
||||||
|
e +
|
||||||
|
")"
|
||||||
|
this._reportError(msg)
|
||||||
|
const osmObj = await downloader.DownloadObjectAsync(id, 0)
|
||||||
|
return { id, osmObj }
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
const msg = "Could not download OSM-object" +
|
||||||
|
id +
|
||||||
|
" dropping it from the changes (" +
|
||||||
|
e +
|
||||||
|
")"
|
||||||
|
this._reportError(msg)
|
||||||
|
this.errors.data.push(e)
|
||||||
|
this.errors.ping()
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Upload the selected changes to OSM. This is typically called with changes for a single theme
|
||||||
|
* @return pending changes which could not be uploaded for some reason; undefined or empty array if successful
|
||||||
*/
|
*/
|
||||||
private async flushSelectChanges(
|
private async flushSelectChanges(
|
||||||
pending: ChangeDescription[],
|
pending: ChangeDescription[],
|
||||||
openChangeset: UIEventSource<number>
|
openChangeset: UIEventSource<number>,
|
||||||
): Promise<boolean> {
|
): Promise<ChangeDescription[]> {
|
||||||
const self = this
|
const self = this
|
||||||
const neededIds = Changes.GetNeededIds(pending)
|
const neededIds = Changes.GetNeededIds(pending)
|
||||||
// We _do not_ pass in the Changes object itself - we want the data from OSM directly in order to apply the changes
|
// We _do not_ pass in the Changes object itself - we want the data from OSM directly in order to apply the changes
|
||||||
const downloader = new OsmObjectDownloader(this.backend, undefined)
|
const downloader = new OsmObjectDownloader(this.backend, undefined)
|
||||||
let osmObjects = await Promise.all<{ id: string; osmObj: OsmObject | "deleted" }>(
|
let osmObjects = await Promise.all<{ id: string; osmObj: OsmObject | "deleted" }>(
|
||||||
neededIds.map(async (id) => {
|
neededIds.map(id => this.getOsmObject(id, downloader)),
|
||||||
try {
|
|
||||||
// Important: we do **not** cache this request, we _always_ need a fresh version!
|
|
||||||
const osmObj = await downloader.DownloadObjectAsync(id, 0)
|
|
||||||
return { id, osmObj }
|
|
||||||
} catch (e) {
|
|
||||||
const msg = "Could not download OSM-object" +
|
|
||||||
id +
|
|
||||||
" dropping it from the changes (" +
|
|
||||||
e +
|
|
||||||
")"
|
|
||||||
this._reportError(msg)
|
|
||||||
this.errors.data.push(e)
|
|
||||||
this.errors.ping()
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
})
|
|
||||||
)
|
)
|
||||||
|
|
||||||
osmObjects = Utils.NoNull(osmObjects)
|
osmObjects = Utils.NoNull(osmObjects)
|
||||||
|
@ -554,7 +573,7 @@ export class Changes {
|
||||||
|
|
||||||
if (pending.length == 0) {
|
if (pending.length == 0) {
|
||||||
console.log("No pending changes...")
|
console.log("No pending changes...")
|
||||||
return true
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
const perType = Array.from(
|
const perType = Array.from(
|
||||||
|
@ -562,15 +581,15 @@ export class Changes {
|
||||||
pending
|
pending
|
||||||
.filter(
|
.filter(
|
||||||
(descr) =>
|
(descr) =>
|
||||||
descr.meta.changeType !== undefined && descr.meta.changeType !== null
|
descr.meta.changeType !== undefined && descr.meta.changeType !== null,
|
||||||
)
|
)
|
||||||
.map((descr) => descr.meta.changeType)
|
.map((descr) => descr.meta.changeType),
|
||||||
),
|
),
|
||||||
([key, count]) => ({
|
([key, count]) => ({
|
||||||
key: key,
|
key: key,
|
||||||
value: count,
|
value: count,
|
||||||
aggregate: true,
|
aggregate: true,
|
||||||
})
|
}),
|
||||||
)
|
)
|
||||||
const motivations = pending
|
const motivations = pending
|
||||||
.filter((descr) => descr.meta.specialMotivation !== undefined)
|
.filter((descr) => descr.meta.specialMotivation !== undefined)
|
||||||
|
@ -609,7 +628,7 @@ export class Changes {
|
||||||
value: count,
|
value: count,
|
||||||
aggregate: true,
|
aggregate: true,
|
||||||
}
|
}
|
||||||
})
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
// This method is only called with changedescriptions for this theme
|
// This method is only called with changedescriptions for this theme
|
||||||
|
@ -633,26 +652,42 @@ export class Changes {
|
||||||
...perBinMessage,
|
...perBinMessage,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
const refused: ChangeDescription[] = []
|
||||||
|
let toUpload: ChangeDescription[] = []
|
||||||
|
|
||||||
|
pending.forEach(c => {
|
||||||
|
if (c.id < 0) {
|
||||||
|
toUpload.push(c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const matchFound = !!objects.find(o => o.id === c.id && o.type === c.type)
|
||||||
|
if (matchFound) {
|
||||||
|
toUpload.push(c)
|
||||||
|
} else {
|
||||||
|
refused.push(c)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
await this._changesetHandler.UploadChangeset(
|
await this._changesetHandler.UploadChangeset(
|
||||||
(csId, remappings) => {
|
(csId, remappings) => {
|
||||||
if (remappings.size > 0) {
|
if (remappings.size > 0) {
|
||||||
pending = pending.map((ch) => ChangeDescriptionTools.rewriteIds(ch, remappings))
|
toUpload = toUpload.map((ch) => ChangeDescriptionTools.rewriteIds(ch, remappings))
|
||||||
}
|
}
|
||||||
|
|
||||||
const changes: {
|
const changes: {
|
||||||
newObjects: OsmObject[]
|
newObjects: OsmObject[]
|
||||||
modifiedObjects: OsmObject[]
|
modifiedObjects: OsmObject[]
|
||||||
deletedObjects: OsmObject[]
|
deletedObjects: OsmObject[]
|
||||||
} = self.CreateChangesetObjects(pending, objects)
|
} = self.CreateChangesetObjects(toUpload, objects)
|
||||||
|
|
||||||
return Changes.createChangesetFor("" + csId, changes)
|
return Changes.createChangesetFor("" + csId, changes)
|
||||||
},
|
},
|
||||||
metatags,
|
metatags,
|
||||||
openChangeset
|
openChangeset,
|
||||||
)
|
)
|
||||||
|
|
||||||
console.log("Upload successful!")
|
console.log("Upload successful! Refused changes are",refused)
|
||||||
return true
|
return refused
|
||||||
}
|
}
|
||||||
|
|
||||||
private async flushChangesAsync(): Promise<void> {
|
private async flushChangesAsync(): Promise<void> {
|
||||||
|
@ -670,44 +705,42 @@ export class Changes {
|
||||||
pendingPerTheme.get(theme).push(changeDescription)
|
pendingPerTheme.get(theme).push(changeDescription)
|
||||||
}
|
}
|
||||||
|
|
||||||
const successes = await Promise.all(
|
const refusedChanges: ChangeDescription[][] = await Promise.all(
|
||||||
Array.from(pendingPerTheme, async ([theme, pendingChanges]) => {
|
Array.from(pendingPerTheme, async ([theme, pendingChanges]) => {
|
||||||
try {
|
try {
|
||||||
const openChangeset = UIEventSource.asInt(
|
const openChangeset = UIEventSource.asInt(
|
||||||
this.state.osmConnection.GetPreference(
|
this.state.osmConnection.GetPreference(
|
||||||
"current-open-changeset-" + theme
|
"current-open-changeset-" + theme,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
console.log(
|
console.log(
|
||||||
"Using current-open-changeset-" +
|
"Using current-open-changeset-" +
|
||||||
theme +
|
theme +
|
||||||
" from the preferences, got " +
|
" from the preferences, got " +
|
||||||
openChangeset.data
|
openChangeset.data,
|
||||||
)
|
)
|
||||||
|
|
||||||
const result = await self.flushSelectChanges(pendingChanges, openChangeset)
|
const refused = await self.flushSelectChanges(pendingChanges, openChangeset)
|
||||||
if (result) {
|
if (!refused) {
|
||||||
this.errors.setData([])
|
this.errors.setData([])
|
||||||
}
|
}
|
||||||
return result
|
return refused
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this._reportError(e)
|
this._reportError(e)
|
||||||
console.error("Could not upload some changes:", e)
|
console.error("Could not upload some changes:", e)
|
||||||
this.errors.data.push(e)
|
this.errors.data.push(e)
|
||||||
this.errors.ping()
|
this.errors.ping()
|
||||||
return false
|
return pendingChanges
|
||||||
}
|
}
|
||||||
})
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!successes.some((s) => s == false)) {
|
// We keep all the refused changes to try them again
|
||||||
// All changes successfull, we clear the data!
|
this.pendingChanges.setData(refusedChanges.flatMap(c => c))
|
||||||
this.pendingChanges.setData([])
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(
|
console.error(
|
||||||
"Could not handle changes - probably an old, pending changeset in localstorage with an invalid format; erasing those",
|
"Could not handle changes - probably an old, pending changeset in localstorage with an invalid format; erasing those",
|
||||||
e
|
e,
|
||||||
)
|
)
|
||||||
this.errors.data.push(e)
|
this.errors.data.push(e)
|
||||||
this.errors.ping()
|
this.errors.ping()
|
||||||
|
|
|
@ -154,7 +154,7 @@ export class ChangesetHandler {
|
||||||
if (this._reportError) {
|
if (this._reportError) {
|
||||||
this._reportError(e)
|
this._reportError(e)
|
||||||
}
|
}
|
||||||
console.warn("Could not open/upload changeset due to ", e, "trying t")
|
console.warn("Could not open/upload changeset due to ", e, "trying again with a another fresh changeset ")
|
||||||
openChangeset.setData(undefined)
|
openChangeset.setData(undefined)
|
||||||
|
|
||||||
throw e
|
throw e
|
||||||
|
@ -187,7 +187,7 @@ export class ChangesetHandler {
|
||||||
await this.UpdateTags(csId, rewrittenTags)
|
await this.UpdateTags(csId, rewrittenTags)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (this._reportError) {
|
if (this._reportError) {
|
||||||
this._reportError(e)
|
this._reportError("Could not reuse changeset, might be closed: " + e.stacktrace ?? "" + e)
|
||||||
}
|
}
|
||||||
console.warn("Could not upload, changeset is probably closed: ", e)
|
console.warn("Could not upload, changeset is probably closed: ", e)
|
||||||
openChangeset.setData(undefined)
|
openChangeset.setData(undefined)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue