Skip to content

Commit

Permalink
Merge pull request #9422 from rtibbles/paging_dr_facility_user
Browse files Browse the repository at this point in the history
Fix pagination issues for facility user page
  • Loading branch information
marcellamaki authored May 12, 2022
2 parents 05f5883 + 73f9f14 commit 124bc32
Show file tree
Hide file tree
Showing 5 changed files with 132 additions and 143 deletions.
3 changes: 3 additions & 0 deletions kolibri/core/auth/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,8 @@ class FacilityUserViewSet(ValuesViewset):
DjangoFilterBackend,
filters.SearchFilter,
)
order_by_field = "username"

queryset = FacilityUser.objects.all()
serializer_class = FacilityUserSerializer
filter_class = FacilityUserFilter
Expand Down Expand Up @@ -338,6 +340,7 @@ def consolidate(self, items, queryset):
roles.append(role)
item["roles"] = roles
output.append(item)
output = sorted(output, key=lambda x: x[self.order_by_field])
return output

def set_password_if_needed(self, instance, serializer):
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import pickBy from 'lodash/pickBy';
import { FacilityUserResource } from 'kolibri.resources';
import samePageCheckGenerator from 'kolibri.utils.samePageCheckGenerator';
import { _userState } from '../mappers';
Expand All @@ -6,7 +7,13 @@ export function showUserPage(store, toRoute) {
store.dispatch('preparePage');
const facilityId = toRoute.params.facility_id || store.getters.activeFacilityId;
return FacilityUserResource.fetchCollection({
getParams: { member_of: facilityId, page_size: 30 },
getParams: pickBy({
member_of: facilityId,
page: toRoute.query.page || 1,
page_size: toRoute.query.page_size || 30,
search: toRoute.query.search && toRoute.query.search.trim(),
user_type: toRoute.query.user_type,
}),
force: true,
}).only(
samePageCheckGenerator(store),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,32 +9,29 @@
:layout12="{ span: 5, alignment: 'right' }"
class="text-filter"
>
<FilterTextbox v-model="filterInput" :placeholder="filterPlaceholder" />
<slot name="filter"></slot>
</KGridItem>
</KGrid>

<div>
<slot
:items="userList"
:filterInput="filterInput"
>
<slot>
</slot>
</div>

<nav class="pagination-nav">
<span dir="auto" class="pagination-label">
{{ $tr('pagination', { visibleStartRange, visibleEndRange, numFilteredItems }) }}
{{ $translate('pagination', { visibleStartRange, visibleEndRange, numFilteredItems }) }}
</span>
<KButtonGroup>
<KIconButton
:ariaLabel="$tr('previousResults')"
:ariaLabel="$translate('previousResults')"
:disabled="previousButtonDisabled"
size="small"
icon="back"
@click="changePage(-1)"
/>
<KIconButton
:ariaLabel="$tr('nextResults')"
:ariaLabel="$translate('nextResults')"
:disabled="nextButtonDisabled"
size="small"
icon="forward"
Expand All @@ -50,172 +47,64 @@
<script>
import clamp from 'lodash/clamp';
import FilterTextbox from 'kolibri.coreVue.components.FilterTextbox';
import { FacilityUserResource } from 'kolibri.resources';
import store from 'kolibri.coreVue.vuex.store';
import { _userState } from '../modules/mappers';
import PaginatedListContainer from 'kolibri.coreVue.components.PaginatedListContainer';
import { crossComponentTranslator } from 'kolibri.utils.i18n';
export default {
name: 'PaginatedListContainerWithBackend',
components: {
FilterTextbox,
},
props: {
// The entire list of items
items: {
type: Array,
required: true,
},
filterPlaceholder: {
type: String,
required: true,
},
itemsPerPage: {
type: Number,
required: false,
default: 30,
required: true,
},
totalPageNumber: {
type: Number,
required: false,
default: 1,
required: true,
},
totalUsers: {
value: {
type: Number,
required: true,
},
roleFilter: {
type: Object,
required: false,
default: null,
},
excludeMemberOf: {
type: String,
required: false,
default: '',
},
userAssignmentType: {
type: String,
required: false,
default: '',
numFilteredItems: {
type: Number,
required: true,
},
},
data() {
return {
filterInput: '',
currentPage: 1,
userList: this.items,
totalPageNumbers: this.totalPageNumber,
usersCount: this.totalUsers,
};
},
computed: {
numFilteredItems() {
return this.usersCount;
},
startRange() {
return (this.currentPage - 1) * this.itemsPerPage;
return (this.value - 1) * this.itemsPerPage;
},
visibleStartRange() {
// return this.currentPage;
return Math.min(this.startRange + 1, this.numFilteredItems);
},
endRange() {
return this.currentPage * this.itemsPerPage;
return this.value * this.itemsPerPage;
},
visibleEndRange() {
return Math.min(this.endRange, this.numFilteredItems);
},
previousButtonDisabled() {
return this.currentPage === 1 || this.numFilteredItems === 0;
return this.value === 1 || this.numFilteredItems === 0;
},
nextButtonDisabled() {
return (
this.totalPageNumbers === 1 ||
this.currentPage === this.totalPageNumbers ||
this.totalPageNumber === 1 ||
this.value === this.totalPageNumber ||
this.numFilteredItems === 0
);
},
},
watch: {
filterInput: {
handler() {
this.currentPage = 1;
this.get_users();
},
},
roleFilter: {
handler() {
this.currentPage = 1;
this.get_users();
},
},
items: {
handler() {
this.get_users();
},
},
beforeCreate() {
this.$translator = crossComponentTranslator(PaginatedListContainer);
},
methods: {
get_users() {
const facilityId = store.getters.activeFacilityId;
FacilityUserResource.fetchCollection({
getParams: {
member_of: facilityId,
page_size: this.itemsPerPage,
page: this.currentPage,
search: this.filterInput,
exclude_member_of: !this.excludeMemberOf ? '' : this.excludeMemberOf,
user_type:
!this.roleFilter || this.roleFilter.value === 'all' ? '' : this.roleFilter.value,
exclude_user_type: this.userAssignmentType === 'coaches' ? 'learner' : '',
},
force: true,
}).then(
users => {
this.currentPage = users.page;
this.userList = users.results.map(_userState);
this.totalPageNumbers = users.total_pages;
this.usersCount = users.count;
},
error => {
// check if this error is raised by the api because of currentPage is more than
// the total pages
if (
error.response.status === 404 &&
error.response.data &&
error.response.data[0].id === 'NOT_FOUND'
) {
// set the currentPage to 1 and recall the api
this.currentPage = 1;
this.get_users();
} else {
store.dispatch('handleApiError', error);
}
}
);
},
changePage(change) {
// Clamp the newPage number between the bounds if browser doesn't correctly
// disable buttons (see #6454 issue with old versions of MS Edge)
this.currentPage = clamp(this.currentPage + change, 1, this.totalPageNumbers);
this.get_users();
},
},
$trs: {
previousResults: {
message: 'Previous results',
context:
'Text which indicates the previous page of results when a user makes a search query.\n',
},
nextResults: {
message: 'Next results',
context: 'Text which indicates the next page of results when a user makes a search query.',
this.$emit('input', clamp(this.value + change, 1, this.totalPageNumber));
},
pagination: {
message:
'{ visibleStartRange, number } - { visibleEndRange, number } of { numFilteredItems, number }',
context: "Refers to pagination. Only translate the word \"of''.",
$translate(msg, params) {
return this.$translator.$tr(msg, params);
},
},
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,29 @@
import mock from 'xhr-mock';
import { mount, RouterLinkStub } from '@vue/test-utils';
import VueRouter from 'vue-router';
import makeStore from '../../../../test/makeStore';
import UserPage from '../index';

jest.mock('kolibri.lib.logging');
jest.mock('kolibri.urls');
jest.mock('lockr');

const router = new VueRouter({
routes: [
{
path: '/userpage/',
name: 'UserPage',
},
],
});

UserPage.computed.newUserLink = () => ({});

function makeWrapper() {
const store = makeStore();
const wrapper = mount(UserPage, {
store,
router,
stubs: {
RouterLink: RouterLinkStub,
},
Expand Down
Loading

0 comments on commit 124bc32

Please sign in to comment.