Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
23e49e6
Adds translator object + strings for search recs feature
akolson Aug 29, 2024
3e85347
change SearchOrBrowserWindow from Vuetify to KDS components
akolson Aug 29, 2024
fe76812
add about recommendations link
akolson Sep 3, 2024
1b5fe71
Update Translator object to expose a destructurable interface for str…
akolson Sep 3, 2024
13c7d15
Merge branch 'search-recs-strings-feature-file' into implement-recomm…
akolson Sep 3, 2024
f51e08f
adds recommendations title
akolson Sep 3, 2024
eeb8537
updates strings
akolson Sep 3, 2024
003d109
Merge branch 'search-recs-strings-feature-file' into implement-recomm…
akolson Sep 3, 2024
a43dcbb
Adds about recommendations modal
akolson Sep 3, 2024
675cdae
updates strings
akolson Sep 3, 2024
c56f907
Merge branch 'search-recs-strings-feature-file' into implement-recomm…
akolson Sep 3, 2024
3532e75
Adds reponsiveness to page
akolson Sep 4, 2024
c8f8db9
Adds sample data request
akolson Sep 5, 2024
fa81206
Merge branch 'merge-into-search-recs' into implement-recommendations-…
akolson Sep 5, 2024
3a74adc
Add latest KDS rc4 as dependency
akolson Sep 6, 2024
835e78c
Adds KCard
akolson Sep 9, 2024
b80aedb
Merge branch 'search-recommendations' into implement-recommendations-…
akolson Sep 18, 2024
e3f0cba
Adds KCardGrid
akolson Sep 24, 2024
93b8ef3
Adds preliminary logic to load recommendations
akolson Oct 31, 2024
078638c
Merge remote-tracking branch 'upstream/unstable' into merge-into-sear…
akolson Oct 31, 2024
4f1f84e
Merge branch 'merge-into-search-recs' into implement-recommendations-…
akolson Oct 31, 2024
1d000da
Merge branch 'merge-into-search-recs' into implement-recommendations-…
akolson Oct 31, 2024
8e200d5
fixes bug in translateMetadataString
akolson Oct 31, 2024
dc29890
Make minor tweaks to UI
akolson Nov 4, 2024
7009c10
updates kds version
akolson Nov 13, 2024
30d397f
Adds pagination logic
akolson Nov 27, 2024
7e98efc
Refactors code
akolson Dec 3, 2024
e4e2eeb
More code refators and logic fixes
akolson Dec 3, 2024
4ce5884
code clean-up
akolson Dec 3, 2024
95a1b52
adds ai flag check on UI and fetch
akolson Dec 4, 2024
0cb9469
Merge remote-tracking branch 'upstream/search-recommendations' into i…
akolson Dec 4, 2024
9f8de26
minor UI fixes
akolson Dec 4, 2024
596e109
further UI improvements
akolson Dec 5, 2024
7574c06
UI fixes
akolson Dec 9, 2024
c6e3a0c
additional UI fixes
akolson Dec 10, 2024
75df365
Updates kds to latest version
akolson Dec 11, 2024
48cda0d
Merge branch 'search-recommendations' into implement-recommendations-…
akolson Dec 13, 2024
47725f3
Add todo to relax an unlikely situation where channel names will be null
akolson Feb 7, 2025
6b85c8d
Removes ambiguous selected proo from recommended resource card
akolson Feb 7, 2025
2126cd6
makes adjustments to for latest CA API changes
akolson Mar 18, 2025
d3633e0
adds /recommendations endpoint
akolson Mar 21, 2025
067938a
Adds new envar to hold recommendation api url
akolson Mar 31, 2025
6a971e4
Adds new envar to hold recommendation api url
akolson Mar 31, 2025
7e868da
Updates recommendations cache to store topic and channel ids
akolson Mar 31, 2025
0ebbf32
Bumbs KDS to v5.0.0-r10
akolson Mar 31, 2025
3f7a9aa
api connection code tweaks
akolson Mar 31, 2025
0dc1220
Fixes failing tests
akolson Mar 31, 2025
e443f49
Updates /connect endpoint to use new response
akolson Apr 1, 2025
39568ad
Implements review feedback
akolson Apr 8, 2025
8a16b55
Fixes failing tests
akolson Apr 8, 2025
43482a6
Remove redundant loadChannel from node
akolson Apr 9, 2025
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
28 changes: 19 additions & 9 deletions contentcuration/automation/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Generated by Django 3.2.24 on 2024-08-05 21:23
# Generated by Django 3.2.24 on 2025-03-26 11:12
import uuid

import django.db.models.deletion
Expand All @@ -7,26 +7,36 @@


class Migration(migrations.Migration):

initial = True

dependencies = [
('kolibri_public', '0003_alter_file_preset'),
('kolibri_public', '0005_alter_localfile_extension'),
('contentcuration', '0151_embeddings_embeddingscontentnode'),
]

operations = [
migrations.CreateModel(
name='RecommendationsCache',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True,
serialize=False)),
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('request_hash', models.CharField(max_length=32, null=True)),
('rank', models.FloatField(default=0.0, null=True)),
('topic_id', models.UUIDField(blank=True, null=True)),
('rank', models.IntegerField(default=0, null=True)),
('override_threshold', models.BooleanField(default=False)),
('timestamp', models.DateTimeField(auto_now_add=True)),
('contentnode', models.ForeignKey(blank=True, null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name='recommendations',
to='kolibri_public.contentnode')),
('channel', models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name='channel_recommendations',
to='contentcuration.channel')),
('contentnode', models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name='contentnode_recommendations',
to='kolibri_public.contentnode')),
],
),
migrations.AddIndex(
Expand Down
14 changes: 12 additions & 2 deletions contentcuration/automation/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
from django.db import models
from kolibri_public.models import ContentNode

from contentcuration.models import Channel


REQUEST_HASH_INDEX_NAME = "request_hash_idx"
CONTENTNODE_INDEX_NAME = "contentnode_idx"
Expand All @@ -11,14 +13,22 @@
class RecommendationsCache(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
request_hash = models.CharField(max_length=32, null=True)
topic_id = models.UUIDField(null=True, blank=True)
contentnode = models.ForeignKey(
ContentNode,
null=True,
blank=True,
related_name='recommendations',
related_name='contentnode_recommendations',
on_delete=models.CASCADE,
)
channel = models.ForeignKey(
Channel,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since contentnode is associated with kolibri_public.models.ContentNode, I think it would make sense if this was also in alignment with kolibri_public. Although, I think as its written this seems best suited to be able to return the main_tree_id. Just more of a reflection and not a requested change.

null=True,
blank=True,
related_name='channel_recommendations',
on_delete=models.CASCADE,
)
rank = models.FloatField(default=0.0, null=True)
rank = models.IntegerField(default=0, null=True)
override_threshold = models.BooleanField(default=False)
timestamp = models.DateTimeField(auto_now_add=True)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,41 @@
from django.db import IntegrityError
from kolibri_public.models import ContentNode

from contentcuration.models import Channel
from contentcuration.tests.base import StudioTestCase


class TestRecommendationsCache(StudioTestCase):

def setUp(self):
self.topic_id = uuid.uuid4()
self.content_node = ContentNode.objects.create(
id=uuid.uuid4(),
title='Test Content Node',
content_id=uuid.uuid4(),
channel_id=uuid.uuid4(),
)
self.channel = Channel.objects.create(
id=uuid.uuid4(),
name='Test Channel',
actor_id=1,
)
self.cache = RecommendationsCache.objects.create(
request_hash='test_hash',
topic_id=self.topic_id,
contentnode=self.content_node,
rank=1.0,
channel=self.channel,
rank=1,
override_threshold=False
)

def test_cache_creation(self):
self.assertIsInstance(self.cache, RecommendationsCache)
self.assertEqual(self.cache.request_hash, 'test_hash')
self.assertEqual(self.cache.topic_id, self.topic_id)
self.assertEqual(self.cache.contentnode, self.content_node)
self.assertEqual(self.cache.rank, 1.0)
self.assertEqual(self.cache.channel, self.channel)
self.assertEqual(self.cache.rank, 1)
self.assertFalse(self.cache.override_threshold)

def test_cache_retrieval(self):
Expand All @@ -38,8 +49,10 @@ def test_cache_uniqueness(self):
with self.assertRaises(IntegrityError):
RecommendationsCache.objects.create(
request_hash='test_hash',
topic_id=self.topic_id,
contentnode=self.content_node,
rank=2.0,
channel=self.channel,
rank=2,
override_threshold=True
)

Expand Down
16 changes: 9 additions & 7 deletions contentcuration/automation/utils/appnexus/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import requests
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.retry import Retry
from urllib3 import Retry

from . import errors

Expand Down Expand Up @@ -77,7 +77,7 @@ def __new__(cls, *args, **kwargs):

def __init__(
self,
url_prefix="",
url_prefix="stable",
):
self.url_prefix = url_prefix
if not self.session:
Expand Down Expand Up @@ -161,17 +161,19 @@ def connect(self, **kwargs):
""" Establishes a connection to the backend service. """
try:
request = BackendRequest(method="GET", path=self.connect_endpoint, **kwargs)
self._make_request(request)
return True
api_response = self._make_request(request)
response_data = api_response.json()
status = response_data.get("status", None)
return status == "OK"
except Exception:
return False

def make_request(self, request):
""" Make a request to the backend service. """
try:
response = self._make_request(request)
response_body = dict(data=response.json())
return BackendResponse(**response_body)
api_response = self._make_request(request)
response_data = api_response.json()
return BackendResponse(data=response_data)
except ValueError as e:
logging.exception(e)
raise errors.InvalidResponse("Invalid response from backend")
Expand Down
2 changes: 1 addition & 1 deletion contentcuration/contentcuration/dev_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@

ROOT_URLCONF = "contentcuration.dev_urls"

INSTALLED_APPS += ("drf_yasg", "automation")
INSTALLED_APPS += ("drf_yasg",)
Original file line number Diff line number Diff line change
@@ -1,40 +1,46 @@
<template>

<KCard
class="recommended-resource-card"
:to="to"
:title="title"
orientation="horizontal"
:headingLevel="headingLevel"
:title="node.title"
:headingLevel="2"
thumbnailScaleType="contain"
thumbnailDisplay="small"
thumbnailAlign="right"
:thumbnailSrc="thumbnailSrc"
:style="{ margin: '16px 0 16px 0' }"
:thumbnailSrc="node.thumbnail_src"
@click="onClick"
>
<template #aboveTitle>
<div>
<KIcon
icon="practiceSolid"
/>
<span>Practice</span>
</div>
<template #select>
<Checkbox
:key="`checkbox-${node.id}`"
:inputValue="isSelected(node)"
@input="toggleSelected(node)"
>
<span class="visuallyhidden">{{ $tr('selectCard', { title: node.title }) }}</span>
</Checkbox>
</template>
<template #title>
<template #aboveTitle>
<ContentNodeLearningActivityIcon
:learningActivities="learningActivities"
showEachActivityIcon
includeText
small
/>
</template>
<template #belowTitle>
<div class="bellow-title-style">
<p>
below title slot section for the KCard component:
below title slot section for the KCard component
</p>
<div>
<KTextTruncator
:text="channelName"
:maxLines="2"
/>
</div>
</template>
<template #footer>
<div class="align-right-style">
<KIconButton icon="openNewTab" class="card-icon-size" />
<KIconButton icon="thumbDown" class="card-icon-size" />
</div>
<KFixedGrid :numCols="2">
<KFixedGridItem alignment="right">
<KIconButton icon="openNewTab" />
<KIconButton icon="thumbDown" />
</KFixedGridItem>
</KFixedGrid>
</template>
</KCard>

Expand All @@ -43,37 +49,69 @@

<script>

import find from 'lodash/find';
import LearningActivities from 'kolibri-constants/labels/LearningActivities';
import { mapState } from 'vuex';
import { ContentKindLearningActivityDefaults } from 'shared/leUtils/ContentKinds';
import Checkbox from 'shared/views/form/Checkbox';
import ContentNodeLearningActivityIcon from 'shared/views/ContentNodeLearningActivityIcon';

export default {
name: 'RecommendedResourceCard',
components: {
Checkbox,
ContentNodeLearningActivityIcon,
},
props: {
to: {
node: {
type: Object,
required: true,
},
title: {
type: String,
default: null,
},
computed: {
...mapState('importFromChannels', ['selected']),
channelName() {
return this.node.title;
},
learningActivities() {
if (this.node.learning_activities && Object.keys(this.node.learning_activities).length) {
return this.node.learning_activities;
}
return {
[ContentKindLearningActivityDefaults[this.node.kind] || LearningActivities.EXPLORE]: true,
};
},
headingLevel: {
type: Number,
required: true,
isSelected() {
return function(node) {
return Boolean(find(this.selected, { id: node.id }));
};
},
thumbnailSrc: {
type: String,
default: null,
},
methods: {
toggleSelected(node) {
this.$emit('change_selected', { nodes: [node], isSelected: !this.isSelected(node) });
},
onClick() {
this.$emit('preview', this.node);
},
},
$trs: {
selectCard: 'Select { title }',
},
};

</script>


<style>
.align-right-style{
display: flex;
justify-content: flex-end;
}
.recommended-resource-card {
max-width:400px
.visuallyhidden {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0 0 0 0);
border: 0;
}
</style>
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
<DropdownWrapper
component="VFlex"
sm6
md5
lg4
xl3
class="pr-4"
md6
lg6
xl6
class="pr-3"
>
<template #default="{ attach, menuProps }">
<VSelect
Expand All @@ -23,7 +23,7 @@
/>
</template>
</DropdownWrapper>
<VFlex sm6 md5 lg4 xl3 class="pr-5">
<VFlex sm6 md6 lg6 xl6>
<LanguageDropdown v-model="languageFilter" />
</VFlex>
</VLayout>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,10 @@
<style lang="less" scoped>

.modal-container.fluid {
max-width: 1200px;
display: flex;
align-items: center;
justify-content: center;
max-width: 100%;
}

</style>
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<VNavigationDrawer
permanent
floating
class="import-search-filters px-2"
class="import-search-filters"
>
<!-- Channel -->
<p class="font-weight-bold grey--text mb-1">
Expand Down
Loading
Loading