Skip to content

Commit

Permalink
Start implementing permission on data entry
Browse files Browse the repository at this point in the history
  • Loading branch information
romain-gilliotte committed Apr 21, 2020
1 parent 1e1e024 commit eaba731
Show file tree
Hide file tree
Showing 15 changed files with 207 additions and 132 deletions.
6 changes: 3 additions & 3 deletions api/src/storage/queries.js
Expand Up @@ -48,7 +48,7 @@ function listWaitingInvitations(userEmail) {
{ $unwind: '$project' },
{
$project: {
'projectId': 1, 'email': 1, 'accepted': 1,
'projectId': 1, 'email': 1, 'accepted': 1, 'dataEntry': 1,
'project.owner': 1, 'project.country': 1, 'project.name': 1, 'project.start': 1, 'project.end': 1
}
}
Expand All @@ -61,7 +61,7 @@ function listProjectInvitations(userEmail, projectId) {
{ $lookup: { from: 'project', localField: 'projectId', foreignField: '_id', as: 'project' } },
{ $unwind: '$project' },
{ $match: { $or: [{ email: userEmail }, { 'project.owner': userEmail }] } },
{ $project: { 'projectId': 1, 'email': 1, 'accepted': 1 } }
{ $project: { 'projectId': 1, 'email': 1, 'accepted': 1, 'dataEntry': 1, } }
]);
}

Expand All @@ -71,7 +71,7 @@ async function getInvitation(userEmail, id) {
{ $lookup: { from: 'project', localField: 'projectId', foreignField: '_id', as: 'project' } },
{ $unwind: '$project' },
{ $match: { $or: [{ email: userEmail }, { 'project.owner': userEmail }] } },
{ $project: { 'projectId': 1, 'email': 1, 'accepted': 1 } }
{ $project: { 'projectId': 1, 'email': 1, 'accepted': 1, 'dataEntry': 1, } }
]).next();
}

