Add column sorting

This commit is contained in:
shamoon
2026-09-08 09:52:46 -07:00
parent 279e075f58
commit 2d25400a23
8 changed files with 79 additions and 9 deletions
@@ -13,10 +13,10 @@
<table class="table table-sm align-middle mb-0 bg-body"> <table class="table table-sm align-middle mb-0 bg-body">
<thead> <thead>
<tr> <tr>
<th scope="col" i18n>Created</th> <th scope="col" class="fw-normal" pngxSortable="created" [currentSortField]="sortField()" [currentSortReverse]="sortReverse()" (sort)="onSort($event)" i18n>Created</th>
<th scope="col" i18n>Status</th> <th scope="col" class="fw-normal" pngxSortable="status" [currentSortField]="sortField()" [currentSortReverse]="sortReverse()" (sort)="onSort($event)" i18n>Status</th>
<th scope="col" i18n>Size</th> <th scope="col" i18n>Size</th>
<th scope="col" i18n>Expires</th> <th scope="col" class="fw-normal" pngxSortable="expiration" [currentSortField]="sortField()" [currentSortReverse]="sortReverse()" (sort)="onSort($event)" i18n>Expires</th>
<th scope="col" i18n>Documents</th> <th scope="col" i18n>Documents</th>
<th scope="col" i18n>File version</th> <th scope="col" i18n>File version</th>
<th scope="col" class="text-end" i18n>Actions</th> <th scope="col" class="text-end" i18n>Actions</th>
@@ -123,6 +123,16 @@ describe('ShareLinkBundleListComponent', () => {
expect(service.list).toHaveBeenLastCalledWith(2, 25, 'created', true) expect(service.list).toHaveBeenLastCalledWith(2, 25, 'created', true)
}) })
it('sorts bundles and returns to the first page', () => {
fixture.detectChanges()
component.page.set(2)
component.onSort({ column: 'status', reverse: false })
expect(component.page()).toBe(1)
expect(service.list).toHaveBeenLastCalledWith(1, 25, 'status', false)
})
it('marks expired share link bundles', () => { it('marks expired share link bundles', () => {
service.list.mockReturnValue( service.list.mockReturnValue(
of({ of({
@@ -16,6 +16,10 @@ import {
ShareLinkBundleSummary, ShareLinkBundleSummary,
} from 'src/app/data/share-link-bundle' } from 'src/app/data/share-link-bundle'
import { SETTINGS_KEYS } from 'src/app/data/ui-settings' import { SETTINGS_KEYS } from 'src/app/data/ui-settings'
import {
SortEvent,
SortableDirective,
} from 'src/app/directives/sortable.directive'
import { FileSizePipe } from 'src/app/pipes/file-size.pipe' import { FileSizePipe } from 'src/app/pipes/file-size.pipe'
import { ShareLinkBundleService } from 'src/app/services/rest/share-link-bundle.service' import { ShareLinkBundleService } from 'src/app/services/rest/share-link-bundle.service'
import { SettingsService } from 'src/app/services/settings.service' import { SettingsService } from 'src/app/services/settings.service'
@@ -35,6 +39,7 @@ import { LoadingComponentWithPermissions } from 'src/app/components/loading-comp
NgbPaginationModule, NgbPaginationModule,
NgbPopoverModule, NgbPopoverModule,
NgxBootstrapIconsModule, NgxBootstrapIconsModule,
SortableDirective,
FileSizePipe, FileSizePipe,
], ],
}) })
@@ -52,6 +57,8 @@ export class ShareLinkBundleListComponent
readonly copiedSlug = signal<string | null>(null) readonly copiedSlug = signal<string | null>(null)
readonly total = signal(0) readonly total = signal(0)
readonly page = signal(1) readonly page = signal(1)
readonly sortField = signal('created')
readonly sortReverse = signal(true)
readonly statuses = ShareLinkBundleStatus readonly statuses = ShareLinkBundleStatus
readonly fileVersions = FileVersion readonly fileVersions = FileVersion
@@ -90,7 +97,12 @@ export class ShareLinkBundleListComponent
} }
this.error.set(null) this.error.set(null)
return this.shareLinkBundleService return this.shareLinkBundleService
.list(this.page(), this.pageSize, 'created', true) .list(
this.page(),
this.pageSize,
this.sortField(),
this.sortReverse()
)
.pipe( .pipe(
catchError((error) => { catchError((error) => {
if (!silent) { if (!silent) {
@@ -138,6 +150,13 @@ export class ShareLinkBundleListComponent
this.triggerRefresh(false) this.triggerRefresh(false)
} }
onSort(event: SortEvent): void {
this.sortField.set(event.column || 'created')
this.sortReverse.set(event.column ? event.reverse : true)
this.page.set(1)
this.triggerRefresh(false)
}
copy(bundle: ShareLinkBundleSummary): void { copy(bundle: ShareLinkBundleSummary): void {
if (bundle.status !== ShareLinkBundleStatus.Ready) { if (bundle.status !== ShareLinkBundleStatus.Ready) {
return return
@@ -12,9 +12,9 @@
<table class="table table-sm align-middle mb-0 bg-body"> <table class="table table-sm align-middle mb-0 bg-body">
<thead> <thead>
<tr> <tr>
<th scope="col" i18n>Document</th> <th scope="col" class="fw-normal" pngxSortable="document__title" [currentSortField]="sortField()" [currentSortReverse]="sortReverse()" (sort)="onSort($event)" i18n>Document</th>
<th scope="col" i18n>Created</th> <th scope="col" class="fw-normal" pngxSortable="created" [currentSortField]="sortField()" [currentSortReverse]="sortReverse()" (sort)="onSort($event)" i18n>Created</th>
<th scope="col" i18n>Expires</th> <th scope="col" class="fw-normal" pngxSortable="expiration" [currentSortField]="sortField()" [currentSortReverse]="sortReverse()" (sort)="onSort($event)" i18n>Expires</th>
<th scope="col" i18n>File version</th> <th scope="col" i18n>File version</th>
<th scope="col" class="text-end" i18n>Actions</th> <th scope="col" class="text-end" i18n>Actions</th>
</tr> </tr>
@@ -75,6 +75,16 @@ describe('ShareLinkListComponent', () => {
expect(service.list).toHaveBeenLastCalledWith(2, 25, 'created', true) expect(service.list).toHaveBeenLastCalledWith(2, 25, 'created', true)
}) })
it('sorts links and returns to the first page', () => {
fixture.detectChanges()
component.page.set(2)
component.onSort({ column: 'expiration', reverse: false })
expect(component.page()).toBe(1)
expect(service.list).toHaveBeenLastCalledWith(1, 25, 'expiration', false)
})
it('marks expired share links', () => { it('marks expired share links', () => {
service.list.mockReturnValue( service.list.mockReturnValue(
of({ of({
@@ -12,6 +12,10 @@ import { FileVersion, ShareLink } from 'src/app/data/share-link'
import { SHARE_LINK_BUNDLE_FILE_VERSION_LABELS } from 'src/app/data/share-link-bundle' import { SHARE_LINK_BUNDLE_FILE_VERSION_LABELS } from 'src/app/data/share-link-bundle'
import { SETTINGS_KEYS } from 'src/app/data/ui-settings' import { SETTINGS_KEYS } from 'src/app/data/ui-settings'
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive' import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
import {
SortEvent,
SortableDirective,
} from 'src/app/directives/sortable.directive'
import { DocumentTitlePipe } from 'src/app/pipes/document-title.pipe' import { DocumentTitlePipe } from 'src/app/pipes/document-title.pipe'
import { import {
PermissionAction, PermissionAction,
@@ -34,6 +38,7 @@ import { environment } from 'src/environments/environment'
NgbPaginationModule, NgbPaginationModule,
NgxBootstrapIconsModule, NgxBootstrapIconsModule,
RouterModule, RouterModule,
SortableDirective,
], ],
}) })
export class ShareLinkListComponent export class ShareLinkListComponent
@@ -48,6 +53,8 @@ export class ShareLinkListComponent
readonly links = signal<ShareLink[]>([]) readonly links = signal<ShareLink[]>([])
readonly total = signal(0) readonly total = signal(0)
readonly page = signal(1) readonly page = signal(1)
readonly sortField = signal('created')
readonly sortReverse = signal(true)
readonly copiedID = signal<number | null>(null) readonly copiedID = signal<number | null>(null)
readonly copiedDocumentID = signal<number | null>(null) readonly copiedDocumentID = signal<number | null>(null)
readonly error = signal<string | null>(null) readonly error = signal<string | null>(null)
@@ -85,7 +92,7 @@ export class ShareLinkListComponent
this.loading.set(true) this.loading.set(true)
this.error.set(null) this.error.set(null)
this.shareLinkService this.shareLinkService
.list(this.page(), this.pageSize, 'created', true) .list(this.page(), this.pageSize, this.sortField(), this.sortReverse())
.pipe(takeUntil(this.unsubscribeNotifier)) .pipe(takeUntil(this.unsubscribeNotifier))
.subscribe({ .subscribe({
next: (results) => { next: (results) => {
@@ -109,6 +116,13 @@ export class ShareLinkListComponent
this.reload() this.reload()
} }
onSort(event: SortEvent): void {
this.sortField.set(event.column || 'created')
this.sortReverse.set(event.column ? event.reverse : true)
this.page.set(1)
this.reload()
}
getShareUrl(link: ShareLink): string { getShareUrl(link: ShareLink): string {
const apiURL = new URL(environment.apiBaseUrl) const apiURL = new URL(environment.apiBaseUrl)
return `${apiURL.origin}${apiURL.pathname.replace(/\/api\/$/, '/share/')}${ return `${apiURL.origin}${apiURL.pathname.replace(/\/api\/$/, '/share/')}${
+17
View File
@@ -3796,6 +3796,23 @@ class TestDocumentApi(DirectoriesMixin, ConsumeTaskMixin, APITestCase):
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
def test_order_share_links_by_document_title(self) -> None:
document_zulu = Document.objects.create(title="Zulu")
document_alpha = Document.objects.create(title="Alpha")
ShareLink.objects.create(document=document_zulu, slug="zulu-link")
ShareLink.objects.create(document=document_alpha, slug="alpha-link")
response = self.client.get(
"/api/share_links/?ordering=document__title",
format="json",
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(
[link["document_title"] for link in response.data["results"]],
["Alpha", "Zulu"],
)
def test_share_links_permissions_aware(self) -> None: def test_share_links_permissions_aware(self) -> None:
""" """
GIVEN: GIVEN:
+1 -1
View File
@@ -4571,7 +4571,7 @@ class ShareLinkViewSet(
PermittedObjectsFilter, PermittedObjectsFilter,
) )
filterset_class = ShareLinkFilterSet filterset_class = ShareLinkFilterSet
ordering_fields = ("created", "expiration", "document") ordering_fields = ("created", "expiration", "document__title")
@extend_schema_view( @extend_schema_view(