Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lms/lib/courseware_search/lms_filter_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,4 @@ def exclude_dictionary(self, **kwargs):
if not getattr(settings, "SEARCH_SKIP_SHOW_IN_CATALOG_FILTERING", True):
exclude_dictionary['catalog_visibility'] = [CATALOG_VISIBILITY_ABOUT, CATALOG_VISIBILITY_NONE]

return exclude_dictionary
return exclude_dictionary
10 changes: 6 additions & 4 deletions lms/static/js/discovery/collections/filters.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
return Backbone.Collection.extend({
model: Filter,
getTerms: function() {
return this.reduce(function(terms, filter) {
terms[filter.id] = filter.get('query');
return terms;
}, {});
return this.reduce(function(memo, filter) {
const type = filter.get('type');
if (!memo[type]) memo[type] = [];
memo[type].push(filter.get('query'));
return memo;
}, {});
}
});
});
Expand Down
44 changes: 37 additions & 7 deletions lms/static/js/discovery/discovery_factory.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
var filterBar = new FilterBar({collection: filters});
var refineSidebar = new RefineSidebar({
collection: search.discovery.facetOptions,
meanings: meanings
meanings: meanings || {},
filtersCollection: filters
});
var listing;
var courseListingModel = search.discovery;
Expand All @@ -37,12 +38,19 @@

dispatcher.listenTo(refineSidebar, 'selectOption', function(type, query, name) {
form.showLoadingIndicator();
if (filters.get(type)) {
removeFilter(type);
const exist = filters.findWhere({ type: type, query: query });
if (exist) {
filters.remove(exist);
} else {
filters.add({type: type, query: query, name: name});
search.refineSearch(filters.getTerms());
filters.add({ type: type, query: query, name: name });
refineSidebar.render();
}

const terms = groupTerms(filters.toJSON());
const queryString = flattenTermsToQuery(terms);
Backbone.history.navigate('search?' + queryString, { trigger: true });

search.refineSearch(terms);
});

dispatcher.listenTo(filterBar, 'clearFilter', removeFilter);
Expand Down Expand Up @@ -95,9 +103,31 @@
search.refineSearch(filters.getTerms());
}
}
// Group flat list of terms into { type: [queries...] }
function groupTerms(termsList) {
const grouped = {};
_.each(termsList, function(termObj) {
if (!grouped[termObj.type]) {
grouped[termObj.type] = [];
}
grouped[termObj.type].push(termObj.query);
});
return grouped;
}

// Flatten grouped terms into query string
function flattenTermsToQuery(terms) {
const pairs = [];
_.each(terms, function(values, key) {
_.each(values, function(val) {
pairs.push(encodeURIComponent(key) + '=' + encodeURIComponent(val));
});
});
return pairs.join('&');
}

