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

Handle removing users #152

Merged
merged 4 commits into from
Mar 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,7 @@ default void insertOrUpdateSystemUser(long systemId, long userId, boolean isAdmi

@SqlUpdate("insert into users_deployable_systems (deployable_system_id, user_id, system_admin) values (:systemId, :userId, :admin)")
void addUserToSystem(@Bind("systemId") long systemId, @Bind("userId") long userId, @Bind("admin") boolean isAdmin);

@SqlUpdate("delete from users_deployable_systems where deployable_system_id = :systemId and user_id = :userId")
void deleteUserFromSystem(@Bind("systemId") long systemId, @Bind("userId") long userId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -158,12 +158,22 @@ public Response deleteSystem(@PathParam("id") Long id) {
}

@POST
@Path("/{id}/users")
@Path("/{id}/user")
@Timed
@ExceptionMetered
@RolesAllowed({ "admin" })
public Response addUsersToSystem(@PathParam("id") long systemId, List<SystemUser> users) {
users.forEach(user -> deployableSystemDao.insertOrUpdateSystemUser(systemId, user.getUserId(), user.isAdmin()));
public Response addUserToSystem(@PathParam("id") long systemId, SystemUser user) {
deployableSystemDao.insertOrUpdateSystemUser(systemId, user.getUserId(), user.isAdmin());
return Response.accepted().build();
}

@DELETE
@Path("/{systemId}/users/{userId}")
@Timed
@ExceptionMetered
@RolesAllowed({ "admin" })
public Response removeUserFromSystem(@PathParam("systemId") long systemId, @PathParam("userId") long userId) {
deployableSystemDao.deleteUserFromSystem(systemId, userId);
return Response.noContent().build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -291,5 +291,24 @@ void shouldUpdateUserSystemLinkWhenExists() {
assertThat(systemUsers).extracting("userId", "admin").contains(tuple(userId, false));
}
}

@Nested
class DeleteUserFromSystem {

@Test
void shouldDeleteUserSystemLink() {
var userId = insertUserRecord(handle, "jdoe");
var deployableSystemId = insertDeployableSystem(handle, "system1");
insertUserToDeployableSystemLink(handle, userId, deployableSystemId, true);

var systemUsers = dao.findUsersForSystem(deployableSystemId);
assertThat(systemUsers).hasSize(1);

dao.deleteUserFromSystem(deployableSystemId, userId);

systemUsers = dao.findUsersForSystem(deployableSystemId);
assertThat(systemUsers).isEmpty();
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -478,25 +478,44 @@ void shouldNotAuditIfDeleteDoesNotChangeDB() {
}

@Nested
class AddUsersToSystem {
class AddUserToSystem {

@Test
void shouldAddGivenUsersToGivenSystem() {
void shouldAddGivenUserToGivenSystem() {
var systemUser = DeployableSystem.SystemUser.builder()
.userId(2L)
.admin(false)
.build();

var token = generateJwt(true);
var response = RESOURCES.client().target("/systems/{id}/users")
var response = RESOURCES.client().target("/systems/{id}/user")
.resolveTemplate("id", 1L)
.request()
.cookie("sessionToken", token)
.post(json(List.of(systemUser)));
.post(json(systemUser));

assertAcceptedResponse(response);

verify(DEPLOYABLE_SYSTEM_DAO).insertOrUpdateSystemUser(1L, 2L, false);
}
}

@Nested
class RemoveUserFromSystem {

@Test
void shouldRemoveUserSystemLinkMatchingGivenSystemIdAndUserId() {
var token = generateJwt(true);
var response = RESOURCES.client().target("/systems/{systemId}/users/{userId}")
.resolveTemplate("systemId", 1L)
.resolveTemplate("userId", 1L)
.request()
.cookie("sessionToken", token)
.delete();

assertNoContentResponse(response);

verify(DEPLOYABLE_SYSTEM_DAO).deleteUserFromSystem(1L, 1L);
}
}
}
1 change: 1 addition & 0 deletions ui/.env
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export API_BASE_URL=http://localhost:8080/
export HMR_PORT=9000
chrisrohr marked this conversation as resolved.
Show resolved Hide resolved
1 change: 1 addition & 0 deletions ui/.env.gitpod
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export API_BASE_URL=`gp url 8080`
export HMR_PORT=443
9 changes: 7 additions & 2 deletions ui/quasar.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ module.exports = configure(function (ctx) {
},

env: {
API_BASE_URL: process.env.API_BASE_URL
API_BASE_URL: process.env.API_BASE_URL,
HMR_PORT: process.env.HMR_PORT
},

vueRouterMode: 'hash' // available values: 'hash', 'history'
Expand Down Expand Up @@ -90,7 +91,11 @@ module.exports = configure(function (ctx) {
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#devServer
devServer: {
// https: true
open: true // opens browser window automatically
open: true, // opens browser window automatically
hmr: {
// This port is used for the websocket during development for hot reloads. It is needed in case we are in a cloud IDE like gitpod
clientPort: process.env.HMR_PORT
}
},

// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#framework
Expand Down
165 changes: 81 additions & 84 deletions ui/src/pages/AdminSystemPage.vue
Original file line number Diff line number Diff line change
@@ -1,36 +1,59 @@
<template>
<q-page padding>
<q-table title="Deployable Systems" :columns="systemColumns" :rows="adminSystemStore.systems" :loading="adminSystemStore.loading" v-model:pagination="adminSystemStore.pagination" @request="adminSystemStore.load">

<template v-slot:body-cell-createdAt="props">
<q-td :props="props">
{{ fromNow(props.row.createdAt) }}
<q-tooltip class="bg-grey-5 text-black">
{{ formatDate(props.row.createdAt) }}
</q-tooltip>
</q-td>
</template>

<template v-slot:body-cell-updatedAt="props">
<q-td :props="props">
{{ fromNow(props.row.updatedAt) }}
<q-tooltip class="bg-grey-5 text-black">
{{ formatDate(props.row.updatedAt) }}
</q-tooltip>
</q-td>
<template v-slot:body="props">
<q-tr :props="props">
<q-td>
<q-btn round size="sm" color="primary" :icon="props.expand ? 'remove' : 'add'" @click="props.expand = !props.expand"/>
</q-td>
<q-td key="name" :props="props">
{{ props.row.name }}
</q-td>
<q-td key="createdAt" :props="props">
{{ fromNow(props.row.createdAt) }}
<q-tooltip class="bg-grey-5 text-black">
{{ formatDate(props.row.createdAt) }}
</q-tooltip>
</q-td>
<q-td key="updatedAt" :props="props">
{{ fromNow(props.row.updatedAt) }}
<q-tooltip class="bg-grey-5 text-black">
{{ formatDate(props.row.updatedAt) }}
</q-tooltip>
</q-td>
<q-td key="actions" :props="props">
<q-btn size="sm" icon="group_add" @click="startAssignUsers(props.row)" class="on-left">
<q-tooltip>Assign Users</q-tooltip>
</q-btn>
<q-btn size="sm" icon="delete" @click="confirmDelete(props.row)">
<q-tooltip>Delete System</q-tooltip>
</q-btn>
</q-td>
</q-tr>
<q-tr v-show="props.expand" :props="props">
<q-td colspan="100%">
<q-table :columns="systemUserCols" :rows="props.row.users" :pagination="userPagination" hide-pagination>
<template v-slot:body="users">
<q-tr>
<q-td key="username">
<q-icon name="fa-solid fa-crown" v-if="users.row.admin" class="text-yellow-14">
<q-tooltip class="bg-grey-5 text-black">
User is an Admin for this System
</q-tooltip>
</q-icon>
{{ userStore.userForId(users.row.userId).displayName }}
</q-td>
<q-td key="actions">
<q-btn size="sm" icon="delete" @click="removeUserFromSystem(props.row.id, users.row.userId)">
<q-tooltip>Remove User</q-tooltip>
</q-btn>
</q-td>
</q-tr>
</template>
</q-table>
</q-td>
</q-tr>
</template>

<template v-slot:body-cell-actions="props">
<q-td :props="props">
<q-btn size="sm" icon="group_add" @click="startAssignUsers(props.row)" class="on-left">
<q-tooltip>Assign Users</q-tooltip>
</q-btn>
<q-btn size="sm" icon="delete" @click="confirmDelete(props.row)">
<q-tooltip>Delete System</q-tooltip>
</q-btn>
</q-td>
</template>

</q-table>

<q-page-sticky position="bottom-right" :offset="[18, 18]">
Expand Down Expand Up @@ -63,31 +86,17 @@
<q-select
outlined
dense
label="Select a user"
style="min-width: 205px"
v-model="selectedUser"
:options="allUsers"/>
<q-btn label="Add User" @click="addUserToSystem" class="on-right" color="primary"/>
</q-card-section>
<q-card-section>
<q-table :rows="systemUsers.users" :columns="selectedUserCols" :pagination="{ rowsNumber: 2000 }" hide-pagination>
<template v-slot:body-cell-admin="props">
<q-td :props="props">
<q-checkbox v-model="props.row.admin"/>
</q-td>
</template>

<template v-slot:body-cell-action="props">
<q-td :props="props">
<q-btn size="sm" icon="delete" @click="removeSelectedUser(props.row)">
<q-tooltip>Remove User</q-tooltip>
</q-btn>
</q-td>
</template>
</q-table>
<q-checkbox v-model="selectedUserAdmin" label="System Admin?" dense/>
</q-card-section>
<q-card-actions align="right">
<q-btn flat v-close-popup>Cancel</q-btn>
<q-btn flat color="primary" @click="addUsersToSystem">Save</q-btn>
<q-btn flat color="primary" @click="addOrUpdateUserInSystem">Save</q-btn>
</q-card-actions>
</q-card>
</q-dialog>
Expand All @@ -112,18 +121,18 @@ const activeSystem = ref({
})
const showAssignUsers = ref(false)
const selectedUser = ref(null)
const systemUsers = ref({
systemId: null,
users: []
})
const selectedUserAdmin = ref(false)
const selectedSystem = ref(null)
const allUsers = ref([])

// Constant data
const systemColumns = [
{
name: 'expand'
},
{
name: 'name',
label: 'Name',
field: 'name',
align: 'left'
},
{
Expand All @@ -142,23 +151,21 @@ const systemColumns = [
align: 'left'
}
]
const selectedUserCols = [
const systemUserCols = [
{
name: 'username',
label: 'User',
field: 'displayName',
align: 'left'
},
{
name: 'admin',
label: 'Admin?',
label: 'User Name',
align: 'left'
},
{
name: 'action',
name: 'actions',
label: 'Actions',
align: 'left'
}
]
const userPagination = {
rowsPerPage: 1000
}

// Methods
function createSystem () {
Expand All @@ -178,43 +185,33 @@ function deleteSystem (id) {
}

function startAssignUsers (system) {
userStore.load({ pagination: { page: 1, rowsPerPage: 2000 } }).then(() => {
allUsers.value = userToOption(userStore.users)

systemUsers.value.systemId = system.id
populateNameOnExistingUsers(system.users)
systemUsers.value.users = system.users
showAssignUsers.value = true
})
allUsers.value = userToOption(userStore.users)
.filter(user => isNewUser(user, system.users))
selectedSystem.value = system.id
showAssignUsers.value = true
}

function userToOption (users) {
return _.sortBy(users.map(e => { return { label: e.displayName, value: e.id } }), ['label'])
}

function populateNameOnExistingUsers (users) {
users.forEach(user => {
user.displayName = _.find(allUsers.value, u => u.value === user.userId).label
})
}

function addUserToSystem () {
systemUsers.value.users.push({ userId: selectedUser.value.value, displayName: selectedUser.value.label, admin: false })
function isNewUser (user, systemUsers) {
return _.indexOf(systemUsers.map(u => u.id), user.value) === -1
}

function removeSelectedUser (selectedUser) {
systemUsers.value.users = _.without(systemUsers.value.users, selectedUser)
function addOrUpdateUserInSystem () {
adminSystemStore.assignUserToSystem(selectedSystem.value, { userId: selectedUser.value.value, admin: selectedUserAdmin.value })
.then(() => { showAssignUsers.value = false })
}

function addUsersToSystem () {
adminSystemStore.assignUsersToSystem(systemUsers.value.systemId, systemUsers.value.users)
.then(() => {
showAssignUsers.value = false
})
function removeUserFromSystem (systemId, userId) {
adminSystemStore.removeUserFromSystem(systemId, userId)
}

onMounted(() => {
adminSystemStore.load()
userStore.load({ pagination: { page: 1, rowsPerPage: 2000 } }).then(() => {
adminSystemStore.load()
})
})

</script>
11 changes: 8 additions & 3 deletions ui/src/stores/adminSystemStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,15 @@ export const useAdminSystemStore = defineStore('adminSystem', () => {
.then(() => load())
}

async function assignUsersToSystem (systemId, usersToSet) {
return api.post(`/systems/${systemId}/users`, usersToSet)
async function assignUserToSystem (systemId, userToSet) {
return api.post(`/systems/${systemId}/user`, userToSet)
.then(() => load())
}

return { systems, loading, load, create, deleteSystem, assignUsersToSystem }
function removeUserFromSystem (systemId, userId) {
api.delete(`/systems/${systemId}/users/${userId}`)
.then(() => load())
}

return { systems, loading, load, create, deleteSystem, assignUserToSystem, removeUserFromSystem }
})
Loading