Skip to content

Commit

Permalink
Merge pull request #198 from loneil/feature/shareSubmission
Browse files Browse the repository at this point in the history
Manage Submission Users
  • Loading branch information
jujaga authored Jul 12, 2021
2 parents e068eda + 5597637 commit 7d1ecad
Show file tree
Hide file tree
Showing 13 changed files with 774 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export default {

<style scoped>
.notification-container {
z-index: 99;
z-index: 999;
position: fixed;
top: 0;
left: 25%;
Expand Down
1 change: 1 addition & 0 deletions app/frontend/src/components/designer/FormViewer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<FormViewerActions
:draftEnabled="form.enableSubmitterDraft"
:formId="form.id"
:isDraft="submissionRecord.draft"
:permissions="permissions"
:readOnly="readOnly"
:submissionId="submissionId"
Expand Down
13 changes: 13 additions & 0 deletions app/frontend/src/components/designer/FormViewerActions.vue
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,24 @@
</v-tooltip>
</router-link>
</span>

<!-- Go to draft edit -->
<span v-if="submissionId" class="ml-2">
<ManageSubmissionUsers :isDraft="isDraft" :submissionId="submissionId" />
</span>
</v-col>
</v-row>
</template>

<script>
import { FormPermissions } from '@/utils/constants';
import ManageSubmissionUsers from '@/components/forms/submission/ManageSubmissionUsers.vue';
export default {
name: 'MySubmissionsActions',
components: {
ManageSubmissionUsers,
},
props: {
draftEnabled: {
type: Boolean,
Expand All @@ -64,6 +73,10 @@ export default {
type: String,
default: undefined,
},
isDraft: {
type: Boolean,
default: false,
},
permissions: {
type: Array,
},
Expand Down
295 changes: 295 additions & 0 deletions app/frontend/src/components/forms/submission/ManageSubmissionUsers.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,295 @@
<template>
<span>
<v-tooltip bottom>
<template #activator="{ on, attrs }">
<v-btn
color="primary"
@click="dialog = true"
icon
v-bind="attrs"
v-on="on"
>
<v-icon>group</v-icon>
</v-btn>
</template>
<span>Manage Team Members</span>
</v-tooltip>
<v-dialog v-model="dialog" width="600">
<v-card>
<v-card-title class="headline pb-0">
Manage Team Members
</v-card-title>

<v-card-text>
<hr />

<v-row v-if="isDraft">
<v-col cols="9">
<form autocomplete="off">
<v-autocomplete
v-model="userSearchSelection"
clearable
dense
:filter="filterObject"
hide-details
:items="userSearchResults"
label="Enter a name, e-mail, or username"
:loading="isLoadingDropdown"
return-object
:search-input.sync="findUsers"
>
<!-- no data -->
<template #no-data>
<div class="px-2">
Can't find someone? They may not have joined the site.<br />
Kindly send them a link to the site and ask them to log
in.
</div>
</template>
<!-- selected user -->
<template #selection="data">
<span
v-bind="data.attrs"
:input-value="data.selected"
close
@click="data.select"
@click:close="remove(data.item)"
>
{{ data.item.fullName }}
</span>
</template>
<!-- users found in dropdown -->
<template #item="data">
<template v-if="typeof data.item !== 'object'">
<v-list-item-content v-text="data.item" />
</template>
<template v-else>
<v-list-item-content>
<v-list-item-title v-html="data.item.fullName" />
<v-list-item-subtitle v-html="data.item.username" />
<v-list-item-subtitle v-html="data.item.email" />
</v-list-item-content>
</template>
</template>
</v-autocomplete>
</form>
</v-col>
<v-col cols="3">
<v-btn
color="primary"
:disabled="!userSearchSelection"
:loading="isLoadingDropdown"
@click="addUser"
>
<span>Add</span>
</v-btn>
</v-col>
</v-row>
<div v-else>
You can only invite and manage team members while this form is a draft
</div>

<p class="mt-5">
<strong>Team members for this submission:</strong>
</p>

<v-skeleton-loader :loading="isLoadingTable" type="table-row">
<v-simple-table dense>
<template>
<thead>
<tr>
<th class="text-left">Name</th>
<th class="text-left">Username</th>
<th class="text-left">Email</th>
<th class="text-left" v-if="isDraft">Actions</th>
</tr>
</thead>
<tbody>
<tr :key="item.userId" v-for="item in userTableList">
<td>{{ item.fullName }}</td>
<td>{{ item.username }}</td>
<td>{{ item.email }}</td>
<td v-if="isDraft">
<v-btn
color="red"
icon
:disabled="item.isOwner"
@click="removeUser(item)"
>
<v-icon>remove_circle</v-icon>
</v-btn>
</td>
</tr>
</tbody>
</template>
</v-simple-table>
</v-skeleton-loader>
</v-card-text>

<v-card-actions class="justify-center">
<v-btn class="mb-5 close-dlg" color="primary" @click="dialog = false">
<span>Close</span>
</v-btn>
</v-card-actions>
</v-card>

<BaseDialog
v-model="showDeleteDialog"
type="CONTINUE"
@close-dialog="showDeleteDialog = false"
@continue-dialog="modifyPermissions(userToDelete.id, []); showDeleteDialog = false"
>
<template #title>Remove {{ userToDelete.username }}</template>
<template #text>
Are you sure you wish to remove
<strong>{{ userToDelete.username }}</strong
>? They will no longer have permissions for this submission.
</template>
<template #button-text-continue>
<span>Remove</span>
</template>
</BaseDialog>
</v-dialog>
</span>
</template>

<script>
import { mapActions } from 'vuex';

import { FormPermissions } from '@/utils/constants';
import { rbacService, userService } from '@/services';

export default {
name: 'ManageSubmissionUsers',
props: {
isDraft: {
type: Boolean,
required: true,
},
submissionId: {
type: String,
required: true,
},
},
data() {
return {
dialog: false,
isLoadingTable: true,
showDeleteDialog: false,
userTableList: [],
userToDelete: {},

// search box
findUsers: null,
isLoadingDropdown: false,
userSearchResults: [],
userSearchSelection: null,
};
},
methods: {
...mapActions('notifications', ['addNotification']),
// show users in dropdown that have a text match on multiple properties
addUser() {
if (this.userSearchSelection) {
const id = this.userSearchSelection.id;
if (this.userTableList.some((u) => u.id === id)) {
this.addNotification({
type: 'warning',
message: `User ${this.userSearchSelection.username} is already in the list of team members.`,
});
} else {
this.modifyPermissions(id, [
FormPermissions.SUBMISSION_UPDATE,
FormPermissions.SUBMISSION_READ,
]);
}
}
// reset search field
this.userSearchSelection = null;
},
filterObject(item, queryText) {
return Object.values(item).some((v) => v !== null && v.toLocaleLowerCase().includes(queryText.toLocaleLowerCase()));
},
async getSubmissionUsers() {
this.isLoadingTable = true;
try {
const response = await rbacService.getSubmissionUsers({
formSubmissionId: this.submissionId,
});
if (response.data) {
this.userTableList = this.transformResponseToTable(response.data);
}
} catch (error) {
this.addNotification({
message:
'An error occured while trying to fetch users for this submission.',
consoleError: `Error getting users for ${this.submissionId}: ${error}`,
});
} finally {
this.isLoadingTable = false;
}
},
async modifyPermissions(userId, permissions) {
this.isLoadingTable = true;
try {
// Add the selected user with read/update permissions on this submission
const response = await rbacService.setSubmissionUserPermissions(
{ permissions: permissions },
{
formSubmissionId: this.submissionId,
userId: userId,
}
);
if (response.data) {
this.userTableList = this.transformResponseToTable(response.data);
}
} catch (error) {
this.addNotification({
message:
'An error occured while trying to update users for this submission.',
consoleError: `Error setting user permissions. Sub: ${this.submissionId} User: ${userId} Error: ${error}`,
});
} finally {
this.isLoadingTable = false;
}
},
removeUser(userRow) {
this.userToDelete = userRow;
this.showDeleteDialog = true;
},
transformResponseToTable(responseData) {
return responseData
.map((su) => {
return {
email: su.user.email,
fullName: su.user.fullName,
id: su.userId,
isOwner: su.permissions.includes(
FormPermissions.SUBMISSION_CREATE
),
username: su.user.username,
};
})
.sort((a, b) => b.isOwner - a.isOwner);
},
},
watch: {
// Get a list of user objects from database
async findUsers(input) {
if (!input) return;
this.isLoadingDropdown = true;
try {
const response = await userService.getUsers({ search: input });
this.userSearchResults = response.data;
} catch (error) {
console.error(`Error getting users: ${error}`); // eslint-disable-line no-console
} finally {
this.isLoadingDropdown = false;
}
},
},
created() {
this.getSubmissionUsers();
},
};
</script>
5 changes: 2 additions & 3 deletions app/frontend/src/components/forms/submission/NotesPanel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,7 @@
<script>
import { mapActions, mapGetters } from 'vuex';
import formService from '@/services/formService';
import { rbacService } from '@/services';
import { formService, rbacService } from '@/services';
export default {
name: 'NotesPanel',
Expand Down Expand Up @@ -101,7 +100,7 @@ export default {
const user = await rbacService.getCurrentUser();
const body = {
note: this.newNote,
userId: user.data.id
userId: user.data.id,
};
const response = await formService.addNote(this.submissionId, body);
if (!response.data) {
Expand Down
27 changes: 26 additions & 1 deletion app/frontend/src/services/rbacService.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,5 +69,30 @@ export default {
*/
setUserForms(requestBody, params = {}) {
return appAxios().put(`${ApiRoutes.RBAC}/users`, requestBody, { params });
}
},

//
// Submission Management calls
//

/**
* @function getSubmissionUsers
* Get the list of associated users for a submission
* @param {Object} [params={}] The query parameters
* @returns {Promise} An axios response
*/
getSubmissionUsers(params = {}) {
return appAxios().get(`${ApiRoutes.RBAC}/submissions`, { params });
},

/**
* @function setFormUsers
* Set permissions for a user on the form
* @param {Object} requestBody The request body containing the permissions list
* @param {Object} [params={}] The query parameters
* @returns {Promise} An axios response
*/
setSubmissionUserPermissions(requestBody, params = {}) {
return appAxios().put(`${ApiRoutes.RBAC}/submissions`, requestBody, { params });
},
};
Loading

0 comments on commit 7d1ecad

Please sign in to comment.