Skip to content

Commit

Permalink
Allow expert validators to classify mapping issues
Browse files Browse the repository at this point in the history
> Note: this includes a new database migration

* Show validators in expert mode an additional form allowing them to
(optionally) classify the type and number of problems they discover with
mapping work that is leading to invalidation of the task

* Record identified mapping issues in a `task_mapping_issues` database
table

* Display noted mapping issues in the task history immediately below the
invalidation where they were recorded

* Allow mapping issues fixed by the validator on behalf of the mapper
to be noted when marking a task as validated

* Display in the task history when a task was validated with fixes, and
show the identified list of issues addressed by the validator

* Add a new page, accessible from the account-nav dropdown menu, on
which project managers and admins can manage the available mapping-issue
categories

* Seed the mapping-issue categories with a couple of initial categories

* Add server RESTful API endpoints for managing mapping-issue categories

* Add new `mapping_issue_categories` and `task_mapping_issues` database
tables in new migration

* Add support on server for optional inclusion of noted mapping issues
during task validation/invalidation
  • Loading branch information
nrotstan committed May 24, 2019
1 parent af4b5db commit 7dbd98a
Show file tree
Hide file tree
Showing 22 changed files with 1,171 additions and 11 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
(function () {

'use strict';

/**
* Mapping-issue Categories controller which manages viewing, creating and
* editing categories
*/
angular
.module('taskingManager')
.controller('mappingIssueCategoriesEditController',
['$routeParams', '$location', 'mappingIssueService', mappingIssueCategoriesEditController]);

function mappingIssueCategoriesEditController($routeParams, $location, mappingIssueService) {
var vm = this;

vm.category = {};
vm.isNew = false;
vm.isCategoryFound = false;
vm.isSaveEditsSuccessful = true;
vm.isArchiveSuccessful = true;
vm.isDeleteSuccessful = true;
vm.isCreateNewSuccessful = true;
vm.id = 0;

activate();

function activate() {
vm.id = $routeParams.id;
if (vm.id === 'new'){
vm.isNew = true;
}
else {
vm.id = parseInt(vm.id, 10);
var resultsPromise = mappingIssueService.getMappingIssueCategories(true);
resultsPromise.then(function (data) {
// On success
vm.category = data.categories.find(function(category) {
return category.categoryId === vm.id;
});

vm.isCategoryFound = !!vm.category;
}, function(){
// On error
vm.isCategoryFound = false;
});
}
}

/**
* Cancel editing the category by going to the list of categories
*/
vm.cancel = function(){
$location.path('/admin/mapping-issues/categories');
};

/**
* Save the edits made to the category
*/
vm.saveEdits = function(){
vm.isSaveEditsSuccessful = true;
var resultsPromise = mappingIssueService.updateMappingIssueCategory(vm.category, vm.id);
resultsPromise.then(function () {
// On success
$location.path('/admin/mapping-issues/categories');
}, function () {
// On error
vm.isSaveEditsSuccessful = false;
});
};

/**
* Archive the category
*/
vm.archive = function(){
vm.category.archived = true;
vm.updateArchivedFlag()
};

/**
* Unarchive the category
*/
vm.unarchive = function(){
vm.category.archived = false;
vm.updateArchivedFlag()
};

/**
* Perform an update of the category's archived flag
*/
vm.updateArchivedFlag = function() {
vm.isArchiveSuccessful = true;
var resultsPromise = mappingIssueService.updateMappingIssueCategory(vm.category, vm.id);
resultsPromise.then(function () {
// On success
$location.path('/admin/mapping-issues/categories');
}, function () {
// On error
vm.isArchiveSuccessful = false;
});
};

/**
* Delete the category
*/
vm.delete = function(){
vm.isDeleteSuccessful = true;
var resultsPromise = mappingIssueService.deleteMappingIssueCategory(vm.id);
resultsPromise.then(function () {
// On success
$location.path('/admin/mapping-issues/categories');
}, function () {
// On error
vm.isDeleteSuccessful = false;
});
};

/**
* Create a new category
*/
vm.createNewMappingIssueCategory = function(){
vm.isCreateNewSuccessful = true;
var resultsPromise = mappingIssueService.createMappingIssueCategory(vm.category);
resultsPromise.then(function () {
// On success
$location.path('/admin/mapping-issues/categories');
}, function () {
// On error
vm.isCreateNewSuccessful = false;
});
};
}
})();
89 changes: 89 additions & 0 deletions client/app/admin/mapping-issues/mapping-issue-categories-edit.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<div class="inner">
<section class="section section--asided">
<header class="section__header section__header--nodescription">
<div class="inner">
<h1 class="section__title">
{{ 'Edit Mapping-issue Category' | translate }}
</h1>
</div>
</header>
<div class="section-container">
<div class="section__body section__body--secondary">
<div class="inner">
<div class="pull-right padding-at-bottom">
<button class="button button--outline" type="button"
ng-show="!mappingIssueCategoriesEditCtrl.category.archived"
ng-click="mappingIssueCategoriesEditCtrl.archive()">
{{ 'Archive' | translate }}
</button>
<button class="button button--outline" type="button"
ng-show="mappingIssueCategoriesEditCtrl.category.archived"
ng-click="mappingIssueCategoriesEditCtrl.unarchive()">
{{ 'Unarchive' | translate }}
</button>
<button class="button button--outline" type="button"
ng-click="mappingIssueCategoriesEditCtrl.delete()">
{{ 'Delete' | translate }}
</button>
</div>
<div class="clearfix"></div>

