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…
	
	Add table
		Add a link
		
	
		Reference in a new issue