Extend data model for validation issues

Add the Issues pane

1. Add a class to represent the validation issue
2. Extend the data model for an validation issue to
  (1) add a severity level field (useful for identify save-blocking issues later)
  (2) replace single entity with an array of entities (useful for issues involving multiple entities)
  (3) add a coordinates field for highlighting the location of the issue on the map
  (4) add a fixes field for possible automatic fixes

3. Update existing validation modules to use the new data model
This commit is contained in:
Xiaoming Gao
2018-12-18 20:03:57 -05:00
committed by Xiaoming Gao
parent 0d0521c936
commit 07a53fe6ea
14 changed files with 225 additions and 80 deletions
+12 -6
View File
@@ -2,27 +2,33 @@ import _isEmpty from 'lodash-es/isEmpty';
import { t } from '../util/locale';
import { utilTagText } from '../util/index';
import {
ValidationIssueType,
ValidationIssueSeverity,
validationIssue,
} from './validation_issue';
export function validationDeprecatedTag() {
var validation = function(changes) {
var warnings = [];
var issues = [];
for (var i = 0; i < changes.created.length; i++) {
var change = changes.created[i],
deprecatedTags = change.deprecatedTags();
if (!_isEmpty(deprecatedTags)) {
var tags = utilTagText({ tags: deprecatedTags });
warnings.push({
id: 'deprecated_tags',
issues.push(new validationIssue({
type: ValidationIssueType.deprecated_tags,
severity: ValidationIssueSeverity.warning,
message: t('validations.deprecated_tags', { tags: tags }),
entity: change
});
entities: [change],
}));
}
}
return warnings;
return issues;
};
+14 -7
View File
@@ -1,5 +1,10 @@
import { t } from '../util/locale';
import { utilDisplayName } from '../util';
import {
ValidationIssueType,
ValidationIssueSeverity,
validationIssue,
} from './validation_issue';
export function validationDisconnectedHighway(context) {
@@ -24,7 +29,7 @@ export function validationDisconnectedHighway(context) {
var validation = function(changes, graph) {
var warnings = [];
var issues = [];
for (var i = 0; i < changes.created.length; i++) {
var entity = changes.created[i];
@@ -38,16 +43,18 @@ export function validationDisconnectedHighway(context) {
entityLabel = utilDisplayType(entity.id)
}
}
warnings.push({
id: 'disconnected_highway',
message: t('validations.disconnected_highway', {entityLabel: entityLabel}),
issues.push(new validationIssue({
type: ValidationIssueType.disconnected_highway,
severity: ValidationIssueSeverity.error,
message: t('validations.disconnected_highway'),
tooltip: t('validations.disconnected_highway_tooltip'),
entity: entity
});
entities: [entity],
}));
}
}
return warnings;
return issues;
};
+1
View File
@@ -1,5 +1,6 @@
export { validationDeprecatedTag } from './deprecated_tag';
export { validationDisconnectedHighway } from './disconnected_highway';
export { ValidationIssueType, ValidationIssueSeverity } from './validation_issue';
export { validationManyDeletions } from './many_deletions';
export { validationMapCSSChecks } from './mapcss_checks';
export { validationMissingTag } from './missing_tag';
+15 -8
View File
@@ -1,11 +1,15 @@
import { t } from '../util/locale';
import {
ValidationIssueType,
ValidationIssueSeverity,
validationIssue,
} from './validation_issue';
export function validationManyDeletions() {
var threshold = 100;
var validation = function(changes, graph) {
var warnings = [];
var issues = [];
var nodes=0, ways=0, areas=0, relations=0;
changes.deleted.forEach(function(c) {
@@ -15,14 +19,17 @@ export function validationManyDeletions() {
else if (c.type === 'relation') {relations++;}
});
if (changes.deleted.length > threshold) {
warnings.push({
id: 'many_deletions',
message: t('validations.many_deletions',
{ n: changes.deleted.length, p: nodes, l: ways, a:areas, r: relations })
});
issues.push(new validationIssue({
type: ValidationIssueType.many_deletions,
severity: ValidationIssueSeverity.warning,
message: t(
'validations.many_deletions',
{ n: changes.deleted.length, p: nodes, l: ways, a:areas, r: relations }
),
}));
}
return warnings;
return issues;
};
+3 -3
View File
@@ -5,7 +5,7 @@ export function validationMapCSSChecks() {
if (!services.maprules) return [];
var rules = services.maprules.validationRules();
var warnings = [];
var issues = [];
var createdModified = ['created', 'modified'];
for (var i = 0; i < rules.length; i++) {
@@ -14,12 +14,12 @@ export function validationMapCSSChecks() {
var type = createdModified[j];
var entities = changes[type];
for (var k = 0; k < entities.length; k++) {
rule.findWarnings(entities[k], graph, warnings);
rule.findIssues(entities[k], graph, issues);
}
}
}
return warnings;
return issues;
};
return validation;
}
+12 -7
View File
@@ -1,6 +1,10 @@
import _without from 'lodash-es/without';
import { t } from '../util/locale';
import {
ValidationIssueType,
ValidationIssueSeverity,
validationIssue,
} from './validation_issue';
export function validationMissingTag() {
@@ -12,23 +16,24 @@ export function validationMissingTag() {
var validation = function(changes, graph) {
var types = ['point', 'line', 'area', 'relation'],
warnings = [];
issues = [];
for (var i = 0; i < changes.created.length; i++) {
var change = changes.created[i],
geometry = change.geometry(graph);
if (types.indexOf(geometry) !== -1 && !hasTags(change, graph)) {
warnings.push({
id: 'missing_tag',
issues.push(new validationIssue({
type: ValidationIssueType.missing_tag,
severity: ValidationIssueSeverity.error,
message: t('validations.untagged_' + geometry),
tooltip: t('validations.untagged_' + geometry + '_tooltip'),
entity: change
});
entities: [change],
}));
}
}
return warnings;
return issues;
};
+12 -7
View File
@@ -1,23 +1,28 @@
import { t } from '../util/locale';
import { osmIsSimpleMultipolygonOuterMember } from '../osm';
import {
ValidationIssueType,
ValidationIssueSeverity,
validationIssue,
} from './validation_issue';
export function validationOldMultipolygon() {
return function validation(changes, graph) {
var warnings = [];
var issues = [];
for (var i = 0; i < changes.created.length; i++) {
var entity = changes.created[i];
var parent = osmIsSimpleMultipolygonOuterMember(entity, graph);
if (parent) {
warnings.push({
id: 'old_multipolygon',
issues.push(new validationIssue({
type: ValidationIssueType.old_multipolygon,
severity: ValidationIssueSeverity.warning,
message: t('validations.old_multipolygon'),
tooltip: t('validations.old_multipolygon_tooltip'),
entity: parent
});
entities: [parent],
}));
}
}
return warnings;
return issues;
};
}
+12 -6
View File
@@ -1,5 +1,10 @@
import _isEmpty from 'lodash-es/isEmpty';
import { t } from '../util/locale';
import {
ValidationIssueType,
ValidationIssueSeverity,
validationIssue,
} from './validation_issue';
// https://github.com/openstreetmap/josm/blob/mirror/src/org/
@@ -25,22 +30,23 @@ export function validationTagSuggestsArea() {
var validation = function(changes, graph) {
var warnings = [];
var issues = [];
for (var i = 0; i < changes.created.length; i++) {
var change = changes.created[i],
geometry = change.geometry(graph),
suggestion = (geometry === 'line' ? tagSuggestsArea(change.tags) : undefined);
if (suggestion) {
warnings.push({
id: 'tag_suggests_area',
issues.push(new validationIssue({
type: ValidationIssueType.tag_suggests_area,
severity: ValidationIssueSeverity.warning,
message: t('validations.tag_suggests_area', { tag: suggestion }),
entity: change
});
entities: [change],
}));
}
}
return warnings;
return issues;
};
+41
View File
@@ -0,0 +1,41 @@
import _isObject from 'lodash-es/isObject';
var ValidationIssueType = Object.freeze({
deprecated_tags: 'deprecated_tags',
disconnected_highway: 'disconnected_highway',
many_deletions: 'many_deletions',
missing_tag: 'missing_tag',
old_multipolygon: 'old_multipolygon',
tag_suggests_area: 'tag_suggests_area',
map_rule_issue: 'map_rule_issue',
});
var ValidationIssueSeverity = Object.freeze({
warning: 'warning',
error: 'error',
});
export { ValidationIssueType, ValidationIssueSeverity };
export function validationIssue(attrs) {
if (!_isObject(attrs)) throw new Error('Input attrs is not an object');
if (!attrs.type || !ValidationIssueType.hasOwnProperty(attrs.type)) {
throw new Error('Invalid attrs.type: ' + attrs.type);
}
if (!attrs.severity || !ValidationIssueSeverity.hasOwnProperty(attrs.severity)) {
throw new Error('Invalid attrs.severity: ' + attr.severity);
}
if (!attrs.message) throw new Error('attrs.message is empty');
this.type = attrs.type;
this.severity = attrs.severity;
this.message = attrs.message;
this.tooltip = attrs.tooltip;
this.entities = attrs.entities; // expect an array of entities
this.coordinates = attrs.coordinates; // expect an array of [lon, lat]
this.fixes = attrs.fixes; // expect an array of functions for possible fixes
}