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;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
AsJson() {
|
|
||||||
return {
|
|
||||||
and: this.and.map(a => a.AsJson())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
optimize(): TagsFilter | boolean {
|
optimize(): TagsFilter | boolean {
|
||||||
if(this.and.length === 0){
|
if(this.and.length === 0){
|
||||||
return true
|
return true
|
||||||
|
|
|
@ -85,12 +85,6 @@ export class Or extends TagsFilter {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
AsJson() {
|
|
||||||
return {
|
|
||||||
or: this.or.map(o => o.AsJson())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
optimize(): TagsFilter | boolean {
|
optimize(): TagsFilter | boolean {
|
||||||
|
|
||||||
if(this.or.length === 0){
|
if(this.or.length === 0){
|
||||||
|
|
|
@ -167,6 +167,34 @@ export class TagUtils {
|
||||||
return new Tag(tag[0], tag[1]);
|
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
|
* 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 asChange(properties: any): { k: string, v: string }[]
|
||||||
|
|
||||||
abstract AsJson() ;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an optimized version (or self) of this tagsFilter
|
* 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))
|
parts.push(encodeURIComponent(key) + "=" + encodeURIComponent(QueryParameters.knownSources[key].data))
|
||||||
}
|
}
|
||||||
|
if(!Utils.runningFromConsole){
|
||||||
// Don't pollute the history every time a parameter changes
|
// Don't pollute the history every time a parameter changes
|
||||||
history.replaceState(null, "", "?" + parts.join("&") + Hash.Current());
|
history.replaceState(null, "", "?" + parts.join("&") + Hash.Current());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -93,7 +93,6 @@ export class AskMetadata extends Combine implements FlowStep<{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if ([ obj.features, obj.intro, obj.wikilink, obj.source].some(v => v === undefined)){
|
if ([ obj.features, obj.intro, obj.wikilink, obj.source].some(v => v === undefined)){
|
||||||
console.log("Obj is", obj)
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,10 +30,14 @@ export class CreateNotes extends Combine {
|
||||||
|
|
||||||
const tags: string [] = []
|
const tags: string [] = []
|
||||||
for (const key in f.properties) {
|
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] === "") {
|
if (f.properties[key] === "") {
|
||||||
continue
|
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 lat = f.geometry.coordinates[1]
|
||||||
const lon = f.geometry.coordinates[0]
|
const lon = f.geometry.coordinates[0]
|
||||||
|
|
|
@ -10,6 +10,8 @@ import FileSelectorButton from "../Input/FileSelectorButton";
|
||||||
import {FlowStep} from "./FlowStep";
|
import {FlowStep} from "./FlowStep";
|
||||||
import {parse} from "papaparse";
|
import {parse} from "papaparse";
|
||||||
import {FixedUiElement} from "../Base/FixedUiElement";
|
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> }> {
|
class FileSelector extends InputElementMap<FileList, { name: string, contents: Promise<string> }> {
|
||||||
constructor(label: BaseUIElement) {
|
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")) {
|
if (parsed.features.some(f => f.geometry.type != "Point")) {
|
||||||
return {error: t.errPointsOnly}
|
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;
|
return parsed;
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
@ -13,6 +13,9 @@ import {VariableUiElement} from "../Base/VariableUIElement";
|
||||||
import {FixedUiElement} from "../Base/FixedUiElement";
|
import {FixedUiElement} from "../Base/FixedUiElement";
|
||||||
import Toggleable from "../Base/Toggleable";
|
import Toggleable from "../Base/Toggleable";
|
||||||
import {BBox} from "../../Logic/BBox";
|
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<{
|
export default class SelectTheme extends Combine implements FlowStep<{
|
||||||
features: any[],
|
features: any[],
|
||||||
|
@ -47,7 +50,6 @@ export default class SelectTheme extends Combine implements FlowStep<{
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const applicablePresets = themeRadios.GetValue().map(theme => {
|
const applicablePresets = themeRadios.GetValue().map(theme => {
|
||||||
if (theme === undefined) {
|
if (theme === undefined) {
|
||||||
return []
|
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")
|
return new FixedUiElement("This theme has no presets loaded. As a result, imports won't work here").SetClass("alert")
|
||||||
}
|
}
|
||||||
}, [themeRadios.GetValue()])),
|
}, [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")
|
this.SetClass("flex flex-col")
|
||||||
|
|
||||||
|
@ -121,5 +111,60 @@ export default class SelectTheme extends Combine implements FlowStep<{
|
||||||
}, [applicablePresets])
|
}, [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/wikidata-sdk": "^6.1.0",
|
||||||
"@types/xml2js": "^0.4.9",
|
"@types/xml2js": "^0.4.9",
|
||||||
"country-language": "^0.1.7",
|
"country-language": "^0.1.7",
|
||||||
"doctest-ts-improved": "^0.8.4",
|
"doctest-ts-improved": "^0.8.5",
|
||||||
"email-validator": "^2.0.4",
|
"email-validator": "^2.0.4",
|
||||||
"escape-html": "^1.0.3",
|
"escape-html": "^1.0.3",
|
||||||
"geojson2svg": "^1.3.1",
|
"geojson2svg": "^1.3.1",
|
||||||
|
@ -6024,9 +6024,9 @@
|
||||||
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="
|
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="
|
||||||
},
|
},
|
||||||
"node_modules/doctest-ts-improved": {
|
"node_modules/doctest-ts-improved": {
|
||||||
"version": "0.8.4",
|
"version": "0.8.5",
|
||||||
"resolved": "https://registry.npmjs.org/doctest-ts-improved/-/doctest-ts-improved-0.8.4.tgz",
|
"resolved": "https://registry.npmjs.org/doctest-ts-improved/-/doctest-ts-improved-0.8.5.tgz",
|
||||||
"integrity": "sha512-CMVSnyDB00sLkTqHIZ20Z/kHD2XczNHwWkD4UC4retGaSfuP8XG4cnAGwkr8qoQq3mjc3w8O4w3OTWrC4HC2vA==",
|
"integrity": "sha512-4zU8fQV263CU3jAi+K7xohhT9b2ZDGw20M4O7AgzW1IoKklmNkSlHMoKZX6gqN1DAouo08R+MD5aSgACG5ILRw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/chai": "^4.3.0",
|
"@types/chai": "^4.3.0",
|
||||||
"chai": "^4.3.6",
|
"chai": "^4.3.6",
|
||||||
|
@ -21413,9 +21413,9 @@
|
||||||
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="
|
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="
|
||||||
},
|
},
|
||||||
"doctest-ts-improved": {
|
"doctest-ts-improved": {
|
||||||
"version": "0.8.4",
|
"version": "0.8.5",
|
||||||
"resolved": "https://registry.npmjs.org/doctest-ts-improved/-/doctest-ts-improved-0.8.4.tgz",
|
"resolved": "https://registry.npmjs.org/doctest-ts-improved/-/doctest-ts-improved-0.8.5.tgz",
|
||||||
"integrity": "sha512-CMVSnyDB00sLkTqHIZ20Z/kHD2XczNHwWkD4UC4retGaSfuP8XG4cnAGwkr8qoQq3mjc3w8O4w3OTWrC4HC2vA==",
|
"integrity": "sha512-4zU8fQV263CU3jAi+K7xohhT9b2ZDGw20M4O7AgzW1IoKklmNkSlHMoKZX6gqN1DAouo08R+MD5aSgACG5ILRw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@types/chai": "^4.3.0",
|
"@types/chai": "^4.3.0",
|
||||||
"chai": "^4.3.6",
|
"chai": "^4.3.6",
|
||||||
|
|
|
@ -72,7 +72,7 @@
|
||||||
"@types/wikidata-sdk": "^6.1.0",
|
"@types/wikidata-sdk": "^6.1.0",
|
||||||
"@types/xml2js": "^0.4.9",
|
"@types/xml2js": "^0.4.9",
|
||||||
"country-language": "^0.1.7",
|
"country-language": "^0.1.7",
|
||||||
"doctest-ts-improved": "^0.8.4",
|
"doctest-ts-improved": "^0.8.5",
|
||||||
"email-validator": "^2.0.4",
|
"email-validator": "^2.0.4",
|
||||||
"escape-html": "^1.0.3",
|
"escape-html": "^1.0.3",
|
||||||
"geojson2svg": "^1.3.1",
|
"geojson2svg": "^1.3.1",
|
||||||
|
|
|
@ -4,7 +4,7 @@ import {Utils} from "../Utils";
|
||||||
|
|
||||||
describe("TestSuite", () => {
|
describe("TestSuite", () => {
|
||||||
|
|
||||||
describe("function onder test", () => {
|
describe("function under test", () => {
|
||||||
it("should work", () => {
|
it("should work", () => {
|
||||||
expect("abc").eq("abc")
|
expect("abc").eq("abc")
|
||||||
})
|
})
|
||||||
|
|
File diff suppressed because one or more lines are too long
Loading…
Add table
Add a link
Reference in a new issue