From 9572f4599be7b4d24f3f6d44441f8351546bd3bc Mon Sep 17 00:00:00 2001 From: Alexander Ackermann Date: Wed, 23 Sep 2026 08:35:34 +0200 Subject: [PATCH 1/4] fix(admin-settings): make apps sortable by status --- .../components/Extensions/ExtensionsList.vue | 28 ++++++++++++++----- .../Extensions/ExtensionsList.spec.ts | 23 +++++++++++++++ 2 files changed, 44 insertions(+), 7 deletions(-) diff --git a/packages/web-app-admin-settings/src/components/Extensions/ExtensionsList.vue b/packages/web-app-admin-settings/src/components/Extensions/ExtensionsList.vue index 30f09789a5d..c5ac26309aa 100644 --- a/packages/web-app-admin-settings/src/components/Extensions/ExtensionsList.vue +++ b/packages/web-app-admin-settings/src/components/Extensions/ExtensionsList.vue @@ -39,7 +39,7 @@ size="small" class="border-0 !rounded-sm !bg-green-200 !text-green-900" > - + - + @@ -73,7 +73,9 @@ const { extensions, filterTerm = '' } = defineProps<{ }>() const { $gettext } = useGettext() -const sortBy = ref('name') +type SortField = 'name' | 'status' + +const sortBy = ref('name') const sortDir = ref(SortDir.Asc) const filteredExtensions = computed(() => { @@ -88,16 +90,27 @@ const filteredExtensions = computed(() => { }) }) +function getStatusLabel(extension: ExtensionInfo) { + return extension.loaded ? $gettext('Active') : $gettext('Failed') +} + +function getSortValue(extension: ExtensionInfo) { + if (unref(sortBy) === 'status') { + return getStatusLabel(extension) + } + return extension.name || '' +} + const items = computed(() => { return [...unref(filteredExtensions)].sort((a, b) => { - const c = (a[unref(sortBy)] || '').toString() - const d = (b[unref(sortBy)] || '').toString() - return unref(sortDir) === SortDir.Desc ? d.localeCompare(c) : c.localeCompare(d) + const result = + getSortValue(a).localeCompare(getSortValue(b)) || (a.name || '').localeCompare(b.name || '') + return unref(sortDir) === SortDir.Desc ? -result : result }) }) function handleSort(event: { sortBy: string; sortDir: SortDir }) { - sortBy.value = event.sortBy as keyof ExtensionInfo + sortBy.value = event.sortBy as SortField sortDir.value = event.sortDir } @@ -121,6 +134,7 @@ const fields = computed(() => [ name: 'status', title: $gettext('Status'), type: 'slot', + sortable: true, width: 'shrink' as const } ]) diff --git a/packages/web-app-admin-settings/tests/unit/components/Extensions/ExtensionsList.spec.ts b/packages/web-app-admin-settings/tests/unit/components/Extensions/ExtensionsList.spec.ts index 9531c91b01f..77632c6ae8a 100644 --- a/packages/web-app-admin-settings/tests/unit/components/Extensions/ExtensionsList.spec.ts +++ b/packages/web-app-admin-settings/tests/unit/components/Extensions/ExtensionsList.spec.ts @@ -51,6 +51,29 @@ describe('ExtensionsList', () => { ]) }) + it('sorts by status ascending and descending, falling back to name', () => { + const { wrapper } = getWrapper({ + extensions: [ + { name: 'Zulu', version: '1.0.0', loaded: false }, + { name: 'Beta', version: '1.0.0', loaded: true }, + { name: 'Alpha', version: '2.0.0', loaded: false } + ] + }) + + ;(wrapper.vm as any).handleSort({ sortBy: 'status', sortDir: SortDir.Asc }) + expect((wrapper.vm as any).items.map((item: { name: string }) => item.name)).toEqual([ + 'Beta', + 'Alpha', + 'Zulu' + ]) + ;(wrapper.vm as any).handleSort({ sortBy: 'status', sortDir: SortDir.Desc }) + expect((wrapper.vm as any).items.map((item: { name: string }) => item.name)).toEqual([ + 'Zulu', + 'Alpha', + 'Beta' + ]) + }) + it('highlights the matching part of app names', () => { const ocTableStub = { props: ['data'], From 1707c9c724d11401de3c5a8fe6bffd75391d32b7 Mon Sep 17 00:00:00 2001 From: Alexander Ackermann Date: Wed, 23 Sep 2026 08:37:24 +0200 Subject: [PATCH 2/4] fix(admin-settings): sort apps by loaded state instead of status label --- .../src/components/Extensions/ExtensionsList.vue | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/packages/web-app-admin-settings/src/components/Extensions/ExtensionsList.vue b/packages/web-app-admin-settings/src/components/Extensions/ExtensionsList.vue index c5ac26309aa..7ba81913a66 100644 --- a/packages/web-app-admin-settings/src/components/Extensions/ExtensionsList.vue +++ b/packages/web-app-admin-settings/src/components/Extensions/ExtensionsList.vue @@ -94,17 +94,20 @@ function getStatusLabel(extension: ExtensionInfo) { return extension.loaded ? $gettext('Active') : $gettext('Failed') } -function getSortValue(extension: ExtensionInfo) { - if (unref(sortBy) === 'status') { - return getStatusLabel(extension) - } - return extension.name || '' +function compareByName(a: ExtensionInfo, b: ExtensionInfo) { + return (a.name || '').localeCompare(b.name || '') +} + +function compareByStatus(a: ExtensionInfo, b: ExtensionInfo) { + return Number(b.loaded) - Number(a.loaded) } const items = computed(() => { return [...unref(filteredExtensions)].sort((a, b) => { const result = - getSortValue(a).localeCompare(getSortValue(b)) || (a.name || '').localeCompare(b.name || '') + unref(sortBy) === 'status' + ? compareByStatus(a, b) || compareByName(a, b) + : compareByName(a, b) return unref(sortDir) === SortDir.Desc ? -result : result }) }) From 870d3053c3cc7299e208f2f6438bd27cf9e8a430 Mon Sep 17 00:00:00 2001 From: Alexander Ackermann Date: Wed, 23 Sep 2026 08:38:42 +0200 Subject: [PATCH 3/4] refactor(admin-settings): reduce apps status sort to minimal change --- .../components/Extensions/ExtensionsList.vue | 35 +++++++------------ .../Extensions/ExtensionsList.spec.ts | 11 +++--- 2 files changed, 16 insertions(+), 30 deletions(-) diff --git a/packages/web-app-admin-settings/src/components/Extensions/ExtensionsList.vue b/packages/web-app-admin-settings/src/components/Extensions/ExtensionsList.vue index 7ba81913a66..21c2937efd8 100644 --- a/packages/web-app-admin-settings/src/components/Extensions/ExtensionsList.vue +++ b/packages/web-app-admin-settings/src/components/Extensions/ExtensionsList.vue @@ -39,7 +39,7 @@ size="small" class="border-0 !rounded-sm !bg-green-200 !text-green-900" > - + - + @@ -73,9 +73,7 @@ const { extensions, filterTerm = '' } = defineProps<{ }>() const { $gettext } = useGettext() -type SortField = 'name' | 'status' - -const sortBy = ref('name') +const sortBy = ref('name') const sortDir = ref(SortDir.Asc) const filteredExtensions = computed(() => { @@ -90,30 +88,21 @@ const filteredExtensions = computed(() => { }) }) -function getStatusLabel(extension: ExtensionInfo) { - return extension.loaded ? $gettext('Active') : $gettext('Failed') -} - -function compareByName(a: ExtensionInfo, b: ExtensionInfo) { - return (a.name || '').localeCompare(b.name || '') -} - -function compareByStatus(a: ExtensionInfo, b: ExtensionInfo) { - return Number(b.loaded) - Number(a.loaded) -} - const items = computed(() => { return [...unref(filteredExtensions)].sort((a, b) => { - const result = - unref(sortBy) === 'status' - ? compareByStatus(a, b) || compareByName(a, b) - : compareByName(a, b) - return unref(sortDir) === SortDir.Desc ? -result : result + const field = unref(sortBy) + if (field === 'status') { + const result = Number(b.loaded) - Number(a.loaded) + return unref(sortDir) === SortDir.Desc ? -result : result + } + const c = (a[field] || '').toString() + const d = (b[field] || '').toString() + return unref(sortDir) === SortDir.Desc ? d.localeCompare(c) : c.localeCompare(d) }) }) function handleSort(event: { sortBy: string; sortDir: SortDir }) { - sortBy.value = event.sortBy as SortField + sortBy.value = event.sortBy as keyof ExtensionInfo | 'status' sortDir.value = event.sortDir } diff --git a/packages/web-app-admin-settings/tests/unit/components/Extensions/ExtensionsList.spec.ts b/packages/web-app-admin-settings/tests/unit/components/Extensions/ExtensionsList.spec.ts index 77632c6ae8a..2c37b0532a5 100644 --- a/packages/web-app-admin-settings/tests/unit/components/Extensions/ExtensionsList.spec.ts +++ b/packages/web-app-admin-settings/tests/unit/components/Extensions/ExtensionsList.spec.ts @@ -51,24 +51,21 @@ describe('ExtensionsList', () => { ]) }) - it('sorts by status ascending and descending, falling back to name', () => { + it('sorts by status ascending and descending', () => { const { wrapper } = getWrapper({ extensions: [ - { name: 'Zulu', version: '1.0.0', loaded: false }, - { name: 'Beta', version: '1.0.0', loaded: true }, - { name: 'Alpha', version: '2.0.0', loaded: false } + { name: 'Alpha', version: '1.0.0', loaded: false }, + { name: 'Beta', version: '1.0.0', loaded: true } ] }) ;(wrapper.vm as any).handleSort({ sortBy: 'status', sortDir: SortDir.Asc }) expect((wrapper.vm as any).items.map((item: { name: string }) => item.name)).toEqual([ 'Beta', - 'Alpha', - 'Zulu' + 'Alpha' ]) ;(wrapper.vm as any).handleSort({ sortBy: 'status', sortDir: SortDir.Desc }) expect((wrapper.vm as any).items.map((item: { name: string }) => item.name)).toEqual([ - 'Zulu', 'Alpha', 'Beta' ]) From 5a69cf6adb5c1b56b8caf3c15f644d80b7cfe915 Mon Sep 17 00:00:00 2001 From: Alexander Ackermann Date: Wed, 23 Sep 2026 08:41:37 +0200 Subject: [PATCH 4/4] refactor(admin-settings): add SortField type for apps list --- .../src/components/Extensions/ExtensionsList.vue | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/web-app-admin-settings/src/components/Extensions/ExtensionsList.vue b/packages/web-app-admin-settings/src/components/Extensions/ExtensionsList.vue index 21c2937efd8..711397fe8d9 100644 --- a/packages/web-app-admin-settings/src/components/Extensions/ExtensionsList.vue +++ b/packages/web-app-admin-settings/src/components/Extensions/ExtensionsList.vue @@ -73,7 +73,9 @@ const { extensions, filterTerm = '' } = defineProps<{ }>() const { $gettext } = useGettext() -const sortBy = ref('name') +type SortField = 'name' | 'status' + +const sortBy = ref('name') const sortDir = ref(SortDir.Asc) const filteredExtensions = computed(() => { @@ -102,7 +104,7 @@ const items = computed(() => { }) function handleSort(event: { sortBy: string; sortDir: SortDir }) { - sortBy.value = event.sortBy as keyof ExtensionInfo | 'status' + sortBy.value = event.sortBy as SortField sortDir.value = event.sortDir }