Skip to content

Commit

Permalink
Add role filter to users view
Browse files Browse the repository at this point in the history
  • Loading branch information
JammingBen committed Feb 23, 2023
1 parent 1af5150 commit 2f2007c
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 41 deletions.
5 changes: 5 additions & 0 deletions changelog/unreleased/enhancement-user-role-filter
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Enhancement: User role filter

Users in the users list can now be filtered by their role assignments.

https://github.com/owncloud/web/pull/8492
69 changes: 59 additions & 10 deletions packages/web-app-admin-settings/src/views/Users.vue
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
:allow-multiple="true"
display-name-attribute="displayName"
:filterable-attributes="['displayName']"
class="oc-mr-s"
@selection-change="filterGroups"
>
<template #image="{ item }">
Expand All @@ -71,6 +72,23 @@
<div v-text="item.displayName" />
</template>
</item-filter>
<item-filter
filter-name="roles"
:filter-label="$gettext('Roles')"
:items="roles"
:show-filter="true"
:allow-multiple="true"
display-name-attribute="displayName"
:filterable-attributes="['displayName']"
@selection-change="filterRoles"
>
<template #image="{ item }">
<avatar-image :width="32" :userid="item.id" :user-name="item.displayName" />
</template>
<template #item="{ item }">
<div v-text="item.displayName" />
</template>
</item-filter>
</div>
</template>
</UsersList>
Expand Down Expand Up @@ -132,6 +150,7 @@ import { toRaw } from 'vue'
import { SpaceResource } from 'web-client/src'
import { useGettext } from 'vue3-gettext'
import { diff } from 'deep-object-diff'
import { format } from 'util'
export default defineComponent({
name: 'UsersView',
Expand Down Expand Up @@ -169,7 +188,18 @@ export default defineComponent({
let loadResourcesEventToken
let userUpdatedEventToken
const groupFilterParam = useRouteQuery('q_groups')
const filters = {
groups: {
param: useRouteQuery('q_groups'),
query: `memberOf/any(m:m/id eq '%s')`,
ids: ref([])
},
roles: {
param: useRouteQuery('q_roles'),
query: `appRoleAssignments/any(m:m/appRoleId eq '%s')`,
ids: ref([])
}
}
const loadGroupsTask = useTask(function* (signal) {
const groupsResponse = yield unref(graphClient).groups.listGroups()
Expand All @@ -181,14 +211,25 @@ export default defineComponent({
roles.value = applicationsResponse.data.value[0].appRoles
})
const loadUsersTask = useTask(function* (signal, groupIds) {
const groupFilter = groupIds?.map((id) => `memberOf/any(m:m/id eq '${id}')`).join(' and ')
const usersResponse = yield unref(graphClient).users.listUsers('displayName', groupFilter)
const loadUsersTask = useTask(function* (signal) {
const filter = Object.values(filters)
.reduce((acc, f) => {
acc.push(
unref(f.ids)
.map((id) => format(f.query, id))
.join(' and ')
)
return acc
}, [])
.filter(Boolean)
.join(' and ')
const usersResponse = yield unref(graphClient).users.listUsers('displayName', filter)
users.value = usersResponse.data.value || []
})
const loadResourcesTask = useTask(function* (signal, groupIds = null) {
yield loadUsersTask.perform(groupIds)
const loadResourcesTask = useTask(function* (signal) {
yield loadUsersTask.perform()
yield loadGroupsTask.perform()
yield loadAppRolesTask.perform()
})
Expand All @@ -212,8 +253,12 @@ export default defineComponent({
})
const filterGroups = (groups) => {
const groupIds = groups.map((g) => g.id)
loadUsersTask.perform(groupIds)
filters.groups.ids.value = groups.map((g) => g.id)
loadUsersTask.perform()
}
const filterRoles = (roles) => {
filters.roles.ids.value = roles.map((r) => r.id)
loadUsersTask.perform()
}
const selectedPersonalDrives = ref([])
Expand Down Expand Up @@ -261,8 +306,11 @@ export default defineComponent({
})
onMounted(async () => {
const groupFilterIds = queryItemAsString(unref(groupFilterParam))?.split('+')
await loadResourcesTask.perform(groupFilterIds)
for (const f in filters) {
filters[f].ids.value = queryItemAsString(unref(filters[f].param))?.split('+') || []
}
await loadResourcesTask.perform()
loadResourcesEventToken = eventBus.subscribe('app.admin-settings.list.load', () => {
loadResourcesTask.perform()
selectedUsers.value = []
Expand Down Expand Up @@ -308,6 +356,7 @@ export default defineComponent({
createUserModalOpen,
batchActions,
filterGroups,
filterRoles,
quotaModalIsOpen,
closeQuotaModal,
spaceQuotaUpdated,
Expand Down
100 changes: 69 additions & 31 deletions packages/web-app-admin-settings/tests/unit/views/Users.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@ const getDefaultGraphMock = () => {
}

const selectors = {
itemFilterGroupsStub: 'item-filter-stub[filtername="groups"]'
itemFilterGroupsStub: 'item-filter-stub[filtername="groups"]',
itemFilterRolesStub: 'item-filter-stub[filtername="roles"]'
}

describe('Users view', () => {
Expand Down Expand Up @@ -334,46 +335,83 @@ describe('Users view', () => {
})

describe('filter', () => {
it('does filter users by groups when the "selectionChange"-event is triggered', async () => {
const graphMock = getDefaultGraphMock()
const { wrapper } = getMountedWrapper({ mountType: mount, graph: graphMock })
await wrapper.vm.loadResourcesTask.last
expect(graphMock.users.listUsers).toHaveBeenCalledTimes(1)
;(wrapper.findComponent<any>(selectors.itemFilterGroupsStub).vm as any).$emit(
'selectionChange',
[{ id: '1' }]
)
await wrapper.vm.$nextTick()
expect(graphMock.users.listUsers).toHaveBeenCalledTimes(2)
expect(graphMock.users.listUsers).toHaveBeenNthCalledWith(
2,
'displayName',
"memberOf/any(m:m/id eq '1')"
)
describe('groups', () => {
it('does filter users by groups when the "selectionChange"-event is triggered', async () => {
const graphMock = getDefaultGraphMock()
const { wrapper } = getMountedWrapper({ mountType: mount, graph: graphMock })
await wrapper.vm.loadResourcesTask.last
expect(graphMock.users.listUsers).toHaveBeenCalledTimes(1)
;(wrapper.findComponent<any>(selectors.itemFilterGroupsStub).vm as any).$emit(
'selectionChange',
[{ id: '1' }]
)
await wrapper.vm.$nextTick()
expect(graphMock.users.listUsers).toHaveBeenCalledTimes(2)
expect(graphMock.users.listUsers).toHaveBeenNthCalledWith(
2,
'displayName',
"memberOf/any(m:m/id eq '1')"
)
})
it('does filter initially if group ids are given via query param', async () => {
const groupIdsQueryParam = '1+2'
const graphMock = getDefaultGraphMock()
const { wrapper } = getMountedWrapper({
mountType: mount,
graph: graphMock,
groupFilterQuery: groupIdsQueryParam
})
await wrapper.vm.loadResourcesTask.last
expect(graphMock.users.listUsers).toHaveBeenCalledWith(
'displayName',
"memberOf/any(m:m/id eq '1') and memberOf/any(m:m/id eq '2')"
)
})
})
it('does filter initially if group ids are given via query param', async () => {
const groupIdsQueryParam = '1+2'
const graphMock = getDefaultGraphMock()
const { wrapper } = getMountedWrapper({
mountType: mount,
graph: graphMock,
queryItem: groupIdsQueryParam
describe('roles', () => {
it('does filter users by roles when the "selectionChange"-event is triggered', async () => {
const graphMock = getDefaultGraphMock()
const { wrapper } = getMountedWrapper({ mountType: mount, graph: graphMock })
await wrapper.vm.loadResourcesTask.last
expect(graphMock.users.listUsers).toHaveBeenCalledTimes(1)
;(wrapper.findComponent<any>(selectors.itemFilterRolesStub).vm as any).$emit(
'selectionChange',
[{ id: '1' }]
)
await wrapper.vm.$nextTick()
expect(graphMock.users.listUsers).toHaveBeenCalledTimes(2)
expect(graphMock.users.listUsers).toHaveBeenNthCalledWith(
2,
'displayName',
"appRoleAssignments/any(m:m/appRoleId eq '1')"
)
})
it('does filter initially if group ids are given via query param', async () => {
const roleIdsQueryParam = '1+2'
const graphMock = getDefaultGraphMock()
const { wrapper } = getMountedWrapper({
mountType: mount,
graph: graphMock,
roleFilterQuery: roleIdsQueryParam
})
await wrapper.vm.loadResourcesTask.last
expect(graphMock.users.listUsers).toHaveBeenCalledWith(
'displayName',
"appRoleAssignments/any(m:m/appRoleId eq '1') and appRoleAssignments/any(m:m/appRoleId eq '2')"
)
})
await wrapper.vm.loadResourcesTask.last
expect(graphMock.users.listUsers).toHaveBeenCalledWith(
'displayName',
"memberOf/any(m:m/id eq '1') and memberOf/any(m:m/id eq '2')"
)
})
})
})

function getMountedWrapper({
mountType = shallowMount,
graph = getDefaultGraphMock(),
queryItem = null
groupFilterQuery = null,
roleFilterQuery = null
} = {}) {
jest.mocked(queryItemAsString).mockImplementation(() => queryItem)
jest.mocked(queryItemAsString).mockImplementationOnce(() => groupFilterQuery)
jest.mocked(queryItemAsString).mockImplementationOnce(() => roleFilterQuery)
const mocks = {
...defaultComponentMocks(),
...getActionMixinMocks({ actions: mixins })
Expand Down

0 comments on commit 2f2007c

Please sign in to comment.