diff --git a/.gitignore b/.gitignore index 4032c66543..b145bc50cd 100644 --- a/.gitignore +++ b/.gitignore @@ -132,3 +132,6 @@ storybook-static/ /**/.yarn/cache .swc + +# ignore local ssl certs +certs/ diff --git a/RELEASE.rst b/RELEASE.rst index f747401899..91564b212f 100644 --- a/RELEASE.rst +++ b/RELEASE.rst @@ -1,6 +1,14 @@ Release Notes ============= +Version 0.30.1 +-------------- + +- Remove imports for builtin django User model (#2012) +- track additional browse events in posthog (#2011) +- Update dependency faker to v35 (#1996) +- Fix support for hardware accelerated embedding generation via ollama (#2008) + Version 0.30.0 (Released February 06, 2025) -------------- diff --git a/channels/api.py b/channels/api.py index 4ecd51cfd5..8dcfd44d6f 100644 --- a/channels/api.py +++ b/channels/api.py @@ -1,11 +1,18 @@ """API for channels""" -from django.contrib.auth.models import Group, User +from typing import TYPE_CHECKING + +from django.contrib.auth.models import Group from django.db import transaction from channels.constants import CHANNEL_ROLE_CHOICES, CHANNEL_ROLE_MODERATORS from channels.models import Channel, ChannelGroupRole +if TYPE_CHECKING: + from django.contrib.auth import get_user_model + + User = get_user_model() + def create_channel_groups_and_roles( channel: Channel, @@ -31,14 +38,14 @@ def get_role_model(channel: Channel, role: str) -> ChannelGroupRole: return ChannelGroupRole.objects.get(channel=channel, role=role) -def add_user_role(channel: Channel, role: str, user: User): +def add_user_role(channel: Channel, role: str, user: "User"): """ Add a user to a channel role's group """ get_role_model(channel, role).group.user_set.add(user) -def remove_user_role(channel: Channel, role: str, user: User): +def remove_user_role(channel: Channel, role: str, user: "User"): """ Remove a user from a channel role's group """ @@ -51,7 +58,7 @@ def get_group_role_name(channel_id: int, role: str) -> str: return f"channel_{channel_name}_{role}" -def is_moderator(user: User, channel_id: int) -> bool: +def is_moderator(user: "User", channel_id: int) -> bool: """ Determine if the user is a moderator for a channel (or a staff user) """ diff --git a/channels/views.py b/channels/views.py index 2fc2114211..331fbe84e2 100644 --- a/channels/views.py +++ b/channels/views.py @@ -3,7 +3,7 @@ import logging from django.conf import settings -from django.contrib.auth.models import User +from django.contrib.auth import get_user_model from django.db.models import Prefetch from django.utils.decorators import method_decorator from django_filters.rest_framework import DjangoFilterBackend @@ -169,6 +169,8 @@ def get_queryset(self): """ Build a queryset of relevant users with moderator permissions for this channel """ + User = get_user_model() + channel_group_name = get_group_role_name( self.kwargs["id"], CHANNEL_ROLE_MODERATORS, @@ -190,6 +192,8 @@ class ChannelModeratorDetailView(APIView): def delete(self, request, *args, **kwargs): # noqa: ARG002 """Remove the user from the moderator groups for this website""" + User = get_user_model() + user = User.objects.get(username=self.kwargs["moderator_name"]) remove_user_role( Channel.objects.get(id=self.kwargs["id"]), CHANNEL_ROLE_MODERATORS, user diff --git a/channels/views_test.py b/channels/views_test.py index 02a71ecd4f..705312fcdd 100644 --- a/channels/views_test.py +++ b/channels/views_test.py @@ -5,7 +5,8 @@ from math import ceil import pytest -from django.contrib.auth.models import Group, User +from django.contrib.auth import get_user_model +from django.contrib.auth.models import Group from django.urls import reverse from channels.api import add_user_role @@ -26,6 +27,8 @@ pytestmark = pytest.mark.django_db +User = get_user_model() + def test_list_channels(user_client): """Test that all channels are returned""" diff --git a/docs/how-to/embeddings.md b/docs/how-to/embeddings.md index 7b4958aa7d..b40b0b3cbd 100644 --- a/docs/how-to/embeddings.md +++ b/docs/how-to/embeddings.md @@ -52,17 +52,17 @@ To get setup: ``` QDRANT_ENCODER=vector_search.encoders.litellm.LiteLLMEncoder -LITELLM_API_BASE=http://docker.for.mac.host.internal:11434 +LITELLM_API_BASE=http://docker.for.mac.host.internal:11434/v1/ QDRANT_DENSE_MODEL= ``` -_Note_ - "LITELLM_API_BASE=http://docker.for.mac.host.internal:11434" is Mac specific - if you are using another OS you will need to figure out what your host machine's docker address is. +_Note_ - "LITELLM_API_BASE=http://docker.for.mac.host.internal:11434/v1/" is Mac specific - if you are using another OS you will need to figure out what your host machine's docker address is. Sample .env file configuration on Mac: ``` QDRANT_ENCODER=vector_search.encoders.litellm.LiteLLMEncoder -LITELLM_API_BASE=http://docker.for.mac.host.internal:11434 +LITELLM_API_BASE=http://docker.for.mac.host.internal:11434/v1/ QDRANT_DENSE_MODEL=all-minilm ``` diff --git a/frontends/main/src/app-pages/ChannelPage/TopicChannelTemplate.tsx b/frontends/main/src/app-pages/ChannelPage/TopicChannelTemplate.tsx index 297a1f9f70..e30ecd4727 100644 --- a/frontends/main/src/app-pages/ChannelPage/TopicChannelTemplate.tsx +++ b/frontends/main/src/app-pages/ChannelPage/TopicChannelTemplate.tsx @@ -27,6 +27,7 @@ import { propsNotNil, backgroundSrcSetCSS } from "ol-utilities" import invariant from "tiny-invariant" import backgroundSteps from "@/public/images/backgrounds/background_steps.jpg" import { usePostHog } from "posthog-js/react" +import { PostHogEvents } from "@/common/constants" const ChildrenContainer = styled.div(({ theme }) => ({ paddingTop: "40px", @@ -79,14 +80,18 @@ const BannerSkeleton = styled(Skeleton)(({ theme }) => ({ })) type TopicChipsInternalProps = { - title: string topicId: number parentTopicId: number + isTopLevelTopic: boolean } const TopicChipsInternal: React.FC = (props) => { const posthog = usePostHog() - const { title, topicId, parentTopicId } = props + const { topicId, parentTopicId, isTopLevelTopic } = props + const title = isTopLevelTopic ? "Subtopics" : "Related Topics" + const posthogEvent = isTopLevelTopic + ? PostHogEvents.SubTopicClicked + : PostHogEvents.RelatedTopicClicked const subTopicsQuery = useLearningResourceTopics({ parent_topic_id: [parentTopicId], }) @@ -106,7 +111,7 @@ const TopicChipsInternal: React.FC = (props) => { href={topic.channel_url ?? ""} onClick={() => { if (process.env.NEXT_PUBLIC_POSTHOG_API_KEY) { - posthog.capture("related_topic_link_clicked", { topic }) + posthog.capture(posthogEvent, { topic }) } }} label={topic.name} @@ -127,24 +132,17 @@ const TopicChips: React.FC = (props) => { return null } const isTopLevelTopic = topic?.parent === null + const parentTopicId = isTopLevelTopic ? topic.id : topic?.parent - if (isTopLevelTopic) { + if (parentTopicId) { return ( ) - } else if (topic?.parent) { - return ( - - ) - } else return null + } } type SubTopicBreadcrumbsProps = { diff --git a/frontends/main/src/app-pages/DepartmentListingPage/DepartmentListingPage.tsx b/frontends/main/src/app-pages/DepartmentListingPage/DepartmentListingPage.tsx index 75676157c5..5b69ee7078 100644 --- a/frontends/main/src/app-pages/DepartmentListingPage/DepartmentListingPage.tsx +++ b/frontends/main/src/app-pages/DepartmentListingPage/DepartmentListingPage.tsx @@ -31,6 +31,7 @@ import backgroundSteps from "@/public/images/backgrounds/background_steps.jpg" import { aggregateProgramCounts, aggregateCourseCounts } from "@/common/utils" import { useChannelCounts } from "api/hooks/channels" import { usePostHog } from "posthog-js/react" +import { PostHogEvents } from "@/common/constants" const SCHOOL_ICONS: Record = { // School of Architecture and Planning @@ -155,7 +156,9 @@ const SchoolDepartments: React.FC = ({ new URL(department.channel_url).pathname } onClick={() => { - posthog.capture("department_link_clicked", { department }) + posthog.capture(PostHogEvents.DepartmentLinkClicked, { + department, + }) }} > { + const posthog = usePostHog() const { data: topics } = useLearningResourceTopics({ is_toplevel: true }) return ( @@ -119,6 +122,13 @@ const BrowseTopicsSection: React.FC = () => { { + if (process.env.NEXT_PUBLIC_POSTHOG_API_KEY) { + posthog.capture(PostHogEvents.HomeTopicClicked, { + topic: name, + }) + } + }} > @@ -130,7 +140,16 @@ const BrowseTopicsSection: React.FC = () => { }, )} - + { + if (process.env.NEXT_PUBLIC_POSTHOG_API_KEY) { + posthog.capture(PostHogEvents.HomeSeeAllTopicsClicked) + } + }} + size="large" + responsive + > See all diff --git a/frontends/main/src/app-pages/TopicsListingPage/TopicsListingPage.tsx b/frontends/main/src/app-pages/TopicsListingPage/TopicsListingPage.tsx index b52b3dd090..d72f6ed636 100644 --- a/frontends/main/src/app-pages/TopicsListingPage/TopicsListingPage.tsx +++ b/frontends/main/src/app-pages/TopicsListingPage/TopicsListingPage.tsx @@ -23,6 +23,18 @@ import { aggregateProgramCounts, aggregateCourseCounts } from "@/common/utils" import { useChannelCounts } from "api/hooks/channels" import backgroundSteps from "@/public/images/backgrounds/background_steps.jpg" import { usePostHog } from "posthog-js/react" +import type { PostHog } from "posthog-js" +import { PostHogEvents } from "@/common/constants" + +const captureTopicClicked = ( + posthog: PostHog, + event: string, + topic: string, +) => { + if (process.env.NEXT_PUBLIC_POSTHOG_API_KEY) { + posthog.capture(event, { topic }) + } +} type ChannelSummary = { id: number | string @@ -37,12 +49,20 @@ type TopicBoxHeaderProps = { icon?: string href?: string className?: string + posthog?: PostHog } const TopicBoxHeader = styled( - ({ title, icon, href, className }: TopicBoxHeaderProps) => { + ({ title, icon, href, className, posthog }: TopicBoxHeaderProps) => { return ( - + { + if (posthog) { + captureTopicClicked(posthog, PostHogEvents.TopicClicked, title) + } + }} + >