Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/soft deletion ui #704

Merged
merged 26 commits into from
Nov 26, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
b3ed69e
implement new api and refactor user endpoints
bastihav Nov 17, 2020
f3d804b
add another test
bastihav Nov 17, 2020
8bb584a
add checkbox for adding inactive users to the account list
tobias-wiese Nov 17, 2020
f68693c
add inactive tags to public profiles
tobias-wiese Nov 17, 2020
1dc311b
add force delete
bastihav Nov 17, 2020
db5cb58
disable account editing on disabled account
tobias-wiese Nov 17, 2020
173c7cb
adapt user row, if user is inactive
tobias-wiese Nov 17, 2020
3b4594e
show additional lecturer information iff account is active
tobias-wiese Nov 17, 2020
e5b7fd6
Update src/api/api_models/user_management/User.ts
bastihav Nov 17, 2020
c45c57d
fix typo
bastihav Nov 17, 2020
5e4eeab
add softDeletion e2e test
tobias-wiese Nov 17, 2020
4632083
Merge branch 'develop' into feature/soft_deletion
bastihav Nov 25, 2020
ed3cd21
Merge branch 'feature/soft_deletion' into feature/soft_deletion_ui
tobias-wiese Nov 25, 2020
b275cd6
Merge branch 'develop' into feature/soft_deletion_ui
bastihav Nov 26, 2020
25a3b26
Merge branch 'develop' into feature/soft_deletion
bastihav Nov 26, 2020
db5b1d9
update other tests to force delete
bastihav Nov 26, 2020
772cf38
fix tests
bastihav Nov 26, 2020
1f0dae0
update changelog
bastihav Nov 26, 2020
af06043
Merge branch 'feature/soft_deletion' into feature/soft_deletion_ui
bastihav Nov 26, 2020
2269363
fix ui oversights
bastihav Nov 26, 2020
1ee5299
update changelog
bastihav Nov 26, 2020
a77459c
rename parameter
bastihav Nov 26, 2020
e70c9b9
Merge branch 'feature/soft_deletion' into feature/soft_deletion_ui
bastihav Nov 26, 2020
e7ff697
rename parameter
bastihav Nov 26, 2020
8eb2c69
Merge branch 'feature/soft_deletion' into feature/soft_deletion_ui
bastihav Nov 26, 2020
212cce7
fix tests
bastihav Nov 26, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## Feature
- added governmentId to user creation [#701](https://github.com/upb-uc4/ui-web/pull/701)
- add soft deletion of users [#702](https://github.com/upb-uc4/ui-web/pull/702), [#704](https://github.com/upb-uc4/ui-web/pull/704)

## Refactoring
- Merge endpoints for user creation [#700](https://github.com/upb-uc4/ui-web/pull/700)
Expand Down
104 changes: 26 additions & 78 deletions src/api/UserManagement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,20 @@ export default class UserManagement extends Common {
return super.getVersion("/user-management");
}

async getAllUsers(): Promise<APIResponse<User_List>> {
async getUsers(
role?: Role,
usernames?: string[],
is_active?: boolean
): Promise<APIResponse<User_List | Student[] | Lecturer[] | Admin[]>> {
const requestParameter = { params: {} as any };
let endpoint = "/users";

if (usernames) requestParameter.params.usernames = usernames.reduce((a, b) => a + "," + b, "");
if (is_active) requestParameter.params.is_active = is_active;
if (role) endpoint = UserManagement._createEndpointByRole(role);

return await this._axios
.get("/users")
.get(endpoint, requestParameter)
.then((response: AxiosResponse) => {
return {
returnValue: response.data,
Expand All @@ -41,7 +52,7 @@ export default class UserManagement extends Common {
networkError: false,
})
) {
return await this.getAllUsers();
return await this.getUsers(role, usernames, is_active);
}
return {
returnValue: {} as User_List,
Expand All @@ -60,69 +71,6 @@ export default class UserManagement extends Common {
});
}

async getUsers(...usernames: string[]): Promise<APIResponse<User_List>> {
let resp = await this._getByUsername(usernames, "/users");
return resp as APIResponse<User_List>;
}

async getStudents(...usernames: string[]): Promise<APIResponse<Student[]>> {
let resp = await this._getByUsername(usernames, "/students");
return resp as APIResponse<Student[]>;
}

async getLecturers(...usernames: string[]): Promise<APIResponse<Lecturer[]>> {
let resp = await this._getByUsername(usernames, "/lecturers");
return resp as APIResponse<Lecturer[]>;
}

async getAdmins(...usernames: string[]): Promise<APIResponse<Admin[]>> {
let resp = await this._getByUsername(usernames, "/admins");
return resp as APIResponse<Admin[]>;
}

async _getByUsername(usernames: string[], endpoint: string): Promise<APIResponse<User_List | Student[] | Lecturer[] | Admin[]>> {
const requestParameter = { params: {} as any };
requestParameter.params.usernames = usernames.reduce((a, b) => a + "," + b, "");

return await this._axios
.get(endpoint, requestParameter)
.then((response: AxiosResponse) => {
return {
returnValue: response.data,
statusCode: response.status,
error: {} as APIError,
networkError: false,
};
})
.catch(async (error: AxiosError) => {
if (error.response) {
if (
await handleAuthenticationError({
statusCode: error.response.status,
error: error.response.data as APIError,
returnValue: {} as User_List,
networkError: false,
})
) {
return await this._getByUsername(usernames, endpoint);
}
return {
returnValue: {} as User_List,
statusCode: error.response.status,
error: error.response.data as APIError,
networkError: false,
};
} else {
return {
returnValue: {} as User_List,
statusCode: 0,
error: {} as APIError,
networkError: true,
};
}
});
}

async deleteUser(username: string): Promise<APIResponse<boolean>> {
return await this._axios
.delete(`/users/${username}`)
Expand Down Expand Up @@ -163,13 +111,12 @@ export default class UserManagement extends Common {
});
}

async getAllUsersByRole(role: Role): Promise<APIResponse<Student[] | Lecturer[] | Admin[]>> {
let endpoint = UserManagement._createEndpointByRole(role);
async forceDeleteUser(username: string): Promise<APIResponse<boolean>> {
return await this._axios
.get(endpoint)
.delete(`/users/${username}/force`)
.then((response: AxiosResponse) => {
return {
returnValue: response.data,
returnValue: true,
statusCode: response.status,
error: {} as APIError,
networkError: false,
Expand All @@ -185,17 +132,17 @@ export default class UserManagement extends Common {
networkError: false,
})
) {
return await this.getAllUsersByRole(role);
return await this.forceDeleteUser(username);
}
return {
returnValue: [],
returnValue: false,
statusCode: error.response.status,
error: error.response.data as APIError,
networkError: false,
};
} else {
return {
returnValue: [],
returnValue: false,
statusCode: 0,
error: {} as APIError,
networkError: true,
Expand Down Expand Up @@ -364,19 +311,20 @@ export default class UserManagement extends Common {
let endpoint = "";
switch (role) {
case Role.STUDENT: {
endpoint += "/students";
endpoint = "/students";
break;
}
case Role.LECTURER: {
endpoint += "/lecturers";
endpoint = "/lecturers";
break;
}
case Role.ADMIN: {
endpoint += "/admins";
endpoint = "/admins";
break;
}
case Role.NONE: {
new Error("Endpoint undefined");
default: {
endpoint = "/users";
break;
}
}
return endpoint;
Expand Down
1 change: 1 addition & 0 deletions src/api/api_models/user_management/User.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ export default interface User {
birthDate: string;
phoneNumber: string;
enrollmentIdSecret: string;
isActive: boolean;
}
15 changes: 14 additions & 1 deletion src/components/account/list/AccountList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@
type: String,
required: true,
},
showInactive: {
type: Boolean,
required: true,
},
},
setup(props: any) {
let busy = ref(false);
Expand All @@ -43,12 +47,21 @@
await getUsers();
});

watch(
() => props.showInactive,
() => {
getUsers();
}
);

async function getUsers() {
busy.value = true;
const userManagement: UserManagement = new UserManagement();

const genericResponseHandler = new GenericResponseHandler("users");
const response = await userManagement.getAllUsers();
const response = props.showInactive
? await userManagement.getUsers()
: await userManagement.getUsers(undefined, undefined, true);
const userLists = genericResponseHandler.handleResponse(response);
users.value = Object.values(userLists).flat();
busy.value = false;
Expand Down
11 changes: 11 additions & 0 deletions src/components/account/list/RoleFilter.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,16 @@
{{ vrole }}
</button>
</div>
<div class="flex w-full ml-3 items-center justify-end">
<input id="toggleInactive" v-model="showInactiveAccounts" type="checkbox" class="m-2" />
<label class="text-sm text-gray-700">inactive</label>
</div>
</div>
</template>
<script lang="ts">
import { Role } from "@/entities/Role";
import { ref } from "vue";
import { useModelWrapper } from "@/use/helpers/ModelWrapper";

export default {
name: "RoleFilter",
Expand All @@ -26,6 +32,10 @@
type: String,
required: true,
},
showInactive: {
type: Boolean,
default: false,
},
},
emits: ["update:selectedRole"],
setup(props: any, { emit }: any) {
Expand All @@ -39,6 +49,7 @@
return {
roles,
select,
showInactiveAccounts: useModelWrapper(props, emit, "showInactive"),
};
},
};
Expand Down
22 changes: 16 additions & 6 deletions src/components/account/list/UserRow.vue
Original file line number Diff line number Diff line change
@@ -1,16 +1,27 @@
<template>
<div
:id="'user_' + user.username"
class="px-6 py-4 whitespace-no-wrap border-gray-200 cursor-pointer hover:bg-gray-200"
:class="{ 'rounded-t-lg': isFirstRow, 'rounded-b-lg': isLastRow, 'border-b': !isLastRow }"
class="px-6 py-4 whitespace-no-wrap border-gray-200"
:class="{
'rounded-t-lg': isFirstRow,
'rounded-b-lg': isLastRow,
'border-b': !isLastRow,
'cursor-pointer hover:bg-gray-200': user.isActive,
}"
@click="editAccount(user.username)"
>
<div class="flex items-center">
<div class="w-full flex justify-between">
<div class="flex items-center">
<img class="hidden sm:block w-12 h-12 rounded-full" :src="profilePicture" alt="profile_picture" />
<div class="sm:ml-4">
<div class="text leading-5 font-medium text-blue-900 mb-1 truncate">{{ user.firstName }} {{ user.lastName }}</div>
<div v-if="user.isActive" class="mb-1 truncate">
<label class="text leading-5 font-medium text-blue-900 mr-2">{{ user.firstName }} {{ user.lastName }}</label>
</div>
<div v-if="!user.isActive" class="mb-1 truncate">
<label v-if="isLecturer" class="text leading-5 font-medium text-blue-900 mr-2">{{ user.firstName }} {{ user.lastName }}</label>
<label class="text-gray-600 italic">(inactive)</label>
</div>
<div class="hidden sm:flex text leading-5 text-gray-500 truncate">@{{ user.username }}</div>
<span
class="sm:hidden inline-block text-xs px-2 rounded-lg font-semibold leading-5 tracking-wide mb-1 w-16 text-center"
Expand Down Expand Up @@ -39,7 +50,7 @@
</span>
</div>

<div class="flex-col hidden sm:flex items-baseline" :class="[isStudent ? 'sm:flex' : 'sm:invisible']">
<div class="flex-col hidden sm:flex items-baseline" :class="[isStudent && user.isActive ? 'sm:flex' : 'sm:invisible']">
<div class="leading-5 text-blue-900 ml-1 mb-1">{{ student.matriculationId }}</div>
<div class="hidden sm:flex items-center leading-5 text-gray-500">
<span class="mr-2 fa-stack text-xs" style="font-size: 0.63em">
Expand Down Expand Up @@ -86,13 +97,12 @@
setup(props: any) {
let profilePicture = ref("");
function editAccount(username: string) {
router.push({ path: "/editAccount/" + username });
if (props.user.isActive) router.push({ path: "/editAccount/" + username });
}
const isStudent = props.user.role === Role.STUDENT;
const isLecturer = props.user.role === Role.LECTURER;
const isAdmin = props.user.role === Role.ADMIN;
const student = props.user as Student;

profilePicture.value = process.env.VUE_APP_API_BASE_URL + "/user-management/users/" + props.user.username + "/thumbnail?";

return { editAccount, isStudent, isLecturer, isAdmin, student, profilePicture };
Expand Down
2 changes: 1 addition & 1 deletion src/components/course/edit/sections/LecturerSection.vue
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@
async function getLecturers() {
const userManagement: UserManagement = new UserManagement();
const handler = new GenericResponseHandler("lecturers");
const response = await userManagement.getAllUsersByRole(Role.LECTURER);
const response = await userManagement.getUsers(Role.LECTURER);
const result = handler.handleResponse(response);
if (result) {
lecturers.value = result as Lecturer[];
Expand Down
4 changes: 2 additions & 2 deletions src/components/course/list/common/CourseList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,8 @@
courses.value = genericResponseHandler.handleResponse(response);
}
const lecturerIds = new Set(courses.value.map((course) => course.lecturerId));
const resp = await userManagement.getLecturers(...lecturerIds);
lecturers.value = genericResponseHandler.handleResponse(resp);
const resp = await userManagement.getUsers(Role.LECTURER, [...lecturerIds]);
lecturers.value = genericResponseHandler.handleResponse(resp) as Lecturer[];
busy.value = false;
}

Expand Down
1 change: 1 addition & 0 deletions src/entities/AdminEntity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export default class AdminEntity implements Admin {
birthDate = "";
phoneNumber = "";
enrollmentIdSecret = "";
isActive = true;

constructor() {}
}
1 change: 1 addition & 0 deletions src/entities/LecturerEntity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export default class LecturerEntity implements Lecturer {
birthDate = "";
phoneNumber = "";
enrollmentIdSecret = "";
isActive = true;

constructor() {}
}
1 change: 1 addition & 0 deletions src/entities/StudentEntity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@ export default class StudentEntity implements Student {
birthDate = "";
phoneNumber = "";
enrollmentIdSecret = "";
isActive = true;
constructor() {}
}
1 change: 1 addition & 0 deletions src/entities/UserEntity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ export default class UserEntity implements User {
birthDate = "";
phoneNumber = "";
enrollmentIdSecret = "";
isActive = true;
}
7 changes: 5 additions & 2 deletions src/views/admin/AdminAccountList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@
</div>
</div>

<role-filter v-model:selected-role="selectedRole" class="w-full my-4" />
<role-filter v-model:selected-role="selectedRole" v-model:show-inactive="showInactive" class="w-full my-4" />

<accountList :key="refreshKey" :selected-role="selectedRole" :filter="message" />
<accountList :key="refreshKey" :selected-role="selectedRole" :filter="message" :show-inactive="showInactive" />
<div class="flex justify-center mt-16">
<router-link to="/createAccount">
<button id="addAccount" title="Add a new User" class="px-4 btn btn-green-primary-500">New Account</button>
Expand Down Expand Up @@ -62,6 +62,8 @@

let selectedRole = ref("All" as Role);

let showInactive = ref(false);

function refresh() {
refreshKey.value = !refreshKey.value;
}
Expand All @@ -70,6 +72,7 @@
refresh,
message,
selectedRole,
showInactive,
};
},
};
Expand Down
3 changes: 2 additions & 1 deletion src/views/lecturer/PublicProfile.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@
<div class="flex items-center mt-12">
<img id="picture" class="w-32 h-32 mb-4 rounded-full object-cover mx-16 border-4 border-blue-700" :src="profilePicture" />
<div class="flex flex-col">
<label v-if="!lecturer.isActive" class="text-md italic text-red-400">(inactive)</label>
<h1 class="text-3xl font-medium text-gray-700">
{{ lecturer.firstName + " " + lecturer.lastName }}
<span class="text-xl"> (@{{ lecturer.username }}) </span>
</h1>
<h2 class="text-xl text-gray-700 text-blue-700 italic">{{ lecturer.role }}</h2>
</div>
</div>
<div class="flex flex-col mx-64">
<div v-if="lecturer.isActive" class="flex flex-col mx-64">
<div class="w-full mr-12 flex flex-col mb-4">
<div class="flex mb-2 align-baseline">
<label class="block text-gray-700 text-lg font-medium">Research Area</label>
Expand Down
Loading