Skip to content

Commit

Permalink
Add primary language filtering to useBaseSearch.
Browse files Browse the repository at this point in the history
Allow language selection logic to be reused.
  • Loading branch information
rtibbles committed Dec 17, 2024
1 parent 012d8fd commit 1f5a9a4
Show file tree
Hide file tree
Showing 11 changed files with 372 additions and 143 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { get } from '@vueuse/core';
import isEmpty from 'lodash/isEmpty';
import pick from 'lodash/pick';
import { computed, getCurrentInstance } from 'vue';
import { primaryLanguageKey } from 'kolibri-common/composables/useBaseSearch';
import { ExternalPagePaths, PageNames } from '../constants';

function _decodeBackLinkQuery(query) {
Expand All @@ -17,6 +18,10 @@ export default function useContentLink(store) {

function _makeNodeLink(id, isResource, query, deviceId) {
const params = get(route).params;
const oldQuery = get(route).query || {};
if (!isResource && oldQuery[primaryLanguageKey]) {
query[primaryLanguageKey] = oldQuery[primaryLanguageKey];
}
return {
name: isResource ? PageNames.TOPICS_CONTENT : PageNames.TOPICS_TOPIC,
params: pick({ id, deviceId: deviceId || params.deviceId }, ['id', 'deviceId']),
Expand Down
84 changes: 61 additions & 23 deletions kolibri/plugins/learn/assets/src/views/LibraryPage/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,16 @@
v-else-if="!displayingSearchResults && !rootNodesLoading"
data-test="channels"
>
<h1 class="channels-label">
{{ channelsLabel }}
</h1>
<KFixedGrid numCols="4">
<KFixedGridItem span="3">
<h1 class="channels-label">
{{ channelsLabel }}
</h1>
</KFixedGridItem>
<KFixedGridItem span="1">
<LanguageSelector :primary="true" />
</KFixedGridItem>
</KFixedGrid>
<p
v-if="isLocalLibraryEmpty"
data-test="nothing-in-lib-label"
Expand Down Expand Up @@ -109,6 +116,7 @@
:class="windowIsLarge ? 'side-panel' : ''"
data-test="side-panel-local"
:width="`${sidePanelWidth}px`"
:showLanguages="displayingSearchResults"
/>
</div>

Expand All @@ -122,6 +130,7 @@
v-model="searchTerms"
data-test="side-panel"
:width="`${sidePanelWidth}px`"
:showLanguages="displayingSearchResults"
/>
</SidePanelModal>

Expand Down Expand Up @@ -176,20 +185,22 @@
<script>
import { get, set } from '@vueuse/core';
import orderBy from 'lodash/orderBy';
import { onMounted, getCurrentInstance, ref, watch } from 'vue';
import commonCoreStrings from 'kolibri/uiText/commonCoreStrings';
import useKResponsiveWindow from 'kolibri-design-system/lib/composables/useKResponsiveWindow';
import useUser from 'kolibri/composables/useUser';
import samePageCheckGenerator from 'kolibri-common/utils/samePageCheckGenerator';
import { getContentLangActive } from 'kolibri/utils/i18n';
import ContentNodeResource from 'kolibri-common/apiResources/ContentNodeResource';
import { mapState } from 'vuex';
import MeteredConnectionNotificationModal from 'kolibri-common/components/MeteredConnectionNotificationModal.vue';
import appCapabilities, { checkCapability } from 'kolibri/utils/appCapabilities';
import LearningActivityChip from 'kolibri-common/components/ResourceDisplayAndSearch/LearningActivityChip.vue';
import { searchKeys } from 'kolibri-common/composables/useBaseSearch';
import SidePanelModal from 'kolibri-common/components/SidePanelModal';
import SearchFiltersPanel from 'kolibri-common/components/SearchFiltersPanel';
import LanguageSelector from 'kolibri-common/components/SearchFiltersPanel/LanguageSelector';
import useChannels from 'kolibri-common/composables/useChannels';
import { KolibriStudioId, PageNames } from '../../constants';
import useCardViewStyle from '../../composables/useCardViewStyle';
Expand Down Expand Up @@ -233,6 +244,7 @@
LearnAppBarPage,
OtherLibraries,
PostSetupModalGroup,
LanguageSelector,
},
mixins: [commonLearnStrings, commonCoreStrings],
setup(props) {
Expand Down Expand Up @@ -261,6 +273,9 @@
removeFilterTag,
clearSearch,
currentRoute,
createBaseSearchGetParams,
ensurePrimaryLanguage,
primaryLanguage,
} = useSearch();
search();
const { fetchResumableContentNodes } = useLearnerResources();
Expand Down Expand Up @@ -300,20 +315,35 @@
// we want them to be in the same order as the channels list
set(
rootNodes,
channels
.map(channel => {
const node = channelCollection.find(n => n.channel_id === channel.id);
if (node) {
// The `channel` comes with additional data that is
// not returned from the ContentNodeResource.
// Namely thumbnail, description and tagline (so far)
node.title = channel.name || node.title;
node.thumbnail = channel.thumbnail;
node.description = channel.tagline || channel.description;
return node;
}
})
.filter(Boolean),
// Sort channels by relevance to the current user interface language
orderBy(
channels
.map(channel => {
const node = channelCollection.find(n => n.channel_id === channel.id);
if (node) {
// The `channel` comes with additional data that is
// not returned from the ContentNodeResource.
// Namely thumbnail, description and tagline (so far)
node.title = channel.name || node.title;
node.thumbnail = channel.thumbnail;
node.description = channel.tagline || channel.description;
node.included_languages = channel.included_languages;
return node;
}
})
.filter(Boolean),
[
c => getContentLangActive(c.lang, get(primaryLanguage)),
c =>
Math.max(
...c.included_languages.map(l =>
getContentLangActive(l, get(primaryLanguage)),
),
),
'title',
],
['desc', 'desc', 'asc'],
),
);
store.commit('CORE_SET_PAGE_LOADING', false);
Expand All @@ -332,7 +362,8 @@
}
function _showLibrary(baseurl) {
return fetchChannels({ baseurl }).then(channels => {
const params = createBaseSearchGetParams();
return fetchChannels(params).then(channels => {
if (!channels.length && isUserLoggedIn) {
router.replace({ name: PageNames.CONTENT_UNAVAILABLE });
return;
Expand All @@ -342,9 +373,7 @@
return;
}
const query = currentRoute().query;
if (searchKeys.some(key => query[key])) {
if (get(displayingSearchResults)) {
// If currently on a route with search terms
// just finish early and let the component handle loading
store.commit('CORE_SET_PAGE_LOADING', false);
Expand All @@ -357,7 +386,10 @@
});
}
function showLibrary() {
async function showLibrary() {
if (!(await ensurePrimaryLanguage())) {
return;
}
set(rootNodesLoading, true);
store.commit('CORE_SET_PAGE_LOADING', true);
if (props.deviceId) {
Expand Down Expand Up @@ -386,6 +418,12 @@
}
});
watch(primaryLanguage, () => {
if (!displayingSearchResults.value) {
showLibrary();
}
});
showLibrary();
return {
Expand Down
16 changes: 8 additions & 8 deletions kolibri/plugins/learn/assets/src/views/TopicsPage/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -370,12 +370,14 @@
removeFilterTag,
clearSearch,
currentRoute,
createBaseSearchGetParams,
ensurePrimaryLanguage,
} = useSearch(topic);
const { back, genContentLinkKeepCurrentBackLink } = useContentLink();
const { windowBreakpoint, windowIsLarge, windowIsSmall } = useKResponsiveWindow();
const { channelsMap, fetchChannels } = useChannels();
const { fetchContentNodeProgress, fetchContentNodeTreeProgress } = useContentNodeProgress();
const { isUserLoggedIn, isCoach, isAdmin, isSuperuser } = useUser();
const { isUserLoggedIn } = useUser();
const { fetchUserDownloadRequests } = useDownloadRequests(store);
const isRoot = ref(false);
Expand Down Expand Up @@ -430,10 +432,7 @@
let id = props.id;
const route = currentRoute();
const skip = route.query && route.query.skip === 'true';
const params = {
include_coach_content: get(isAdmin) || get(isCoach) || get(isSuperuser),
baseurl,
};
const params = createBaseSearchGetParams(false);
if (get(isUserLoggedIn) && !baseurl) {
fetchContentNodeTreeProgress({ id, params });
}
Expand Down Expand Up @@ -486,7 +485,10 @@
});
}
function showTopicsTopic() {
async function showTopicsTopic() {
if (!(await ensurePrimaryLanguage())) {
return;
}
return store.dispatch('loading').then(() => {
const route = currentRoute();
store.commit('SET_PAGE_NAME', route.name);
Expand Down Expand Up @@ -557,8 +559,6 @@
type: String,
default: null,
},
// Our linting doesn't detect usage in the setup function yet.
// eslint-disable-next-line vue/no-unused-properties
id: {
type: String,
required: true,
Expand Down
22 changes: 20 additions & 2 deletions kolibri/plugins/learn/assets/test/views/library-page.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,13 @@ const CHANNEL = {
name: 'test channel',
root: 'test root',
thumbnail: 'test thumbnail',
lang: {
id: 'es-es',
lang_name: 'Español',
lang_code: 'es',
lang_subcode: 'es',
lang_direction: 'ltr',
},
};

jest.mock('kolibri-common/composables/useChannels');
Expand All @@ -53,7 +60,13 @@ jest.mock('kolibri/urls');

async function makeWrapper({ options, fullMount = false } = {}) {
const store = new Store({
state: { core: { loading: false } },
state: {
core: { loading: false },
route: {
query: {},
name: PageNames.TOPICS_TOPIC,
},
},
getters: {
isUserLoggedIn: jest.fn(),
isLearner: jest.fn(),
Expand All @@ -70,6 +83,9 @@ async function makeWrapper({ options, fullMount = false } = {}) {
SET_PAGE_NAME: jest.fn(),
CORE_SET_PAGE_LOADING: jest.fn(),
CORE_SET_ERROR: jest.fn(),
SET_QUERY(state, query) {
state.route.query = query;
},
},
});
let wrapper;
Expand All @@ -94,8 +110,10 @@ describe('LibraryPage', () => {
}),
);
ContentNodeResource.fetchCollection.mockImplementation(() =>
Promise.resolve([{ id: 'test', title: 'test', channel_id: CHANNEL_ID }]),
Promise.resolve([{ id: 'test', title: 'test', channel_id: CHANNEL_ID, lang: CHANNEL.lang }]),
);
router.push = jest.fn().mockReturnValue(Promise.resolve());
router.replace = jest.fn().mockReturnValue(Promise.resolve());
});
describe('filters button', () => {
it('is visible when the page is not large and channels are available', async () => {
Expand Down
17 changes: 6 additions & 11 deletions packages/kolibri-common/components/SearchChips.vue
Original file line number Diff line number Diff line change
Expand Up @@ -40,26 +40,21 @@
name: 'SearchChips',
mixins: [commonCoreStrings],
setup() {
const { availableLanguages } = injectBaseSearch();
const languagesMap = availableLanguages.value.reduce((map, lang) => {
map[lang.id] = lang;
const { languageOptions, activeSearchTerms } = injectBaseSearch();
const languagesMap = languageOptions.value.reduce((map, lang) => {
map[lang.value] = lang.label;
return map;
}, {});
const { channelsMap } = useChannels();
return {
channelsMap,
languagesMap,
activeSearchTerms,
};
},
props: {
searchTerms: {
type: Object,
default: () => ({}),
},
},
computed: {
items() {
return flatMap(this.searchTerms, (value, key) => {
return flatMap(this.activeSearchTerms, (value, key) => {
if (key === 'keywords' && value && value.length) {
return [
{
Expand All @@ -82,7 +77,7 @@
methods: {
translate(key, value) {
if (key === 'languages') {
return this.languagesMap[value].lang_name;
return this.languagesMap[value];
}
if (key === 'channels') {
return this.channelsMap[value].name;
Expand Down
Loading

0 comments on commit 1f5a9a4

Please sign in to comment.