<form class="form" ng-hide="!mappingIssueCategoriesEditCtrl.isNew && !mappingIssueCategoriesEditCtrl.isCategoryFound">
<ul class="form__standard">
<li>
<label class="form__label" for="form-name">{{ 'Name' | translate }}*</label>
<input type="text" class="form__control form__control--medium" id="form-name"
ng-model="mappingIssueCategoriesEditCtrl.category.name"
name="form-name" placeholder="{{ 'Enter a name for the category' | translate }}"/>
</li>
<li>
<label class="form__label" for="form-description">{{ 'Short Description' | translate }}*</label>
<input type="text" class="form__control form__control--medium" id="form-description"
ng-model="mappingIssueCategoriesEditCtrl.category.description"
name="form-name" placeholder="{{ 'Enter a short description of the category' | translate }}"/>
</li>
</ul>
<div ng-show="!mappingIssueCategoriesEditCtrl.isNew">
<button class="button" type="button"
ng-click="mappingIssueCategoriesEditCtrl.saveEdits()">
{{ 'Save edits' | translate }}
</button>
<button class="button button--achromic button--secondary" type="button"
ng-click="mappingIssueCategoriesEditCtrl.cancel()">
{{ 'Cancel' | translate }}
</button>
</div>
<div ng-show="mappingIssueCategoriesEditCtrl.isNew">
<button class="button" type="button"
ng-click="mappingIssueCategoriesEditCtrl.createNewMappingIssueCategory()">
{{ 'Create new category' | translate }}
</button>
<button class="button button--achromic button--secondary" type="button"
ng-click="mappingIssueCategoriesEditCtrl.cancel()">
{{ 'Cancel' | translate }}
</button>
</div>
</form>
<div>
<div class="error error--below" ng-show="!mappingIssueCategoriesEditCtrl.isNew && !mappingIssueCategoriesEditCtrl.isCategoryFound">
<p>{{ 'No category found.' | translate }}</p>
<button class="button button--achromic" type="button" ng-click="mappingIssueCategoriesEditCtrl.cancel()">{{ 'Go back to category overview' | translate }}</button>
</div>
<div class="error error--below" ng-show="!mappingIssueCategoriesEditCtrl.isSaveEditsSuccessful">
<p>{{ 'Failed to save edits. Please try again.' | translate }}</p>
</div>
<div class="error error--below" ng-show="!mappingIssueCategoriesEditCtrl.isArchiveSuccessful">
<p>{{ 'Failed to archive/unarchive category. Please try again.' | translate }}</p>
</div>
<div class="error error--below" ng-show="!mappingIssueCategoriesEditCtrl.isDeleteSuccessful">
<p>{{ 'Failed to delete category. Note that categories cannot be deleted once in use, but must be archived instead' | translate }}</p>
</div>
<div class="error error--below" ng-show="!mappingIssueCategoriesEditCtrl.isCreateNewSuccessful">
<p>{{ 'Failed to create a new category. Please try again.' | translate }}</p>
</div>
</div>
</div>
</div>
</div>
</section>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
(function () {

'use strict';

/**
* Mapping-issue Categories controller which manages viewing, creating and
* editing categories of mapping issues that can be flagged during validation.
*/
angular
.module('taskingManager')
.controller('mappingIssueCategoriesController',
['$location', 'mappingIssueService', mappingIssueCategoriesController]);

function mappingIssueCategoriesController($location, mappingIssueService) {
var vm = this;

vm.includeArchived = false;
vm.issueCategories = [];

loadCategories();

function loadCategories() {
var resultsPromise = mappingIssueService.getMappingIssueCategories(vm.includeArchived);
resultsPromise.then(function (data) {
// On success
vm.issueCategories = data.categories;
}, function(){
// On error
});
}

/**
* Toggle whether to fetch categories that have been archived when fetching
* the issue categories
*/
vm.toggleIncudeArchived = function(){
vm.includeArchived = !vm.includeArchived;
loadCategories();
};

/**
* Create a new license
*/
vm.createNewMappingIssueCategory = function(){
$location.path('/admin/mapping-issues/categories/edit/new');
}
}
})();
57 changes: 57 additions & 0 deletions client/app/admin/mapping-issues/mapping-issue-categories.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<div class="inner">
<section class="section section--asided">
<header class="section__header section__header--nodescription">
<div class="inner">
<div class="section__header--left">
<h1 class="section__title">
{{ 'Mapping-issue Categories' | translate }}
</h1>
</div>
<button class="button button--primary pull-right" type="button"
ng-click="mappingIssueCategoriesCtrl.createNewMappingIssueCategory()">
{{ 'Create new category' | translate }}
</button>
</div>

