forked from MapComplete/MapComplete
Import helper: Improve error messages of non-matching presets; fix bug if a value is 'null' in source geojson
This commit is contained in:
parent
74f00b333b
commit
b119e1ac1d
13 changed files with 131 additions and 53 deletions
|
@ -148,12 +148,6 @@ export class And extends TagsFilter {
|
|||
return result;
|
||||
}
|
||||
|
||||
AsJson() {
|
||||
return {
|
||||
and: this.and.map(a => a.AsJson())
|
||||
}
|
||||
}
|
||||
|
||||
optimize(): TagsFilter | boolean {
|
||||
if(this.and.length === 0){
|
||||
return true
|
||||
|
|
|
@ -85,12 +85,6 @@ export class Or extends TagsFilter {
|
|||
return result;
|
||||
}
|
||||
|
||||
AsJson() {
|
||||
return {
|
||||
or: this.or.map(o => o.AsJson())
|
||||
}
|
||||
}
|
||||
|
||||
optimize(): TagsFilter | boolean {
|
||||
|
||||
if(this.or.length === 0){
|
||||
|
|
|
@ -167,6 +167,34 @@ export class TagUtils {
|
|||
return new Tag(tag[0], tag[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns wether or not a keys is (probably) a valid key.
|
||||
*
|
||||
* // should accept common keys
|
||||
* TagUtils.isValidKey("name") // => true
|
||||
* TagUtils.isValidKey("image:0") // => true
|
||||
* TagUtils.isValidKey("alt_name") // => true
|
||||
*
|
||||
* // should refuse short keys
|
||||
* TagUtils.isValidKey("x") // => false
|
||||
* TagUtils.isValidKey("xy") // => false
|
||||
*
|
||||
* // should refuse a string with >255 characters
|
||||
* let a255 = ""
|
||||
* for(let i = 0; i < 255; i++) { a255 += "a"; }
|
||||
* a255.length // => 255
|
||||
* TagUtils.isValidKey(a255) // => true
|
||||
* TagUtils.isValidKey("a"+a255) // => false
|
||||
*
|
||||
* // Should refuse unexpected characters
|
||||
* TagUtils.isValidKey("with space") // => false
|
||||
* TagUtils.isValidKey("some$type") // => false
|
||||
* TagUtils.isValidKey("_name") // => false
|
||||
*/
|
||||
public static isValidKey(key: string): boolean {
|
||||
return key.match(/^[a-z][a-z0-9:_]{2,253}[a-z0-9]$/) !== null
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a tag configuration (a json) into a TagsFilter
|
||||
*
|
||||
|
|
|
@ -26,8 +26,6 @@ export abstract class TagsFilter {
|
|||
*/
|
||||
abstract asChange(properties: any): { k: string, v: string }[]
|
||||
|
||||
abstract AsJson() ;
|
||||
|
||||
/**
|
||||
* Returns an optimized version (or self) of this tagsFilter
|
||||
*/
|
||||
|
|
|
@ -91,8 +91,10 @@ export class QueryParameters {
|
|||
|
||||
parts.push(encodeURIComponent(key) + "=" + encodeURIComponent(QueryParameters.knownSources[key].data))
|
||||
}
|
||||
if(!Utils.runningFromConsole){
|
||||
// Don't pollute the history every time a parameter changes
|
||||
history.replaceState(null, "", "?" + parts.join("&") + Hash.Current());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -93,7 +93,6 @@ export class AskMetadata extends Combine implements FlowStep<{
|
|||
return false;
|
||||
}
|
||||
if ([ obj.features, obj.intro, obj.wikilink, obj.source].some(v => v === undefined)){
|
||||
console.log("Obj is", obj)
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -30,10 +30,14 @@ export class CreateNotes extends Combine {
|
|||
|
||||
const tags: string [] = []
|
||||
for (const key in f.properties) {
|
||||
if (f.properties[key] === null || f.properties[key] === undefined) {
|
||||
console.warn("Null or undefined key for ", f.properties)
|
||||
continue
|
||||
}
|
||||
if (f.properties[key] === "") {
|
||||
continue
|
||||
}
|
||||
tags.push(key + "=" + f.properties[key].replace(/=/, "\\=").replace(/;/g, "\\;").replace(/\n/g, "\\n"))
|
||||
tags.push(key + "=" + (f.properties[key]+"").replace(/=/, "\\=").replace(/;/g, "\\;").replace(/\n/g, "\\n"))
|
||||
}
|
||||
const lat = f.geometry.coordinates[1]
|
||||
const lon = f.geometry.coordinates[0]
|
||||
|
|
|
@ -10,6 +10,8 @@ import FileSelectorButton from "../Input/FileSelectorButton";
|
|||
import {FlowStep} from "./FlowStep";
|
||||
import {parse} from "papaparse";
|
||||
import {FixedUiElement} from "../Base/FixedUiElement";
|
||||
import {del} from "idb-keyval";
|
||||
import {TagUtils} from "../../Logic/Tags/TagUtils";
|
||||
|
||||
class FileSelector extends InputElementMap<FileList, { name: string, contents: Promise<string> }> {
|
||||
constructor(label: BaseUIElement) {
|
||||
|
@ -72,6 +74,17 @@ export class RequestFile extends Combine implements FlowStep<{features: any[]}>
|
|||
if (parsed.features.some(f => f.geometry.type != "Point")) {
|
||||
return {error: t.errPointsOnly}
|
||||
}
|
||||
parsed.features.forEach(f => {
|
||||
const props = f.properties
|
||||
for (const key in props) {
|
||||
if(props[key] === undefined || props[key] === null || props[key] === ""){
|
||||
delete props[key]
|
||||
}
|
||||
if(!TagUtils.isValidKey(key)){
|
||||
return {error: "Probably an invalid key: "+key}
|
||||
}
|
||||
}
|
||||
})
|
||||
return parsed;
|
||||
|
||||
} catch (e) {
|
||||
|
|
|
@ -13,6 +13,9 @@ import {VariableUiElement} from "../Base/VariableUIElement";
|
|||
import {FixedUiElement} from "../Base/FixedUiElement";
|
||||
import Toggleable from "../Base/Toggleable";
|
||||
import {BBox} from "../../Logic/BBox";
|
||||
import BaseUIElement from "../BaseUIElement";
|
||||
import PresetConfig from "../../Models/ThemeConfig/PresetConfig";
|
||||
import List from "../Base/List";
|
||||
|
||||
export default class SelectTheme extends Combine implements FlowStep<{
|
||||
features: any[],
|
||||
|
@ -47,7 +50,6 @@ export default class SelectTheme extends Combine implements FlowStep<{
|
|||
})
|
||||
|
||||
|
||||
|
||||
const applicablePresets = themeRadios.GetValue().map(theme => {
|
||||
if (theme === undefined) {
|
||||
return []
|
||||
|
@ -78,20 +80,8 @@ export default class SelectTheme extends Combine implements FlowStep<{
|
|||
return new FixedUiElement("This theme has no presets loaded. As a result, imports won't work here").SetClass("alert")
|
||||
}
|
||||
}, [themeRadios.GetValue()])),
|
||||
new VariableUiElement(nonMatchedElements.map(unmatched => {
|
||||
if(unmatched === undefined || unmatched.length === 0){
|
||||
return
|
||||
}
|
||||
return new Combine([new FixedUiElement(unmatched.length+" objects dont match any presets").SetClass("alert"),
|
||||
...applicablePresets.data.map(preset => preset.title.txt +" needs tags "+ preset.tags.map(t => t.asHumanString()).join(" & ")),
|
||||
,
|
||||
new Toggleable( new Title( "The following elements don't match any of the presets"),
|
||||
new Combine( unmatched.map(feat => JSON.stringify(feat.properties))).SetClass("flex flex-col")
|
||||
)
|
||||
|
||||
]) .SetClass("flex flex-col")
|
||||
|
||||
}))
|
||||
new VariableUiElement(nonMatchedElements.map(unmatched => SelectTheme.nonMatchedElementsPanel(unmatched, applicablePresets.data), [applicablePresets]))
|
||||
]);
|
||||
this.SetClass("flex flex-col")
|
||||
|
||||
|
@ -121,5 +111,60 @@ export default class SelectTheme extends Combine implements FlowStep<{
|
|||
}, [applicablePresets])
|
||||
}
|
||||
|
||||
private static nonMatchedElementsPanel(unmatched: any[], applicablePresets: PresetConfig[]): BaseUIElement {
|
||||
if (unmatched === undefined || unmatched.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
const applicablePresetsOverview = applicablePresets.map(preset => new Combine([
|
||||
preset.title.txt, "needs tags",
|
||||
new FixedUiElement(preset.tags.map(t => t.asHumanString()).join(" & ")).SetClass("thanks")
|
||||
]))
|
||||
|
||||
const unmatchedPanels: BaseUIElement[] = []
|
||||
for (const feat of unmatched) {
|
||||
const parts: BaseUIElement[] = []
|
||||
parts.push(new Combine(Object.keys(feat.properties).map(k =>
|
||||
k+"="+feat.properties[k]
|
||||
)).SetClass("flex flex-col"))
|
||||
|
||||
for (const preset of applicablePresets) {
|
||||
const tags = new And(preset.tags).asChange({})
|
||||
const missing = []
|
||||
for (const {k, v} of tags) {
|
||||
if (preset[k] === undefined) {
|
||||
missing.push(
|
||||
`Expected ${k}=${v}, but it is completely missing`
|
||||
)
|
||||
} else if (feat.properties[k] !== v) {
|
||||
missing.push(
|
||||
`Property with key ${k} does not have expected value ${v}; instead it is ${feat.properties}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (missing.length > 0) {
|
||||
parts.push(
|
||||
new Combine([
|
||||
new FixedUiElement(`Preset ${preset.title.txt} is not applicable:`),
|
||||
new List(missing)
|
||||
]).SetClass("flex flex-col alert")
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
unmatchedPanels.push(new Combine(parts).SetClass("flex flex-col"))
|
||||
}
|
||||
|
||||
return new Combine([
|
||||
new FixedUiElement(unmatched.length + " objects dont match any presets").SetClass("alert"),
|
||||
...applicablePresetsOverview,
|
||||
new Toggleable(new Title("The following elements don't match any of the presets"),
|
||||
new Combine(unmatchedPanels))
|
||||
]).SetClass("flex flex-col")
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
14
package-lock.json
generated
14
package-lock.json
generated
|
@ -26,7 +26,7 @@
|
|||
"@types/wikidata-sdk": "^6.1.0",
|
||||
"@types/xml2js": "^0.4.9",
|
||||
"country-language": "^0.1.7",
|
||||
"doctest-ts-improved": "^0.8.4",
|
||||
"doctest-ts-improved": "^0.8.5",
|
||||
"email-validator": "^2.0.4",
|
||||
"escape-html": "^1.0.3",
|
||||
"geojson2svg": "^1.3.1",
|
||||
|
@ -6024,9 +6024,9 @@
|
|||
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="
|
||||
},
|
||||
"node_modules/doctest-ts-improved": {
|
||||
"version": "0.8.4",
|
||||
"resolved": "https://registry.npmjs.org/doctest-ts-improved/-/doctest-ts-improved-0.8.4.tgz",
|
||||
"integrity": "sha512-CMVSnyDB00sLkTqHIZ20Z/kHD2XczNHwWkD4UC4retGaSfuP8XG4cnAGwkr8qoQq3mjc3w8O4w3OTWrC4HC2vA==",
|
||||
"version": "0.8.5",
|
||||
"resolved": "https://registry.npmjs.org/doctest-ts-improved/-/doctest-ts-improved-0.8.5.tgz",
|
||||
"integrity": "sha512-4zU8fQV263CU3jAi+K7xohhT9b2ZDGw20M4O7AgzW1IoKklmNkSlHMoKZX6gqN1DAouo08R+MD5aSgACG5ILRw==",
|
||||
"dependencies": {
|
||||
"@types/chai": "^4.3.0",
|
||||
"chai": "^4.3.6",
|
||||
|
@ -21413,9 +21413,9 @@
|
|||
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="
|
||||
},
|
||||
"doctest-ts-improved": {
|
||||
"version": "0.8.4",
|
||||
"resolved": "https://registry.npmjs.org/doctest-ts-improved/-/doctest-ts-improved-0.8.4.tgz",
|
||||
"integrity": "sha512-CMVSnyDB00sLkTqHIZ20Z/kHD2XczNHwWkD4UC4retGaSfuP8XG4cnAGwkr8qoQq3mjc3w8O4w3OTWrC4HC2vA==",
|
||||
"version": "0.8.5",
|
||||
"resolved": "https://registry.npmjs.org/doctest-ts-improved/-/doctest-ts-improved-0.8.5.tgz",
|
||||
"integrity": "sha512-4zU8fQV263CU3jAi+K7xohhT9b2ZDGw20M4O7AgzW1IoKklmNkSlHMoKZX6gqN1DAouo08R+MD5aSgACG5ILRw==",
|
||||
"requires": {
|
||||
"@types/chai": "^4.3.0",
|
||||
"chai": "^4.3.6",
|
||||
|
|
|
@ -72,7 +72,7 @@
|
|||
"@types/wikidata-sdk": "^6.1.0",
|
||||
"@types/xml2js": "^0.4.9",
|
||||
"country-language": "^0.1.7",
|
||||
"doctest-ts-improved": "^0.8.4",
|
||||
"doctest-ts-improved": "^0.8.5",
|
||||
"email-validator": "^2.0.4",
|
||||
"escape-html": "^1.0.3",
|
||||
"geojson2svg": "^1.3.1",
|
||||
|
|
|
@ -4,7 +4,7 @@ import {Utils} from "../Utils";
|
|||
|
||||
describe("TestSuite", () => {
|
||||
|
||||
describe("function onder test", () => {
|
||||
describe("function under test", () => {
|
||||
it("should work", () => {
|
||||
expect("abc").eq("abc")
|
||||
})
|
||||
|
|
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue