From 6471f0b8d91b8bdf8dd23803dc0b71cb73987136 Mon Sep 17 00:00:00 2001 From: Alex Velez Date: Mon, 16 Dec 2024 16:52:48 -0500 Subject: [PATCH] Refactor useFetch to return hasMore, loadingMore and count --- .../coach/assets/src/composables/useFetch.js | 79 +++++++------------ .../src/composables/useResourceSelection.js | 31 +++----- .../UpdatedResourceSelection.vue | 22 ++++-- .../subPages/SelectFromBookmarks.vue | 8 +- .../subPages/SelectFromChannels.vue | 8 +- .../subPages/SelectionIndex.vue | 3 +- 6 files changed, 69 insertions(+), 82 deletions(-) diff --git a/kolibri/plugins/coach/assets/src/composables/useFetch.js b/kolibri/plugins/coach/assets/src/composables/useFetch.js index a3af105d960..31823a6c626 100644 --- a/kolibri/plugins/coach/assets/src/composables/useFetch.js +++ b/kolibri/plugins/coach/assets/src/composables/useFetch.js @@ -1,6 +1,5 @@ import get from 'lodash/get'; import { ref, computed } from 'vue'; -import { ViewMoreButtonStates } from '../constants'; /** * A composable for managing fetch operations with optional methods for additional data fetching. @@ -43,7 +42,7 @@ import { ViewMoreButtonStates } from '../constants'; * ```js * // Suppose the response object looks like this: * const response = { - * payload: [...], + * results: [...], * more: { page: 2 } * }; * @@ -52,6 +51,22 @@ import { ViewMoreButtonStates } from '../constants'; * useFetch({ moreKey: "more" }); * ``` * + * @param {string} [options.countKey] The key in the response object where the count of + * the data is located. Count is the total number of items. + * * You can use `lodash.get`-compatible keys to access nested objects (e.g., "data.count"). + * + * Example: + * ```js + * // Suppose the response object looks like this: + * const response = { + * results: [...], + * count: 100 + * }; + * + * // By specifying `countKey`, you tell the composable where to find the count of the data: + * const { count } = useFetch({ countKey: "count" }); + * console.log(count); // Outputs: 100 + * ``` * * @param {(more, ...args) => Promise} [options.fetchMoreMethod] Function to fetch more data. * * This function receives a "more" object as its first argument. This "more" object is specified @@ -68,57 +83,31 @@ import { ViewMoreButtonStates } from '../constants'; * ``` * * - * @param {Object.} [options.additionalDataKeys] An object that maps additional - * data keys for the response object. - * * In the `{ key: value }` pair: - * - The `key` will be used as the property name in the returned `additionalData` object. - * - The `value` specifies the key in the response object from which the data will be retrieved. - * - * Example: - * ```js - * const additionalDataKeys = { - * userId: "user_id", // The `userId` property in `additionalData` will map to `response.user_id` - * userName: "name" // The `userName` property in `additionalData` will map to `response.name` - * }; - * - * const { additionalData } = useFetch({ additionalDataKeys }); - * console.log(additionalData.userId); // Outputs the value of `response.user_id` - * console.log(additionalData.userName); // Outputs the value of `response.name` - * ``` - * - * * @typedef {Object} FetchObject * @property {any} data The main fetched data. * @property {Object} error Error object if a fetch data failed. + * @property {any} count The count of the fetched data. E.g., the total number of items. * @property {boolean} loading Data loading state. This loading doesnt reflect the loading when - * fetching more data. refer to moreState for that. - * @property {string} moreState State of the fetch more data, it could be LOADING, HAS_MORE, - * NO_MORE or ERROR. - * @property {Object} additionalData Extra data specified by `additionalDataKeys`. + * fetching more data. refer to `loadingMore` for that. + * @property {boolean} loadingMore Loading state when fetching more data. This is different from + * `loading` which is for the main data fetch. + * @property {boolean} hasMore A computed property to check if there is more data to fetch. * @property {(...args) => Promise} fetchData A method to manually trigger the main fetch. * @property {(...args) => Promise} fetchMore A method to manually trigger fetch more data. * * @returns {FetchObject} An object with properties and methods for managing the fetch process. */ export default function useFetch(options) { - const { fetchMethod, fetchMoreMethod, dataKey, moreKey, additionalDataKeys } = options || {}; + const { fetchMethod, fetchMoreMethod, dataKey, moreKey, countKey } = options || {}; const loading = ref(false); const data = ref(null); const error = ref(null); const more = ref(null); + const count = ref(null); const loadingMore = ref(false); - const additionalData = ref(null); - const moreState = computed(() => { - if (loadingMore.value) { - return ViewMoreButtonStates.LOADING; - } - if (more.value) { - return ViewMoreButtonStates.HAS_MORE; - } - return ViewMoreButtonStates.NO_MORE; - }); + const hasMore = computed(() => more.value != null); const _setFromKeys = (response, loadingMore) => { let responseData; @@ -141,17 +130,8 @@ export default function useFetch(options) { more.value = get(response, moreKey) || null; } - if (additionalDataKeys) { - const newAdditionalData = {}; - // The `key` will be used as the property name in the returned `additionalData` object. - // The `value` specifies the key in the response object from which the data will be get. - for (const [key, value] of Object.entries(additionalDataKeys)) { - // if value is an empty string, that means that no specific data is required, - // but the whole response object. - newAdditionalData[key] = value === '' ? response : get(response, value); - } - - additionalData.value = newAdditionalData; + if (countKey) { + count.value = get(response, countKey) || null; } }; @@ -190,9 +170,10 @@ export default function useFetch(options) { return { data, error, + count, loading, - moreState, - additionalData, + hasMore, + loadingMore, fetchData, fetchMore, }; diff --git a/kolibri/plugins/coach/assets/src/composables/useResourceSelection.js b/kolibri/plugins/coach/assets/src/composables/useResourceSelection.js index 7768b26c767..5da33f93be4 100644 --- a/kolibri/plugins/coach/assets/src/composables/useResourceSelection.js +++ b/kolibri/plugins/coach/assets/src/composables/useResourceSelection.js @@ -45,6 +45,7 @@ export default function useResourceSelection() { const selectionRules = ref([]); const selectedResources = ref([]); + const topic = ref(null); const bookmarksFetch = useFetch({ fetchMethod: () => @@ -57,9 +58,7 @@ export default function useResourceSelection() { }), dataKey: 'results', moreKey: 'more', - additionalDataKeys: { - count: 'count', - }, + countKey: 'count', }); const channelsFetch = useFetch({ @@ -71,24 +70,16 @@ export default function useResourceSelection() { }), }); - const treeFetch = useFetch({ - fetchMethod: () => - ContentNodeResource.fetchTree({ id: topicId.value, params: { include_coach_content: true } }), - fetchMoreMethod: more => ContentNodeResource.fetchTree({ id: topicId.value, params: more }), - dataKey: 'children.results', - moreKey: 'children.more.params', - additionalDataKeys: { - topic: '', // return the whole response as topic - }, - }); + const fetchTree = async (params = {}) => { + topic.value = await ContentNodeResource.fetchTree(params); + return topic.value.children; + }; - const topic = computed(() => { - if (topicId.value) { - const { additionalData } = treeFetch; - const { topic } = additionalData.value; - return topic; - } - return null; + const treeFetch = useFetch({ + fetchMethod: () => fetchTree({ id: topicId.value, params: { include_coach_content: true } }), + fetchMoreMethod: more => fetchTree(more), + dataKey: 'results', + moreKey: 'more', }); watch(topicId, () => { diff --git a/kolibri/plugins/coach/assets/src/views/lessons/LessonSummaryPage/UpdatedResourceSelection.vue b/kolibri/plugins/coach/assets/src/views/lessons/LessonSummaryPage/UpdatedResourceSelection.vue index 3ed799fffcd..2d93d9fb739 100644 --- a/kolibri/plugins/coach/assets/src/views/lessons/LessonSummaryPage/UpdatedResourceSelection.vue +++ b/kolibri/plugins/coach/assets/src/views/lessons/LessonSummaryPage/UpdatedResourceSelection.vue @@ -36,7 +36,7 @@ import { ContentNodeKinds } from 'kolibri/constants'; import ContentCardList from '../../lessons/LessonResourceSelectionPage/ContentCardList.vue'; import ResourceSelectionBreadcrumbs from '../../lessons/LessonResourceSelectionPage/SearchTools/ResourceSelectionBreadcrumbs.vue'; - import { PageNames } from '../../../constants'; + import { PageNames, ViewMoreButtonStates } from '../../../constants'; export default { name: 'UpdatedResourceSelection', @@ -62,16 +62,19 @@ type: Array, required: true, }, - viewMoreButtonState: { - type: String, - required: false, - default: null, + hasMore: { + type: Boolean, + default: false, }, fetchMore: { type: Function, required: false, default: null, }, + loadingMore: { + type: Boolean, + default: false, + }, selectionRules: { type: Array, required: false, @@ -109,6 +112,15 @@ showSelectAll() { return this.canSelectAll && this.multi && this.selectableContentList.length > 0; }, + viewMoreButtonState() { + if (this.loadingMore) { + return ViewMoreButtonStates.LOADING; + } + if (this.hasMore) { + return ViewMoreButtonStates.HAS_MORE; + } + return ViewMoreButtonStates.NO_MORE; + }, }, methods: { contentLink(content) { diff --git a/kolibri/plugins/coach/assets/src/views/lessons/LessonSummaryPage/sidePanels/LessonResourceSelection/subPages/SelectFromBookmarks.vue b/kolibri/plugins/coach/assets/src/views/lessons/LessonSummaryPage/sidePanels/LessonResourceSelection/subPages/SelectFromBookmarks.vue index 7c0ad41685f..bc8bea2a7bc 100644 --- a/kolibri/plugins/coach/assets/src/views/lessons/LessonSummaryPage/sidePanels/LessonResourceSelection/subPages/SelectFromBookmarks.vue +++ b/kolibri/plugins/coach/assets/src/views/lessons/LessonSummaryPage/sidePanels/LessonResourceSelection/subPages/SelectFromBookmarks.vue @@ -4,8 +4,9 @@