Expand Down
45 changes: 35 additions & 10 deletions api/src/storage/schema/invitation.js
@@ -1,17 +1,42 @@
module.exports = {
"type": "object",
"additionalProperties": false,
"required": [
definitions: require('./_definitions'),
type: "object",
additionalProperties: false,
required: [
"email",
"accepted"
],
"properties": {
"email": {
"type": "string",
"format": "email"
properties: {
projectId: {
type: "string",
match: "^[0-9a-z]{24}$"
},
"accepted": {
"type": "boolean"
email: {
type: "string",
format: "email"
},
accepted: {
type: "boolean"
},
dataEntry: {
type: "object",
required: ["dataSourceIds", "siteIds"],
properties: {
dataSourceIds: {
type: "array",
uniqueItems: true,
items: {
$ref: "#/definitions/uuid"
}
},
siteIds: {
type: "array",
uniqueItems: true,
items: {
$ref: "#/definitions/uuid"
}
}
}
}
}
}
};
63 changes: 31 additions & 32 deletions frontend/src/components/pages/input-home/project-input-home.html
Expand Up @@ -10,36 +10,35 @@
</div>
</div>

<legend>Avancement des saisies</legend>
<div ng-if="$ctrl.activeDataSources.length != 0">
<legend>Avancement des saisies</legend>

<table class="table table-bordered table-striped">
<thead>
<tr>
<th style="width: 25%">Formulaire</th>
<th style="width: 10%; text-align: center;">#</th>
<th translate="shared.state"></th>
</tr>
</thead>
<tbody>
<tr ng-if="$ctrl.activeDataSources.length == 0">
<td colspan="3" translate="project.no_data_source" style="text-align: center;"></td>
</tr>
<tr ng-repeat="dataSource in $ctrl.activeDataSources track by dataSource.id">
<td>
<a class="btn btn-default btn-xs" title="{{dataSource.name}}"
ui-sref="project.usage.list({dataSourceId: dataSource.id})">
<i class="fa fa-pencil"></i>
{{dataSource.name|maxLength:30}}
</a>
</td>
<td style="text-align: center;">
<i class="fa fa-circle-o-notch fa-spin" ng-if="!$ctrl.status[dataSource.id]"></i>
{{$ctrl.status[dataSource.id].total}}
</td>
<td ng-if="$ctrl.status">
<progress-bar done="$ctrl.status[dataSource.id].complete"
incomplete="$ctrl.status[dataSource.id].incomplete"></progress-bar>
</td>
</tr>
</tbody>
</table>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th style="width: 25%">Formulaire</th>
<th style="width: 10%; text-align: center;">#</th>
<th translate="shared.state"></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="dataSource in $ctrl.activeDataSources track by dataSource.id">
<td>
<a class="btn btn-default btn-xs" title="{{dataSource.name}}"
ui-sref="project.usage.list({dataSourceId: dataSource.id})">
<i class="fa fa-pencil"></i>
{{dataSource.name|maxLength:30}}
</a>
</td>
<td style="text-align: center;">
<i class="fa fa-circle-o-notch fa-spin" ng-if="!$ctrl.status[dataSource.id]"></i>
{{$ctrl.status[dataSource.id].total}}
</td>
<td ng-if="$ctrl.status">
<progress-bar done="$ctrl.status[dataSource.id].complete"
incomplete="$ctrl.status[dataSource.id].incomplete"></progress-bar>
</td>
</tr>
</tbody>
</table>
</div>
21 changes: 17 additions & 4 deletions frontend/src/components/pages/input-home/project-input-home.js
Expand Up @@ -21,29 +21,42 @@ module.config($stateProvider => {
module.component(__componentName, {
bindings: {
project: '<',
invitations: '<',
users: '<'
},
template: require(__templatePath),
controller: class ProjectInputHomeController {

constructor($scope) {
constructor($scope, $rootScope) {
"ngInject";

this.$scope = $scope;
this.$rootScope = $rootScope;
}

async $onChanges(changes) {
this.status = {};
const myEmail = this.$rootScope.profile.email;
const myInvitation = this.invitations.find(i => i.email === myEmail);

// A datasource is active if we can perform data entry in at least one site.
this.activeDataSources = this.project.forms.filter(ds => {
// FIXME this was copy pasted from input-menu
const mySites = myInvitation ? myInvitation.dataEntry.siteIds : this.project.entities.map(s => s.id);
const myDss = myInvitation ? myInvitation.dataEntry.dataSourceIds : this.project.forms.map(ds => ds.id);

return ds.active
&& ds.elements.some(variable => variable.active)
&& ds.entities.some(siteId => this.project.entities.find(site => site.id == siteId).active)
&& myDss.includes(ds.id)
&& ds.entities.some(siteId =>
this.project.entities.find(site => site.id == siteId).active &&
mySites.includes(siteId)
);
});

this.status = {};
this.activeDataSources.forEach(async ds => {
this.status[ds.id] = await Input.fetchFormShortStatus(this.project, ds.id);
const siteIds = myInvitation ? myInvitation.dataEntry.siteIds : null;
this.status[ds.id] = await Input.fetchFormShortStatus(this.project, ds.id, siteIds);
this.$scope.$apply();
});
}
Expand Down
12 changes: 10 additions & 2 deletions frontend/src/components/pages/input-list/project-input-list.js
Expand Up @@ -23,6 +23,7 @@ module.component(__componentName, {
bindings: {
'project': '<',
'dataSourceId': '<',
'invitations': '<'
},
template: require(__templatePath),

Expand Down Expand Up @@ -63,14 +64,21 @@ module.component(__componentName, {
this.dataSource = this.project.forms.find(ds => ds.id === this.dataSourceId);

// Define sites (depending on user permissions)
this.sites = this.project.entities.filter(e => e.active && this.dataSource.entities.includes(e.id));
const myInvitation = this.invitations.find(i => i.email === this.userEmail);
this.sites = this.project.entities.filter(e =>
e.active && this.dataSource.entities.includes(e.id) &&
(!myInvitation || myInvitation.dataEntry.siteIds.includes(e.id))
);

this.loading = true;
this.load();
}

async load() {
this.inputsStatus = await Input.fetchFormStatus(this.project, this.dataSourceId);
const myInvitation = this.invitations.find(i => i.email === this.userEmail);
const siteIds = myInvitation ? myInvitation.dataEntry.siteIds : null;

this.inputsStatus = await Input.fetchFormStatus(this.project, this.dataSourceId, siteIds);

// Those list tell which rows should be displayed.
this.visibleStatus = Object.keys(this.inputsStatus).slice(0, 20);
Expand Down
Expand Up @@ -11,7 +11,7 @@
</a>
</div>

<div class="panel panel-default">
<div class="panel panel-default" ng-if="$ctrl.activeDataSources.length != 0">
<div class="panel-heading">
<h4 class="panel-title" translate="project.input"></h4>
</div>
Expand All @@ -26,11 +26,6 @@ <h4 class="panel-title" translate="project.input"></h4>
<i class="fa fa-pencil fa-fw"></i>
<span>{{dataSource.name}}</span>
</a>

<a class="list-group-item" ng-if="$ctrl.activeDataSources.length === 0" disable-if="1">
<i class="fa fa-pencil fa-fw"></i>
<span translate="project.no_data_source"></span>
</a>
</div>
</div>
</div>
Expand Down
17 changes: 13 additions & 4 deletions frontend/src/components/pages/input-menu/project-input-menu.js
Expand Up @@ -26,24 +26,33 @@ module.component(__componentName, {

controller: class ProjectInputMenuController {

constructor($state, $stateParams) {
constructor($state, $stateParams, $rootScope) {
"ngInject";

this.$rootScope = $rootScope;
this.$state = $state;
this.$stateParams = $stateParams;
}

$onChanges() {
const myEmail = this.$rootScope.profile.email;
const myInvitation = this.invitations.find(i => i.email === myEmail);

// A datasource is active if we can perform data entry in at least one site.
this.activeDataSources = this.project.forms.filter(ds => {
const mySites = myInvitation ? myInvitation.dataEntry.siteIds : this.project.entities.map(s => s.id);
const myDss = myInvitation ? myInvitation.dataEntry.dataSourceIds : this.project.forms.map(ds => ds.id);

return ds.active
&& ds.elements.some(variable => variable.active)
&& ds.entities.some(siteId => this.project.entities.find(site => site.id == siteId).active)
&& myDss.includes(ds.id)
&& ds.entities.some(siteId =>
this.project.entities.find(site => site.id == siteId).active &&
mySites.includes(siteId)
);
});
}
}
});


export default module.name;

Expand Up @@ -55,7 +55,7 @@ <h4 class="panel-title" translate="shared.configure"></h4>
<span translate="indicator.extra"></span>
</a>

<a ui-sref="project.config.user_list" ui-sref-active="active" class="list-group-item"
<a ui-sref="project.config.invitation_list" ui-sref-active="active" class="list-group-item"
disable-if="!$ctrl.project._id">

<span class="label label-default pull-right">{{$ctrl.invitations.length}}</span>
Expand Down
68 changes: 44 additions & 24 deletions frontend/src/components/pages/structure-user/project-user-list.html
Expand Up @@ -3,34 +3,54 @@
<a ng-click="$ctrl.onEditClicked()" translate="project.add_invitation"></a>
</div>

<div ng-if="$ctrl.invitations.length">
<columns-panel ng-repeat="invitation in $ctrl.invitations">
<pane-title>{{invitation.email}}</pane-title>
<pane-body>
<div class="panel-buttons btn-group">
<a ng-click="$ctrl.onEditClicked(invitation)" class="btn btn-default btn-xxs">
<i class="fa fa-pencil"></i>
<span translate="shared.edit"></span>
<columns-panel ng-repeat="invitation in $ctrl.invitations">
<pane-title>{{invitation.email}}</pane-title>
<pane-body>
<div>
<strong>Invitation acceptée:</strong>
{{invitation.accepted}}
</div>

<strong>Autorisations de saisie:</strong>

<div>
Lieux de collecte:
<element-groups ids="invitation.dataEntry.siteIds" items="$ctrl.project.entities"
groups="$ctrl.project.groups">
</element-groups>
</div>

<div>
Sources de données:
<element-groups ids="invitation.dataEntry.dataSourceIds" items="$ctrl.project.forms">
</element-groups>
</div>

<div class="panel-buttons btn-group">
<a ng-click="$ctrl.onEditClicked(invitation)" class="btn btn-default btn-xxs">
<i class="fa fa-pencil"></i>
<span translate="shared.edit"></span>
</a>

<div class="btn-group" uib-dropdown>
<a class="btn btn-default btn-xxs" uib-dropdown-toggle>
<span class="caret"></span>
</a>

<div class="btn-group" uib-dropdown>
<a class="btn btn-default btn-xxs" uib-dropdown-toggle>
<span class="caret"></span>
</a>

<ul class="dropdown-menu" uib-dropdown-menu>
<li>
<a ng-click="$ctrl.onDeleteClicked(invitation)">
<i class="fa fa-trash text-danger"></i>
<span class="text-danger" translate="shared.delete"></span>
</a>
</li>
</ul>
</div>
<ul class="dropdown-menu" uib-dropdown-menu>
<li>
<a ng-click="$ctrl.onDeleteClicked(invitation)">
<i class="fa fa-trash text-danger"></i>
<span class="text-danger" translate="shared.delete"></span>
</a>
</li>
</ul>
</div>
</pane-body>
</columns-panel>
</div>
</pane-body>
</columns-panel>

<div ng-if="$ctrl.invitations.length">
<a class="btn btn-default" ng-click="$ctrl.onEditClicked()">
<i class="fa fa-plus"></i>
<span translate="project.add_invitation"></span>
Expand Down
Expand Up @@ -11,8 +11,8 @@ const module = angular.module(__moduleName, [uiRouter, 'ng-sortable', mtProjectU

module.config($stateProvider => {

$stateProvider.state('project.config.user_list', {
url: '/users',
$stateProvider.state('project.config.invitation_list', {
url: '/invitations',
component: __componentName,
});
});
Expand Down Expand Up @@ -52,7 +52,8 @@ module.component(__componentName, {
if (oldIvt) {
if (newIvt) {
// Replace
const response = await axios.put(`/invitation/${invitation._id}`, newIvt);
const { _id, ...body } = newIvt;
const response = await axios.put(`/invitation/${_id}`, body);

this.invitations.splice(
this.invitations.indexOf(oldIvt),
Expand Down

0 comments on commit eaba731

Please sign in to comment.