function quote(string) {
return '"' + string + '"';
function quote(str) {
return '"' + str + '"';
}
};
});
Expand Down
7 changes: 5 additions & 2 deletions lms/static/js/discovery/models/filter.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
(function(define) {
define(['backbone'], function(Backbone) {
'use strict';

return Backbone.Model.extend({
idAttribute: 'type',

defaults: {
type: 'search_query',
query: '',
name: ''
},
initialize: function() {
// Manually set model ID used by collection.get()
this.set('id', this.get('type') + '|' + this.get('query'));
}
});
});
Expand Down
90 changes: 77 additions & 13 deletions lms/static/js/discovery/models/search_state.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,36 @@
'js/discovery/collections/filters'
], function(_, Backbone, CourseDiscovery, Filters) {
'use strict';
// ✅ Add the conversion function inside the define scope
function convertAggsToFacets(aggs) {
const facets = {};

for (const facetName in aggs) {
if (!aggs.hasOwnProperty(facetName)) continue;

const facetAgg = aggs[facetName];
const terms = facetAgg.terms || {};
const termsList = [];

for (const term in terms) {
if (!terms.hasOwnProperty(term)) continue;

if (['total', 'other'].includes(term)) continue; // skip metadata
termsList.push({
name: term,
count: terms[term],
});
}

facets[facetName] = {
displayName: facetName,
name: facetName,
terms: termsList,
};
}

return facets;
}

return Backbone.Model.extend({

Expand All @@ -32,33 +62,44 @@

refineSearch: function(terms) {
this.reset();
this.terms = terms;
this.sendQuery(this.buildQuery(0));
},

if (terms) {
this.terms = terms;

} else {
this.terms = {};
}

const data = this.buildQuery(0);
console.log('sending data:', data);
this.sendQuery(data);
},
loadNextPage: function() {
if (this.hasNextPage()) {
this.sendQuery(this.buildQuery(this.page + 1));
}
},

// private

hasNextPage: function() {
var total = this.discovery.get('totalCount');
return total - ((this.page + 1) * this.pageSize) > 0;
},

sendQuery: function(data) {
// eslint-disable-next-line no-unused-expressions
this.jqhxr && this.jqhxr.abort();
if (this.jqhxr) {
this.jqhxr.abort();
}
console.log('Sending data to backend:', data);

this.jqhxr = this.discovery.fetch({
type: 'POST',
data: data
data: JSON.stringify(data),
contentType: 'application/json',
dataType: 'json'
});

return this.jqhxr;
},

buildQuery: function(pageIndex) {
var data = {
search_string: this.searchTerm,
Expand All @@ -68,7 +109,17 @@
_.extend(data, this.terms);
return data;
},

// this groupTerms added to group the search terms and send to refineSearch
groupTerms: function(termsList) {
const grouped = {};
_.each(termsList, function(termObj) {
if (!grouped[termObj.type]) {
grouped[termObj.type] = [];
}
grouped[termObj.type].push(termObj.query);
});
return grouped;
},
reset: function() {
this.discovery.reset();
this.page = 0;
Expand All @@ -81,11 +132,24 @@
this.trigger('error');
}
},

onSync: function(collection, response, options) {
// ✅ Convert aggs to facets
if (!response.facets && response.aggs) {
response.facets = convertAggsToFacets(response.aggs);
console.log('✅ Converted facets:', response.facets);
}

// ✅ Safely parse options.data
let pageIndex = 0;
try {
const parsedData = typeof options.data === 'string' ? JSON.parse(options.data) : options.data;
pageIndex = parsedData.page_index || 0;
} catch (e) {
console.warn('Failed to parse options.data:', e);
}
var total = this.discovery.get('totalCount');
var originalSearchTerm = this.searchTerm;
if (options.data.page_index === 0) {
if (pageIndex === 0) {
if (total === 0) {
// list all courses
this.cachedDiscovery().done(function(cached) {
Expand All @@ -111,7 +175,7 @@
this.trigger('search', this.searchTerm, total);
}
} else {
this.page = options.data.page_index;
this.page = pageIndex;
this.trigger('next');
}
},
Expand Down
2 changes: 2 additions & 0 deletions lms/static/js/discovery/views/courses_listing.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@
var item = new CourseCardView({model: result});
return item.render().el;
}, this);
console.log("Rendering", items.length, "course cards");

HtmlUtils.append(
this.$list,
HtmlUtils.HTML(items)
Expand Down
40 changes: 36 additions & 4 deletions lms/static/js/discovery/views/filter_bar.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,44 @@
this.hide();
},

// clearFilter: function(event) {
// var $target = $(event.currentTarget);
// var filter = this.collection.get($target.data('type'));
// this.trigger('clearFilter', filter.id);
// },
clearFilter: function(event) {
var $target = $(event.currentTarget);
var filter = this.collection.get($target.data('type'));
this.trigger('clearFilter', filter.id);
},
var $target = $(event.currentTarget); // event.currentTarget is .discovery-button
var type = $target.data('type');
var query = $target.data('query');

if (!type || !query) {
console.warn('Missing data-type or data-query');
return;
}

var uid = type + '|' + query;
var filter = this.collection.get(uid);

if (!filter) {
console.warn('Filter not found for uid:', uid);
return;
}

this.collection.remove(filter);
// ✅ Uncheck the matching checkbox
// Unselect the corresponding filter button in the facet list
var selector = 'button.facet-option.discovery-button[data-facet="' + type + '"][data-value="' + query + '"]';
var $button = $(selector);

if ($button.length) {
$button.removeClass('selected');
} else {
console.warn('Filter button not found to unselect:', selector);
}

// ✅ Trigger new search with updated filter state
this.trigger('clearFilter');
},
clearAll: function(event) {
this.trigger('clearAll');
},
Expand Down
27 changes: 27 additions & 0 deletions lms/static/js/discovery/views/refine_sidebar.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@

initialize: function(options) {
this.meanings = options.meanings || {};
this.filtersCollection = options.filtersCollection || null; //initialize filterCollection colled in discovery_factory
this.$container = this.$el.find('.search-facets-lists');
this.facetTpl = HtmlUtils.template($('#facet-tpl').html());
this.facetOptionTpl = HtmlUtils.template($('#facet_option-tpl').html());
this.fullOptions = {};
},

facetName: function(key) {
Expand All @@ -39,6 +41,11 @@
return HtmlUtils.joinHtml.apply(this, _.map(options, function(option) {
var data = _.clone(option.attributes);
data.name = this.termName(data.facet, data.term);
// this added to handle selected orgs as selected // replaced with returns
data.selected = this.filtersCollection && this.filtersCollection.any(function(filter) {
return filter.get('type') === data.facet && filter.get('query') === data.term;
});

return this.facetOptionTpl(data);
}, this));
},
Expand All @@ -54,6 +61,26 @@

render: function() {
var grouped = this.collection.groupBy('facet');
// added tho render for each selected orgs
_.each(grouped, function(currentOptions, facetKey) {
var termMap = {};
_.each(currentOptions, function(model) {
termMap[model.get('term')] = model;
});

if (this.fullOptions[facetKey]) {
_.each(this.fullOptions[facetKey], function(model) {
var term = model.get('term');
if (!termMap[term]) {
termMap[term] = model;
}
});
}

this.fullOptions[facetKey] = _.values(termMap);
grouped[facetKey] = this.fullOptions[facetKey];
}, this);

var htmlSnippet = HtmlUtils.joinHtml.apply(
this, _.map(grouped, function(options, facetKey) {
if (options.length > 0) {
Expand Down
2 changes: 1 addition & 1 deletion lms/templates/discovery/filter.underscore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<button data-value="<%- query %>" class="facet-option discovery-button" data-type="<%- type %>">
<button data-value="<%- query %>"data-query="<%- query %>" class="facet-option discovery-button" data-type="<%- type %>">
<span class="query"><%- name %></span>
<span aria-hidden="true" class="fa fa-times" aria-hidden="true"></span>
</button>