From 429975a4f0c25f181d4189d90c7247b19c4b2f7c Mon Sep 17 00:00:00 2001 From: Alex Velez Date: Wed, 4 Mar 2026 15:46:13 -0500 Subject: [PATCH 1/5] Account for deleted channels in channels admin filters --- .../pages/Channels/ChannelTable.vue | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/contentcuration/contentcuration/frontend/administration/pages/Channels/ChannelTable.vue b/contentcuration/contentcuration/frontend/administration/pages/Channels/ChannelTable.vue index 34b2ef0870..6b4e3644af 100644 --- a/contentcuration/contentcuration/frontend/administration/pages/Channels/ChannelTable.vue +++ b/contentcuration/contentcuration/frontend/administration/pages/Channels/ChannelTable.vue @@ -156,8 +156,14 @@ import LanguageDropdown from 'shared/views/LanguageDropdown'; const channelTypeFilterMap = { - kolibriStudio: { label: 'Kolibri Studio Library', params: { public: true, deleted: false } }, - community: { label: 'Community Library', params: { has_community_library_submission: true } }, + kolibriStudio: { + label: 'Kolibri Studio Library', + params: { public: true, deleted: false }, + }, + community: { + label: 'Community Library', + params: { has_community_library_submission: true }, + }, unlisted: { label: 'Unlisted Channels', params: { has_community_library_submission: false, public: false }, @@ -198,10 +204,11 @@ }; } else if (channelTypeFilter.value === 'unlisted') { return { - live: { label: 'Live', params: {} }, - draft: { label: 'Draft', params: { published: false } }, - published: { label: 'Published', params: { published: true } }, - cheffed: { label: 'Sushi chef', params: { cheffed: true } }, + live: { label: 'Live', params: { deleted: false } }, + draft: { label: 'Draft', params: { published: false, deleted: false } }, + published: { label: 'Published', params: { published: true, deleted: false } }, + cheffed: { label: 'Sushi chef', params: { cheffed: true, deleted: false } }, + deleted: { label: 'Deleted', params: { deleted: true } }, }; } return {}; From e01eee103a564191ed8a583e595fb14866b2fd64 Mon Sep 17 00:00:00 2001 From: Alex Velez Date: Wed, 4 Mar 2026 15:55:31 -0500 Subject: [PATCH 2/5] Add All Channels channel type filter option --- .../pages/Channels/ChannelTable.vue | 40 +++++++++++++++---- .../frontend/shared/composables/useFilter.js | 5 ++- 2 files changed, 35 insertions(+), 10 deletions(-) diff --git a/contentcuration/contentcuration/frontend/administration/pages/Channels/ChannelTable.vue b/contentcuration/contentcuration/frontend/administration/pages/Channels/ChannelTable.vue index 6b4e3644af..8be945dcb5 100644 --- a/contentcuration/contentcuration/frontend/administration/pages/Channels/ChannelTable.vue +++ b/contentcuration/contentcuration/frontend/administration/pages/Channels/ChannelTable.vue @@ -21,7 +21,6 @@ item-value="key" label="Channel Type" box - clearable :menu-props="{ offsetY: true }" /> @@ -154,17 +153,29 @@ import Checkbox from 'shared/views/form/Checkbox'; import IconButton from 'shared/views/IconButton'; import LanguageDropdown from 'shared/views/LanguageDropdown'; + import { CommunityLibraryStatus } from 'shared/constants'; + + const ChannelTypeFilter = { + ALL: 'all', + KOLIBRI_LIBRARY: 'kolibriLibrary', + COMMUNITY_LIBRARY: 'communityLibrary', + UNLISTED: 'unlisted', + }; const channelTypeFilterMap = { - kolibriStudio: { + [ChannelTypeFilter.ALL]: { + label: 'All Channels', + params: {}, + }, + [ChannelTypeFilter.KOLIBRI_LIBRARY]: { label: 'Kolibri Studio Library', params: { public: true, deleted: false }, }, - community: { + [ChannelTypeFilter.COMMUNITY_LIBRARY]: { label: 'Community Library', params: { has_community_library_submission: true }, }, - unlisted: { + [ChannelTypeFilter.UNLISTED]: { label: 'Unlisted Channels', params: { has_community_library_submission: false, public: false }, }, @@ -184,25 +195,28 @@ const store = proxy.$store; const statusFilterMap = computed(() => { - if (channelTypeFilter.value === 'kolibriStudio') { + if (channelTypeFilter.value === ChannelTypeFilter.KOLIBRI_LIBRARY) { return { live: { label: 'Live', params: {} }, cheffed: { label: 'Sushi chef', params: { cheffed: true } }, }; - } else if (channelTypeFilter.value === 'community') { + } else if (channelTypeFilter.value === ChannelTypeFilter.COMMUNITY_LIBRARY) { return { live: { label: 'Live', params: { community_library_live: true } }, needsReview: { label: 'Needs review', params: { community_library_live: false, - latest_community_library_submission_status: ['PENDING', 'REJECTED'], + latest_community_library_submission_status: [ + CommunityLibraryStatus.PENDING, + CommunityLibraryStatus.REJECTED, + ], }, }, published: { label: 'Published', params: {} }, cheffed: { label: 'Sushi chef', params: { cheffed: true } }, }; - } else if (channelTypeFilter.value === 'unlisted') { + } else if (channelTypeFilter.value === ChannelTypeFilter.UNLISTED) { return { live: { label: 'Live', params: { deleted: false } }, draft: { label: 'Draft', params: { published: false, deleted: false } }, @@ -210,6 +224,14 @@ cheffed: { label: 'Sushi chef', params: { cheffed: true, deleted: false } }, deleted: { label: 'Deleted', params: { deleted: true } }, }; + } else if (channelTypeFilter.value === ChannelTypeFilter.ALL) { + return { + live: { label: 'Live', params: { deleted: false } }, + published: { label: 'Published', params: { published: true, deleted: false } }, + draft: { label: 'Draft', params: { published: false, deleted: false } }, + cheffed: { label: 'Sushi chef', params: { cheffed: true, deleted: false } }, + deleted: { label: 'Deleted', params: { deleted: true } }, + }; } return {}; }); @@ -237,7 +259,9 @@ } = useFilter({ name: 'channelType', filterMap: channelTypeFilterMap, + defaultValue: ChannelTypeFilter.ALL, }); + // Temporal wrapper, must be removed after migrating to KSelect const channelTypeFilter = computed({ get: () => _channelTypeFilter.value.value || undefined, diff --git a/contentcuration/contentcuration/frontend/shared/composables/useFilter.js b/contentcuration/contentcuration/frontend/shared/composables/useFilter.js index fbc07698c9..2dd713bba9 100644 --- a/contentcuration/contentcuration/frontend/shared/composables/useFilter.js +++ b/contentcuration/contentcuration/frontend/shared/composables/useFilter.js @@ -34,9 +34,10 @@ import { useQueryParams } from './useQueryParams'; * @param {Object} params The parameters for the filter. * @param {string} params.name The name of the filter used in query params. * @param {Object} params.filterMap A map of available filters. + * @param {string|null} [params.defaultValue] Optional default value if query param is not set. * @returns {UseFilterReturn} */ -export function useFilter({ name, filterMap }) { +export function useFilter({ name, filterMap, defaultValue = null }) { const route = useRoute(); const { updateQueryParams } = useQueryParams(); @@ -44,7 +45,7 @@ export function useFilter({ name, filterMap }) { get: () => { const routeFilter = route.query[name]; const filterOption = options.value.find(option => option.value === routeFilter); - return filterOption || {}; + return filterOption || options.value.find(option => option.value === defaultValue) || {}; }, set: value => { updateQueryParams({ From 22478adf2f9757390b1b8eb88b6e86c92b870e04 Mon Sep 17 00:00:00 2001 From: Alex Velez Date: Wed, 4 Mar 2026 16:11:22 -0500 Subject: [PATCH 3/5] Fix filters responsiveness --- .../pages/Channels/ChannelTable.vue | 30 +++++++++++-------- .../Channels/__tests__/channelTable.spec.js | 6 ++-- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/contentcuration/contentcuration/frontend/administration/pages/Channels/ChannelTable.vue b/contentcuration/contentcuration/frontend/administration/pages/Channels/ChannelTable.vue index 8be945dcb5..3826efdcc9 100644 --- a/contentcuration/contentcuration/frontend/administration/pages/Channels/ChannelTable.vue +++ b/contentcuration/contentcuration/frontend/administration/pages/Channels/ChannelTable.vue @@ -5,13 +5,13 @@ {{ `${$formatNumber(count)} ${count === 1 ? 'channel' : 'channels'}` }} @@ -44,8 +44,8 @@ { - const options = channelStatusOptions.value; - channelStatusFilter.value = options.length ? options[0].value : null; - }); + watch( + channelTypeFilter, + () => { + const options = channelStatusOptions.value; + channelStatusFilter.value = options.length ? options[0].value : null; + }, + { immediate: true }, + ); const filterFetchQueryParams = computed(() => { return { diff --git a/contentcuration/contentcuration/frontend/administration/pages/Channels/__tests__/channelTable.spec.js b/contentcuration/contentcuration/frontend/administration/pages/Channels/__tests__/channelTable.spec.js index f0e17e9366..c35516a05b 100644 --- a/contentcuration/contentcuration/frontend/administration/pages/Channels/__tests__/channelTable.spec.js +++ b/contentcuration/contentcuration/frontend/administration/pages/Channels/__tests__/channelTable.spec.js @@ -55,8 +55,8 @@ describe('channelTable', () => { }); describe('filters', () => { it('changing channel type filter should set query params', async () => { - wrapper.vm.channelTypeFilter = 'community'; - expect(router.currentRoute.query.channelType).toBe('community'); + wrapper.vm.channelTypeFilter = 'communityLibrary'; + expect(router.currentRoute.query.channelType).toBe('communityLibrary'); }); it('changing language filter should set query params', () => { wrapper.vm.languageFilter = 'en'; @@ -72,7 +72,7 @@ describe('channelTable', () => { expect(router.currentRoute.query.keywords).toBe('keyword test'); }); it('changing channel type filter should reset channel status filter', async () => { - wrapper.vm.channelTypeFilter = 'community'; + wrapper.vm.channelTypeFilter = 'communityLibrary'; wrapper.vm.channelStatusFilter = 'published'; await wrapper.vm.$nextTick(); wrapper.vm.channelTypeFilter = 'unlisted'; From 3c6342c7487da9bb46569ecf4c3c0e3e4f6f6059 Mon Sep 17 00:00:00 2001 From: Alex Velez Date: Mon, 9 Mar 2026 14:20:19 -0500 Subject: [PATCH 4/5] Fix pending submissions not being shown for live community library channels --- .../frontend/administration/pages/Channels/ChannelTable.vue | 1 - 1 file changed, 1 deletion(-) diff --git a/contentcuration/contentcuration/frontend/administration/pages/Channels/ChannelTable.vue b/contentcuration/contentcuration/frontend/administration/pages/Channels/ChannelTable.vue index 3826efdcc9..e139cbfcab 100644 --- a/contentcuration/contentcuration/frontend/administration/pages/Channels/ChannelTable.vue +++ b/contentcuration/contentcuration/frontend/administration/pages/Channels/ChannelTable.vue @@ -206,7 +206,6 @@ needsReview: { label: 'Needs review', params: { - community_library_live: false, latest_community_library_submission_status: [ CommunityLibraryStatus.PENDING, CommunityLibraryStatus.REJECTED, From dfd820f3de62f3782d07bbdc011f7e98b5eb4d4b Mon Sep 17 00:00:00 2001 From: Alex Velez Date: Tue, 10 Mar 2026 16:14:34 -0500 Subject: [PATCH 5/5] Use ChannelTypeFilter constant on channelTable tests --- .../frontend/administration/constants.js | 7 +++++++ .../administration/pages/Channels/ChannelTable.vue | 9 +-------- .../pages/Channels/__tests__/channelTable.spec.js | 12 ++++++------ 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/contentcuration/contentcuration/frontend/administration/constants.js b/contentcuration/contentcuration/frontend/administration/constants.js index c0af1bb794..5bd1a3fa0a 100644 --- a/contentcuration/contentcuration/frontend/administration/constants.js +++ b/contentcuration/contentcuration/frontend/administration/constants.js @@ -6,3 +6,10 @@ export const RouteNames = { }; export const rowsPerPageItems = [25, 50, 75, 100]; + +export const ChannelTypeFilter = { + ALL: 'all', + KOLIBRI_LIBRARY: 'kolibriLibrary', + COMMUNITY_LIBRARY: 'communityLibrary', + UNLISTED: 'unlisted', +}; diff --git a/contentcuration/contentcuration/frontend/administration/pages/Channels/ChannelTable.vue b/contentcuration/contentcuration/frontend/administration/pages/Channels/ChannelTable.vue index e139cbfcab..2dfdec7fae 100644 --- a/contentcuration/contentcuration/frontend/administration/pages/Channels/ChannelTable.vue +++ b/contentcuration/contentcuration/frontend/administration/pages/Channels/ChannelTable.vue @@ -143,7 +143,7 @@ import { mapGetters, mapActions } from 'vuex'; import { getCurrentInstance, onMounted, ref, computed, watch } from 'vue'; import transform from 'lodash/transform'; - import { RouteNames, rowsPerPageItems } from '../../constants'; + import { ChannelTypeFilter, RouteNames, rowsPerPageItems } from '../../constants'; import { useTable } from '../../composables/useTable'; import ChannelItem from './ChannelItem'; import { useKeywordSearch } from 'shared/composables/useKeywordSearch'; @@ -155,13 +155,6 @@ import LanguageDropdown from 'shared/views/LanguageDropdown'; import { CommunityLibraryStatus } from 'shared/constants'; - const ChannelTypeFilter = { - ALL: 'all', - KOLIBRI_LIBRARY: 'kolibriLibrary', - COMMUNITY_LIBRARY: 'communityLibrary', - UNLISTED: 'unlisted', - }; - const channelTypeFilterMap = { [ChannelTypeFilter.ALL]: { label: 'All Channels', diff --git a/contentcuration/contentcuration/frontend/administration/pages/Channels/__tests__/channelTable.spec.js b/contentcuration/contentcuration/frontend/administration/pages/Channels/__tests__/channelTable.spec.js index c35516a05b..07c08be509 100644 --- a/contentcuration/contentcuration/frontend/administration/pages/Channels/__tests__/channelTable.spec.js +++ b/contentcuration/contentcuration/frontend/administration/pages/Channels/__tests__/channelTable.spec.js @@ -1,8 +1,8 @@ import { mount, createLocalVue } from '@vue/test-utils'; import Vuex, { Store } from 'vuex'; import router from '../../../router'; -import { RouteNames } from '../../../constants'; -import ChannelTable from '../ChannelTable'; +import { ChannelTypeFilter, RouteNames } from '../../../constants'; +import ChannelTable from '../ChannelTable.vue'; const localVue = createLocalVue(); @@ -55,8 +55,8 @@ describe('channelTable', () => { }); describe('filters', () => { it('changing channel type filter should set query params', async () => { - wrapper.vm.channelTypeFilter = 'communityLibrary'; - expect(router.currentRoute.query.channelType).toBe('communityLibrary'); + wrapper.vm.channelTypeFilter = ChannelTypeFilter.COMMUNITY_LIBRARY; + expect(router.currentRoute.query.channelType).toBe(ChannelTypeFilter.COMMUNITY_LIBRARY); }); it('changing language filter should set query params', () => { wrapper.vm.languageFilter = 'en'; @@ -72,10 +72,10 @@ describe('channelTable', () => { expect(router.currentRoute.query.keywords).toBe('keyword test'); }); it('changing channel type filter should reset channel status filter', async () => { - wrapper.vm.channelTypeFilter = 'communityLibrary'; + wrapper.vm.channelTypeFilter = ChannelTypeFilter.COMMUNITY_LIBRARY; wrapper.vm.channelStatusFilter = 'published'; await wrapper.vm.$nextTick(); - wrapper.vm.channelTypeFilter = 'unlisted'; + wrapper.vm.channelTypeFilter = ChannelTypeFilter.UNLISTED; await wrapper.vm.$nextTick(); expect(wrapper.vm.channelStatusFilter).toBe('live'); });