Skip to content

Commit

Permalink
Merge pull request #4156 from bjester/fix-json-issue-2
Browse files Browse the repository at this point in the history
Finalize public API integration with import search
  • Loading branch information
bjester authored Jun 21, 2023
2 parents bcabc71 + 7700c2d commit da46564
Show file tree
Hide file tree
Showing 9 changed files with 142 additions and 36 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
<ResourcePanel
:nodeId="nodeId"
:channelId="channelId"
:useRouting="useRouting"
@close="$emit('close')"
>
<template #navigation>
Expand Down Expand Up @@ -66,6 +67,10 @@
type: Boolean,
default: true,
},
useRouting: {
type: Boolean,
default: true,
},
},
methods: {
// @public
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,8 @@
>
<VTab
class="px-2"
:to="{ query: { tab: 'questions' } }"
exact
@change="tab = 'questions'"
>
{{ $tr('questions') }}
<Icon
Expand All @@ -90,8 +90,8 @@
</VTab>
<VTab
class="px-2"
:to="{ query: { tab: 'details' } }"
exact
@change="tab = 'details'"
>
{{ $tr('details') }}
<Icon
Expand Down Expand Up @@ -587,11 +587,16 @@
type: Boolean,
default: false,
},
useRouting: {
type: Boolean,
default: true,
},
},
data() {
return {
loading: false,
showAnswers: false,
currentTab: 'details',
};
},
computed: {
Expand Down Expand Up @@ -619,6 +624,10 @@
if (!this.isExercise) {
return;
}
if (!this.useRouting) {
this.currentTab = value;
return;
}
// If viewing an exercise, we need to synchronize the the route's
// query params with the 'tab' value
const newRoute = { query: { tab: value } };
Expand All @@ -629,8 +638,8 @@
}
},
get() {
if (!this.isExercise) {
return 'details';
if (!this.isExercise || !this.useRouting) {
return this.currentTab;
}
return this.$route.query.tab || 'questions';
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,9 @@ export const DraggableRegions = {
TOPIC_VIEW: 'topicView',
CLIPBOARD: 'clipboard',
};

/**
* Default page size for the import search page
* @type {number}
*/
export const ImportSearchPageSize = 10;
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
import Checkbox from 'shared/views/form/Checkbox';
import LoadingText from 'shared/views/LoadingText';
import { constantsTranslationMixin } from 'shared/mixins';
import { ChannelListTypes } from 'shared/constants';
export default {
name: 'ContentTreeList',
Expand Down Expand Up @@ -154,8 +155,16 @@
},
mounted() {
this.loading = true;
let params = {};
const channelListType = this.$route.query.channel_list || ChannelListTypes.PUBLIC;
if (channelListType === ChannelListTypes.PUBLIC) {
// TODO: load from public API instead
// TODO: challenging because of node_id->id and root_id->channel_id
params = { published: true };
}
return Promise.all([
this.loadChildren({ parent: this.topicId }),
this.loadChildren({ parent: this.topicId, ...params }),
this.loadAncestors({ id: this.topicId }),
]).then(() => {
this.loading = false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
fixed
:permanent="false"
:nodeId="previewNode.id"
:useRouting="false"
@close="showPreview = false"
>
<template #actions>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,12 @@
import { mapActions, mapGetters, mapState } from 'vuex';
import debounce from 'lodash/debounce';
import find from 'lodash/find';
import { ImportSearchPageSize } from '../../constants';
import BrowsingCard from './BrowsingCard';
import SavedSearchesModal from './SavedSearchesModal';
import SearchFilters from './SearchFilters';
import SearchFilterBar from './SearchFilterBar';
import logging from 'shared/logging';
import Pagination from 'shared/views/Pagination';
import Checkbox from 'shared/views/form/Checkbox';
import LoadingText from 'shared/views/LoadingText';
Expand Down Expand Up @@ -137,9 +139,13 @@
nodes() {
return this.getContentNodes(this.nodeIds) || [];
},
pageSizeOptions() {
return [10, 15, 25];
},
pageSize: {
get() {
return Number(this.$route.query.page_size) || 25;
const pageSize = Number(this.$route.query.page_size);
return this.pageSizeOptions.find(p => p === pageSize) || ImportSearchPageSize;
},
set(page_size) {
this.$router.push({
Expand All @@ -152,9 +158,6 @@
});
},
},
pageSizeOptions() {
return [25, 50, 100];
},
isSelected() {
return function(node) {
return Boolean(find(this.selected, { id: node.id }));
Expand Down Expand Up @@ -190,6 +193,7 @@
function() {
this.fetchResourceSearchResults({
...this.$route.query,
page_size: this.pageSize,
keywords: this.currentSearchTerm,
exclude_channel: this.currentChannelId,
last: undefined,
Expand All @@ -200,8 +204,9 @@
this.pageCount = page.total_pages;
this.totalCount = page.count;
})
.catch(() => {
.catch(e => {
this.loadFailed = true;
logging.error(e);
});
},
1000,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { ContentKindsNames } from 'shared/leUtils/ContentKinds';
import { findLicense } from 'shared/utils/helpers';
import { RolesNames } from 'shared/leUtils/Roles';
import { isNodeComplete } from 'shared/utils/validation';
import * as publicApi from 'shared/data/public';

import db from 'shared/data/db';

Expand All @@ -34,6 +35,20 @@ export function loadContentNode(context, id) {
});
}

/**
* @param context
* @param {string} id
* @param {string} nodeId - Note: this is `node_id` not `id`
* @param {string} rootId
* @return {Promise<{}>}
*/
export async function loadPublicContentNode(context, { id, nodeId, rootId }) {
const publicNode = await publicApi.getContentNode(nodeId);
const localNode = publicApi.convertContentNodeResponse(id, rootId, publicNode);
context.commit('ADD_CONTENTNODE', localNode);
return localNode;
}

export function loadContentNodeByNodeId(context, nodeId) {
const channelId = context.rootState.currentChannel.currentChannelId;
return loadContentNodes(context, { '[node_id+channel_id]__in': [[nodeId, channelId]] })
Expand All @@ -47,8 +62,12 @@ export function loadContentNodeByNodeId(context, nodeId) {
});
}

export function loadChildren(context, { parent }) {
return loadContentNodes(context, { parent });
export function loadChildren(context, { parent, published = null }) {
const params = { parent };
if (published !== null) {
params.published = published;
}
return loadContentNodes(context, params);
}

export function loadAncestors(context, { id }) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import partition from 'lodash/partition';
import { ImportSearchPageSize } from '../../constants';
import client from 'shared/client';
import urls from 'shared/urls';
import * as publicApi from 'shared/data/public';
import { NOVALUE, ChannelListTypes } from 'shared/constants';

import { Channel, SavedSearch } from 'shared/data/resources';

export async function fetchResourceSearchResults(context, params) {
params = { ...params };
delete params['last'];
params.page_size = params.page_size || 25;
params.page_size = params.page_size || ImportSearchPageSize;
params.channel_list = params.channel_list || ChannelListTypes.PUBLIC;

const response = await client.get(urls.search_list(), { params });
Expand All @@ -27,28 +27,48 @@ export async function fetchResourceSearchResults(context, params) {
)
: Promise.resolve([]);

await Promise.all([
const [privateNodesLoaded, publicNodesLoaded] = await Promise.all([
// the loadContentNodes action already loads the nodes into vuex
privatePromise,
Promise.all(
// The public API is cached, so we can hopefully call it multiple times without
// worrying too much about performance
publicNodes.map(async node => {
const publicNode = await publicApi.getContentNode(node.node_id).catch(() => null);
if (!publicNode) {
return;
}
return publicApi.convertContentNodeResponse(node.id, node.root_id, publicNode);
publicNodes.map(node => {
return context
.dispatch(
'contentNode/loadPublicContentNode',
{
id: node.id,
nodeId: node.node_id,
rootId: node.root_id,
},
{ root: true }
)
.catch(() => null);
})
)
.then(nodes => nodes.filter(Boolean))
.then(nodes => {
context.commit('contentNode/ADD_CONTENTNODES', nodes, { root: true });
return nodes;
}),
).then(nodes => nodes.filter(Boolean)),
]);

return response.data;
// In case we failed to obtain data for all nodes, filter out the ones we didn't get
const results = response.data.results
.map(node => {
return (
privateNodesLoaded.find(n => n.id === node.id) ||
publicNodesLoaded.find(n => n.id === node.id)
);
})
.filter(Boolean);
// This won't work across multiple pages, if we fail to load some nodes, but that should be rare
const countDiff = response.data.results.length - results.length;
const count = response.data.count - countDiff;
const pageDiff = Math.floor(countDiff / params.page_size);

return {
count,
page: response.data.page,
results,
total_pages: response.data.total_pages - pageDiff,
};
}

export function loadChannels(context, params) {
Expand Down
48 changes: 40 additions & 8 deletions contentcuration/contentcuration/frontend/shared/data/public.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*
* See bottom of file for '@typedef's
*/
import isString from 'lodash/isString';
import isFunction from 'lodash/isFunction';
import { RolesNames } from 'shared/leUtils/Roles';
import { findLicense } from 'shared/utils/helpers';
Expand All @@ -29,6 +30,14 @@ function _convertMetadataLabel(key, obj) {
return converted;
}

/**
* @see MPTTModel for explanation of this calculation
* @param obj
* @return {number}
*/
const total_count = obj =>
obj['kind'] === ContentKindsNames.TOPIC ? (obj['rght'] - obj['lft'] - 1) / 2 : 1;

const CONTENT_NODE_FIELD_MAP = {
// `destination field`: `source field` or value producing function
node_id: 'id',
Expand All @@ -54,21 +63,44 @@ const CONTENT_NODE_FIELD_MAP = {
categories: _convertMetadataLabel.bind({}, 'categories'),
resource_types: _convertMetadataLabel.bind({}, 'resource_types'),
tags: _convertMetadataLabel.bind({}, 'tags'),
extra_fields: obj => ({ options: JSON.parse(obj['options']) }),
extra_fields: obj => {
const options = obj['options'];
let randomize = true;

if (obj['kind'] === ContentKindsNames.EXERCISE) {
const assessmentMetadata = obj['assessmentmetadata'];
if (!options['completion_criteria'] && assessmentMetadata['mastery_model']) {
options['completion_criteria'] = {
model: 'mastery',
threshold: {
// TODO: remove JSON.parse, since we shouldn't be receiving strings from the API
mastery_model: isString(assessmentMetadata['mastery_model'])
? JSON.parse(assessmentMetadata['mastery_model'])
: assessmentMetadata['mastery_model'],
},
};
}
randomize = Boolean(assessmentMetadata['randomize']);
}
return {
options,
randomize,
};
},
role_visibility: obj => (obj['coach_content'] ? RolesNames.COACH : RolesNames.LEARNER),
license: obj => findLicense(obj['license_name']),
license_description: 'license_description',
copyright_holder: 'license_owner',
coach_count: 'num_coach_contents',
// see MPTTModel for explanation of this calculation
total_count: obj =>
obj['kind'] === ContentKindsNames.TOPIC ? (obj['rght'] - obj['lft'] - 1) / 2 : 1,

total_count,
language: obj => obj['lang']['id'],
thumbnail_src: obj => new URL(obj['thumbnail'], window.location.origin).pathname,
thumbnail_src: obj =>
obj['thumbnail'] ? new URL(obj['thumbnail'], window.location.origin).toString() : null,
thumbnail_encoding: () => '{}',

// unavailable in public API
// 'resource_count': 'num_resources',
resource_count: total_count, // fake it, assume all nodes are resources
// 'root_id': 'root_id',
};

Expand All @@ -77,9 +109,9 @@ const CONTENT_NODE_FIELD_MAP = {
* @param {string} id - the actual ID of the node on Studio's side
* @param {string} root_id - the root content node ID
* @param {PublicContentNode} publicNode
* @return {Promise<{id}>}
* @return {{id}}
*/
export async function convertContentNodeResponse(id, root_id, publicNode) {
export function convertContentNodeResponse(id, root_id, publicNode) {
const contentNode = {
// the public API does not return the actual id, but 'node_id' as the id, so this requires
// us to know what the actual id is
Expand Down

0 comments on commit da46564

Please sign in to comment.