Commits (3)
This diff is collapsed.
{
"name": "@coscine/usermanagement",
"version": "1.3.0",
"version": "1.4.0",
"private": true,
"directories": {
"doc": "docs"
......@@ -12,11 +12,11 @@
"test:unit": "vue-cli-service test:unit"
},
"dependencies": {
"@coscine/api-connection": "^1.5.0",
"@coscine/app-util": "^1.1.0",
"@types/jquery": "^3.3.31",
"@coscine/api-connection": "^1.8.0",
"@coscine/app-util": "^1.2.0",
"@types/jquery": "^3.3.32",
"@types/vue-select": "^2.5.0",
"bootstrap-vue": "^2.2.1",
"bootstrap-vue": "^2.4.2",
"jquery": "^3.4.1",
"vue": "^2.6.11",
"vue-i18n": "^8.15.3",
......@@ -24,23 +24,23 @@
"vue-select": "^3.4.0"
},
"devDependencies": {
"@semantic-release/commit-analyzer": "^6.3.3",
"@semantic-release/git": "^7.0.18",
"@semantic-release/gitlab": "^4.1.0",
"@semantic-release/npm": "^5.3.5",
"@semantic-release/release-notes-generator": "^7.3.5",
"@types/chai": "^4.2.7",
"@types/mocha": "^5.2.7",
"@vue/cli-plugin-babel": "^4.1.2",
"@vue/cli-plugin-typescript": "^4.1.2",
"@vue/cli-plugin-unit-mocha": "^4.1.2",
"@vue/cli-service": "^4.1.2",
"@vue/test-utils": "1.0.0-beta.30",
"@semantic-release/commit-analyzer": "^8.0.1",
"@semantic-release/git": "^9.0.0",
"@semantic-release/gitlab": "^6.0.2",
"@semantic-release/npm": "^7.0.3",
"@semantic-release/release-notes-generator": "^9.0.0",
"@types/chai": "^4.2.9",
"@types/mocha": "^7.0.1",
"@vue/cli-plugin-babel": "^4.2.2",
"@vue/cli-plugin-typescript": "^4.2.2",
"@vue/cli-plugin-unit-mocha": "^4.2.2",
"@vue/cli-service": "^4.2.2",
"@vue/test-utils": "1.0.0-beta.31",
"chai": "^4.2.0",
"typescript": "^3.7.4",
"typescript": "^3.7.5",
"vue-template-compiler": "^2.6.11",
"semantic-release": "^15.14.0",
"@hutson/semantic-delivery-gitlab": "^9.0.8"
"semantic-release": "^17.0.3",
"@hutson/semantic-delivery-gitlab": "^9.1.0"
},
"repository": {
"type": "git",
......
......@@ -8,6 +8,7 @@
<script lang="ts">
import Vue from 'vue';
import UserManagement from './components/UserManagement.vue';
import { GuidUtil } from '@coscine/app-util';
export default Vue.extend({
name: 'users',
......@@ -17,16 +18,8 @@ export default Vue.extend({
projectId: '',
};
},
methods: {
getProjectId() {
const baseUrl = (_spPageContextInfo as any).siteAbsoluteUrl;
const splitUrl = baseUrl.split('/');
const projectUri = splitUrl[splitUrl.length - 1];
return projectUri;
},
},
beforeMount() {
this.projectId = this.getProjectId();
created() {
this.projectId = GuidUtil.getProjectId();
},
components: {
UserManagement,
......
<template>
<div class="UserManagement">
<div class="UserManagement">
<b-container>
<b-row align-h="between">
<b-row align-h="between">
<b-col md="6" align-self="start">
<div class="adaptAlign">
<b-row>
<b-col sm="5" @keydown.enter.prevent.self="">
<v-select v-model="searchString" :class="{'adaptSelect':true, 'no-results':(queriedUsers.length === 0)}" :placeholder="$t('searchUserPlaceholder')" :options="queriedUsers" :filterBy="filterMock" @change="validateSelection()" @input="setNewRole" @search="fetchUserOptions">
<v-select v-model="searchString" :class="{'adaptSelect':true, 'no-results':(queriedUsers.length === 0)}" :placeholder="$t('searchUserPlaceholder')" :options="queriedUsers" :filterBy="filterMock" @change="validateSelection()" @input="setNewRole" @search="triggerFetchOptions" :selectable="option => !option.hasProjectRole">
<template v-if="searchString !== ''" slot="no-options">
{{ $t('noUserOptions') }}
</template>
......@@ -15,7 +15,7 @@
</template>
<template slot="option" slot-scope="option">
<div class="d-center">
{{ option.displayName }}
{{ option.displayName }} <span v-if=option.hasProjectRole>{{ $t('alreadyGotRole') }}</span>
</div>
</template>
<template slot="selected-option" slot-scope="option">
......@@ -30,7 +30,7 @@
v-model="newUserRole.role.id"
:options="roles"
@change="validateSelection()"
@input="validateSelection()">
@input="validateSelection()">
</b-form-select>
</b-col>
<b-col sm="4">
......@@ -46,15 +46,15 @@
type="search"
id="filterInput"
:placeholder="$t('typeToSearch')"
></b-form-input>
></b-form-input>
</b-input-group>
</b-col>
</b-row>
<br>
<b-row>
<b-col>
<b-table
id="userTable"
<b-table
id="userTable"
:fields="headers"
:items="projectRoles"
:busy="isBusy"
......@@ -71,7 +71,7 @@
striped
bordered
outlined
hover
hover
head-variant="dark"
class="adaptTable"
@filtered="onFiltered"
......@@ -80,7 +80,7 @@
<b-spinner class="align-middle"></b-spinner>
<strong style="margin-left: 1%;">{{ $t('loading') }}</strong>
</div>
<template v-slot:cell(roleName)="row">
<template v-slot:cell(roleName)="row">
<b-form-select
v-model="row.item.role.id"
:options="roles"
......@@ -92,19 +92,19 @@
<button type="button" class="btn btn-secondary deleteUser" :disabled="!row.item.enabledControls" name="deleteUser" v-on:click="prepareDeletion(row.item)">{{ $t('deleteUser') }}</button>
</template>
</b-table>
</b-col>
</b-col>
</b-row>
<b-row>
<b-col id="perPageSelection" sm="2">
<b-col id="perPageSelection" sm="2">
<label for="perPageSelect">{{ $t('perPage') }}</label>
<b-form-select
<b-form-select
v-model="perPage"
id="perPageSelect"
size="sm"
:options="pageOptions"
></b-form-select>
</b-col>
<b-col v-if="filteredRows > perPage" offset-sm="7" sm="3">
<b-col v-if="filteredRows > perPage" offset-sm="7" sm="3">
<b-pagination
v-model="currentPage"
:total-rows="filteredRows"
......@@ -132,8 +132,11 @@
<script lang="ts">
import Vue from 'vue';
import { RoleApi, UserApi, ProjectRoleApi, ProjectApi, defaultOnCatch } from '@coscine/api-connection';
import { LanguageUtil } from '@coscine/app-util';
import vSelect from 'vue-select';
import 'vue-select/dist/vue-select.css';
import { ToastPlugin } from 'bootstrap-vue';
Vue.use(ToastPlugin);
Vue.component('v-select', vSelect);
......@@ -141,11 +144,9 @@ export default Vue.extend({
name: 'UserManagement',
created() {
ProjectApi.getProjectInformation(this.parentId,
(response: any) => {this.projectName = response.data.displayName; },
defaultOnCatch);
(response: any) => {this.projectName = response.data.displayName; });
},
mounted() {
// TODO: Add catch and do something on failure
RoleApi.getRoles(
(response: any) => { for (const datum of response.data) {
this.roles.push({ value: datum.id, text: datum.displayName });
......@@ -154,9 +155,7 @@ export default Vue.extend({
this.newUserRole.role.id = this.roleDefaultId;
}
}
},
defaultOnCatch,
);
});
this.getProjectRoles();
},
data() {
......@@ -214,6 +213,7 @@ export default Vue.extend({
},
},
queriedUsers: [] as object[],
queryTimer: 0,
searchString: '',
selectionIsValid: false,
ownerCount: 0,
......@@ -254,6 +254,15 @@ export default Vue.extend({
this.newUserRole.user = value;
this.validateSelection();
},
triggerFetchOptions(search: string, loading: (value: boolean) => {}) {
clearTimeout(this.queryTimer);
if (search.length < 3) {
this.queriedUsers = [];
return;
}
this.queryTimer = setTimeout(() => (this.fetchUserOptions(search, loading)), 1000)
;
},
fetchUserOptions(search: string, loading: (value: boolean) => {}) {
if (search !== '') {
loading(true);
......@@ -308,9 +317,20 @@ export default Vue.extend({
}) {
projectRole.projectId = this.parentId;
projectRole.role.displayName = projectRole.role.displayName === 'Member' ? 'Owner' : 'Member';
let text = '';
if (LanguageUtil.getLanguage() === 'de') {
text = this.$t('changedRole1') + projectRole.user.displayName +
this.$t('changedRole2') + this.getRoleNameFromId(projectRole.role.id) +
this.$t('changedRole3') + this.projectName;
} else {
text = this.$t('changedRole1') + projectRole.user.displayName
+ this.$t('changedRole2') + this.projectName + this.$t('changedRole3')
+ projectRole.role.displayName;
}
ProjectRoleApi.setProjectRole(
projectRole,
(response: any) => {
this.makeToast(text, this.$t('userManagement').toString());
if (projectRole.role.displayName === 'Owner') {
this.ownerCount++;
} else {
......@@ -323,20 +343,41 @@ export default Vue.extend({
((currentProjectRole as any).role.displayName !== 'Owner' || this.ownerCount > 1));
}
}
},
defaultOnCatch);
});
},
getRoleNameFromId(roleId: string) {
for (const role of this.roles) {
if ((role as any).value === roleId) {
return (role as any).text;
}
}
},
addUser(projectRole: {
projectId: string,
user: { id: string, displayName: string, givenname: string, surname: string, emailAddress: string },
role: { id: string, displayName: string },
}) {
this.selectionIsValid = false;
this.searchString = '';
projectRole.projectId = this.parentId;
let text = '';
if (LanguageUtil.getLanguage() === 'de') {
text = this.$t('addedUser1') + projectRole.user.displayName +
this.$t('addedUser2') + this.getRoleNameFromId(projectRole.role.id) +
this.$t('addedUser3') + this.projectName + this.$t('addedUser4');
} else {
text = this.$t('addedUser1') + projectRole.user.displayName +
this.$t('addedUser2') + this.projectName +
this.$t('addedUser3') + this.getRoleNameFromId(projectRole.role.id) + this.$t('addedUser4');
}
ProjectRoleApi.setProjectRole(
projectRole,
(response: any) => { this.getProjectRoles(); },
defaultOnCatch);
(response: any) => {
this.getProjectRoles();
this.makeToast(text, this.$t('userManagement').toString());
this.queriedUsers = [];
this.newUserRole.user.id = '';
});
},
prepareDeletion(projectRole: {
projectId: string,
......@@ -347,11 +388,15 @@ export default Vue.extend({
this.$bvModal.show('deleteUserModal');
},
deleteUser() {
const text = this.$t('removedUser1') + this.candidateForDeletion.user.displayName +
this.$t('removedUser2') + this.projectName + this.$t('removedUser3');
ProjectRoleApi.deleteUser(
this.candidateForDeletion.projectId,
this.candidateForDeletion.user.id,
this.candidateForDeletion.role.id,
(response: any) => { this.getProjectRoles(); },
(response: any) => {
this.getProjectRoles();
this.makeToast(text, this.$t('userManagement').toString()); },
defaultOnCatch);
},
filterMock(option: any, label: any, search: string) {
......@@ -361,6 +406,17 @@ export default Vue.extend({
this.filteredRows = filteredItems.length;
this.currentPage = 1;
},
makeToast(text: string = 'Message', givenTitle: string = 'Title') {
this.$bvToast.toast(
text,
{
title: givenTitle,
autoHideDelay: 5000,
toaster: 'b-toaster-bottom-right',
noCloseButton: true,
},
);
},
},
});
</script>
......@@ -378,7 +434,7 @@ export default Vue.extend({
}
.UserManagement .adaptAlign .col-sm-5 .no-results .vs__actions {
display: none;
}
}
.UserManagement .adaptAlign .col-sm-4 {
padding-right: 4px;
padding-left: 0px;
......@@ -390,15 +446,15 @@ export default Vue.extend({
.UserManagement .col-sm-3 {
padding-right: 4px;
padding-left: 0px;
text-align: left;
text-align: left;
}
.UserManagement .col-sm-3 #perPageSelect{
padding-right: 4px;
text-align: left;
margin-left: 0px;
padding-left: 0px;
padding-left: 0px;
}
.UserManagement .adaptTable .deleteUser:hover{
background-color: #9b0517;
......
......@@ -13,6 +13,7 @@ export default {
clear: 'Leeren',
perPage: 'Pro Seite',
searchUserPlaceholder: 'Nutzer suchen...',
alreadyGotRole: '(bereits Teilnehmer)',
selectRolePlaceholder: 'Rolle auswählen',
firstName: 'Vorname',
lastName: 'Nachname',
......@@ -28,4 +29,14 @@ export default {
cancel: 'Abbrechen',
emptyTableText: 'Keine Nutzer gefunden.',
emptyFilterText: 'Keine Nutzer gefunden die mit Ihrer Anfrage übereinstimmen.',
addedUser1: '',
addedUser2: ' als ',
addedUser3: ' zu ',
addedUser4: ' hinzugefügt',
removedUser1: '',
removedUser2: ' von ',
removedUser3: ' gelöscht',
changedRole1: '',
changedRole2: ' ist nun ',
changedRole3: ' in ',
};
......@@ -13,6 +13,7 @@ export default {
clear: 'Clear',
perPage: 'Per page',
searchUserPlaceholder: 'Search a user...',
alreadyGotRole: '(Already added)',
selectRolePlaceholder: 'Select Role',
firstName: 'First Name',
lastName: 'Last Name',
......@@ -28,4 +29,14 @@ export default {
cancel: 'Cancel',
emptyTableText: 'No user found.',
emptyFilterText: 'No user found matching your request.',
addedUser1: 'Added ',
addedUser2: ' to ',
addedUser3: ' as ',
addedUser4: '',
removedUser1: 'Removed ',
removedUser2: ' from ',
removedUser3: '',
changedRole1: 'Changed ',
changedRole2: ' in ',
changedRole3: ' to ',
};