<div class="inner">
<p>
{{ 'Broad categories for classifying mapping issues noted during validation' | translate }}
</p>
<div class="padding-at-top padding-at-bottom">
<input type="checkbox" ng-click="mappingIssueCategoriesCtrl.toggleIncudeArchived()" />
<label>{{ 'Include Archived' | translate }}</label>
</div>
</div>
</header>
<div class="section-container">
<div class="section__body section__body--secondary">
<div class="inner">
<table class="table table--zebra">
<thead>
<tr>
<th>{{ 'Name' | translate }}</th>
<th>{{ 'Description' | translate }}</th>
<th ng-if="mappingIssueCategoriesCtrl.includeArchived">{{ 'Archived' | translate }}</th>
<th>{{ 'Edit' | translate }}</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="category in mappingIssueCategoriesCtrl.issueCategories">
<td><strong>{{ category.name }}</strong></td>
<td>{{ category.description }}</td>
<td ng-if="mappingIssueCategoriesCtrl.includeArchived">
<span class="fa fa-check" ng-show="category.archived"></span>
</td>
<td>
<a href="/admin/mapping-issues/categories/edit/{{ category.categoryId }}">
{{ 'Edit' | translate }}
</a>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</section>
</div>
8 changes: 8 additions & 0 deletions client/app/components/account-nav/account-nav.directive.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,14 @@
$location.path('admin/licenses');
};

/**
* Navigate to the mapping-issue category management page
*/
vm.goToManageMappingIssueCategories = function () {
$location.path('admin/mapping-issues/categories');
vm.showDropdown = false;
};

/**
* Navigate to the project dashboard page
*/
Expand Down
5 changes: 5 additions & 0 deletions client/app/components/account-nav/account-nav.html
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@
{{ 'Manage licenses' | translate }}
</a>
</li>
<li ng-show="accountNavCtrl.account.role === 'PROJECT_MANAGER' || accountNavCtrl.account.role === 'ADMIN'">
<a title="{{ 'Manage mapping-issue categories' | translate }}" class="drop__menu-item" ng-click="accountNavCtrl.goToManageMappingIssueCategories()">
{{ 'Manage issues' | translate }}
</a>
</li>
<li ng-show="accountNavCtrl.account.role === 'PROJECT_MANAGER' || accountNavCtrl.account.role === 'ADMIN'">
<a title="{{ 'Project dashboard' | translate }}" class="drop__menu-item" ng-click="accountNavCtrl.goToProjectDashboard()">
{{ 'Project dashboard' | translate }}
Expand Down
Loading

0 comments on commit 7dbd98a

Please sign in to comment.