From 55320c52be1fc4072095b248b53964051788054e Mon Sep 17 00:00:00 2001 From: Matt Bertrand Date: Fri, 6 Sep 2024 12:14:24 -0400 Subject: [PATCH 01/18] Sort news items by publish_date (#1522) --- frontends/api/src/generated/v0/api.ts | 12 ++++++------ .../mit-learn/src/pages/HomePage/HomePage.test.tsx | 10 +++++++--- .../src/pages/HomePage/NewsEventsSection.tsx | 2 +- news_events/constants.py | 8 ++++---- news_events/filters_test.py | 14 ++++++++++---- openapi/specs/v0.yaml | 8 ++++---- 6 files changed, 32 insertions(+), 22 deletions(-) diff --git a/frontends/api/src/generated/v0/api.ts b/frontends/api/src/generated/v0/api.ts index db2e0f6058..eab427e399 100644 --- a/frontends/api/src/generated/v0/api.ts +++ b/frontends/api/src/generated/v0/api.ts @@ -4529,7 +4529,7 @@ export const NewsEventsApiAxiosParamCreator = function ( * @param {Array} [feed_type] The type of item * `news` - News * `events` - Events * @param {number} [limit] Number of results to return per page. * @param {number} [offset] The initial index from which to return the results. - * @param {NewsEventsListSortbyEnum} [sortby] Sort By * `id` - Object ID ascending * `-id` - Object ID descending * `event_date` - Event date ascending * `-event_date` - Event date descending * `created` - Creation date ascending * `-created` - Creation date descending + * @param {NewsEventsListSortbyEnum} [sortby] Sort By * `id` - Object ID ascending * `-id` - Object ID descending * `event_date` - Event date ascending * `-event_date` - Event date descending * `news_date` - Creation date ascending * `-news_date` - Creation date descending * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -4647,7 +4647,7 @@ export const NewsEventsApiFp = function (configuration?: Configuration) { * @param {Array} [feed_type] The type of item * `news` - News * `events` - Events * @param {number} [limit] Number of results to return per page. * @param {number} [offset] The initial index from which to return the results. - * @param {NewsEventsListSortbyEnum} [sortby] Sort By * `id` - Object ID ascending * `-id` - Object ID descending * `event_date` - Event date ascending * `-event_date` - Event date descending * `created` - Creation date ascending * `-created` - Creation date descending + * @param {NewsEventsListSortbyEnum} [sortby] Sort By * `id` - Object ID ascending * `-id` - Object ID descending * `event_date` - Event date ascending * `-event_date` - Event date descending * `news_date` - Creation date ascending * `-news_date` - Creation date descending * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -4785,8 +4785,8 @@ export interface NewsEventsApiNewsEventsListRequest { readonly offset?: number /** - * Sort By * `id` - Object ID ascending * `-id` - Object ID descending * `event_date` - Event date ascending * `-event_date` - Event date descending * `created` - Creation date ascending * `-created` - Creation date descending - * @type {'-created' | '-event_date' | '-id' | 'created' | 'event_date' | 'id'} + * Sort By * `id` - Object ID ascending * `-id` - Object ID descending * `event_date` - Event date ascending * `-event_date` - Event date descending * `news_date` - Creation date ascending * `-news_date` - Creation date descending + * @type {'-event_date' | '-id' | '-news_date' | 'event_date' | 'id' | 'news_date'} * @memberof NewsEventsApiNewsEventsList */ readonly sortby?: NewsEventsListSortbyEnum @@ -4865,12 +4865,12 @@ export type NewsEventsListFeedTypeEnum = * @export */ export const NewsEventsListSortbyEnum = { - Created: "-created", EventDate: "-event_date", Id: "-id", - Created2: "created", + NewsDate: "-news_date", EventDate2: "event_date", Id2: "id", + NewsDate2: "news_date", } as const export type NewsEventsListSortbyEnum = (typeof NewsEventsListSortbyEnum)[keyof typeof NewsEventsListSortbyEnum] diff --git a/frontends/mit-learn/src/pages/HomePage/HomePage.test.tsx b/frontends/mit-learn/src/pages/HomePage/HomePage.test.tsx index 3f1f9eb441..44da718dde 100644 --- a/frontends/mit-learn/src/pages/HomePage/HomePage.test.tsx +++ b/frontends/mit-learn/src/pages/HomePage/HomePage.test.tsx @@ -52,7 +52,11 @@ const setupAPIs = () => { ) setMockResponse.get( - urls.newsEvents.list({ feed_type: ["news"], limit: 6, sortby: "-created" }), + urls.newsEvents.list({ + feed_type: ["news"], + limit: 6, + sortby: "-news_date", + }), {}, ) setMockResponse.get( @@ -152,7 +156,7 @@ describe("Home Page News and Events", () => { urls.newsEvents.list({ feed_type: ["news"], limit: 6, - sortby: "-created", + sortby: "-news_date", }), news, ) @@ -203,7 +207,7 @@ describe("Home Page News and Events", () => { urls.newsEvents.list({ feed_type: ["news"], limit: 6, - sortby: "-created", + sortby: "-news_date", }), news, ) diff --git a/frontends/mit-learn/src/pages/HomePage/NewsEventsSection.tsx b/frontends/mit-learn/src/pages/HomePage/NewsEventsSection.tsx index 125ae6aec9..4aabd7b0f2 100644 --- a/frontends/mit-learn/src/pages/HomePage/NewsEventsSection.tsx +++ b/frontends/mit-learn/src/pages/HomePage/NewsEventsSection.tsx @@ -188,7 +188,7 @@ const NewsEventsSection: React.FC = () => { const { data: news } = useNewsEventsList({ feed_type: [NewsEventsListFeedTypeEnum.News], limit: 6, - sortby: "-created", + sortby: "-news_date", }) const { data: events } = useNewsEventsList({ diff --git a/news_events/constants.py b/news_events/constants.py index badee4c537..47f0963f44 100644 --- a/news_events/constants.py +++ b/news_events/constants.py @@ -29,13 +29,13 @@ class FeedType(ExtendedEnum): "title": "Event date descending", "sort": "-event_details__event_datetime", }, - "created": { + "news_date": { "title": "Creation date ascending", - "sort": "created_on", + "sort": "news_details__publish_date", }, - "-created": { + "-news_date": { "title": "Creation date descending", - "sort": "-created_on", + "sort": "-news_details__publish_date", }, } diff --git a/news_events/filters_test.py b/news_events/filters_test.py index c2743bea70..07792ccbce 100644 --- a/news_events/filters_test.py +++ b/news_events/filters_test.py @@ -47,18 +47,24 @@ def test_item_filter_feed_type(client, multifilter): ) -@pytest.mark.parametrize("sortby", ["created", "event_date"]) +@pytest.mark.parametrize( + ("sortby", "feed_type"), + [ + ("news_date", FeedType.news.name), + ("event_date", FeedType.events.name), + ], +) @pytest.mark.parametrize("descending", [True, False]) -def test_learning_resource_sortby(client, sortby, descending): +def test_learning_resource_sortby(client, sortby, feed_type, descending): """Test that the query is sorted in the correct order""" - source = FeedSourceFactory.create(feed_type=FeedType.events.name) + source = FeedSourceFactory.create(feed_type=feed_type) items = FeedItemFactory.create_batch(4, source=source, is_event=True) sortby_param = sortby if descending: sortby_param = f"-{sortby}" results = client.get( - f"{ITEM_API_URL}?feed_type=events&sortby={sortby_param}" + f"{ITEM_API_URL}?feed_type={feed_type}&sortby={sortby_param}" ).json()["results"] def get_sort_field(item): diff --git a/openapi/specs/v0.yaml b/openapi/specs/v0.yaml index 17a9ce048c..6067f0d6f2 100644 --- a/openapi/specs/v0.yaml +++ b/openapi/specs/v0.yaml @@ -326,12 +326,12 @@ paths: schema: type: string enum: - - -created - -event_date - -id - - created + - -news_date - event_date - id + - news_date description: |- Sort By @@ -339,8 +339,8 @@ paths: * `-id` - Object ID descending * `event_date` - Event date ascending * `-event_date` - Event date descending - * `created` - Creation date ascending - * `-created` - Creation date descending + * `news_date` - Creation date ascending + * `-news_date` - Creation date descending tags: - news_events responses: From df12493d57767640fa5bdf0f4991a8f6a7f2aea8 Mon Sep 17 00:00:00 2001 From: Carey P Gumaer Date: Fri, 6 Sep 2024 16:21:52 -0400 Subject: [PATCH 02/18] display sub topics on topic channels (#1511) * remove broken parent_topic_name API parameter and replace with parent_topic_id, fetch and display subtopics on topic channel pages * fix typo * fix existing tests * test that subtopic chips appear properly * mobile styles * adjust desktop top padding * move topic test to its own describe block * regenerate openapi after rebase * don't display subtopics section at all if there are no results * filter out topics without a channel url * adjust channel search box top margins * put top padding on a wrapper container instead of margin on search input * update spacing rules again * use propsNotNil on channel_url * simplify test * remove waitFor * remove conditional --- frontends/api/src/generated/v1/api.ts | 22 +++---- .../pages/ChannelPage/ChannelPage.test.tsx | 35 ++++++++++ .../src/pages/ChannelPage/ChannelPage.tsx | 65 ++++++++++++++++++- .../pages/ChannelPage/ChannelSearch.test.tsx | 10 +++ .../src/pages/ChannelPage/ChannelSearch.tsx | 2 - .../ChannelPage/DefaultChannelTemplate.tsx | 9 ++- .../EditChannelAppearanceForm.test.tsx | 12 +++- learning_resources/filters.py | 24 ++----- openapi/specs/v1.yaml | 4 +- 9 files changed, 147 insertions(+), 36 deletions(-) diff --git a/frontends/api/src/generated/v1/api.ts b/frontends/api/src/generated/v1/api.ts index 821164a00d..09d442f00e 100644 --- a/frontends/api/src/generated/v1/api.ts +++ b/frontends/api/src/generated/v1/api.ts @@ -21272,7 +21272,7 @@ export const TopicsApiAxiosParamCreator = function ( * @param {number} [limit] Number of results to return per page. * @param {Array} [name] Multiple values may be separated by commas. * @param {number} [offset] The initial index from which to return the results. - * @param {Array} [parent_topic_name] Multiple values may be separated by commas. + * @param {Array} [parent_topic_id] Multiple values may be separated by commas. * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -21281,7 +21281,7 @@ export const TopicsApiAxiosParamCreator = function ( limit?: number, name?: Array, offset?: number, - parent_topic_name?: Array, + parent_topic_id?: Array, options: RawAxiosRequestConfig = {}, ): Promise => { const localVarPath = `/api/v1/topics/` @@ -21316,8 +21316,8 @@ export const TopicsApiAxiosParamCreator = function ( localVarQueryParameter["offset"] = offset } - if (parent_topic_name) { - localVarQueryParameter["parent_topic_name"] = parent_topic_name.join( + if (parent_topic_id) { + localVarQueryParameter["parent_topic_id"] = parent_topic_id.join( COLLECTION_FORMATS.csv, ) } @@ -21399,7 +21399,7 @@ export const TopicsApiFp = function (configuration?: Configuration) { * @param {number} [limit] Number of results to return per page. * @param {Array} [name] Multiple values may be separated by commas. * @param {number} [offset] The initial index from which to return the results. - * @param {Array} [parent_topic_name] Multiple values may be separated by commas. + * @param {Array} [parent_topic_id] Multiple values may be separated by commas. * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -21408,7 +21408,7 @@ export const TopicsApiFp = function (configuration?: Configuration) { limit?: number, name?: Array, offset?: number, - parent_topic_name?: Array, + parent_topic_id?: Array, options?: RawAxiosRequestConfig, ): Promise< ( @@ -21421,7 +21421,7 @@ export const TopicsApiFp = function (configuration?: Configuration) { limit, name, offset, - parent_topic_name, + parent_topic_id, options, ) const index = configuration?.serverIndex ?? 0 @@ -21497,7 +21497,7 @@ export const TopicsApiFactory = function ( requestParameters.limit, requestParameters.name, requestParameters.offset, - requestParameters.parent_topic_name, + requestParameters.parent_topic_id, options, ) .then((request) => request(axios, basePath)) @@ -21556,10 +21556,10 @@ export interface TopicsApiTopicsListRequest { /** * Multiple values may be separated by commas. - * @type {Array} + * @type {Array} * @memberof TopicsApiTopicsList */ - readonly parent_topic_name?: Array + readonly parent_topic_id?: Array } /** @@ -21601,7 +21601,7 @@ export class TopicsApi extends BaseAPI { requestParameters.limit, requestParameters.name, requestParameters.offset, - requestParameters.parent_topic_name, + requestParameters.parent_topic_id, options, ) .then((request) => request(this.axios, this.basePath)) diff --git a/frontends/mit-learn/src/pages/ChannelPage/ChannelPage.test.tsx b/frontends/mit-learn/src/pages/ChannelPage/ChannelPage.test.tsx index 98f6230fff..39643f66ce 100644 --- a/frontends/mit-learn/src/pages/ChannelPage/ChannelPage.test.tsx +++ b/frontends/mit-learn/src/pages/ChannelPage/ChannelPage.test.tsx @@ -11,6 +11,7 @@ import { } from "../../test-utils" import ChannelSearch from "./ChannelSearch" import { assertHeadings } from "ol-test-utilities" +import invariant from "tiny-invariant" jest.mock("./ChannelSearch", () => { const actual = jest.requireActual("./ChannelSearch") @@ -104,6 +105,21 @@ const setupApis = ( results: [], }) + if ( + channel.channel_type === ChannelTypeEnum.Topic && + channel.topic_detail.topic + ) { + const subTopics = factories.learningResources.topics({ count: 5 }) + setMockResponse.get( + urls.topics.list({ parent_topic_id: [channel.topic_detail.topic] }), + subTopics, + ) + return { + channel, + subTopics, + } + } + return { channel, } @@ -250,6 +266,25 @@ describe.each(NON_UNIT_CHANNEL_TYPES)( }, ) +describe("Channel Pages, Topic only", () => { + test("Subtopics display", async () => { + const { channel, subTopics } = setupApis({ + search_filter: "topic=Physics", + channel_type: ChannelTypeEnum.Topic, + }) + renderTestApp({ url: `/c/${channel.channel_type}/${channel.name}` }) + + invariant(subTopics) + const links = await screen.findAllByRole("link", { + // name arg can be string, regex, or function + name: (name) => subTopics?.results.map((t) => t.name).includes(name), + }) + links.forEach((link, i) => { + expect(link).toHaveAttribute("href", subTopics.results[i].channel_url) + }) + }) +}) + describe("Channel Pages, Unit only", () => { it("Sets the expected meta tags", async () => { const { channel } = setupApis({ diff --git a/frontends/mit-learn/src/pages/ChannelPage/ChannelPage.tsx b/frontends/mit-learn/src/pages/ChannelPage/ChannelPage.tsx index 4e68174a03..022e35ebe4 100644 --- a/frontends/mit-learn/src/pages/ChannelPage/ChannelPage.tsx +++ b/frontends/mit-learn/src/pages/ChannelPage/ChannelPage.tsx @@ -9,16 +9,71 @@ import type { BooleanFacets, } from "@mitodl/course-search-utils" import { ChannelTypeEnum } from "api/v0" +import { useLearningResourceTopics } from "api/hooks/learningResources" +import { ChipLink, Container, styled, Typography } from "ol-components" +import { propsNotNil } from "ol-utilities" + +const SubTopicsContainer = styled(Container)(({ theme }) => ({ + marginBottom: "60px", + [theme.breakpoints.down("sm")]: { + marginBottom: "24px", + }, +})) + +const SubTopicsHeader = styled(Typography)(({ theme }) => ({ + marginBottom: "10px", + ...theme.typography.subtitle1, +})) + +const ChipsContainer = styled.div({ + display: "flex", + flexWrap: "wrap", + gap: "12px", +}) type RouteParams = { channelType: ChannelTypeEnum name: string } +type SubTopicDisplayProps = { + parentTopicId: number +} + +const SubTopicsDisplay: React.FC = (props) => { + const { parentTopicId } = props + const topicsQuery = useLearningResourceTopics({ + parent_topic_id: [parentTopicId], + }) + const totalSubtopics = topicsQuery.data?.results.length ?? 0 + const subTopics = topicsQuery.data?.results.filter( + propsNotNil(["channel_url"]), + ) + return ( + totalSubtopics > 0 && ( + + Related Topics + + {subTopics?.map((topic) => ( + + ))} + + + ) + ) +} + const ChannelPage: React.FC = () => { const { channelType, name } = useParams() const channelQuery = useChannelDetail(String(channelType), String(name)) const searchParams: Facets & BooleanFacets = {} + const publicDescription = channelQuery.data?.public_description if (channelQuery.data?.search_filter) { const urlParams = new URLSearchParams(channelQuery.data.search_filter) @@ -37,7 +92,15 @@ const ChannelPage: React.FC = () => { channelType && ( <> -

{channelQuery.data?.public_description}

+ {publicDescription && ( + {publicDescription} + )} + {channelQuery.data?.channel_type === ChannelTypeEnum.Topic && + channelQuery.data?.topic_detail?.topic ? ( + + ) : null} {channelQuery.data?.search_filter && ( theme.breakpoints.down("md")} { padding-bottom: 35px; - margin-top: 40px; } ` diff --git a/frontends/mit-learn/src/pages/ChannelPage/DefaultChannelTemplate.tsx b/frontends/mit-learn/src/pages/ChannelPage/DefaultChannelTemplate.tsx index 1a997ddafe..eade19f177 100644 --- a/frontends/mit-learn/src/pages/ChannelPage/DefaultChannelTemplate.tsx +++ b/frontends/mit-learn/src/pages/ChannelPage/DefaultChannelTemplate.tsx @@ -12,6 +12,13 @@ import { } from "./ChannelPageTemplate" import MetaTags from "@/page-components/MetaTags/MetaTags" +const ChildrenContainer = styled.div(({ theme }) => ({ + paddingTop: "40px", + [theme.breakpoints.down("sm")]: { + paddingTop: "24px", + }, +})) + const ChannelControlsContainer = styled.div(({ theme }) => ({ display: "flex", flexDirection: "row", @@ -107,7 +114,7 @@ const DefaultChannelTemplate: React.FC = ({ } /> - {children} + {children} ) } diff --git a/frontends/mit-learn/src/pages/ChannelPage/EditChannelAppearanceForm.test.tsx b/frontends/mit-learn/src/pages/ChannelPage/EditChannelAppearanceForm.test.tsx index 6fcdb13e5a..5c18977909 100644 --- a/frontends/mit-learn/src/pages/ChannelPage/EditChannelAppearanceForm.test.tsx +++ b/frontends/mit-learn/src/pages/ChannelPage/EditChannelAppearanceForm.test.tsx @@ -9,7 +9,7 @@ import { factories, urls, setMockResponse } from "api/test-utils" import { channels as factory } from "api/test-utils/factories" import { makeChannelViewPath, makeChannelEditPath } from "@/common/urls" import { makeWidgetListResponse } from "ol-widgets/src/factories" -import type { Channel } from "api/v0" +import { ChannelTypeEnum, type Channel } from "api/v0" const setupApis = (channelOverrides: Partial) => { const channel = factory.channel({ is_moderator: true, ...channelOverrides }) @@ -33,6 +33,16 @@ const setupApis = (channelOverrides: Partial) => { results: [], }) + if ( + channel.channel_type === ChannelTypeEnum.Topic && + channel.topic_detail.topic + ) { + setMockResponse.get( + urls.topics.list({ parent_topic_id: [channel.topic_detail.topic] }), + factories.learningResources.topics({ count: 5 }), + ) + } + return channel } diff --git a/learning_resources/filters.py b/learning_resources/filters.py index 45c362283b..ca5635bb9f 100644 --- a/learning_resources/filters.py +++ b/learning_resources/filters.py @@ -264,9 +264,9 @@ class TopicFilter(FilterSet): label="Topic name", method="filter_name", ) - parent_topic_name = CharInFilter( - label="Parent topic name", - method="filter_parent_topic_name", + parent_topic_id = NumberInFilter( + label="Parent topic ID", + method="filter_parent_topic_id", ) is_toplevel = BooleanFilter( label="Filter top-level topics", @@ -281,18 +281,6 @@ def filter_toplevel(self, queryset, _, value): """Filter by top-level (parent == null)""" return queryset.filter(parent__isnull=value) - def filter_parent_topic_name(self, queryset, _, values): - """Filter by parent topic name (up to 2 levels deep)""" - - nested_topic_filter = Q() - - for topic in values: - nested_topic_filter |= Q( - parent__isnull=False, parent__name__iexact=topic - ) | Q( - parent__isnull=False, - parent__parent__isnull=False, - parent__parent__name__iexact=topic, - ) - - return queryset.filter(nested_topic_filter) + def filter_parent_topic_id(self, queryset, _, values): + """Get direct children of a topic""" + return queryset.filter(parent_id__in=values) diff --git a/openapi/specs/v1.yaml b/openapi/specs/v1.yaml index 4c1e3ab103..dbf8de09b2 100644 --- a/openapi/specs/v1.yaml +++ b/openapi/specs/v1.yaml @@ -6308,11 +6308,11 @@ paths: schema: type: integer - in: query - name: parent_topic_name + name: parent_topic_id schema: type: array items: - type: string + type: number description: Multiple values may be separated by commas. explode: false style: form From 64a59e8fd0ac93a16e7bc365633d451396175167 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 7 Sep 2024 07:31:01 +0000 Subject: [PATCH 03/18] Update dependency Django to v4.2.16 (#1529) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- poetry.lock | 8 ++++---- pyproject.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/poetry.lock b/poetry.lock index 125e0c94ee..ef353fd8a0 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1023,13 +1023,13 @@ static3 = "*" [[package]] name = "django" -version = "4.2.15" +version = "4.2.16" description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design." optional = false python-versions = ">=3.8" files = [ - {file = "Django-4.2.15-py3-none-any.whl", hash = "sha256:61ee4a130efb8c451ef3467c67ca99fdce400fedd768634efc86a68c18d80d30"}, - {file = "Django-4.2.15.tar.gz", hash = "sha256:c77f926b81129493961e19c0e02188f8d07c112a1162df69bfab178ae447f94a"}, + {file = "Django-4.2.16-py3-none-any.whl", hash = "sha256:1ddc333a16fc139fd253035a1606bb24261951bbc3a6ca256717fa06cc41a898"}, + {file = "Django-4.2.16.tar.gz", hash = "sha256:6f1616c2786c408ce86ab7e10f792b8f15742f7b7b7460243929cb371e7f1dad"}, ] [package.dependencies] @@ -4856,4 +4856,4 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] [metadata] lock-version = "2.0" python-versions = "3.12.5" -content-hash = "2a2644881d49d8110e2cc869fd6ae6bcf93423d27e7b55638e51197de093f912" +content-hash = "b30b3ee131665446326279ac331de86adb22d4139cf03f8581628673d9465659" diff --git a/pyproject.toml b/pyproject.toml index a26c6a1699..27467ee9c0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,7 @@ cffi = "^1.15.1" cryptography = "^43.0.0" dj-database-url = "^2.0.0" dj-static = "^0.0.6" -Django = "4.2.15" +Django = "4.2.16" django-anymail = {extras = ["mailgun"], version = "^11.0"} django-bitfield = "^2.2.0" django-cache-memoize = "^0.2.0" From dfd769a1ebc6678ace62bb923b7a9ce318f97dd4 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 7 Sep 2024 10:35:52 +0000 Subject: [PATCH 04/18] Update dependency ruff to v0.6.4 (#1530) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- poetry.lock | 40 ++++++++++++++++++++-------------------- pyproject.toml | 2 +- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/poetry.lock b/poetry.lock index ef353fd8a0..fe1026aeda 100644 --- a/poetry.lock +++ b/poetry.lock @@ -4067,29 +4067,29 @@ files = [ [[package]] name = "ruff" -version = "0.6.3" +version = "0.6.4" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.6.3-py3-none-linux_armv6l.whl", hash = "sha256:97f58fda4e309382ad30ede7f30e2791d70dd29ea17f41970119f55bdb7a45c3"}, - {file = "ruff-0.6.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:3b061e49b5cf3a297b4d1c27ac5587954ccb4ff601160d3d6b2f70b1622194dc"}, - {file = "ruff-0.6.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:34e2824a13bb8c668c71c1760a6ac7d795ccbd8d38ff4a0d8471fdb15de910b1"}, - {file = "ruff-0.6.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bddfbb8d63c460f4b4128b6a506e7052bad4d6f3ff607ebbb41b0aa19c2770d1"}, - {file = "ruff-0.6.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ced3eeb44df75353e08ab3b6a9e113b5f3f996bea48d4f7c027bc528ba87b672"}, - {file = "ruff-0.6.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:47021dff5445d549be954eb275156dfd7c37222acc1e8014311badcb9b4ec8c1"}, - {file = "ruff-0.6.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:7d7bd20dc07cebd68cc8bc7b3f5ada6d637f42d947c85264f94b0d1cd9d87384"}, - {file = "ruff-0.6.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:500f166d03fc6d0e61c8e40a3ff853fa8a43d938f5d14c183c612df1b0d6c58a"}, - {file = "ruff-0.6.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:42844ff678f9b976366b262fa2d1d1a3fe76f6e145bd92c84e27d172e3c34500"}, - {file = "ruff-0.6.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70452a10eb2d66549de8e75f89ae82462159855e983ddff91bc0bce6511d0470"}, - {file = "ruff-0.6.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:65a533235ed55f767d1fc62193a21cbf9e3329cf26d427b800fdeacfb77d296f"}, - {file = "ruff-0.6.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d2e2c23cef30dc3cbe9cc5d04f2899e7f5e478c40d2e0a633513ad081f7361b5"}, - {file = "ruff-0.6.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d8a136aa7d228975a6aee3dd8bea9b28e2b43e9444aa678fb62aeb1956ff2351"}, - {file = "ruff-0.6.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:f92fe93bc72e262b7b3f2bba9879897e2d58a989b4714ba6a5a7273e842ad2f8"}, - {file = "ruff-0.6.3-py3-none-win32.whl", hash = "sha256:7a62d3b5b0d7f9143d94893f8ba43aa5a5c51a0ffc4a401aa97a81ed76930521"}, - {file = "ruff-0.6.3-py3-none-win_amd64.whl", hash = "sha256:746af39356fee2b89aada06c7376e1aa274a23493d7016059c3a72e3b296befb"}, - {file = "ruff-0.6.3-py3-none-win_arm64.whl", hash = "sha256:14a9528a8b70ccc7a847637c29e56fd1f9183a9db743bbc5b8e0c4ad60592a82"}, - {file = "ruff-0.6.3.tar.gz", hash = "sha256:183b99e9edd1ef63be34a3b51fee0a9f4ab95add123dbf89a71f7b1f0c991983"}, + {file = "ruff-0.6.4-py3-none-linux_armv6l.whl", hash = "sha256:c4b153fc152af51855458e79e835fb6b933032921756cec9af7d0ba2aa01a258"}, + {file = "ruff-0.6.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:bedff9e4f004dad5f7f76a9d39c4ca98af526c9b1695068198b3bda8c085ef60"}, + {file = "ruff-0.6.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d02a4127a86de23002e694d7ff19f905c51e338c72d8e09b56bfb60e1681724f"}, + {file = "ruff-0.6.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7862f42fc1a4aca1ea3ffe8a11f67819d183a5693b228f0bb3a531f5e40336fc"}, + {file = "ruff-0.6.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eebe4ff1967c838a1a9618a5a59a3b0a00406f8d7eefee97c70411fefc353617"}, + {file = "ruff-0.6.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:932063a03bac394866683e15710c25b8690ccdca1cf192b9a98260332ca93408"}, + {file = "ruff-0.6.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:50e30b437cebef547bd5c3edf9ce81343e5dd7c737cb36ccb4fe83573f3d392e"}, + {file = "ruff-0.6.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c44536df7b93a587de690e124b89bd47306fddd59398a0fb12afd6133c7b3818"}, + {file = "ruff-0.6.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ea086601b22dc5e7693a78f3fcfc460cceabfdf3bdc36dc898792aba48fbad6"}, + {file = "ruff-0.6.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b52387d3289ccd227b62102c24714ed75fbba0b16ecc69a923a37e3b5e0aaaa"}, + {file = "ruff-0.6.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:0308610470fcc82969082fc83c76c0d362f562e2f0cdab0586516f03a4e06ec6"}, + {file = "ruff-0.6.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:803b96dea21795a6c9d5bfa9e96127cc9c31a1987802ca68f35e5c95aed3fc0d"}, + {file = "ruff-0.6.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:66dbfea86b663baab8fcae56c59f190caba9398df1488164e2df53e216248baa"}, + {file = "ruff-0.6.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:34d5efad480193c046c86608dbba2bccdc1c5fd11950fb271f8086e0c763a5d1"}, + {file = "ruff-0.6.4-py3-none-win32.whl", hash = "sha256:f0f8968feea5ce3777c0d8365653d5e91c40c31a81d95824ba61d871a11b8523"}, + {file = "ruff-0.6.4-py3-none-win_amd64.whl", hash = "sha256:549daccee5227282289390b0222d0fbee0275d1db6d514550d65420053021a58"}, + {file = "ruff-0.6.4-py3-none-win_arm64.whl", hash = "sha256:ac4b75e898ed189b3708c9ab3fc70b79a433219e1e87193b4f2b77251d058d14"}, + {file = "ruff-0.6.4.tar.gz", hash = "sha256:ac3b5bfbee99973f80aa1b7cbd1c9cbce200883bdd067300c22a6cc1c7fba212"}, ] [[package]] @@ -4856,4 +4856,4 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] [metadata] lock-version = "2.0" python-versions = "3.12.5" -content-hash = "b30b3ee131665446326279ac331de86adb22d4139cf03f8581628673d9465659" +content-hash = "09989937bea2537e2d7e1efaa12fcfa0a95298ec2fc3e99859e0df33f4d65d9c" diff --git a/pyproject.toml b/pyproject.toml index 27467ee9c0..a889a84328 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -77,7 +77,7 @@ django-scim2 = "^0.19.1" django-oauth-toolkit = "^2.3.0" youtube-transcript-api = "^0.6.2" posthog = "^3.5.0" -ruff = "0.6.3" +ruff = "0.6.4" dateparser = "^1.2.0" uwsgitop = "^0.12" From 1c290a86970876fc22b76746a1c72cacb0318fd3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 7 Sep 2024 14:13:32 +0000 Subject: [PATCH 05/18] Update mcr.microsoft.com/playwright Docker tag to v1.47.0 (#1531) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- e2e_testing/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e_testing/Dockerfile b/e2e_testing/Dockerfile index d5e2dc07dd..bec62aa0b5 100644 --- a/e2e_testing/Dockerfile +++ b/e2e_testing/Dockerfile @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/playwright:v1.46.1-jammy +FROM mcr.microsoft.com/playwright:v1.47.0-jammy COPY . /tests From a2ddda6329cc4d1bccbf79bd28f1beeccbd0944b Mon Sep 17 00:00:00 2001 From: Matt Bertrand Date: Mon, 9 Sep 2024 09:54:13 -0400 Subject: [PATCH 06/18] Add LearningResource.delivery field (eventually to replace learning_format) (#1510) --- frontends/api/src/generated/v1/api.ts | 508 ++++++++++++++++- learning_resources/constants.py | 14 +- learning_resources/etl/ocw.py | 24 +- learning_resources/etl/ocw_test.py | 41 +- learning_resources/etl/prolearn.py | 7 +- learning_resources/etl/prolearn_test.py | 9 +- learning_resources/etl/sloan.py | 11 +- learning_resources/etl/sloan_test.py | 6 +- learning_resources/etl/utils.py | 6 +- learning_resources/etl/utils_test.py | 6 +- learning_resources/etl/xpro.py | 8 +- learning_resources/etl/xpro_test.py | 11 +- learning_resources/factories.py | 2 + learning_resources/filters.py | 12 + learning_resources/filters_test.py | 33 ++ ...2_learningresource_delivery_format_pace.py | 145 +++++ learning_resources/models.py | 7 + learning_resources/serializers.py | 19 + learning_resources/serializers_test.py | 5 + learning_resources_search/constants.py | 8 + learning_resources_search/serializers.py | 11 + learning_resources_search/serializers_test.py | 55 ++ openapi/specs/v1.yaml | 525 ++++++++++++++++++ .../data.json | 1 + 24 files changed, 1433 insertions(+), 41 deletions(-) create mode 100644 learning_resources/migrations/0062_learningresource_delivery_format_pace.py diff --git a/frontends/api/src/generated/v1/api.ts b/frontends/api/src/generated/v1/api.ts index 09d442f00e..4ce15dede9 100644 --- a/frontends/api/src/generated/v1/api.ts +++ b/frontends/api/src/generated/v1/api.ts @@ -40,7 +40,7 @@ import { } from "./base" /** - * * `resource_type` - resource_type * `certification` - certification * `certification_type` - certification_type * `offered_by` - offered_by * `platform` - platform * `topic` - topic * `department` - department * `level` - level * `course_feature` - course_feature * `professional` - professional * `free` - free * `learning_format` - learning_format * `resource_category` - resource_category + * * `resource_type` - resource_type * `certification` - certification * `certification_type` - certification_type * `offered_by` - offered_by * `platform` - platform * `topic` - topic * `department` - department * `level` - level * `course_feature` - course_feature * `professional` - professional * `free` - free * `learning_format` - learning_format * `delivery` - delivery * `resource_category` - resource_category * @export * @enum {string} */ @@ -58,6 +58,7 @@ export const AggregationsEnumDescriptions = { professional: "professional", free: "free", learning_format: "learning_format", + delivery: "delivery", resource_category: "resource_category", } as const @@ -110,6 +111,10 @@ export const AggregationsEnum = { * learning_format */ LearningFormat: "learning_format", + /** + * delivery + */ + Delivery: "delivery", /** * resource_category */ @@ -674,6 +679,12 @@ export interface CourseResource { * @memberof CourseResource */ learning_format: Array + /** + * + * @type {Array} + * @memberof CourseResource + */ + delivery: Array /** * Return true if the resource is free/has a free option * @type {boolean} @@ -802,6 +813,36 @@ export const CourseResourceCertificationTypeCodeEnum = { export type CourseResourceCertificationTypeCodeEnum = (typeof CourseResourceCertificationTypeCodeEnum)[keyof typeof CourseResourceCertificationTypeCodeEnum] +/** + * + * @export + * @interface CourseResourceDeliveryInner + */ +export interface CourseResourceDeliveryInner { + /** + * + * @type {string} + * @memberof CourseResourceDeliveryInner + */ + code: CourseResourceDeliveryInnerCodeEnum + /** + * + * @type {string} + * @memberof CourseResourceDeliveryInner + */ + name: string +} + +export const CourseResourceDeliveryInnerCodeEnum = { + Online: "online", + Hybrid: "hybrid", + InPerson: "in_person", + Offline: "offline", +} as const + +export type CourseResourceDeliveryInnerCodeEnum = + (typeof CourseResourceDeliveryInnerCodeEnum)[keyof typeof CourseResourceDeliveryInnerCodeEnum] + /** * * @export @@ -928,6 +969,40 @@ export const CourseResourceResourceTypeEnum = { export type CourseResourceResourceTypeEnum = (typeof CourseResourceResourceTypeEnum)[keyof typeof CourseResourceResourceTypeEnum] +/** + * * `online` - Online * `hybrid` - Hybrid * `in_person` - In person * `offline` - Offline + * @export + * @enum {string} + */ + +export const DeliveryEnumDescriptions = { + online: "Online", + hybrid: "Hybrid", + in_person: "In person", + offline: "Offline", +} as const + +export const DeliveryEnum = { + /** + * Online + */ + Online: "online", + /** + * Hybrid + */ + Hybrid: "hybrid", + /** + * In person + */ + InPerson: "in_person", + /** + * Offline + */ + Offline: "offline", +} as const + +export type DeliveryEnum = (typeof DeliveryEnum)[keyof typeof DeliveryEnum] + /** * * `1` - Civil and Environmental Engineering * `2` - Mechanical Engineering * `3` - Materials Science and Engineering * `4` - Architecture * `5` - Chemistry * `6` - Electrical Engineering and Computer Science * `7` - Biology * `8` - Physics * `9` - Brain and Cognitive Sciences * `10` - Chemical Engineering * `11` - Urban Studies and Planning * `12` - Earth, Atmospheric, and Planetary Sciences * `14` - Economics * `15` - Management * `16` - Aeronautics and Astronautics * `17` - Political Science * `18` - Mathematics * `20` - Biological Engineering * `21A` - Anthropology * `21G` - Global Languages * `21H` - History * `21L` - Literature * `21M` - Music and Theater Arts * `22` - Nuclear Science and Engineering * `24` - Linguistics and Philosophy * `CC` - Concourse * `CMS-W` - Comparative Media Studies/Writing * `EC` - Edgerton Center * `ES` - Experimental Study Group * `ESD` - Engineering Systems Division * `HST` - Medical Engineering and Science * `IDS` - Data, Systems, and Society * `MAS` - Media Arts and Sciences * `PE` - Athletics, Physical Education and Recreation * `SP` - Special Programs * `STS` - Science, Technology, and Society * `WGS` - Women\'s and Gender Studies * @export @@ -1335,6 +1410,12 @@ export interface LearningPathResource { * @memberof LearningPathResource */ learning_format: Array + /** + * + * @type {Array} + * @memberof LearningPathResource + */ + delivery: Array /** * Return true if the resource is free/has a free option * @type {boolean} @@ -3565,6 +3646,12 @@ export interface PercolateQuerySubscriptionRequestRequest { * @memberof PercolateQuerySubscriptionRequestRequest */ learning_format?: Array + /** + * The delivery options in which the learning resource is offered * `online` - Online * `hybrid` - Hybrid * `in_person` - In person * `offline` - Offline + * @type {Array} + * @memberof PercolateQuerySubscriptionRequestRequest + */ + delivery?: Array /** * The category of learning resource * `course` - Course * `program` - Program * `learning_material` - Learning Material * @type {Array} @@ -3958,6 +4045,12 @@ export interface PodcastEpisodeResource { * @memberof PodcastEpisodeResource */ learning_format: Array + /** + * + * @type {Array} + * @memberof PodcastEpisodeResource + */ + delivery: Array /** * Return true if the resource is free/has a free option * @type {boolean} @@ -4280,6 +4373,12 @@ export interface PodcastResource { * @memberof PodcastResource */ learning_format: Array + /** + * + * @type {Array} + * @memberof PodcastResource + */ + delivery: Array /** * Return true if the resource is free/has a free option * @type {boolean} @@ -4822,6 +4921,12 @@ export interface ProgramResource { * @memberof ProgramResource */ learning_format: Array + /** + * + * @type {Array} + * @memberof ProgramResource + */ + delivery: Array /** * Return true if the resource is free/has a free option * @type {boolean} @@ -5623,6 +5728,12 @@ export interface VideoPlaylistResource { * @memberof VideoPlaylistResource */ learning_format: Array + /** + * + * @type {Array} + * @memberof VideoPlaylistResource + */ + delivery: Array /** * Return true if the resource is free/has a free option * @type {boolean} @@ -5933,6 +6044,12 @@ export interface VideoResource { * @memberof VideoResource */ learning_format: Array + /** + * + * @type {Array} + * @memberof VideoResource + */ + delivery: Array /** * Return true if the resource is free/has a free option * @type {boolean} @@ -8269,6 +8386,7 @@ export const CoursesApiAxiosParamCreator = function ( * @param {boolean} [certification] * @param {Array} [certification_type] The type of certification offered * `micromasters` - Micromasters Credential * `professional` - Professional Certificate * `completion` - Certificate of Completion * `none` - No Certificate * @param {Array} [course_feature] Multiple values may be separated by commas. + * @param {Array>} [delivery] The delivery of course/program resources * `online` - Online * `hybrid` - Hybrid * `in_person` - In person * `offline` - Offline * @param {Array} [department] The department that offers learning resources * `1` - Civil and Environmental Engineering * `2` - Mechanical Engineering * `3` - Materials Science and Engineering * `4` - Architecture * `5` - Chemistry * `6` - Electrical Engineering and Computer Science * `7` - Biology * `8` - Physics * `9` - Brain and Cognitive Sciences * `10` - Chemical Engineering * `11` - Urban Studies and Planning * `12` - Earth, Atmospheric, and Planetary Sciences * `14` - Economics * `15` - Management * `16` - Aeronautics and Astronautics * `17` - Political Science * `18` - Mathematics * `20` - Biological Engineering * `21A` - Anthropology * `21G` - Global Languages * `21H` - History * `21L` - Literature * `21M` - Music and Theater Arts * `22` - Nuclear Science and Engineering * `24` - Linguistics and Philosophy * `CC` - Concourse * `CMS-W` - Comparative Media Studies/Writing * `EC` - Edgerton Center * `ES` - Experimental Study Group * `ESD` - Engineering Systems Division * `HST` - Medical Engineering and Science * `IDS` - Data, Systems, and Society * `MAS` - Media Arts and Sciences * `PE` - Athletics, Physical Education and Recreation * `SP` - Special Programs * `STS` - Science, Technology, and Society * `WGS` - Women\'s and Gender Studies * @param {boolean} [free] The course/program is offered for free * @param {Array>} [learning_format] The learning format of course/program resources * `online` - Online * `hybrid` - Hybrid * `in_person` - In person @@ -8290,6 +8408,7 @@ export const CoursesApiAxiosParamCreator = function ( certification?: boolean, certification_type?: Array, course_feature?: Array, + delivery?: Array>, department?: Array, free?: boolean, learning_format?: Array>, @@ -8336,6 +8455,10 @@ export const CoursesApiAxiosParamCreator = function ( ) } + if (delivery) { + localVarQueryParameter["delivery"] = delivery + } + if (department) { localVarQueryParameter["department"] = department } @@ -8556,6 +8679,7 @@ export const CoursesApiFp = function (configuration?: Configuration) { * @param {boolean} [certification] * @param {Array} [certification_type] The type of certification offered * `micromasters` - Micromasters Credential * `professional` - Professional Certificate * `completion` - Certificate of Completion * `none` - No Certificate * @param {Array} [course_feature] Multiple values may be separated by commas. + * @param {Array>} [delivery] The delivery of course/program resources * `online` - Online * `hybrid` - Hybrid * `in_person` - In person * `offline` - Offline * @param {Array} [department] The department that offers learning resources * `1` - Civil and Environmental Engineering * `2` - Mechanical Engineering * `3` - Materials Science and Engineering * `4` - Architecture * `5` - Chemistry * `6` - Electrical Engineering and Computer Science * `7` - Biology * `8` - Physics * `9` - Brain and Cognitive Sciences * `10` - Chemical Engineering * `11` - Urban Studies and Planning * `12` - Earth, Atmospheric, and Planetary Sciences * `14` - Economics * `15` - Management * `16` - Aeronautics and Astronautics * `17` - Political Science * `18` - Mathematics * `20` - Biological Engineering * `21A` - Anthropology * `21G` - Global Languages * `21H` - History * `21L` - Literature * `21M` - Music and Theater Arts * `22` - Nuclear Science and Engineering * `24` - Linguistics and Philosophy * `CC` - Concourse * `CMS-W` - Comparative Media Studies/Writing * `EC` - Edgerton Center * `ES` - Experimental Study Group * `ESD` - Engineering Systems Division * `HST` - Medical Engineering and Science * `IDS` - Data, Systems, and Society * `MAS` - Media Arts and Sciences * `PE` - Athletics, Physical Education and Recreation * `SP` - Special Programs * `STS` - Science, Technology, and Society * `WGS` - Women\'s and Gender Studies * @param {boolean} [free] The course/program is offered for free * @param {Array>} [learning_format] The learning format of course/program resources * `online` - Online * `hybrid` - Hybrid * `in_person` - In person @@ -8577,6 +8701,7 @@ export const CoursesApiFp = function (configuration?: Configuration) { certification?: boolean, certification_type?: Array, course_feature?: Array, + delivery?: Array>, department?: Array, free?: boolean, learning_format?: Array>, @@ -8602,6 +8727,7 @@ export const CoursesApiFp = function (configuration?: Configuration) { certification, certification_type, course_feature, + delivery, department, free, learning_format, @@ -8731,6 +8857,7 @@ export const CoursesApiFactory = function ( requestParameters.certification, requestParameters.certification_type, requestParameters.course_feature, + requestParameters.delivery, requestParameters.department, requestParameters.free, requestParameters.learning_format, @@ -8878,6 +9005,13 @@ export interface CoursesApiCoursesListRequest { */ readonly course_feature?: Array + /** + * The delivery of course/program resources * `online` - Online * `hybrid` - Hybrid * `in_person` - In person * `offline` - Offline + * @type {Array>} + * @memberof CoursesApiCoursesList + */ + readonly delivery?: Array> + /** * The department that offers learning resources * `1` - Civil and Environmental Engineering * `2` - Mechanical Engineering * `3` - Materials Science and Engineering * `4` - Architecture * `5` - Chemistry * `6` - Electrical Engineering and Computer Science * `7` - Biology * `8` - Physics * `9` - Brain and Cognitive Sciences * `10` - Chemical Engineering * `11` - Urban Studies and Planning * `12` - Earth, Atmospheric, and Planetary Sciences * `14` - Economics * `15` - Management * `16` - Aeronautics and Astronautics * `17` - Political Science * `18` - Mathematics * `20` - Biological Engineering * `21A` - Anthropology * `21G` - Global Languages * `21H` - History * `21L` - Literature * `21M` - Music and Theater Arts * `22` - Nuclear Science and Engineering * `24` - Linguistics and Philosophy * `CC` - Concourse * `CMS-W` - Comparative Media Studies/Writing * `EC` - Edgerton Center * `ES` - Experimental Study Group * `ESD` - Engineering Systems Division * `HST` - Medical Engineering and Science * `IDS` - Data, Systems, and Society * `MAS` - Media Arts and Sciences * `PE` - Athletics, Physical Education and Recreation * `SP` - Special Programs * `STS` - Science, Technology, and Society * `WGS` - Women\'s and Gender Studies * @type {Array<'1' | '10' | '11' | '12' | '14' | '15' | '16' | '17' | '18' | '2' | '20' | '21A' | '21G' | '21H' | '21L' | '21M' | '22' | '24' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | 'CC' | 'CMS-W' | 'EC' | 'ES' | 'ESD' | 'HST' | 'IDS' | 'MAS' | 'PE' | 'SP' | 'STS' | 'WGS'>} @@ -9063,6 +9197,7 @@ export class CoursesApi extends BaseAPI { requestParameters.certification, requestParameters.certification_type, requestParameters.course_feature, + requestParameters.delivery, requestParameters.department, requestParameters.free, requestParameters.learning_format, @@ -9152,6 +9287,17 @@ export type CoursesListCertificationTypeEnum = /** * @export */ +export const CoursesListDeliveryEnum = { + Online: "online", + Hybrid: "hybrid", + InPerson: "in_person", + Offline: "offline", +} as const +export type CoursesListDeliveryEnum = + (typeof CoursesListDeliveryEnum)[keyof typeof CoursesListDeliveryEnum] +/** + * @export + */ export const CoursesListDepartmentEnum = { _1: "1", _10: "10", @@ -9619,6 +9765,7 @@ export const FeaturedApiAxiosParamCreator = function ( * @param {boolean} [certification] * @param {Array} [certification_type] The type of certification offered * `micromasters` - Micromasters Credential * `professional` - Professional Certificate * `completion` - Certificate of Completion * `none` - No Certificate * @param {Array} [course_feature] Multiple values may be separated by commas. + * @param {Array>} [delivery] The delivery of course/program resources * `online` - Online * `hybrid` - Hybrid * `in_person` - In person * `offline` - Offline * @param {Array} [department] The department that offers learning resources * `1` - Civil and Environmental Engineering * `2` - Mechanical Engineering * `3` - Materials Science and Engineering * `4` - Architecture * `5` - Chemistry * `6` - Electrical Engineering and Computer Science * `7` - Biology * `8` - Physics * `9` - Brain and Cognitive Sciences * `10` - Chemical Engineering * `11` - Urban Studies and Planning * `12` - Earth, Atmospheric, and Planetary Sciences * `14` - Economics * `15` - Management * `16` - Aeronautics and Astronautics * `17` - Political Science * `18` - Mathematics * `20` - Biological Engineering * `21A` - Anthropology * `21G` - Global Languages * `21H` - History * `21L` - Literature * `21M` - Music and Theater Arts * `22` - Nuclear Science and Engineering * `24` - Linguistics and Philosophy * `CC` - Concourse * `CMS-W` - Comparative Media Studies/Writing * `EC` - Edgerton Center * `ES` - Experimental Study Group * `ESD` - Engineering Systems Division * `HST` - Medical Engineering and Science * `IDS` - Data, Systems, and Society * `MAS` - Media Arts and Sciences * `PE` - Athletics, Physical Education and Recreation * `SP` - Special Programs * `STS` - Science, Technology, and Society * `WGS` - Women\'s and Gender Studies * @param {boolean} [free] The course/program is offered for free * @param {Array>} [learning_format] The learning format of course/program resources * `online` - Online * `hybrid` - Hybrid * `in_person` - In person @@ -9640,6 +9787,7 @@ export const FeaturedApiAxiosParamCreator = function ( certification?: boolean, certification_type?: Array, course_feature?: Array, + delivery?: Array>, department?: Array, free?: boolean, learning_format?: Array>, @@ -9686,6 +9834,10 @@ export const FeaturedApiAxiosParamCreator = function ( ) } + if (delivery) { + localVarQueryParameter["delivery"] = delivery + } + if (department) { localVarQueryParameter["department"] = department } @@ -9820,6 +9972,7 @@ export const FeaturedApiFp = function (configuration?: Configuration) { * @param {boolean} [certification] * @param {Array} [certification_type] The type of certification offered * `micromasters` - Micromasters Credential * `professional` - Professional Certificate * `completion` - Certificate of Completion * `none` - No Certificate * @param {Array} [course_feature] Multiple values may be separated by commas. + * @param {Array>} [delivery] The delivery of course/program resources * `online` - Online * `hybrid` - Hybrid * `in_person` - In person * `offline` - Offline * @param {Array} [department] The department that offers learning resources * `1` - Civil and Environmental Engineering * `2` - Mechanical Engineering * `3` - Materials Science and Engineering * `4` - Architecture * `5` - Chemistry * `6` - Electrical Engineering and Computer Science * `7` - Biology * `8` - Physics * `9` - Brain and Cognitive Sciences * `10` - Chemical Engineering * `11` - Urban Studies and Planning * `12` - Earth, Atmospheric, and Planetary Sciences * `14` - Economics * `15` - Management * `16` - Aeronautics and Astronautics * `17` - Political Science * `18` - Mathematics * `20` - Biological Engineering * `21A` - Anthropology * `21G` - Global Languages * `21H` - History * `21L` - Literature * `21M` - Music and Theater Arts * `22` - Nuclear Science and Engineering * `24` - Linguistics and Philosophy * `CC` - Concourse * `CMS-W` - Comparative Media Studies/Writing * `EC` - Edgerton Center * `ES` - Experimental Study Group * `ESD` - Engineering Systems Division * `HST` - Medical Engineering and Science * `IDS` - Data, Systems, and Society * `MAS` - Media Arts and Sciences * `PE` - Athletics, Physical Education and Recreation * `SP` - Special Programs * `STS` - Science, Technology, and Society * `WGS` - Women\'s and Gender Studies * @param {boolean} [free] The course/program is offered for free * @param {Array>} [learning_format] The learning format of course/program resources * `online` - Online * `hybrid` - Hybrid * `in_person` - In person @@ -9841,6 +9994,7 @@ export const FeaturedApiFp = function (configuration?: Configuration) { certification?: boolean, certification_type?: Array, course_feature?: Array, + delivery?: Array>, department?: Array, free?: boolean, learning_format?: Array>, @@ -9866,6 +10020,7 @@ export const FeaturedApiFp = function (configuration?: Configuration) { certification, certification_type, course_feature, + delivery, department, free, learning_format, @@ -9952,6 +10107,7 @@ export const FeaturedApiFactory = function ( requestParameters.certification, requestParameters.certification_type, requestParameters.course_feature, + requestParameters.delivery, requestParameters.department, requestParameters.free, requestParameters.learning_format, @@ -10015,6 +10171,13 @@ export interface FeaturedApiFeaturedListRequest { */ readonly course_feature?: Array + /** + * The delivery of course/program resources * `online` - Online * `hybrid` - Hybrid * `in_person` - In person * `offline` - Offline + * @type {Array>} + * @memberof FeaturedApiFeaturedList + */ + readonly delivery?: Array> + /** * The department that offers learning resources * `1` - Civil and Environmental Engineering * `2` - Mechanical Engineering * `3` - Materials Science and Engineering * `4` - Architecture * `5` - Chemistry * `6` - Electrical Engineering and Computer Science * `7` - Biology * `8` - Physics * `9` - Brain and Cognitive Sciences * `10` - Chemical Engineering * `11` - Urban Studies and Planning * `12` - Earth, Atmospheric, and Planetary Sciences * `14` - Economics * `15` - Management * `16` - Aeronautics and Astronautics * `17` - Political Science * `18` - Mathematics * `20` - Biological Engineering * `21A` - Anthropology * `21G` - Global Languages * `21H` - History * `21L` - Literature * `21M` - Music and Theater Arts * `22` - Nuclear Science and Engineering * `24` - Linguistics and Philosophy * `CC` - Concourse * `CMS-W` - Comparative Media Studies/Writing * `EC` - Edgerton Center * `ES` - Experimental Study Group * `ESD` - Engineering Systems Division * `HST` - Medical Engineering and Science * `IDS` - Data, Systems, and Society * `MAS` - Media Arts and Sciences * `PE` - Athletics, Physical Education and Recreation * `SP` - Special Programs * `STS` - Science, Technology, and Society * `WGS` - Women\'s and Gender Studies * @type {Array<'1' | '10' | '11' | '12' | '14' | '15' | '16' | '17' | '18' | '2' | '20' | '21A' | '21G' | '21H' | '21L' | '21M' | '22' | '24' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | 'CC' | 'CMS-W' | 'EC' | 'ES' | 'ESD' | 'HST' | 'IDS' | 'MAS' | 'PE' | 'SP' | 'STS' | 'WGS'>} @@ -10152,6 +10315,7 @@ export class FeaturedApi extends BaseAPI { requestParameters.certification, requestParameters.certification_type, requestParameters.course_feature, + requestParameters.delivery, requestParameters.department, requestParameters.free, requestParameters.learning_format, @@ -10203,6 +10367,17 @@ export type FeaturedListCertificationTypeEnum = /** * @export */ +export const FeaturedListDeliveryEnum = { + Online: "online", + Hybrid: "hybrid", + InPerson: "in_person", + Offline: "offline", +} as const +export type FeaturedListDeliveryEnum = + (typeof FeaturedListDeliveryEnum)[keyof typeof FeaturedListDeliveryEnum] +/** + * @export + */ export const FeaturedListDepartmentEnum = { _1: "1", _10: "10", @@ -10697,6 +10872,7 @@ export const LearningResourcesApiAxiosParamCreator = function ( * @param {boolean} [certification] * @param {Array} [certification_type] The type of certification offered * `micromasters` - Micromasters Credential * `professional` - Professional Certificate * `completion` - Certificate of Completion * `none` - No Certificate * @param {Array} [course_feature] Multiple values may be separated by commas. + * @param {Array>} [delivery] The delivery of course/program resources * `online` - Online * `hybrid` - Hybrid * `in_person` - In person * `offline` - Offline * @param {Array} [department] The department that offers learning resources * `1` - Civil and Environmental Engineering * `2` - Mechanical Engineering * `3` - Materials Science and Engineering * `4` - Architecture * `5` - Chemistry * `6` - Electrical Engineering and Computer Science * `7` - Biology * `8` - Physics * `9` - Brain and Cognitive Sciences * `10` - Chemical Engineering * `11` - Urban Studies and Planning * `12` - Earth, Atmospheric, and Planetary Sciences * `14` - Economics * `15` - Management * `16` - Aeronautics and Astronautics * `17` - Political Science * `18` - Mathematics * `20` - Biological Engineering * `21A` - Anthropology * `21G` - Global Languages * `21H` - History * `21L` - Literature * `21M` - Music and Theater Arts * `22` - Nuclear Science and Engineering * `24` - Linguistics and Philosophy * `CC` - Concourse * `CMS-W` - Comparative Media Studies/Writing * `EC` - Edgerton Center * `ES` - Experimental Study Group * `ESD` - Engineering Systems Division * `HST` - Medical Engineering and Science * `IDS` - Data, Systems, and Society * `MAS` - Media Arts and Sciences * `PE` - Athletics, Physical Education and Recreation * `SP` - Special Programs * `STS` - Science, Technology, and Society * `WGS` - Women\'s and Gender Studies * @param {boolean} [free] The course/program is offered for free * @param {Array>} [learning_format] The learning format of course/program resources * `online` - Online * `hybrid` - Hybrid * `in_person` - In person @@ -10718,6 +10894,7 @@ export const LearningResourcesApiAxiosParamCreator = function ( certification?: boolean, certification_type?: Array, course_feature?: Array, + delivery?: Array>, department?: Array, free?: boolean, learning_format?: Array>, @@ -10764,6 +10941,10 @@ export const LearningResourcesApiAxiosParamCreator = function ( ) } + if (delivery) { + localVarQueryParameter["delivery"] = delivery + } + if (department) { localVarQueryParameter["department"] = department } @@ -11169,6 +11350,7 @@ export const LearningResourcesApiFp = function (configuration?: Configuration) { * @param {boolean} [certification] * @param {Array} [certification_type] The type of certification offered * `micromasters` - Micromasters Credential * `professional` - Professional Certificate * `completion` - Certificate of Completion * `none` - No Certificate * @param {Array} [course_feature] Multiple values may be separated by commas. + * @param {Array>} [delivery] The delivery of course/program resources * `online` - Online * `hybrid` - Hybrid * `in_person` - In person * `offline` - Offline * @param {Array} [department] The department that offers learning resources * `1` - Civil and Environmental Engineering * `2` - Mechanical Engineering * `3` - Materials Science and Engineering * `4` - Architecture * `5` - Chemistry * `6` - Electrical Engineering and Computer Science * `7` - Biology * `8` - Physics * `9` - Brain and Cognitive Sciences * `10` - Chemical Engineering * `11` - Urban Studies and Planning * `12` - Earth, Atmospheric, and Planetary Sciences * `14` - Economics * `15` - Management * `16` - Aeronautics and Astronautics * `17` - Political Science * `18` - Mathematics * `20` - Biological Engineering * `21A` - Anthropology * `21G` - Global Languages * `21H` - History * `21L` - Literature * `21M` - Music and Theater Arts * `22` - Nuclear Science and Engineering * `24` - Linguistics and Philosophy * `CC` - Concourse * `CMS-W` - Comparative Media Studies/Writing * `EC` - Edgerton Center * `ES` - Experimental Study Group * `ESD` - Engineering Systems Division * `HST` - Medical Engineering and Science * `IDS` - Data, Systems, and Society * `MAS` - Media Arts and Sciences * `PE` - Athletics, Physical Education and Recreation * `SP` - Special Programs * `STS` - Science, Technology, and Society * `WGS` - Women\'s and Gender Studies * @param {boolean} [free] The course/program is offered for free * @param {Array>} [learning_format] The learning format of course/program resources * `online` - Online * `hybrid` - Hybrid * `in_person` - In person @@ -11190,6 +11372,7 @@ export const LearningResourcesApiFp = function (configuration?: Configuration) { certification?: boolean, certification_type?: Array, course_feature?: Array, + delivery?: Array>, department?: Array, free?: boolean, learning_format?: Array>, @@ -11216,6 +11399,7 @@ export const LearningResourcesApiFp = function (configuration?: Configuration) { certification, certification_type, course_feature, + delivery, department, free, learning_format, @@ -11450,6 +11634,7 @@ export const LearningResourcesApiFactory = function ( requestParameters.certification, requestParameters.certification_type, requestParameters.course_feature, + requestParameters.delivery, requestParameters.department, requestParameters.free, requestParameters.learning_format, @@ -11701,6 +11886,13 @@ export interface LearningResourcesApiLearningResourcesListRequest { */ readonly course_feature?: Array + /** + * The delivery of course/program resources * `online` - Online * `hybrid` - Hybrid * `in_person` - In person * `offline` - Offline + * @type {Array>} + * @memberof LearningResourcesApiLearningResourcesList + */ + readonly delivery?: Array> + /** * The department that offers learning resources * `1` - Civil and Environmental Engineering * `2` - Mechanical Engineering * `3` - Materials Science and Engineering * `4` - Architecture * `5` - Chemistry * `6` - Electrical Engineering and Computer Science * `7` - Biology * `8` - Physics * `9` - Brain and Cognitive Sciences * `10` - Chemical Engineering * `11` - Urban Studies and Planning * `12` - Earth, Atmospheric, and Planetary Sciences * `14` - Economics * `15` - Management * `16` - Aeronautics and Astronautics * `17` - Political Science * `18` - Mathematics * `20` - Biological Engineering * `21A` - Anthropology * `21G` - Global Languages * `21H` - History * `21L` - Literature * `21M` - Music and Theater Arts * `22` - Nuclear Science and Engineering * `24` - Linguistics and Philosophy * `CC` - Concourse * `CMS-W` - Comparative Media Studies/Writing * `EC` - Edgerton Center * `ES` - Experimental Study Group * `ESD` - Engineering Systems Division * `HST` - Medical Engineering and Science * `IDS` - Data, Systems, and Society * `MAS` - Media Arts and Sciences * `PE` - Athletics, Physical Education and Recreation * `SP` - Special Programs * `STS` - Science, Technology, and Society * `WGS` - Women\'s and Gender Studies * @type {Array<'1' | '10' | '11' | '12' | '14' | '15' | '16' | '17' | '18' | '2' | '20' | '21A' | '21G' | '21H' | '21L' | '21M' | '22' | '24' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | 'CC' | 'CMS-W' | 'EC' | 'ES' | 'ESD' | 'HST' | 'IDS' | 'MAS' | 'PE' | 'SP' | 'STS' | 'WGS'>} @@ -11980,6 +12172,7 @@ export class LearningResourcesApi extends BaseAPI { requestParameters.certification, requestParameters.certification_type, requestParameters.course_feature, + requestParameters.delivery, requestParameters.department, requestParameters.free, requestParameters.learning_format, @@ -12091,6 +12284,17 @@ export type LearningResourcesListCertificationTypeEnum = /** * @export */ +export const LearningResourcesListDeliveryEnum = { + Online: "online", + Hybrid: "hybrid", + InPerson: "in_person", + Offline: "offline", +} as const +export type LearningResourcesListDeliveryEnum = + (typeof LearningResourcesListDeliveryEnum)[keyof typeof LearningResourcesListDeliveryEnum] +/** + * @export + */ export const LearningResourcesListDepartmentEnum = { _1: "1", _10: "10", @@ -12245,6 +12449,7 @@ export const LearningResourcesSearchApiAxiosParamCreator = function ( * @param {boolean | null} [certification] True if the learning resource offers a certificate * @param {Array} [certification_type] The type of certificate * `micromasters` - Micromasters Credential * `professional` - Professional Certificate * `completion` - Certificate of Completion * `none` - No Certificate * @param {Array} [course_feature] The course feature. Possible options are at api/v1/course_features/ + * @param {Array} [delivery] The delivery options in which the learning resource is offered * `online` - Online * `hybrid` - Hybrid * `in_person` - In person * `offline` - Offline * @param {Array} [department] The department that offers the learning resource * `1` - Civil and Environmental Engineering * `2` - Mechanical Engineering * `3` - Materials Science and Engineering * `4` - Architecture * `5` - Chemistry * `6` - Electrical Engineering and Computer Science * `7` - Biology * `8` - Physics * `9` - Brain and Cognitive Sciences * `10` - Chemical Engineering * `11` - Urban Studies and Planning * `12` - Earth, Atmospheric, and Planetary Sciences * `14` - Economics * `15` - Management * `16` - Aeronautics and Astronautics * `17` - Political Science * `18` - Mathematics * `20` - Biological Engineering * `21A` - Anthropology * `21G` - Global Languages * `21H` - History * `21L` - Literature * `21M` - Music and Theater Arts * `22` - Nuclear Science and Engineering * `24` - Linguistics and Philosophy * `CC` - Concourse * `CMS-W` - Comparative Media Studies/Writing * `EC` - Edgerton Center * `ES` - Experimental Study Group * `ESD` - Engineering Systems Division * `HST` - Medical Engineering and Science * `IDS` - Data, Systems, and Society * `MAS` - Media Arts and Sciences * `PE` - Athletics, Physical Education and Recreation * `SP` - Special Programs * `STS` - Science, Technology, and Society * `WGS` - Women\'s and Gender Studies * @param {boolean | null} [dev_mode] If true return raw open search results with score explanations * @param {boolean | null} [free] @@ -12275,6 +12480,7 @@ export const LearningResourcesSearchApiAxiosParamCreator = function ( certification?: boolean | null, certification_type?: Array, course_feature?: Array, + delivery?: Array, department?: Array, dev_mode?: boolean | null, free?: boolean | null, @@ -12331,6 +12537,10 @@ export const LearningResourcesSearchApiAxiosParamCreator = function ( localVarQueryParameter["course_feature"] = course_feature } + if (delivery) { + localVarQueryParameter["delivery"] = delivery + } + if (department) { localVarQueryParameter["department"] = department } @@ -12455,6 +12665,7 @@ export const LearningResourcesSearchApiFp = function ( * @param {boolean | null} [certification] True if the learning resource offers a certificate * @param {Array} [certification_type] The type of certificate * `micromasters` - Micromasters Credential * `professional` - Professional Certificate * `completion` - Certificate of Completion * `none` - No Certificate * @param {Array} [course_feature] The course feature. Possible options are at api/v1/course_features/ + * @param {Array} [delivery] The delivery options in which the learning resource is offered * `online` - Online * `hybrid` - Hybrid * `in_person` - In person * `offline` - Offline * @param {Array} [department] The department that offers the learning resource * `1` - Civil and Environmental Engineering * `2` - Mechanical Engineering * `3` - Materials Science and Engineering * `4` - Architecture * `5` - Chemistry * `6` - Electrical Engineering and Computer Science * `7` - Biology * `8` - Physics * `9` - Brain and Cognitive Sciences * `10` - Chemical Engineering * `11` - Urban Studies and Planning * `12` - Earth, Atmospheric, and Planetary Sciences * `14` - Economics * `15` - Management * `16` - Aeronautics and Astronautics * `17` - Political Science * `18` - Mathematics * `20` - Biological Engineering * `21A` - Anthropology * `21G` - Global Languages * `21H` - History * `21L` - Literature * `21M` - Music and Theater Arts * `22` - Nuclear Science and Engineering * `24` - Linguistics and Philosophy * `CC` - Concourse * `CMS-W` - Comparative Media Studies/Writing * `EC` - Edgerton Center * `ES` - Experimental Study Group * `ESD` - Engineering Systems Division * `HST` - Medical Engineering and Science * `IDS` - Data, Systems, and Society * `MAS` - Media Arts and Sciences * `PE` - Athletics, Physical Education and Recreation * `SP` - Special Programs * `STS` - Science, Technology, and Society * `WGS` - Women\'s and Gender Studies * @param {boolean | null} [dev_mode] If true return raw open search results with score explanations * @param {boolean | null} [free] @@ -12485,6 +12696,7 @@ export const LearningResourcesSearchApiFp = function ( certification?: boolean | null, certification_type?: Array, course_feature?: Array, + delivery?: Array, department?: Array, dev_mode?: boolean | null, free?: boolean | null, @@ -12520,6 +12732,7 @@ export const LearningResourcesSearchApiFp = function ( certification, certification_type, course_feature, + delivery, department, dev_mode, free, @@ -12588,6 +12801,7 @@ export const LearningResourcesSearchApiFactory = function ( requestParameters.certification, requestParameters.certification_type, requestParameters.course_feature, + requestParameters.delivery, requestParameters.department, requestParameters.dev_mode, requestParameters.free, @@ -12625,7 +12839,7 @@ export const LearningResourcesSearchApiFactory = function ( export interface LearningResourcesSearchApiLearningResourcesSearchRetrieveRequest { /** * Show resource counts by category - * @type {Array<'resource_type' | 'certification' | 'certification_type' | 'offered_by' | 'platform' | 'topic' | 'department' | 'level' | 'course_feature' | 'professional' | 'free' | 'learning_format' | 'resource_category'>} + * @type {Array<'resource_type' | 'certification' | 'certification_type' | 'offered_by' | 'platform' | 'topic' | 'department' | 'level' | 'course_feature' | 'professional' | 'free' | 'learning_format' | 'delivery' | 'resource_category'>} * @memberof LearningResourcesSearchApiLearningResourcesSearchRetrieve */ readonly aggregations?: Array @@ -12651,6 +12865,13 @@ export interface LearningResourcesSearchApiLearningResourcesSearchRetrieveReques */ readonly course_feature?: Array + /** + * The delivery options in which the learning resource is offered * `online` - Online * `hybrid` - Hybrid * `in_person` - In person * `offline` - Offline + * @type {Array<'online' | 'hybrid' | 'in_person' | 'offline'>} + * @memberof LearningResourcesSearchApiLearningResourcesSearchRetrieve + */ + readonly delivery?: Array + /** * The department that offers the learning resource * `1` - Civil and Environmental Engineering * `2` - Mechanical Engineering * `3` - Materials Science and Engineering * `4` - Architecture * `5` - Chemistry * `6` - Electrical Engineering and Computer Science * `7` - Biology * `8` - Physics * `9` - Brain and Cognitive Sciences * `10` - Chemical Engineering * `11` - Urban Studies and Planning * `12` - Earth, Atmospheric, and Planetary Sciences * `14` - Economics * `15` - Management * `16` - Aeronautics and Astronautics * `17` - Political Science * `18` - Mathematics * `20` - Biological Engineering * `21A` - Anthropology * `21G` - Global Languages * `21H` - History * `21L` - Literature * `21M` - Music and Theater Arts * `22` - Nuclear Science and Engineering * `24` - Linguistics and Philosophy * `CC` - Concourse * `CMS-W` - Comparative Media Studies/Writing * `EC` - Edgerton Center * `ES` - Experimental Study Group * `ESD` - Engineering Systems Division * `HST` - Medical Engineering and Science * `IDS` - Data, Systems, and Society * `MAS` - Media Arts and Sciences * `PE` - Athletics, Physical Education and Recreation * `SP` - Special Programs * `STS` - Science, Technology, and Society * `WGS` - Women\'s and Gender Studies * @type {Array<'1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | '10' | '11' | '12' | '14' | '15' | '16' | '17' | '18' | '20' | '21A' | '21G' | '21H' | '21L' | '21M' | '22' | '24' | 'CC' | 'CMS-W' | 'EC' | 'ES' | 'ESD' | 'HST' | 'IDS' | 'MAS' | 'PE' | 'SP' | 'STS' | 'WGS'>} @@ -12831,6 +13052,7 @@ export class LearningResourcesSearchApi extends BaseAPI { requestParameters.certification, requestParameters.certification_type, requestParameters.course_feature, + requestParameters.delivery, requestParameters.department, requestParameters.dev_mode, requestParameters.free, @@ -12875,6 +13097,7 @@ export const LearningResourcesSearchRetrieveAggregationsEnum = { Professional: "professional", Free: "free", LearningFormat: "learning_format", + Delivery: "delivery", ResourceCategory: "resource_category", } as const export type LearningResourcesSearchRetrieveAggregationsEnum = @@ -12893,6 +13116,17 @@ export type LearningResourcesSearchRetrieveCertificationTypeEnum = /** * @export */ +export const LearningResourcesSearchRetrieveDeliveryEnum = { + Online: "online", + Hybrid: "hybrid", + InPerson: "in_person", + Offline: "offline", +} as const +export type LearningResourcesSearchRetrieveDeliveryEnum = + (typeof LearningResourcesSearchRetrieveDeliveryEnum)[keyof typeof LearningResourcesSearchRetrieveDeliveryEnum] +/** + * @export + */ export const LearningResourcesSearchRetrieveDepartmentEnum = { _1: "1", _2: "2", @@ -13068,6 +13302,7 @@ export const LearningResourcesUserSubscriptionApiAxiosParamCreator = function ( * @param {boolean | null} [certification] True if the learning resource offers a certificate * @param {Array} [certification_type] The type of certificate * `micromasters` - Micromasters Credential * `professional` - Professional Certificate * `completion` - Certificate of Completion * `none` - No Certificate * @param {Array} [course_feature] The course feature. Possible options are at api/v1/course_features/ + * @param {Array} [delivery] The delivery options in which the learning resource is offered * `online` - Online * `hybrid` - Hybrid * `in_person` - In person * `offline` - Offline * @param {Array} [department] The department that offers the learning resource * `1` - Civil and Environmental Engineering * `2` - Mechanical Engineering * `3` - Materials Science and Engineering * `4` - Architecture * `5` - Chemistry * `6` - Electrical Engineering and Computer Science * `7` - Biology * `8` - Physics * `9` - Brain and Cognitive Sciences * `10` - Chemical Engineering * `11` - Urban Studies and Planning * `12` - Earth, Atmospheric, and Planetary Sciences * `14` - Economics * `15` - Management * `16` - Aeronautics and Astronautics * `17` - Political Science * `18` - Mathematics * `20` - Biological Engineering * `21A` - Anthropology * `21G` - Global Languages * `21H` - History * `21L` - Literature * `21M` - Music and Theater Arts * `22` - Nuclear Science and Engineering * `24` - Linguistics and Philosophy * `CC` - Concourse * `CMS-W` - Comparative Media Studies/Writing * `EC` - Edgerton Center * `ES` - Experimental Study Group * `ESD` - Engineering Systems Division * `HST` - Medical Engineering and Science * `IDS` - Data, Systems, and Society * `MAS` - Media Arts and Sciences * `PE` - Athletics, Physical Education and Recreation * `SP` - Special Programs * `STS` - Science, Technology, and Society * `WGS` - Women\'s and Gender Studies * @param {boolean | null} [dev_mode] If true return raw open search results with score explanations * @param {boolean | null} [free] @@ -13099,6 +13334,7 @@ export const LearningResourcesUserSubscriptionApiAxiosParamCreator = function ( certification?: boolean | null, certification_type?: Array, course_feature?: Array, + delivery?: Array, department?: Array, dev_mode?: boolean | null, free?: boolean | null, @@ -13156,6 +13392,10 @@ export const LearningResourcesUserSubscriptionApiAxiosParamCreator = function ( localVarQueryParameter["course_feature"] = course_feature } + if (delivery) { + localVarQueryParameter["delivery"] = delivery + } + if (department) { localVarQueryParameter["department"] = department } @@ -13271,6 +13511,7 @@ export const LearningResourcesUserSubscriptionApiAxiosParamCreator = function ( * @param {boolean | null} [certification] True if the learning resource offers a certificate * @param {Array} [certification_type] The type of certificate * `micromasters` - Micromasters Credential * `professional` - Professional Certificate * `completion` - Certificate of Completion * `none` - No Certificate * @param {Array} [course_feature] The course feature. Possible options are at api/v1/course_features/ + * @param {Array} [delivery] The delivery options in which the learning resource is offered * `online` - Online * `hybrid` - Hybrid * `in_person` - In person * `offline` - Offline * @param {Array} [department] The department that offers the learning resource * `1` - Civil and Environmental Engineering * `2` - Mechanical Engineering * `3` - Materials Science and Engineering * `4` - Architecture * `5` - Chemistry * `6` - Electrical Engineering and Computer Science * `7` - Biology * `8` - Physics * `9` - Brain and Cognitive Sciences * `10` - Chemical Engineering * `11` - Urban Studies and Planning * `12` - Earth, Atmospheric, and Planetary Sciences * `14` - Economics * `15` - Management * `16` - Aeronautics and Astronautics * `17` - Political Science * `18` - Mathematics * `20` - Biological Engineering * `21A` - Anthropology * `21G` - Global Languages * `21H` - History * `21L` - Literature * `21M` - Music and Theater Arts * `22` - Nuclear Science and Engineering * `24` - Linguistics and Philosophy * `CC` - Concourse * `CMS-W` - Comparative Media Studies/Writing * `EC` - Edgerton Center * `ES` - Experimental Study Group * `ESD` - Engineering Systems Division * `HST` - Medical Engineering and Science * `IDS` - Data, Systems, and Society * `MAS` - Media Arts and Sciences * `PE` - Athletics, Physical Education and Recreation * `SP` - Special Programs * `STS` - Science, Technology, and Society * `WGS` - Women\'s and Gender Studies * @param {boolean | null} [dev_mode] If true return raw open search results with score explanations * @param {boolean | null} [free] @@ -13301,6 +13542,7 @@ export const LearningResourcesUserSubscriptionApiAxiosParamCreator = function ( certification?: boolean | null, certification_type?: Array, course_feature?: Array, + delivery?: Array, department?: Array, dev_mode?: boolean | null, free?: boolean | null, @@ -13357,6 +13599,10 @@ export const LearningResourcesUserSubscriptionApiAxiosParamCreator = function ( localVarQueryParameter["course_feature"] = course_feature } + if (delivery) { + localVarQueryParameter["delivery"] = delivery + } + if (department) { localVarQueryParameter["department"] = department } @@ -13468,6 +13714,7 @@ export const LearningResourcesUserSubscriptionApiAxiosParamCreator = function ( * @param {boolean | null} [certification] True if the learning resource offers a certificate * @param {Array} [certification_type] The type of certificate * `micromasters` - Micromasters Credential * `professional` - Professional Certificate * `completion` - Certificate of Completion * `none` - No Certificate * @param {Array} [course_feature] The course feature. Possible options are at api/v1/course_features/ + * @param {Array} [delivery] The delivery options in which the learning resource is offered * `online` - Online * `hybrid` - Hybrid * `in_person` - In person * `offline` - Offline * @param {Array} [department] The department that offers the learning resource * `1` - Civil and Environmental Engineering * `2` - Mechanical Engineering * `3` - Materials Science and Engineering * `4` - Architecture * `5` - Chemistry * `6` - Electrical Engineering and Computer Science * `7` - Biology * `8` - Physics * `9` - Brain and Cognitive Sciences * `10` - Chemical Engineering * `11` - Urban Studies and Planning * `12` - Earth, Atmospheric, and Planetary Sciences * `14` - Economics * `15` - Management * `16` - Aeronautics and Astronautics * `17` - Political Science * `18` - Mathematics * `20` - Biological Engineering * `21A` - Anthropology * `21G` - Global Languages * `21H` - History * `21L` - Literature * `21M` - Music and Theater Arts * `22` - Nuclear Science and Engineering * `24` - Linguistics and Philosophy * `CC` - Concourse * `CMS-W` - Comparative Media Studies/Writing * `EC` - Edgerton Center * `ES` - Experimental Study Group * `ESD` - Engineering Systems Division * `HST` - Medical Engineering and Science * `IDS` - Data, Systems, and Society * `MAS` - Media Arts and Sciences * `PE` - Athletics, Physical Education and Recreation * `SP` - Special Programs * `STS` - Science, Technology, and Society * `WGS` - Women\'s and Gender Studies * @param {boolean | null} [dev_mode] If true return raw open search results with score explanations * @param {boolean | null} [free] @@ -13500,6 +13747,7 @@ export const LearningResourcesUserSubscriptionApiAxiosParamCreator = function ( certification?: boolean | null, certification_type?: Array, course_feature?: Array, + delivery?: Array, department?: Array, dev_mode?: boolean | null, free?: boolean | null, @@ -13558,6 +13806,10 @@ export const LearningResourcesUserSubscriptionApiAxiosParamCreator = function ( localVarQueryParameter["course_feature"] = course_feature } + if (delivery) { + localVarQueryParameter["delivery"] = delivery + } + if (department) { localVarQueryParameter["department"] = department } @@ -13744,6 +13996,7 @@ export const LearningResourcesUserSubscriptionApiFp = function ( * @param {boolean | null} [certification] True if the learning resource offers a certificate * @param {Array} [certification_type] The type of certificate * `micromasters` - Micromasters Credential * `professional` - Professional Certificate * `completion` - Certificate of Completion * `none` - No Certificate * @param {Array} [course_feature] The course feature. Possible options are at api/v1/course_features/ + * @param {Array} [delivery] The delivery options in which the learning resource is offered * `online` - Online * `hybrid` - Hybrid * `in_person` - In person * `offline` - Offline * @param {Array} [department] The department that offers the learning resource * `1` - Civil and Environmental Engineering * `2` - Mechanical Engineering * `3` - Materials Science and Engineering * `4` - Architecture * `5` - Chemistry * `6` - Electrical Engineering and Computer Science * `7` - Biology * `8` - Physics * `9` - Brain and Cognitive Sciences * `10` - Chemical Engineering * `11` - Urban Studies and Planning * `12` - Earth, Atmospheric, and Planetary Sciences * `14` - Economics * `15` - Management * `16` - Aeronautics and Astronautics * `17` - Political Science * `18` - Mathematics * `20` - Biological Engineering * `21A` - Anthropology * `21G` - Global Languages * `21H` - History * `21L` - Literature * `21M` - Music and Theater Arts * `22` - Nuclear Science and Engineering * `24` - Linguistics and Philosophy * `CC` - Concourse * `CMS-W` - Comparative Media Studies/Writing * `EC` - Edgerton Center * `ES` - Experimental Study Group * `ESD` - Engineering Systems Division * `HST` - Medical Engineering and Science * `IDS` - Data, Systems, and Society * `MAS` - Media Arts and Sciences * `PE` - Athletics, Physical Education and Recreation * `SP` - Special Programs * `STS` - Science, Technology, and Society * `WGS` - Women\'s and Gender Studies * @param {boolean | null} [dev_mode] If true return raw open search results with score explanations * @param {boolean | null} [free] @@ -13775,6 +14028,7 @@ export const LearningResourcesUserSubscriptionApiFp = function ( certification?: boolean | null, certification_type?: Array, course_feature?: Array, + delivery?: Array, department?: Array, dev_mode?: boolean | null, free?: boolean | null, @@ -13811,6 +14065,7 @@ export const LearningResourcesUserSubscriptionApiFp = function ( certification, certification_type, course_feature, + delivery, department, dev_mode, free, @@ -13856,6 +14111,7 @@ export const LearningResourcesUserSubscriptionApiFp = function ( * @param {boolean | null} [certification] True if the learning resource offers a certificate * @param {Array} [certification_type] The type of certificate * `micromasters` - Micromasters Credential * `professional` - Professional Certificate * `completion` - Certificate of Completion * `none` - No Certificate * @param {Array} [course_feature] The course feature. Possible options are at api/v1/course_features/ + * @param {Array} [delivery] The delivery options in which the learning resource is offered * `online` - Online * `hybrid` - Hybrid * `in_person` - In person * `offline` - Offline * @param {Array} [department] The department that offers the learning resource * `1` - Civil and Environmental Engineering * `2` - Mechanical Engineering * `3` - Materials Science and Engineering * `4` - Architecture * `5` - Chemistry * `6` - Electrical Engineering and Computer Science * `7` - Biology * `8` - Physics * `9` - Brain and Cognitive Sciences * `10` - Chemical Engineering * `11` - Urban Studies and Planning * `12` - Earth, Atmospheric, and Planetary Sciences * `14` - Economics * `15` - Management * `16` - Aeronautics and Astronautics * `17` - Political Science * `18` - Mathematics * `20` - Biological Engineering * `21A` - Anthropology * `21G` - Global Languages * `21H` - History * `21L` - Literature * `21M` - Music and Theater Arts * `22` - Nuclear Science and Engineering * `24` - Linguistics and Philosophy * `CC` - Concourse * `CMS-W` - Comparative Media Studies/Writing * `EC` - Edgerton Center * `ES` - Experimental Study Group * `ESD` - Engineering Systems Division * `HST` - Medical Engineering and Science * `IDS` - Data, Systems, and Society * `MAS` - Media Arts and Sciences * `PE` - Athletics, Physical Education and Recreation * `SP` - Special Programs * `STS` - Science, Technology, and Society * `WGS` - Women\'s and Gender Studies * @param {boolean | null} [dev_mode] If true return raw open search results with score explanations * @param {boolean | null} [free] @@ -13886,6 +14142,7 @@ export const LearningResourcesUserSubscriptionApiFp = function ( certification?: boolean | null, certification_type?: Array, course_feature?: Array, + delivery?: Array, department?: Array, dev_mode?: boolean | null, free?: boolean | null, @@ -13921,6 +14178,7 @@ export const LearningResourcesUserSubscriptionApiFp = function ( certification, certification_type, course_feature, + delivery, department, dev_mode, free, @@ -13965,6 +14223,7 @@ export const LearningResourcesUserSubscriptionApiFp = function ( * @param {boolean | null} [certification] True if the learning resource offers a certificate * @param {Array} [certification_type] The type of certificate * `micromasters` - Micromasters Credential * `professional` - Professional Certificate * `completion` - Certificate of Completion * `none` - No Certificate * @param {Array} [course_feature] The course feature. Possible options are at api/v1/course_features/ + * @param {Array} [delivery] The delivery options in which the learning resource is offered * `online` - Online * `hybrid` - Hybrid * `in_person` - In person * `offline` - Offline * @param {Array} [department] The department that offers the learning resource * `1` - Civil and Environmental Engineering * `2` - Mechanical Engineering * `3` - Materials Science and Engineering * `4` - Architecture * `5` - Chemistry * `6` - Electrical Engineering and Computer Science * `7` - Biology * `8` - Physics * `9` - Brain and Cognitive Sciences * `10` - Chemical Engineering * `11` - Urban Studies and Planning * `12` - Earth, Atmospheric, and Planetary Sciences * `14` - Economics * `15` - Management * `16` - Aeronautics and Astronautics * `17` - Political Science * `18` - Mathematics * `20` - Biological Engineering * `21A` - Anthropology * `21G` - Global Languages * `21H` - History * `21L` - Literature * `21M` - Music and Theater Arts * `22` - Nuclear Science and Engineering * `24` - Linguistics and Philosophy * `CC` - Concourse * `CMS-W` - Comparative Media Studies/Writing * `EC` - Edgerton Center * `ES` - Experimental Study Group * `ESD` - Engineering Systems Division * `HST` - Medical Engineering and Science * `IDS` - Data, Systems, and Society * `MAS` - Media Arts and Sciences * `PE` - Athletics, Physical Education and Recreation * `SP` - Special Programs * `STS` - Science, Technology, and Society * `WGS` - Women\'s and Gender Studies * @param {boolean | null} [dev_mode] If true return raw open search results with score explanations * @param {boolean | null} [free] @@ -13997,6 +14256,7 @@ export const LearningResourcesUserSubscriptionApiFp = function ( certification?: boolean | null, certification_type?: Array, course_feature?: Array, + delivery?: Array, department?: Array, dev_mode?: boolean | null, free?: boolean | null, @@ -14031,6 +14291,7 @@ export const LearningResourcesUserSubscriptionApiFp = function ( certification, certification_type, course_feature, + delivery, department, dev_mode, free, @@ -14132,6 +14393,7 @@ export const LearningResourcesUserSubscriptionApiFactory = function ( requestParameters.certification, requestParameters.certification_type, requestParameters.course_feature, + requestParameters.delivery, requestParameters.department, requestParameters.dev_mode, requestParameters.free, @@ -14176,6 +14438,7 @@ export const LearningResourcesUserSubscriptionApiFactory = function ( requestParameters.certification, requestParameters.certification_type, requestParameters.course_feature, + requestParameters.delivery, requestParameters.department, requestParameters.dev_mode, requestParameters.free, @@ -14219,6 +14482,7 @@ export const LearningResourcesUserSubscriptionApiFactory = function ( requestParameters.certification, requestParameters.certification_type, requestParameters.course_feature, + requestParameters.delivery, requestParameters.department, requestParameters.dev_mode, requestParameters.free, @@ -14276,7 +14540,7 @@ export const LearningResourcesUserSubscriptionApiFactory = function ( export interface LearningResourcesUserSubscriptionApiLearningResourcesUserSubscriptionCheckListRequest { /** * Show resource counts by category - * @type {Array<'resource_type' | 'certification' | 'certification_type' | 'offered_by' | 'platform' | 'topic' | 'department' | 'level' | 'course_feature' | 'professional' | 'free' | 'learning_format' | 'resource_category'>} + * @type {Array<'resource_type' | 'certification' | 'certification_type' | 'offered_by' | 'platform' | 'topic' | 'department' | 'level' | 'course_feature' | 'professional' | 'free' | 'learning_format' | 'delivery' | 'resource_category'>} * @memberof LearningResourcesUserSubscriptionApiLearningResourcesUserSubscriptionCheckList */ readonly aggregations?: Array @@ -14302,6 +14566,13 @@ export interface LearningResourcesUserSubscriptionApiLearningResourcesUserSubscr */ readonly course_feature?: Array + /** + * The delivery options in which the learning resource is offered * `online` - Online * `hybrid` - Hybrid * `in_person` - In person * `offline` - Offline + * @type {Array<'online' | 'hybrid' | 'in_person' | 'offline'>} + * @memberof LearningResourcesUserSubscriptionApiLearningResourcesUserSubscriptionCheckList + */ + readonly delivery?: Array + /** * The department that offers the learning resource * `1` - Civil and Environmental Engineering * `2` - Mechanical Engineering * `3` - Materials Science and Engineering * `4` - Architecture * `5` - Chemistry * `6` - Electrical Engineering and Computer Science * `7` - Biology * `8` - Physics * `9` - Brain and Cognitive Sciences * `10` - Chemical Engineering * `11` - Urban Studies and Planning * `12` - Earth, Atmospheric, and Planetary Sciences * `14` - Economics * `15` - Management * `16` - Aeronautics and Astronautics * `17` - Political Science * `18` - Mathematics * `20` - Biological Engineering * `21A` - Anthropology * `21G` - Global Languages * `21H` - History * `21L` - Literature * `21M` - Music and Theater Arts * `22` - Nuclear Science and Engineering * `24` - Linguistics and Philosophy * `CC` - Concourse * `CMS-W` - Comparative Media Studies/Writing * `EC` - Edgerton Center * `ES` - Experimental Study Group * `ESD` - Engineering Systems Division * `HST` - Medical Engineering and Science * `IDS` - Data, Systems, and Society * `MAS` - Media Arts and Sciences * `PE` - Athletics, Physical Education and Recreation * `SP` - Special Programs * `STS` - Science, Technology, and Society * `WGS` - Women\'s and Gender Studies * @type {Array<'1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | '10' | '11' | '12' | '14' | '15' | '16' | '17' | '18' | '20' | '21A' | '21G' | '21H' | '21L' | '21M' | '22' | '24' | 'CC' | 'CMS-W' | 'EC' | 'ES' | 'ESD' | 'HST' | 'IDS' | 'MAS' | 'PE' | 'SP' | 'STS' | 'WGS'>} @@ -14472,7 +14743,7 @@ export interface LearningResourcesUserSubscriptionApiLearningResourcesUserSubscr export interface LearningResourcesUserSubscriptionApiLearningResourcesUserSubscriptionListRequest { /** * Show resource counts by category - * @type {Array<'resource_type' | 'certification' | 'certification_type' | 'offered_by' | 'platform' | 'topic' | 'department' | 'level' | 'course_feature' | 'professional' | 'free' | 'learning_format' | 'resource_category'>} + * @type {Array<'resource_type' | 'certification' | 'certification_type' | 'offered_by' | 'platform' | 'topic' | 'department' | 'level' | 'course_feature' | 'professional' | 'free' | 'learning_format' | 'delivery' | 'resource_category'>} * @memberof LearningResourcesUserSubscriptionApiLearningResourcesUserSubscriptionList */ readonly aggregations?: Array @@ -14498,6 +14769,13 @@ export interface LearningResourcesUserSubscriptionApiLearningResourcesUserSubscr */ readonly course_feature?: Array + /** + * The delivery options in which the learning resource is offered * `online` - Online * `hybrid` - Hybrid * `in_person` - In person * `offline` - Offline + * @type {Array<'online' | 'hybrid' | 'in_person' | 'offline'>} + * @memberof LearningResourcesUserSubscriptionApiLearningResourcesUserSubscriptionList + */ + readonly delivery?: Array + /** * The department that offers the learning resource * `1` - Civil and Environmental Engineering * `2` - Mechanical Engineering * `3` - Materials Science and Engineering * `4` - Architecture * `5` - Chemistry * `6` - Electrical Engineering and Computer Science * `7` - Biology * `8` - Physics * `9` - Brain and Cognitive Sciences * `10` - Chemical Engineering * `11` - Urban Studies and Planning * `12` - Earth, Atmospheric, and Planetary Sciences * `14` - Economics * `15` - Management * `16` - Aeronautics and Astronautics * `17` - Political Science * `18` - Mathematics * `20` - Biological Engineering * `21A` - Anthropology * `21G` - Global Languages * `21H` - History * `21L` - Literature * `21M` - Music and Theater Arts * `22` - Nuclear Science and Engineering * `24` - Linguistics and Philosophy * `CC` - Concourse * `CMS-W` - Comparative Media Studies/Writing * `EC` - Edgerton Center * `ES` - Experimental Study Group * `ESD` - Engineering Systems Division * `HST` - Medical Engineering and Science * `IDS` - Data, Systems, and Society * `MAS` - Media Arts and Sciences * `PE` - Athletics, Physical Education and Recreation * `SP` - Special Programs * `STS` - Science, Technology, and Society * `WGS` - Women\'s and Gender Studies * @type {Array<'1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | '10' | '11' | '12' | '14' | '15' | '16' | '17' | '18' | '20' | '21A' | '21G' | '21H' | '21L' | '21M' | '22' | '24' | 'CC' | 'CMS-W' | 'EC' | 'ES' | 'ESD' | 'HST' | 'IDS' | 'MAS' | 'PE' | 'SP' | 'STS' | 'WGS'>} @@ -14661,7 +14939,7 @@ export interface LearningResourcesUserSubscriptionApiLearningResourcesUserSubscr export interface LearningResourcesUserSubscriptionApiLearningResourcesUserSubscriptionSubscribeCreateRequest { /** * Show resource counts by category - * @type {Array<'resource_type' | 'certification' | 'certification_type' | 'offered_by' | 'platform' | 'topic' | 'department' | 'level' | 'course_feature' | 'professional' | 'free' | 'learning_format' | 'resource_category'>} + * @type {Array<'resource_type' | 'certification' | 'certification_type' | 'offered_by' | 'platform' | 'topic' | 'department' | 'level' | 'course_feature' | 'professional' | 'free' | 'learning_format' | 'delivery' | 'resource_category'>} * @memberof LearningResourcesUserSubscriptionApiLearningResourcesUserSubscriptionSubscribeCreate */ readonly aggregations?: Array @@ -14687,6 +14965,13 @@ export interface LearningResourcesUserSubscriptionApiLearningResourcesUserSubscr */ readonly course_feature?: Array + /** + * The delivery options in which the learning resource is offered * `online` - Online * `hybrid` - Hybrid * `in_person` - In person * `offline` - Offline + * @type {Array<'online' | 'hybrid' | 'in_person' | 'offline'>} + * @memberof LearningResourcesUserSubscriptionApiLearningResourcesUserSubscriptionSubscribeCreate + */ + readonly delivery?: Array + /** * The department that offers the learning resource * `1` - Civil and Environmental Engineering * `2` - Mechanical Engineering * `3` - Materials Science and Engineering * `4` - Architecture * `5` - Chemistry * `6` - Electrical Engineering and Computer Science * `7` - Biology * `8` - Physics * `9` - Brain and Cognitive Sciences * `10` - Chemical Engineering * `11` - Urban Studies and Planning * `12` - Earth, Atmospheric, and Planetary Sciences * `14` - Economics * `15` - Management * `16` - Aeronautics and Astronautics * `17` - Political Science * `18` - Mathematics * `20` - Biological Engineering * `21A` - Anthropology * `21G` - Global Languages * `21H` - History * `21L` - Literature * `21M` - Music and Theater Arts * `22` - Nuclear Science and Engineering * `24` - Linguistics and Philosophy * `CC` - Concourse * `CMS-W` - Comparative Media Studies/Writing * `EC` - Edgerton Center * `ES` - Experimental Study Group * `ESD` - Engineering Systems Division * `HST` - Medical Engineering and Science * `IDS` - Data, Systems, and Society * `MAS` - Media Arts and Sciences * `PE` - Athletics, Physical Education and Recreation * `SP` - Special Programs * `STS` - Science, Technology, and Society * `WGS` - Women\'s and Gender Studies * @type {Array<'1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | '10' | '11' | '12' | '14' | '15' | '16' | '17' | '18' | '20' | '21A' | '21G' | '21H' | '21L' | '21M' | '22' | '24' | 'CC' | 'CMS-W' | 'EC' | 'ES' | 'ESD' | 'HST' | 'IDS' | 'MAS' | 'PE' | 'SP' | 'STS' | 'WGS'>} @@ -14895,6 +15180,7 @@ export class LearningResourcesUserSubscriptionApi extends BaseAPI { requestParameters.certification, requestParameters.certification_type, requestParameters.course_feature, + requestParameters.delivery, requestParameters.department, requestParameters.dev_mode, requestParameters.free, @@ -14941,6 +15227,7 @@ export class LearningResourcesUserSubscriptionApi extends BaseAPI { requestParameters.certification, requestParameters.certification_type, requestParameters.course_feature, + requestParameters.delivery, requestParameters.department, requestParameters.dev_mode, requestParameters.free, @@ -14986,6 +15273,7 @@ export class LearningResourcesUserSubscriptionApi extends BaseAPI { requestParameters.certification, requestParameters.certification_type, requestParameters.course_feature, + requestParameters.delivery, requestParameters.department, requestParameters.dev_mode, requestParameters.free, @@ -15052,6 +15340,7 @@ export const LearningResourcesUserSubscriptionCheckListAggregationsEnum = { Professional: "professional", Free: "free", LearningFormat: "learning_format", + Delivery: "delivery", ResourceCategory: "resource_category", } as const export type LearningResourcesUserSubscriptionCheckListAggregationsEnum = @@ -15070,6 +15359,17 @@ export type LearningResourcesUserSubscriptionCheckListCertificationTypeEnum = /** * @export */ +export const LearningResourcesUserSubscriptionCheckListDeliveryEnum = { + Online: "online", + Hybrid: "hybrid", + InPerson: "in_person", + Offline: "offline", +} as const +export type LearningResourcesUserSubscriptionCheckListDeliveryEnum = + (typeof LearningResourcesUserSubscriptionCheckListDeliveryEnum)[keyof typeof LearningResourcesUserSubscriptionCheckListDeliveryEnum] +/** + * @export + */ export const LearningResourcesUserSubscriptionCheckListDepartmentEnum = { _1: "1", _2: "2", @@ -15254,6 +15554,7 @@ export const LearningResourcesUserSubscriptionListAggregationsEnum = { Professional: "professional", Free: "free", LearningFormat: "learning_format", + Delivery: "delivery", ResourceCategory: "resource_category", } as const export type LearningResourcesUserSubscriptionListAggregationsEnum = @@ -15272,6 +15573,17 @@ export type LearningResourcesUserSubscriptionListCertificationTypeEnum = /** * @export */ +export const LearningResourcesUserSubscriptionListDeliveryEnum = { + Online: "online", + Hybrid: "hybrid", + InPerson: "in_person", + Offline: "offline", +} as const +export type LearningResourcesUserSubscriptionListDeliveryEnum = + (typeof LearningResourcesUserSubscriptionListDeliveryEnum)[keyof typeof LearningResourcesUserSubscriptionListDeliveryEnum] +/** + * @export + */ export const LearningResourcesUserSubscriptionListDepartmentEnum = { _1: "1", _2: "2", @@ -15448,6 +15760,7 @@ export const LearningResourcesUserSubscriptionSubscribeCreateAggregationsEnum = Professional: "professional", Free: "free", LearningFormat: "learning_format", + Delivery: "delivery", ResourceCategory: "resource_category", } as const export type LearningResourcesUserSubscriptionSubscribeCreateAggregationsEnum = @@ -15467,6 +15780,17 @@ export type LearningResourcesUserSubscriptionSubscribeCreateCertificationTypeEnu /** * @export */ +export const LearningResourcesUserSubscriptionSubscribeCreateDeliveryEnum = { + Online: "online", + Hybrid: "hybrid", + InPerson: "in_person", + Offline: "offline", +} as const +export type LearningResourcesUserSubscriptionSubscribeCreateDeliveryEnum = + (typeof LearningResourcesUserSubscriptionSubscribeCreateDeliveryEnum)[keyof typeof LearningResourcesUserSubscriptionSubscribeCreateDeliveryEnum] +/** + * @export + */ export const LearningResourcesUserSubscriptionSubscribeCreateDepartmentEnum = { _1: "1", _2: "2", @@ -16068,6 +16392,7 @@ export const LearningpathsApiAxiosParamCreator = function ( * @param {boolean} [certification] * @param {Array} [certification_type] The type of certification offered * `micromasters` - Micromasters Credential * `professional` - Professional Certificate * `completion` - Certificate of Completion * `none` - No Certificate * @param {Array} [course_feature] Multiple values may be separated by commas. + * @param {Array>} [delivery] The delivery of course/program resources * `online` - Online * `hybrid` - Hybrid * `in_person` - In person * `offline` - Offline * @param {Array} [department] The department that offers learning resources * `1` - Civil and Environmental Engineering * `2` - Mechanical Engineering * `3` - Materials Science and Engineering * `4` - Architecture * `5` - Chemistry * `6` - Electrical Engineering and Computer Science * `7` - Biology * `8` - Physics * `9` - Brain and Cognitive Sciences * `10` - Chemical Engineering * `11` - Urban Studies and Planning * `12` - Earth, Atmospheric, and Planetary Sciences * `14` - Economics * `15` - Management * `16` - Aeronautics and Astronautics * `17` - Political Science * `18` - Mathematics * `20` - Biological Engineering * `21A` - Anthropology * `21G` - Global Languages * `21H` - History * `21L` - Literature * `21M` - Music and Theater Arts * `22` - Nuclear Science and Engineering * `24` - Linguistics and Philosophy * `CC` - Concourse * `CMS-W` - Comparative Media Studies/Writing * `EC` - Edgerton Center * `ES` - Experimental Study Group * `ESD` - Engineering Systems Division * `HST` - Medical Engineering and Science * `IDS` - Data, Systems, and Society * `MAS` - Media Arts and Sciences * `PE` - Athletics, Physical Education and Recreation * `SP` - Special Programs * `STS` - Science, Technology, and Society * `WGS` - Women\'s and Gender Studies * @param {boolean} [free] The course/program is offered for free * @param {Array>} [learning_format] The learning format of course/program resources * `online` - Online * `hybrid` - Hybrid * `in_person` - In person @@ -16089,6 +16414,7 @@ export const LearningpathsApiAxiosParamCreator = function ( certification?: boolean, certification_type?: Array, course_feature?: Array, + delivery?: Array>, department?: Array, free?: boolean, learning_format?: Array>, @@ -16135,6 +16461,10 @@ export const LearningpathsApiAxiosParamCreator = function ( ) } + if (delivery) { + localVarQueryParameter["delivery"] = delivery + } + if (department) { localVarQueryParameter["department"] = department } @@ -16573,6 +16903,7 @@ export const LearningpathsApiFp = function (configuration?: Configuration) { * @param {boolean} [certification] * @param {Array} [certification_type] The type of certification offered * `micromasters` - Micromasters Credential * `professional` - Professional Certificate * `completion` - Certificate of Completion * `none` - No Certificate * @param {Array} [course_feature] Multiple values may be separated by commas. + * @param {Array>} [delivery] The delivery of course/program resources * `online` - Online * `hybrid` - Hybrid * `in_person` - In person * `offline` - Offline * @param {Array} [department] The department that offers learning resources * `1` - Civil and Environmental Engineering * `2` - Mechanical Engineering * `3` - Materials Science and Engineering * `4` - Architecture * `5` - Chemistry * `6` - Electrical Engineering and Computer Science * `7` - Biology * `8` - Physics * `9` - Brain and Cognitive Sciences * `10` - Chemical Engineering * `11` - Urban Studies and Planning * `12` - Earth, Atmospheric, and Planetary Sciences * `14` - Economics * `15` - Management * `16` - Aeronautics and Astronautics * `17` - Political Science * `18` - Mathematics * `20` - Biological Engineering * `21A` - Anthropology * `21G` - Global Languages * `21H` - History * `21L` - Literature * `21M` - Music and Theater Arts * `22` - Nuclear Science and Engineering * `24` - Linguistics and Philosophy * `CC` - Concourse * `CMS-W` - Comparative Media Studies/Writing * `EC` - Edgerton Center * `ES` - Experimental Study Group * `ESD` - Engineering Systems Division * `HST` - Medical Engineering and Science * `IDS` - Data, Systems, and Society * `MAS` - Media Arts and Sciences * `PE` - Athletics, Physical Education and Recreation * `SP` - Special Programs * `STS` - Science, Technology, and Society * `WGS` - Women\'s and Gender Studies * @param {boolean} [free] The course/program is offered for free * @param {Array>} [learning_format] The learning format of course/program resources * `online` - Online * `hybrid` - Hybrid * `in_person` - In person @@ -16594,6 +16925,7 @@ export const LearningpathsApiFp = function (configuration?: Configuration) { certification?: boolean, certification_type?: Array, course_feature?: Array, + delivery?: Array>, department?: Array, free?: boolean, learning_format?: Array>, @@ -16620,6 +16952,7 @@ export const LearningpathsApiFp = function (configuration?: Configuration) { certification, certification_type, course_feature, + delivery, department, free, learning_format, @@ -16875,6 +17208,7 @@ export const LearningpathsApiFactory = function ( requestParameters.certification, requestParameters.certification_type, requestParameters.course_feature, + requestParameters.delivery, requestParameters.department, requestParameters.free, requestParameters.learning_format, @@ -17111,6 +17445,13 @@ export interface LearningpathsApiLearningpathsListRequest { */ readonly course_feature?: Array + /** + * The delivery of course/program resources * `online` - Online * `hybrid` - Hybrid * `in_person` - In person * `offline` - Offline + * @type {Array>} + * @memberof LearningpathsApiLearningpathsList + */ + readonly delivery?: Array> + /** * The department that offers learning resources * `1` - Civil and Environmental Engineering * `2` - Mechanical Engineering * `3` - Materials Science and Engineering * `4` - Architecture * `5` - Chemistry * `6` - Electrical Engineering and Computer Science * `7` - Biology * `8` - Physics * `9` - Brain and Cognitive Sciences * `10` - Chemical Engineering * `11` - Urban Studies and Planning * `12` - Earth, Atmospheric, and Planetary Sciences * `14` - Economics * `15` - Management * `16` - Aeronautics and Astronautics * `17` - Political Science * `18` - Mathematics * `20` - Biological Engineering * `21A` - Anthropology * `21G` - Global Languages * `21H` - History * `21L` - Literature * `21M` - Music and Theater Arts * `22` - Nuclear Science and Engineering * `24` - Linguistics and Philosophy * `CC` - Concourse * `CMS-W` - Comparative Media Studies/Writing * `EC` - Edgerton Center * `ES` - Experimental Study Group * `ESD` - Engineering Systems Division * `HST` - Medical Engineering and Science * `IDS` - Data, Systems, and Society * `MAS` - Media Arts and Sciences * `PE` - Athletics, Physical Education and Recreation * `SP` - Special Programs * `STS` - Science, Technology, and Society * `WGS` - Women\'s and Gender Studies * @type {Array<'1' | '10' | '11' | '12' | '14' | '15' | '16' | '17' | '18' | '2' | '20' | '21A' | '21G' | '21H' | '21L' | '21M' | '22' | '24' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | 'CC' | 'CMS-W' | 'EC' | 'ES' | 'ESD' | 'HST' | 'IDS' | 'MAS' | 'PE' | 'SP' | 'STS' | 'WGS'>} @@ -17414,6 +17755,7 @@ export class LearningpathsApi extends BaseAPI { requestParameters.certification, requestParameters.certification_type, requestParameters.course_feature, + requestParameters.delivery, requestParameters.department, requestParameters.free, requestParameters.learning_format, @@ -17486,6 +17828,17 @@ export type LearningpathsListCertificationTypeEnum = /** * @export */ +export const LearningpathsListDeliveryEnum = { + Online: "online", + Hybrid: "hybrid", + InPerson: "in_person", + Offline: "offline", +} as const +export type LearningpathsListDeliveryEnum = + (typeof LearningpathsListDeliveryEnum)[keyof typeof LearningpathsListDeliveryEnum] +/** + * @export + */ export const LearningpathsListDepartmentEnum = { _1: "1", _10: "10", @@ -18251,6 +18604,7 @@ export const PodcastEpisodesApiAxiosParamCreator = function ( * @param {boolean} [certification] * @param {Array} [certification_type] The type of certification offered * `micromasters` - Micromasters Credential * `professional` - Professional Certificate * `completion` - Certificate of Completion * `none` - No Certificate * @param {Array} [course_feature] Multiple values may be separated by commas. + * @param {Array>} [delivery] The delivery of course/program resources * `online` - Online * `hybrid` - Hybrid * `in_person` - In person * `offline` - Offline * @param {Array} [department] The department that offers learning resources * `1` - Civil and Environmental Engineering * `2` - Mechanical Engineering * `3` - Materials Science and Engineering * `4` - Architecture * `5` - Chemistry * `6` - Electrical Engineering and Computer Science * `7` - Biology * `8` - Physics * `9` - Brain and Cognitive Sciences * `10` - Chemical Engineering * `11` - Urban Studies and Planning * `12` - Earth, Atmospheric, and Planetary Sciences * `14` - Economics * `15` - Management * `16` - Aeronautics and Astronautics * `17` - Political Science * `18` - Mathematics * `20` - Biological Engineering * `21A` - Anthropology * `21G` - Global Languages * `21H` - History * `21L` - Literature * `21M` - Music and Theater Arts * `22` - Nuclear Science and Engineering * `24` - Linguistics and Philosophy * `CC` - Concourse * `CMS-W` - Comparative Media Studies/Writing * `EC` - Edgerton Center * `ES` - Experimental Study Group * `ESD` - Engineering Systems Division * `HST` - Medical Engineering and Science * `IDS` - Data, Systems, and Society * `MAS` - Media Arts and Sciences * `PE` - Athletics, Physical Education and Recreation * `SP` - Special Programs * `STS` - Science, Technology, and Society * `WGS` - Women\'s and Gender Studies * @param {boolean} [free] The course/program is offered for free * @param {Array>} [learning_format] The learning format of course/program resources * `online` - Online * `hybrid` - Hybrid * `in_person` - In person @@ -18272,6 +18626,7 @@ export const PodcastEpisodesApiAxiosParamCreator = function ( certification?: boolean, certification_type?: Array, course_feature?: Array, + delivery?: Array>, department?: Array, free?: boolean, learning_format?: Array>, @@ -18318,6 +18673,10 @@ export const PodcastEpisodesApiAxiosParamCreator = function ( ) } + if (delivery) { + localVarQueryParameter["delivery"] = delivery + } + if (department) { localVarQueryParameter["department"] = department } @@ -18453,6 +18812,7 @@ export const PodcastEpisodesApiFp = function (configuration?: Configuration) { * @param {boolean} [certification] * @param {Array} [certification_type] The type of certification offered * `micromasters` - Micromasters Credential * `professional` - Professional Certificate * `completion` - Certificate of Completion * `none` - No Certificate * @param {Array} [course_feature] Multiple values may be separated by commas. + * @param {Array>} [delivery] The delivery of course/program resources * `online` - Online * `hybrid` - Hybrid * `in_person` - In person * `offline` - Offline * @param {Array} [department] The department that offers learning resources * `1` - Civil and Environmental Engineering * `2` - Mechanical Engineering * `3` - Materials Science and Engineering * `4` - Architecture * `5` - Chemistry * `6` - Electrical Engineering and Computer Science * `7` - Biology * `8` - Physics * `9` - Brain and Cognitive Sciences * `10` - Chemical Engineering * `11` - Urban Studies and Planning * `12` - Earth, Atmospheric, and Planetary Sciences * `14` - Economics * `15` - Management * `16` - Aeronautics and Astronautics * `17` - Political Science * `18` - Mathematics * `20` - Biological Engineering * `21A` - Anthropology * `21G` - Global Languages * `21H` - History * `21L` - Literature * `21M` - Music and Theater Arts * `22` - Nuclear Science and Engineering * `24` - Linguistics and Philosophy * `CC` - Concourse * `CMS-W` - Comparative Media Studies/Writing * `EC` - Edgerton Center * `ES` - Experimental Study Group * `ESD` - Engineering Systems Division * `HST` - Medical Engineering and Science * `IDS` - Data, Systems, and Society * `MAS` - Media Arts and Sciences * `PE` - Athletics, Physical Education and Recreation * `SP` - Special Programs * `STS` - Science, Technology, and Society * `WGS` - Women\'s and Gender Studies * @param {boolean} [free] The course/program is offered for free * @param {Array>} [learning_format] The learning format of course/program resources * `online` - Online * `hybrid` - Hybrid * `in_person` - In person @@ -18474,6 +18834,7 @@ export const PodcastEpisodesApiFp = function (configuration?: Configuration) { certification?: boolean, certification_type?: Array, course_feature?: Array, + delivery?: Array>, department?: Array, free?: boolean, learning_format?: Array>, @@ -18500,6 +18861,7 @@ export const PodcastEpisodesApiFp = function (configuration?: Configuration) { certification, certification_type, course_feature, + delivery, department, free, learning_format, @@ -18589,6 +18951,7 @@ export const PodcastEpisodesApiFactory = function ( requestParameters.certification, requestParameters.certification_type, requestParameters.course_feature, + requestParameters.delivery, requestParameters.department, requestParameters.free, requestParameters.learning_format, @@ -18652,6 +19015,13 @@ export interface PodcastEpisodesApiPodcastEpisodesListRequest { */ readonly course_feature?: Array + /** + * The delivery of course/program resources * `online` - Online * `hybrid` - Hybrid * `in_person` - In person * `offline` - Offline + * @type {Array>} + * @memberof PodcastEpisodesApiPodcastEpisodesList + */ + readonly delivery?: Array> + /** * The department that offers learning resources * `1` - Civil and Environmental Engineering * `2` - Mechanical Engineering * `3` - Materials Science and Engineering * `4` - Architecture * `5` - Chemistry * `6` - Electrical Engineering and Computer Science * `7` - Biology * `8` - Physics * `9` - Brain and Cognitive Sciences * `10` - Chemical Engineering * `11` - Urban Studies and Planning * `12` - Earth, Atmospheric, and Planetary Sciences * `14` - Economics * `15` - Management * `16` - Aeronautics and Astronautics * `17` - Political Science * `18` - Mathematics * `20` - Biological Engineering * `21A` - Anthropology * `21G` - Global Languages * `21H` - History * `21L` - Literature * `21M` - Music and Theater Arts * `22` - Nuclear Science and Engineering * `24` - Linguistics and Philosophy * `CC` - Concourse * `CMS-W` - Comparative Media Studies/Writing * `EC` - Edgerton Center * `ES` - Experimental Study Group * `ESD` - Engineering Systems Division * `HST` - Medical Engineering and Science * `IDS` - Data, Systems, and Society * `MAS` - Media Arts and Sciences * `PE` - Athletics, Physical Education and Recreation * `SP` - Special Programs * `STS` - Science, Technology, and Society * `WGS` - Women\'s and Gender Studies * @type {Array<'1' | '10' | '11' | '12' | '14' | '15' | '16' | '17' | '18' | '2' | '20' | '21A' | '21G' | '21H' | '21L' | '21M' | '22' | '24' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | 'CC' | 'CMS-W' | 'EC' | 'ES' | 'ESD' | 'HST' | 'IDS' | 'MAS' | 'PE' | 'SP' | 'STS' | 'WGS'>} @@ -18789,6 +19159,7 @@ export class PodcastEpisodesApi extends BaseAPI { requestParameters.certification, requestParameters.certification_type, requestParameters.course_feature, + requestParameters.delivery, requestParameters.department, requestParameters.free, requestParameters.learning_format, @@ -18840,6 +19211,17 @@ export type PodcastEpisodesListCertificationTypeEnum = /** * @export */ +export const PodcastEpisodesListDeliveryEnum = { + Online: "online", + Hybrid: "hybrid", + InPerson: "in_person", + Offline: "offline", +} as const +export type PodcastEpisodesListDeliveryEnum = + (typeof PodcastEpisodesListDeliveryEnum)[keyof typeof PodcastEpisodesListDeliveryEnum] +/** + * @export + */ export const PodcastEpisodesListDepartmentEnum = { _1: "1", _10: "10", @@ -19118,6 +19500,7 @@ export const PodcastsApiAxiosParamCreator = function ( * @param {boolean} [certification] * @param {Array} [certification_type] The type of certification offered * `micromasters` - Micromasters Credential * `professional` - Professional Certificate * `completion` - Certificate of Completion * `none` - No Certificate * @param {Array} [course_feature] Multiple values may be separated by commas. + * @param {Array>} [delivery] The delivery of course/program resources * `online` - Online * `hybrid` - Hybrid * `in_person` - In person * `offline` - Offline * @param {Array} [department] The department that offers learning resources * `1` - Civil and Environmental Engineering * `2` - Mechanical Engineering * `3` - Materials Science and Engineering * `4` - Architecture * `5` - Chemistry * `6` - Electrical Engineering and Computer Science * `7` - Biology * `8` - Physics * `9` - Brain and Cognitive Sciences * `10` - Chemical Engineering * `11` - Urban Studies and Planning * `12` - Earth, Atmospheric, and Planetary Sciences * `14` - Economics * `15` - Management * `16` - Aeronautics and Astronautics * `17` - Political Science * `18` - Mathematics * `20` - Biological Engineering * `21A` - Anthropology * `21G` - Global Languages * `21H` - History * `21L` - Literature * `21M` - Music and Theater Arts * `22` - Nuclear Science and Engineering * `24` - Linguistics and Philosophy * `CC` - Concourse * `CMS-W` - Comparative Media Studies/Writing * `EC` - Edgerton Center * `ES` - Experimental Study Group * `ESD` - Engineering Systems Division * `HST` - Medical Engineering and Science * `IDS` - Data, Systems, and Society * `MAS` - Media Arts and Sciences * `PE` - Athletics, Physical Education and Recreation * `SP` - Special Programs * `STS` - Science, Technology, and Society * `WGS` - Women\'s and Gender Studies * @param {boolean} [free] The course/program is offered for free * @param {Array>} [learning_format] The learning format of course/program resources * `online` - Online * `hybrid` - Hybrid * `in_person` - In person @@ -19139,6 +19522,7 @@ export const PodcastsApiAxiosParamCreator = function ( certification?: boolean, certification_type?: Array, course_feature?: Array, + delivery?: Array>, department?: Array, free?: boolean, learning_format?: Array>, @@ -19185,6 +19569,10 @@ export const PodcastsApiAxiosParamCreator = function ( ) } + if (delivery) { + localVarQueryParameter["delivery"] = delivery + } + if (department) { localVarQueryParameter["department"] = department } @@ -19395,6 +19783,7 @@ export const PodcastsApiFp = function (configuration?: Configuration) { * @param {boolean} [certification] * @param {Array} [certification_type] The type of certification offered * `micromasters` - Micromasters Credential * `professional` - Professional Certificate * `completion` - Certificate of Completion * `none` - No Certificate * @param {Array} [course_feature] Multiple values may be separated by commas. + * @param {Array>} [delivery] The delivery of course/program resources * `online` - Online * `hybrid` - Hybrid * `in_person` - In person * `offline` - Offline * @param {Array} [department] The department that offers learning resources * `1` - Civil and Environmental Engineering * `2` - Mechanical Engineering * `3` - Materials Science and Engineering * `4` - Architecture * `5` - Chemistry * `6` - Electrical Engineering and Computer Science * `7` - Biology * `8` - Physics * `9` - Brain and Cognitive Sciences * `10` - Chemical Engineering * `11` - Urban Studies and Planning * `12` - Earth, Atmospheric, and Planetary Sciences * `14` - Economics * `15` - Management * `16` - Aeronautics and Astronautics * `17` - Political Science * `18` - Mathematics * `20` - Biological Engineering * `21A` - Anthropology * `21G` - Global Languages * `21H` - History * `21L` - Literature * `21M` - Music and Theater Arts * `22` - Nuclear Science and Engineering * `24` - Linguistics and Philosophy * `CC` - Concourse * `CMS-W` - Comparative Media Studies/Writing * `EC` - Edgerton Center * `ES` - Experimental Study Group * `ESD` - Engineering Systems Division * `HST` - Medical Engineering and Science * `IDS` - Data, Systems, and Society * `MAS` - Media Arts and Sciences * `PE` - Athletics, Physical Education and Recreation * `SP` - Special Programs * `STS` - Science, Technology, and Society * `WGS` - Women\'s and Gender Studies * @param {boolean} [free] The course/program is offered for free * @param {Array>} [learning_format] The learning format of course/program resources * `online` - Online * `hybrid` - Hybrid * `in_person` - In person @@ -19416,6 +19805,7 @@ export const PodcastsApiFp = function (configuration?: Configuration) { certification?: boolean, certification_type?: Array, course_feature?: Array, + delivery?: Array>, department?: Array, free?: boolean, learning_format?: Array>, @@ -19441,6 +19831,7 @@ export const PodcastsApiFp = function (configuration?: Configuration) { certification, certification_type, course_feature, + delivery, department, free, learning_format, @@ -19567,6 +19958,7 @@ export const PodcastsApiFactory = function ( requestParameters.certification, requestParameters.certification_type, requestParameters.course_feature, + requestParameters.delivery, requestParameters.department, requestParameters.free, requestParameters.learning_format, @@ -19686,6 +20078,13 @@ export interface PodcastsApiPodcastsListRequest { */ readonly course_feature?: Array + /** + * The delivery of course/program resources * `online` - Online * `hybrid` - Hybrid * `in_person` - In person * `offline` - Offline + * @type {Array>} + * @memberof PodcastsApiPodcastsList + */ + readonly delivery?: Array> + /** * The department that offers learning resources * `1` - Civil and Environmental Engineering * `2` - Mechanical Engineering * `3` - Materials Science and Engineering * `4` - Architecture * `5` - Chemistry * `6` - Electrical Engineering and Computer Science * `7` - Biology * `8` - Physics * `9` - Brain and Cognitive Sciences * `10` - Chemical Engineering * `11` - Urban Studies and Planning * `12` - Earth, Atmospheric, and Planetary Sciences * `14` - Economics * `15` - Management * `16` - Aeronautics and Astronautics * `17` - Political Science * `18` - Mathematics * `20` - Biological Engineering * `21A` - Anthropology * `21G` - Global Languages * `21H` - History * `21L` - Literature * `21M` - Music and Theater Arts * `22` - Nuclear Science and Engineering * `24` - Linguistics and Philosophy * `CC` - Concourse * `CMS-W` - Comparative Media Studies/Writing * `EC` - Edgerton Center * `ES` - Experimental Study Group * `ESD` - Engineering Systems Division * `HST` - Medical Engineering and Science * `IDS` - Data, Systems, and Society * `MAS` - Media Arts and Sciences * `PE` - Athletics, Physical Education and Recreation * `SP` - Special Programs * `STS` - Science, Technology, and Society * `WGS` - Women\'s and Gender Studies * @type {Array<'1' | '10' | '11' | '12' | '14' | '15' | '16' | '17' | '18' | '2' | '20' | '21A' | '21G' | '21H' | '21L' | '21M' | '22' | '24' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | 'CC' | 'CMS-W' | 'EC' | 'ES' | 'ESD' | 'HST' | 'IDS' | 'MAS' | 'PE' | 'SP' | 'STS' | 'WGS'>} @@ -19867,6 +20266,7 @@ export class PodcastsApi extends BaseAPI { requestParameters.certification, requestParameters.certification_type, requestParameters.course_feature, + requestParameters.delivery, requestParameters.department, requestParameters.free, requestParameters.learning_format, @@ -19918,6 +20318,17 @@ export type PodcastsListCertificationTypeEnum = /** * @export */ +export const PodcastsListDeliveryEnum = { + Online: "online", + Hybrid: "hybrid", + InPerson: "in_person", + Offline: "offline", +} as const +export type PodcastsListDeliveryEnum = + (typeof PodcastsListDeliveryEnum)[keyof typeof PodcastsListDeliveryEnum] +/** + * @export + */ export const PodcastsListDepartmentEnum = { _1: "1", _10: "10", @@ -20230,6 +20641,7 @@ export const ProgramsApiAxiosParamCreator = function ( * @param {boolean} [certification] * @param {Array} [certification_type] The type of certification offered * `micromasters` - Micromasters Credential * `professional` - Professional Certificate * `completion` - Certificate of Completion * `none` - No Certificate * @param {Array} [course_feature] Multiple values may be separated by commas. + * @param {Array>} [delivery] The delivery of course/program resources * `online` - Online * `hybrid` - Hybrid * `in_person` - In person * `offline` - Offline * @param {Array} [department] The department that offers learning resources * `1` - Civil and Environmental Engineering * `2` - Mechanical Engineering * `3` - Materials Science and Engineering * `4` - Architecture * `5` - Chemistry * `6` - Electrical Engineering and Computer Science * `7` - Biology * `8` - Physics * `9` - Brain and Cognitive Sciences * `10` - Chemical Engineering * `11` - Urban Studies and Planning * `12` - Earth, Atmospheric, and Planetary Sciences * `14` - Economics * `15` - Management * `16` - Aeronautics and Astronautics * `17` - Political Science * `18` - Mathematics * `20` - Biological Engineering * `21A` - Anthropology * `21G` - Global Languages * `21H` - History * `21L` - Literature * `21M` - Music and Theater Arts * `22` - Nuclear Science and Engineering * `24` - Linguistics and Philosophy * `CC` - Concourse * `CMS-W` - Comparative Media Studies/Writing * `EC` - Edgerton Center * `ES` - Experimental Study Group * `ESD` - Engineering Systems Division * `HST` - Medical Engineering and Science * `IDS` - Data, Systems, and Society * `MAS` - Media Arts and Sciences * `PE` - Athletics, Physical Education and Recreation * `SP` - Special Programs * `STS` - Science, Technology, and Society * `WGS` - Women\'s and Gender Studies * @param {boolean} [free] The course/program is offered for free * @param {Array>} [learning_format] The learning format of course/program resources * `online` - Online * `hybrid` - Hybrid * `in_person` - In person @@ -20251,6 +20663,7 @@ export const ProgramsApiAxiosParamCreator = function ( certification?: boolean, certification_type?: Array, course_feature?: Array, + delivery?: Array>, department?: Array, free?: boolean, learning_format?: Array>, @@ -20297,6 +20710,10 @@ export const ProgramsApiAxiosParamCreator = function ( ) } + if (delivery) { + localVarQueryParameter["delivery"] = delivery + } + if (department) { localVarQueryParameter["department"] = department } @@ -20431,6 +20848,7 @@ export const ProgramsApiFp = function (configuration?: Configuration) { * @param {boolean} [certification] * @param {Array} [certification_type] The type of certification offered * `micromasters` - Micromasters Credential * `professional` - Professional Certificate * `completion` - Certificate of Completion * `none` - No Certificate * @param {Array} [course_feature] Multiple values may be separated by commas. + * @param {Array>} [delivery] The delivery of course/program resources * `online` - Online * `hybrid` - Hybrid * `in_person` - In person * `offline` - Offline * @param {Array} [department] The department that offers learning resources * `1` - Civil and Environmental Engineering * `2` - Mechanical Engineering * `3` - Materials Science and Engineering * `4` - Architecture * `5` - Chemistry * `6` - Electrical Engineering and Computer Science * `7` - Biology * `8` - Physics * `9` - Brain and Cognitive Sciences * `10` - Chemical Engineering * `11` - Urban Studies and Planning * `12` - Earth, Atmospheric, and Planetary Sciences * `14` - Economics * `15` - Management * `16` - Aeronautics and Astronautics * `17` - Political Science * `18` - Mathematics * `20` - Biological Engineering * `21A` - Anthropology * `21G` - Global Languages * `21H` - History * `21L` - Literature * `21M` - Music and Theater Arts * `22` - Nuclear Science and Engineering * `24` - Linguistics and Philosophy * `CC` - Concourse * `CMS-W` - Comparative Media Studies/Writing * `EC` - Edgerton Center * `ES` - Experimental Study Group * `ESD` - Engineering Systems Division * `HST` - Medical Engineering and Science * `IDS` - Data, Systems, and Society * `MAS` - Media Arts and Sciences * `PE` - Athletics, Physical Education and Recreation * `SP` - Special Programs * `STS` - Science, Technology, and Society * `WGS` - Women\'s and Gender Studies * @param {boolean} [free] The course/program is offered for free * @param {Array>} [learning_format] The learning format of course/program resources * `online` - Online * `hybrid` - Hybrid * `in_person` - In person @@ -20452,6 +20870,7 @@ export const ProgramsApiFp = function (configuration?: Configuration) { certification?: boolean, certification_type?: Array, course_feature?: Array, + delivery?: Array>, department?: Array, free?: boolean, learning_format?: Array>, @@ -20477,6 +20896,7 @@ export const ProgramsApiFp = function (configuration?: Configuration) { certification, certification_type, course_feature, + delivery, department, free, learning_format, @@ -20563,6 +20983,7 @@ export const ProgramsApiFactory = function ( requestParameters.certification, requestParameters.certification_type, requestParameters.course_feature, + requestParameters.delivery, requestParameters.department, requestParameters.free, requestParameters.learning_format, @@ -20626,6 +21047,13 @@ export interface ProgramsApiProgramsListRequest { */ readonly course_feature?: Array + /** + * The delivery of course/program resources * `online` - Online * `hybrid` - Hybrid * `in_person` - In person * `offline` - Offline + * @type {Array>} + * @memberof ProgramsApiProgramsList + */ + readonly delivery?: Array> + /** * The department that offers learning resources * `1` - Civil and Environmental Engineering * `2` - Mechanical Engineering * `3` - Materials Science and Engineering * `4` - Architecture * `5` - Chemistry * `6` - Electrical Engineering and Computer Science * `7` - Biology * `8` - Physics * `9` - Brain and Cognitive Sciences * `10` - Chemical Engineering * `11` - Urban Studies and Planning * `12` - Earth, Atmospheric, and Planetary Sciences * `14` - Economics * `15` - Management * `16` - Aeronautics and Astronautics * `17` - Political Science * `18` - Mathematics * `20` - Biological Engineering * `21A` - Anthropology * `21G` - Global Languages * `21H` - History * `21L` - Literature * `21M` - Music and Theater Arts * `22` - Nuclear Science and Engineering * `24` - Linguistics and Philosophy * `CC` - Concourse * `CMS-W` - Comparative Media Studies/Writing * `EC` - Edgerton Center * `ES` - Experimental Study Group * `ESD` - Engineering Systems Division * `HST` - Medical Engineering and Science * `IDS` - Data, Systems, and Society * `MAS` - Media Arts and Sciences * `PE` - Athletics, Physical Education and Recreation * `SP` - Special Programs * `STS` - Science, Technology, and Society * `WGS` - Women\'s and Gender Studies * @type {Array<'1' | '10' | '11' | '12' | '14' | '15' | '16' | '17' | '18' | '2' | '20' | '21A' | '21G' | '21H' | '21L' | '21M' | '22' | '24' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | 'CC' | 'CMS-W' | 'EC' | 'ES' | 'ESD' | 'HST' | 'IDS' | 'MAS' | 'PE' | 'SP' | 'STS' | 'WGS'>} @@ -20763,6 +21191,7 @@ export class ProgramsApi extends BaseAPI { requestParameters.certification, requestParameters.certification_type, requestParameters.course_feature, + requestParameters.delivery, requestParameters.department, requestParameters.free, requestParameters.learning_format, @@ -20814,6 +21243,17 @@ export type ProgramsListCertificationTypeEnum = /** * @export */ +export const ProgramsListDeliveryEnum = { + Online: "online", + Hybrid: "hybrid", + InPerson: "in_person", + Offline: "offline", +} as const +export type ProgramsListDeliveryEnum = + (typeof ProgramsListDeliveryEnum)[keyof typeof ProgramsListDeliveryEnum] +/** + * @export + */ export const ProgramsListDepartmentEnum = { _1: "1", _10: "10", @@ -23239,6 +23679,7 @@ export const VideoPlaylistsApiAxiosParamCreator = function ( * @param {boolean} [certification] * @param {Array} [certification_type] The type of certification offered * `micromasters` - Micromasters Credential * `professional` - Professional Certificate * `completion` - Certificate of Completion * `none` - No Certificate * @param {Array} [course_feature] Multiple values may be separated by commas. + * @param {Array>} [delivery] The delivery of course/program resources * `online` - Online * `hybrid` - Hybrid * `in_person` - In person * `offline` - Offline * @param {Array} [department] The department that offers learning resources * `1` - Civil and Environmental Engineering * `2` - Mechanical Engineering * `3` - Materials Science and Engineering * `4` - Architecture * `5` - Chemistry * `6` - Electrical Engineering and Computer Science * `7` - Biology * `8` - Physics * `9` - Brain and Cognitive Sciences * `10` - Chemical Engineering * `11` - Urban Studies and Planning * `12` - Earth, Atmospheric, and Planetary Sciences * `14` - Economics * `15` - Management * `16` - Aeronautics and Astronautics * `17` - Political Science * `18` - Mathematics * `20` - Biological Engineering * `21A` - Anthropology * `21G` - Global Languages * `21H` - History * `21L` - Literature * `21M` - Music and Theater Arts * `22` - Nuclear Science and Engineering * `24` - Linguistics and Philosophy * `CC` - Concourse * `CMS-W` - Comparative Media Studies/Writing * `EC` - Edgerton Center * `ES` - Experimental Study Group * `ESD` - Engineering Systems Division * `HST` - Medical Engineering and Science * `IDS` - Data, Systems, and Society * `MAS` - Media Arts and Sciences * `PE` - Athletics, Physical Education and Recreation * `SP` - Special Programs * `STS` - Science, Technology, and Society * `WGS` - Women\'s and Gender Studies * @param {boolean} [free] The course/program is offered for free * @param {Array>} [learning_format] The learning format of course/program resources * `online` - Online * `hybrid` - Hybrid * `in_person` - In person @@ -23260,6 +23701,7 @@ export const VideoPlaylistsApiAxiosParamCreator = function ( certification?: boolean, certification_type?: Array, course_feature?: Array, + delivery?: Array>, department?: Array, free?: boolean, learning_format?: Array>, @@ -23306,6 +23748,10 @@ export const VideoPlaylistsApiAxiosParamCreator = function ( ) } + if (delivery) { + localVarQueryParameter["delivery"] = delivery + } + if (department) { localVarQueryParameter["department"] = department } @@ -23520,6 +23966,7 @@ export const VideoPlaylistsApiFp = function (configuration?: Configuration) { * @param {boolean} [certification] * @param {Array} [certification_type] The type of certification offered * `micromasters` - Micromasters Credential * `professional` - Professional Certificate * `completion` - Certificate of Completion * `none` - No Certificate * @param {Array} [course_feature] Multiple values may be separated by commas. + * @param {Array>} [delivery] The delivery of course/program resources * `online` - Online * `hybrid` - Hybrid * `in_person` - In person * `offline` - Offline * @param {Array} [department] The department that offers learning resources * `1` - Civil and Environmental Engineering * `2` - Mechanical Engineering * `3` - Materials Science and Engineering * `4` - Architecture * `5` - Chemistry * `6` - Electrical Engineering and Computer Science * `7` - Biology * `8` - Physics * `9` - Brain and Cognitive Sciences * `10` - Chemical Engineering * `11` - Urban Studies and Planning * `12` - Earth, Atmospheric, and Planetary Sciences * `14` - Economics * `15` - Management * `16` - Aeronautics and Astronautics * `17` - Political Science * `18` - Mathematics * `20` - Biological Engineering * `21A` - Anthropology * `21G` - Global Languages * `21H` - History * `21L` - Literature * `21M` - Music and Theater Arts * `22` - Nuclear Science and Engineering * `24` - Linguistics and Philosophy * `CC` - Concourse * `CMS-W` - Comparative Media Studies/Writing * `EC` - Edgerton Center * `ES` - Experimental Study Group * `ESD` - Engineering Systems Division * `HST` - Medical Engineering and Science * `IDS` - Data, Systems, and Society * `MAS` - Media Arts and Sciences * `PE` - Athletics, Physical Education and Recreation * `SP` - Special Programs * `STS` - Science, Technology, and Society * `WGS` - Women\'s and Gender Studies * @param {boolean} [free] The course/program is offered for free * @param {Array>} [learning_format] The learning format of course/program resources * `online` - Online * `hybrid` - Hybrid * `in_person` - In person @@ -23541,6 +23988,7 @@ export const VideoPlaylistsApiFp = function (configuration?: Configuration) { certification?: boolean, certification_type?: Array, course_feature?: Array, + delivery?: Array>, department?: Array, free?: boolean, learning_format?: Array>, @@ -23567,6 +24015,7 @@ export const VideoPlaylistsApiFp = function (configuration?: Configuration) { certification, certification_type, course_feature, + delivery, department, free, learning_format, @@ -23694,6 +24143,7 @@ export const VideoPlaylistsApiFactory = function ( requestParameters.certification, requestParameters.certification_type, requestParameters.course_feature, + requestParameters.delivery, requestParameters.department, requestParameters.free, requestParameters.learning_format, @@ -23813,6 +24263,13 @@ export interface VideoPlaylistsApiVideoPlaylistsListRequest { */ readonly course_feature?: Array + /** + * The delivery of course/program resources * `online` - Online * `hybrid` - Hybrid * `in_person` - In person * `offline` - Offline + * @type {Array>} + * @memberof VideoPlaylistsApiVideoPlaylistsList + */ + readonly delivery?: Array> + /** * The department that offers learning resources * `1` - Civil and Environmental Engineering * `2` - Mechanical Engineering * `3` - Materials Science and Engineering * `4` - Architecture * `5` - Chemistry * `6` - Electrical Engineering and Computer Science * `7` - Biology * `8` - Physics * `9` - Brain and Cognitive Sciences * `10` - Chemical Engineering * `11` - Urban Studies and Planning * `12` - Earth, Atmospheric, and Planetary Sciences * `14` - Economics * `15` - Management * `16` - Aeronautics and Astronautics * `17` - Political Science * `18` - Mathematics * `20` - Biological Engineering * `21A` - Anthropology * `21G` - Global Languages * `21H` - History * `21L` - Literature * `21M` - Music and Theater Arts * `22` - Nuclear Science and Engineering * `24` - Linguistics and Philosophy * `CC` - Concourse * `CMS-W` - Comparative Media Studies/Writing * `EC` - Edgerton Center * `ES` - Experimental Study Group * `ESD` - Engineering Systems Division * `HST` - Medical Engineering and Science * `IDS` - Data, Systems, and Society * `MAS` - Media Arts and Sciences * `PE` - Athletics, Physical Education and Recreation * `SP` - Special Programs * `STS` - Science, Technology, and Society * `WGS` - Women\'s and Gender Studies * @type {Array<'1' | '10' | '11' | '12' | '14' | '15' | '16' | '17' | '18' | '2' | '20' | '21A' | '21G' | '21H' | '21L' | '21M' | '22' | '24' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | 'CC' | 'CMS-W' | 'EC' | 'ES' | 'ESD' | 'HST' | 'IDS' | 'MAS' | 'PE' | 'SP' | 'STS' | 'WGS'>} @@ -23994,6 +24451,7 @@ export class VideoPlaylistsApi extends BaseAPI { requestParameters.certification, requestParameters.certification_type, requestParameters.course_feature, + requestParameters.delivery, requestParameters.department, requestParameters.free, requestParameters.learning_format, @@ -24045,6 +24503,17 @@ export type VideoPlaylistsListCertificationTypeEnum = /** * @export */ +export const VideoPlaylistsListDeliveryEnum = { + Online: "online", + Hybrid: "hybrid", + InPerson: "in_person", + Offline: "offline", +} as const +export type VideoPlaylistsListDeliveryEnum = + (typeof VideoPlaylistsListDeliveryEnum)[keyof typeof VideoPlaylistsListDeliveryEnum] +/** + * @export + */ export const VideoPlaylistsListDepartmentEnum = { _1: "1", _10: "10", @@ -24198,6 +24667,7 @@ export const VideosApiAxiosParamCreator = function ( * @param {boolean} [certification] * @param {Array} [certification_type] The type of certification offered * `micromasters` - Micromasters Credential * `professional` - Professional Certificate * `completion` - Certificate of Completion * `none` - No Certificate * @param {Array} [course_feature] Multiple values may be separated by commas. + * @param {Array>} [delivery] The delivery of course/program resources * `online` - Online * `hybrid` - Hybrid * `in_person` - In person * `offline` - Offline * @param {Array} [department] The department that offers learning resources * `1` - Civil and Environmental Engineering * `2` - Mechanical Engineering * `3` - Materials Science and Engineering * `4` - Architecture * `5` - Chemistry * `6` - Electrical Engineering and Computer Science * `7` - Biology * `8` - Physics * `9` - Brain and Cognitive Sciences * `10` - Chemical Engineering * `11` - Urban Studies and Planning * `12` - Earth, Atmospheric, and Planetary Sciences * `14` - Economics * `15` - Management * `16` - Aeronautics and Astronautics * `17` - Political Science * `18` - Mathematics * `20` - Biological Engineering * `21A` - Anthropology * `21G` - Global Languages * `21H` - History * `21L` - Literature * `21M` - Music and Theater Arts * `22` - Nuclear Science and Engineering * `24` - Linguistics and Philosophy * `CC` - Concourse * `CMS-W` - Comparative Media Studies/Writing * `EC` - Edgerton Center * `ES` - Experimental Study Group * `ESD` - Engineering Systems Division * `HST` - Medical Engineering and Science * `IDS` - Data, Systems, and Society * `MAS` - Media Arts and Sciences * `PE` - Athletics, Physical Education and Recreation * `SP` - Special Programs * `STS` - Science, Technology, and Society * `WGS` - Women\'s and Gender Studies * @param {boolean} [free] The course/program is offered for free * @param {Array>} [learning_format] The learning format of course/program resources * `online` - Online * `hybrid` - Hybrid * `in_person` - In person @@ -24219,6 +24689,7 @@ export const VideosApiAxiosParamCreator = function ( certification?: boolean, certification_type?: Array, course_feature?: Array, + delivery?: Array>, department?: Array, free?: boolean, learning_format?: Array>, @@ -24265,6 +24736,10 @@ export const VideosApiAxiosParamCreator = function ( ) } + if (delivery) { + localVarQueryParameter["delivery"] = delivery + } + if (department) { localVarQueryParameter["department"] = department } @@ -24399,6 +24874,7 @@ export const VideosApiFp = function (configuration?: Configuration) { * @param {boolean} [certification] * @param {Array} [certification_type] The type of certification offered * `micromasters` - Micromasters Credential * `professional` - Professional Certificate * `completion` - Certificate of Completion * `none` - No Certificate * @param {Array} [course_feature] Multiple values may be separated by commas. + * @param {Array>} [delivery] The delivery of course/program resources * `online` - Online * `hybrid` - Hybrid * `in_person` - In person * `offline` - Offline * @param {Array} [department] The department that offers learning resources * `1` - Civil and Environmental Engineering * `2` - Mechanical Engineering * `3` - Materials Science and Engineering * `4` - Architecture * `5` - Chemistry * `6` - Electrical Engineering and Computer Science * `7` - Biology * `8` - Physics * `9` - Brain and Cognitive Sciences * `10` - Chemical Engineering * `11` - Urban Studies and Planning * `12` - Earth, Atmospheric, and Planetary Sciences * `14` - Economics * `15` - Management * `16` - Aeronautics and Astronautics * `17` - Political Science * `18` - Mathematics * `20` - Biological Engineering * `21A` - Anthropology * `21G` - Global Languages * `21H` - History * `21L` - Literature * `21M` - Music and Theater Arts * `22` - Nuclear Science and Engineering * `24` - Linguistics and Philosophy * `CC` - Concourse * `CMS-W` - Comparative Media Studies/Writing * `EC` - Edgerton Center * `ES` - Experimental Study Group * `ESD` - Engineering Systems Division * `HST` - Medical Engineering and Science * `IDS` - Data, Systems, and Society * `MAS` - Media Arts and Sciences * `PE` - Athletics, Physical Education and Recreation * `SP` - Special Programs * `STS` - Science, Technology, and Society * `WGS` - Women\'s and Gender Studies * @param {boolean} [free] The course/program is offered for free * @param {Array>} [learning_format] The learning format of course/program resources * `online` - Online * `hybrid` - Hybrid * `in_person` - In person @@ -24420,6 +24896,7 @@ export const VideosApiFp = function (configuration?: Configuration) { certification?: boolean, certification_type?: Array, course_feature?: Array, + delivery?: Array>, department?: Array, free?: boolean, learning_format?: Array>, @@ -24445,6 +24922,7 @@ export const VideosApiFp = function (configuration?: Configuration) { certification, certification_type, course_feature, + delivery, department, free, learning_format, @@ -24530,6 +25008,7 @@ export const VideosApiFactory = function ( requestParameters.certification, requestParameters.certification_type, requestParameters.course_feature, + requestParameters.delivery, requestParameters.department, requestParameters.free, requestParameters.learning_format, @@ -24593,6 +25072,13 @@ export interface VideosApiVideosListRequest { */ readonly course_feature?: Array + /** + * The delivery of course/program resources * `online` - Online * `hybrid` - Hybrid * `in_person` - In person * `offline` - Offline + * @type {Array>} + * @memberof VideosApiVideosList + */ + readonly delivery?: Array> + /** * The department that offers learning resources * `1` - Civil and Environmental Engineering * `2` - Mechanical Engineering * `3` - Materials Science and Engineering * `4` - Architecture * `5` - Chemistry * `6` - Electrical Engineering and Computer Science * `7` - Biology * `8` - Physics * `9` - Brain and Cognitive Sciences * `10` - Chemical Engineering * `11` - Urban Studies and Planning * `12` - Earth, Atmospheric, and Planetary Sciences * `14` - Economics * `15` - Management * `16` - Aeronautics and Astronautics * `17` - Political Science * `18` - Mathematics * `20` - Biological Engineering * `21A` - Anthropology * `21G` - Global Languages * `21H` - History * `21L` - Literature * `21M` - Music and Theater Arts * `22` - Nuclear Science and Engineering * `24` - Linguistics and Philosophy * `CC` - Concourse * `CMS-W` - Comparative Media Studies/Writing * `EC` - Edgerton Center * `ES` - Experimental Study Group * `ESD` - Engineering Systems Division * `HST` - Medical Engineering and Science * `IDS` - Data, Systems, and Society * `MAS` - Media Arts and Sciences * `PE` - Athletics, Physical Education and Recreation * `SP` - Special Programs * `STS` - Science, Technology, and Society * `WGS` - Women\'s and Gender Studies * @type {Array<'1' | '10' | '11' | '12' | '14' | '15' | '16' | '17' | '18' | '2' | '20' | '21A' | '21G' | '21H' | '21L' | '21M' | '22' | '24' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | 'CC' | 'CMS-W' | 'EC' | 'ES' | 'ESD' | 'HST' | 'IDS' | 'MAS' | 'PE' | 'SP' | 'STS' | 'WGS'>} @@ -24730,6 +25216,7 @@ export class VideosApi extends BaseAPI { requestParameters.certification, requestParameters.certification_type, requestParameters.course_feature, + requestParameters.delivery, requestParameters.department, requestParameters.free, requestParameters.learning_format, @@ -24781,6 +25268,17 @@ export type VideosListCertificationTypeEnum = /** * @export */ +export const VideosListDeliveryEnum = { + Online: "online", + Hybrid: "hybrid", + InPerson: "in_person", + Offline: "offline", +} as const +export type VideosListDeliveryEnum = + (typeof VideosListDeliveryEnum)[keyof typeof VideosListDeliveryEnum] +/** + * @export + */ export const VideosListDepartmentEnum = { _1: "1", _10: "10", diff --git a/learning_resources/constants.py b/learning_resources/constants.py index f0bb624de6..71c464c32a 100644 --- a/learning_resources/constants.py +++ b/learning_resources/constants.py @@ -270,13 +270,25 @@ class LevelType(ExtendedEnum): class LearningResourceFormat(ExtendedEnum): - """Enum for resource formats""" + """Enum for resource learning_format""" online = "Online" hybrid = "Hybrid" in_person = "In person" +class LearningResourceDelivery(ExtendedEnum): + """ + Enum for resource delivery methods. This + will eventually replace LearningResourceFormat + """ + + online = "Online" + hybrid = "Hybrid" + in_person = "In person" + offline = "Offline" + + class CertificationType(ExtendedEnum): """Enum for resource certification types""" diff --git a/learning_resources/etl/ocw.py b/learning_resources/etl/ocw.py index e6a25d6350..5e2eb55051 100644 --- a/learning_resources/etl/ocw.py +++ b/learning_resources/etl/ocw.py @@ -20,6 +20,7 @@ CONTENT_TYPE_VIDEO, VALID_TEXT_FILE_TYPES, Availability, + LearningResourceDelivery, LearningResourceType, OfferedBy, PlatformType, @@ -33,7 +34,11 @@ transform_levels, transform_topics, ) -from learning_resources.models import ContentFile, LearningResource +from learning_resources.models import ( + ContentFile, + LearningResource, + default_learning_format, +) from learning_resources.utils import ( get_s3_object_and_read, parse_instructors, @@ -48,6 +53,22 @@ UNIQUE_FIELD = "url" +def parse_delivery(course_data: dict) -> list[str]: + """ + Parse delivery methods + + Args: + url (str): The course url + + Returns: + list[str]: The delivery method(s) + """ + delivery = default_learning_format() + if not course_data.get("hide_download"): + delivery.append(LearningResourceDelivery.offline.name) + return delivery + + def transform_content_files( s3_resource: boto3.resource, course_prefix: str, @@ -343,6 +364,7 @@ def transform_course(course_data: dict) -> dict: "resource_type": LearningResourceType.course.name, "unique_field": UNIQUE_FIELD, "availability": Availability.anytime.name, + "delivery": parse_delivery(course_data), } diff --git a/learning_resources/etl/ocw_test.py b/learning_resources/etl/ocw_test.py index 08896026ee..1b31ba70b3 100644 --- a/learning_resources/etl/ocw_test.py +++ b/learning_resources/etl/ocw_test.py @@ -9,7 +9,7 @@ from moto import mock_s3 from learning_resources.conftest import OCW_TEST_PREFIX, setup_s3_ocw -from learning_resources.constants import DEPARTMENTS +from learning_resources.constants import DEPARTMENTS, LearningResourceDelivery from learning_resources.etl.constants import CourseNumberType, ETLSource from learning_resources.etl.ocw import ( transform_content_files, @@ -174,19 +174,37 @@ def test_transform_content_file_needs_text_update( "term", "year", "expected_id", + "hide_download", ), [ - ("legacy-uid", None, "legacyuid", False, "Spring", "2005", "16.01+spring_2005"), - (None, "site-uid", "siteuid", True, "", 2005, "16.01_2005"), - (None, "site-uid", "siteuid", True, "Fall", 2005, "16.01+fall_2005"), - (None, "site-uid", "siteuid", True, "Fall", None, "16.01+fall"), - (None, "site-uid", "siteuid", True, "", "", "16.01"), - (None, "site-uid", "siteuid", True, None, None, "16.01"), - (None, None, None, True, "Spring", "2005", None), + ( + "legacy-uid", + None, + "legacyuid", + False, + "Spring", + "2005", + "16.01+spring_2005", + False, + ), + (None, "site-uid", "siteuid", True, "", 2005, "16.01_2005", True), + (None, "site-uid", "siteuid", True, "Fall", 2005, "16.01+fall_2005", None), + (None, "site-uid", "siteuid", True, "Fall", None, "16.01+fall", False), + (None, "site-uid", "siteuid", True, "", "", "16.01", True), + (None, "site-uid", "siteuid", True, None, None, "16.01", False), + (None, None, None, True, "Spring", "2005", None, None), ], ) def test_transform_course( # noqa: PLR0913 - settings, legacy_uid, site_uid, expected_uid, has_extra_num, term, year, expected_id + settings, + legacy_uid, + site_uid, + expected_uid, + has_extra_num, + term, + year, + expected_id, + hide_download, ): """transform_course should return expected data""" settings.OCW_BASE_URL = "http://test.edu/" @@ -201,6 +219,7 @@ def test_transform_course( # noqa: PLR0913 course_json["year"] = year course_json["legacy_uid"] = legacy_uid course_json["site_uid"] = site_uid + course_json["hide_download"] = hide_download course_json["extra_course_numbers"] = "1, 2" if has_extra_num else None extracted_json = { **course_json, @@ -208,10 +227,14 @@ def test_transform_course( # noqa: PLR0913 "slug": "slug", "url": "http://test.edu/slug", } + expected_delivery = [LearningResourceDelivery.online.name] + ( + [] if hide_download else [LearningResourceDelivery.offline.name] + ) transformed_json = transform_course(extracted_json) if expected_uid: assert transformed_json["readable_id"] == expected_id assert transformed_json["etl_source"] == ETLSource.ocw.name + assert transformed_json["delivery"] == expected_delivery assert transformed_json["runs"][0]["run_id"] == expected_uid assert transformed_json["runs"][0]["level"] == ["undergraduate", "high_school"] assert transformed_json["runs"][0]["semester"] == (term if term else None) diff --git a/learning_resources/etl/prolearn.py b/learning_resources/etl/prolearn.py index 5dfbe7b9af..87dd0b404f 100644 --- a/learning_resources/etl/prolearn.py +++ b/learning_resources/etl/prolearn.py @@ -11,7 +11,7 @@ from learning_resources.constants import Availability, CertificationType from learning_resources.etl.constants import ETLSource -from learning_resources.etl.utils import transform_format, transform_topics +from learning_resources.etl.utils import transform_delivery, transform_topics from learning_resources.models import LearningResourceOfferor, LearningResourcePlatform from main.utils import clean_data, now_in_utc @@ -189,6 +189,7 @@ def update_format(unique_resource: dict, resource_format: list[str]): unique_resource["learning_format"] = sorted( set(unique_resource["learning_format"] + resource_format) ) + unique_resource["delivery"] = unique_resource["learning_format"] def extract_data(course_or_program: str) -> list[dict]: @@ -265,7 +266,7 @@ def transform_programs(programs: list[dict]) -> list[dict]: "professional": True, "certification": True, "certification_type": CertificationType.professional.name, - "learning_format": transform_format(program["format_name"]), + "learning_format": transform_delivery(program["format_name"]), "runs": runs, "topics": parse_topic(program, offered_by.code) if offered_by else None, "courses": [ @@ -355,7 +356,7 @@ def _transform_course( "course": { "course_numbers": [], }, - "learning_format": transform_format(course["format_name"]), + "learning_format": transform_delivery(course["format_name"]), "published": True, "topics": parse_topic(course, offered_by.code) if offered_by else None, "runs": runs, diff --git a/learning_resources/etl/prolearn_test.py b/learning_resources/etl/prolearn_test.py index d6c98d48fb..b050f17949 100644 --- a/learning_resources/etl/prolearn_test.py +++ b/learning_resources/etl/prolearn_test.py @@ -32,7 +32,7 @@ transform_programs, update_format, ) -from learning_resources.etl.utils import transform_format +from learning_resources.etl.utils import transform_delivery from learning_resources.factories import ( LearningResourceOfferorFactory, LearningResourcePlatformFactory, @@ -157,7 +157,8 @@ def test_prolearn_transform_programs(mock_csail_programs_data): else None, "etl_source": ETLSource.prolearn.name, "professional": True, - "learning_format": transform_format(program["format_name"]), + "learning_format": transform_delivery(program["format_name"]), + "delivery": transform_delivery(program["format_name"]), "certification": True, "certification_type": CertificationType.professional.name, "runs": [ @@ -226,7 +227,8 @@ def test_prolearn_transform_courses(mock_mitpe_courses_data): "professional": True, "certification": True, "certification_type": CertificationType.professional.name, - "learning_format": transform_format(course["format_name"]), + "learning_format": transform_delivery(course["format_name"]), + "delivery": transform_delivery(course["format_name"]), "topics": parse_topic(course, "mitpe"), "url": course["course_link"] if urlparse(course["course_link"]).path @@ -395,6 +397,7 @@ def test_update_format( first_course["learning_format"] = old_format update_format(first_course, new_format) assert first_course["learning_format"] == sorted(expected_format) + assert first_course["delivery"] == sorted(expected_format) @pytest.mark.parametrize("sloan_api_enabled", [True, False]) diff --git a/learning_resources/etl/sloan.py b/learning_resources/etl/sloan.py index b88b2a0229..fcb3fd289a 100644 --- a/learning_resources/etl/sloan.py +++ b/learning_resources/etl/sloan.py @@ -18,7 +18,7 @@ ) from learning_resources.etl.constants import ETLSource from learning_resources.etl.utils import ( - transform_format, + transform_delivery, transform_topics, ) @@ -202,7 +202,9 @@ def transform_course(course_data: dict, runs_data: dict) -> dict: course_runs_data = [ run for run in runs_data if run["Course_Id"] == course_data["Course_Id"] ] - + format_delivery = list( + {transform_delivery(run["Delivery"])[0] for run in course_runs_data} + ) transformed_course = { "readable_id": course_data["Course_Id"], "title": course_data["Title"], @@ -216,9 +218,8 @@ def transform_course(course_data: dict, runs_data: dict) -> dict: "certification_type": CertificationType.professional.name, "professional": True, "published": True, - "learning_format": list( - {transform_format(run["Delivery"])[0] for run in course_runs_data} - ), + "learning_format": format_delivery, + "delivery": format_delivery, "topics": parse_topics(course_data["Topics"]), "course": { "course_numbers": [], diff --git a/learning_resources/etl/sloan_test.py b/learning_resources/etl/sloan_test.py index f0bb3b4b4f..b3ae4effc8 100644 --- a/learning_resources/etl/sloan_test.py +++ b/learning_resources/etl/sloan_test.py @@ -15,7 +15,7 @@ parse_datetime, parse_image, transform_course, - transform_format, + transform_delivery, transform_run, ) from learning_resources.factories import ( @@ -139,8 +139,9 @@ def test_transform_course(mock_sloan_courses_data, mock_sloan_runs_data): transform_run(run, course_data) for run in course_runs_data ] assert transformed["learning_format"] == list( - {transform_format(run["Delivery"])[0] for run in course_runs_data} + {transform_delivery(run["Delivery"])[0] for run in course_runs_data} ) + assert transformed["delivery"] == transformed["learning_format"] assert transformed["image"] == parse_image(course_data) assert transformed["availability"] == parse_availability(course_runs_data) assert sorted(transformed.keys()) == sorted( @@ -158,6 +159,7 @@ def test_transform_course(mock_sloan_courses_data, mock_sloan_runs_data): "professional", "published", "learning_format", + "delivery", "topics", "course", "runs", diff --git a/learning_resources/etl/utils.py b/learning_resources/etl/utils.py index 5fb7217dc7..f0871b243d 100644 --- a/learning_resources/etl/utils.py +++ b/learning_resources/etl/utils.py @@ -687,7 +687,7 @@ def most_common_topics( return [{"name": topic} for topic in common_topics] -def transform_format(resource_format: str) -> list[str]: +def transform_delivery(resource_delivery: str) -> list[str]: """ Return the correct format of the resource @@ -699,9 +699,9 @@ def transform_format(resource_format: str) -> list[str]: """ try: - return [RESOURCE_FORMAT_MAPPING[resource_format]] + return [RESOURCE_FORMAT_MAPPING[resource_delivery]] except KeyError: - log.exception("Invalid format %s", resource_format) + log.exception("Invalid format %s", resource_delivery) return [LearningResourceFormat.online.name] diff --git a/learning_resources/etl/utils_test.py b/learning_resources/etl/utils_test.py index 4ab54b1053..6a3f37a694 100644 --- a/learning_resources/etl/utils_test.py +++ b/learning_resources/etl/utils_test.py @@ -371,13 +371,15 @@ def test_most_common_topics(): ) def test_parse_format(original, expected): """parse_format should return expected format""" - assert utils.transform_format(original) == [expected] + assert utils.transform_delivery(original) == [expected] def test_parse_bad_format(mocker): """An exception log should be called for invalid formats""" mock_log = mocker.patch("learning_resources.etl.utils.log.exception") - assert utils.transform_format("bad_format") == [LearningResourceFormat.online.name] + assert utils.transform_delivery("bad_format") == [ + LearningResourceFormat.online.name + ] mock_log.assert_called_once_with("Invalid format %s", "bad_format") diff --git a/learning_resources/etl/xpro.py b/learning_resources/etl/xpro.py index 7bbb4cbd4f..8090915284 100644 --- a/learning_resources/etl/xpro.py +++ b/learning_resources/etl/xpro.py @@ -17,7 +17,7 @@ from learning_resources.etl.constants import ETLSource from learning_resources.etl.utils import ( generate_course_numbers_json, - transform_format, + transform_delivery, transform_topics, ) from main.utils import clean_data @@ -145,7 +145,8 @@ def _transform_learning_resource_course(course): "topics": parse_topics(course), "runs": [_transform_run(course_run) for course_run in course["courseruns"]], "resource_type": LearningResourceType.course.name, - "learning_format": transform_format(course.get("format")), + "learning_format": transform_delivery(course.get("format")), + "delivery": transform_delivery(course.get("format")), "course": { "course_numbers": generate_course_numbers_json( course["readable_id"], is_ocw=False @@ -190,7 +191,8 @@ def transform_programs(programs): "topics": parse_topics(program), "platform": XPRO_PLATFORM_TRANSFORM.get(program["platform"], None), "resource_type": LearningResourceType.program.name, - "learning_format": transform_format(program.get("format")), + "learning_format": transform_delivery(program.get("format")), + "delivery": transform_delivery(program.get("format")), "runs": [ { "prices": ( diff --git a/learning_resources/etl/xpro_test.py b/learning_resources/etl/xpro_test.py index 352dc4c136..35af294242 100644 --- a/learning_resources/etl/xpro_test.py +++ b/learning_resources/etl/xpro_test.py @@ -16,7 +16,7 @@ from learning_resources.etl import xpro from learning_resources.etl.constants import CourseNumberType, ETLSource from learning_resources.etl.utils import ( - transform_format, + transform_delivery, ) from learning_resources.etl.xpro import _parse_datetime, parse_topics from learning_resources.factories import ( @@ -110,7 +110,8 @@ def test_xpro_transform_programs(mock_xpro_programs_data): "topics": parse_topics(program_data), "platform": PlatformType.xpro.name, "resource_type": LearningResourceType.program.name, - "learning_format": transform_format(program_data.get("format")), + "learning_format": transform_delivery(program_data.get("format")), + "delivery": transform_delivery(program_data.get("format")), "runs": [ { "run_id": program_data["readable_id"], @@ -140,7 +141,8 @@ def test_xpro_transform_programs(mock_xpro_programs_data): "description": course_data["description"], "url": course_data.get("url", None), "offered_by": xpro.OFFERED_BY, - "learning_format": transform_format(course_data.get("format")), + "learning_format": transform_delivery(course_data.get("format")), + "delivery": transform_delivery(course_data.get("format")), "professional": True, "published": any( course_run.get("current_price", None) @@ -211,7 +213,8 @@ def test_xpro_transform_courses(mock_xpro_courses_data): "description": course_data["description"], "url": course_data.get("url"), "offered_by": xpro.OFFERED_BY, - "learning_format": transform_format(course_data.get("format")), + "learning_format": transform_delivery(course_data.get("format")), + "delivery": transform_delivery(course_data.get("format")), "published": any( course_run.get("current_price", None) for course_run in course_data["courseruns"] diff --git a/learning_resources/factories.py b/learning_resources/factories.py index 4c1a279d92..415c853690 100644 --- a/learning_resources/factories.py +++ b/learning_resources/factories.py @@ -14,6 +14,7 @@ from learning_resources.constants import ( DEPARTMENTS, Availability, + LearningResourceDelivery, LearningResourceFormat, LevelType, PlatformType, @@ -215,6 +216,7 @@ class LearningResourceFactory(DjangoModelFactory): content_tags = factory.PostGeneration(_post_gen_tags) published = True learning_format = factory.List(random.choices(LearningResourceFormat.names())) # noqa: S311 + delivery = factory.List(random.choices(LearningResourceDelivery.names())) # noqa: S311 professional = factory.LazyAttribute( lambda o: o.resource_type in ( diff --git a/learning_resources/filters.py b/learning_resources/filters.py index ca5635bb9f..bf876a682a 100644 --- a/learning_resources/filters.py +++ b/learning_resources/filters.py @@ -16,6 +16,7 @@ LEARNING_RESOURCE_SORTBY_OPTIONS, RESOURCE_CATEGORY_VALUES, CertificationType, + LearningResourceDelivery, LearningResourceFormat, LearningResourceType, LevelType, @@ -109,6 +110,12 @@ class LearningResourceFilter(FilterSet): choices=LearningResourceFormat.as_list(), ) + delivery = MultipleChoiceFilter( + label="The delivery of course/program resources", + method="filter_delivery", + choices=LearningResourceDelivery.as_list(), + ) + certification_type = MultipleChoiceFilter( label="The type of certification offered", choices=CertificationType.as_tuple(), @@ -192,6 +199,11 @@ def filter_format(self, queryset, _, value): values = [[LearningResourceFormat[val].name] for val in value] return multi_or_filter(queryset, "learning_format__contains", values) + def filter_delivery(self, queryset, _, value): + """Format Filter for learning resources""" + values = [[LearningResourceDelivery[val].name] for val in value] + return multi_or_filter(queryset, "delivery__contains", values) + class Meta: model = LearningResource fields = ["professional", "certification"] diff --git a/learning_resources/filters_test.py b/learning_resources/filters_test.py index fc1546de89..4fabb6a398 100644 --- a/learning_resources/filters_test.py +++ b/learning_resources/filters_test.py @@ -9,6 +9,7 @@ LEARNING_MATERIAL_RESOURCE_CATEGORY, LEARNING_RESOURCE_SORTBY_OPTIONS, CertificationType, + LearningResourceDelivery, LearningResourceFormat, LearningResourceType, LevelType, @@ -515,6 +516,38 @@ def test_learning_resource_filter_formats(mock_courses, client): ) +def test_learning_resource_filter_delivery(mock_courses, client): + """Test that the delivery filter works""" + LearningResource.objects.filter(id=mock_courses.ocw_course.id).update( + delivery=[LearningResourceDelivery.online.name] + ) + LearningResource.objects.filter(id=mock_courses.mitx_course.id).update( + delivery=[ + LearningResourceDelivery.online.name, + LearningResourceDelivery.hybrid.name, + ] + ) + LearningResource.objects.filter(id=mock_courses.mitpe_course.id).update( + delivery=[ + LearningResourceDelivery.hybrid.name, + LearningResourceDelivery.in_person.name, + ] + ) + + results = client.get( + f"{RESOURCE_API_URL}?delivery={LearningResourceDelivery.in_person.name}" + ).json()["results"] + assert len(results) == 1 + assert results[0]["id"] == mock_courses.mitpe_course.id + + multiformats_filter = f"delivery={LearningResourceDelivery.in_person.name}&delivery={LearningResourceDelivery.hybrid.name}" + results = client.get(f"{RESOURCE_API_URL}?{multiformats_filter}").json()["results"] + assert len(results) == 2 + assert sorted([result["readable_id"] for result in results]) == sorted( + [mock_courses.mitx_course.readable_id, mock_courses.mitpe_course.readable_id] + ) + + def test_content_file_filter_run_id(mock_content_files, client): """Test that the run_id filter works for contentfiles""" diff --git a/learning_resources/migrations/0062_learningresource_delivery_format_pace.py b/learning_resources/migrations/0062_learningresource_delivery_format_pace.py new file mode 100644 index 0000000000..5318553c48 --- /dev/null +++ b/learning_resources/migrations/0062_learningresource_delivery_format_pace.py @@ -0,0 +1,145 @@ +# Generated by Django 4.2.15 on 2024-09-04 15:18 +from urllib.parse import urljoin + +import django.contrib.postgres.fields +from django.conf import settings +from django.db import migrations, models + +import learning_resources.models +from learning_resources.constants import LearningResourceDelivery +from learning_resources.etl.constants import ETLSource + +OCW_HIDDEN_DOWNLOADS = [ + "11-308j-ecological-urbanism-spring-2024", + "11-309j-sensing-place-photography-as-inquiry-spring-2024", + "11-405-just-money-banking-as-if-society-mattered-spring-2021", + "12-340x-global-warming-science-spring-2020", + "15-053x-optimization-methods-in-business-analytics-summer-2021", + "15-480x-the-science-and-business-of-biotechnology-fall-2021", + "15-481x-adaptive-markets-financial-market-dynamics-and-human-behavior-fall-2022", + "18-01-calculus-i-single-variable-calculus-fall-2020", + "2-009-product-engineering-process-fall-2021", + "2-00b-toy-product-design-spring-2021", + "3-012sx-structure-of-materials-spring-2019", + "3-15x-electrical-optical-and-magnetic-materials-and-devices-spring-2020", + "6-00-introduction-to-computer-science-and-programming-fall-2008", + "6-00sc-introduction-to-computer-science-and-programming-spring-2011", + "6-036-introduction-to-machine-learning-fall-2020", + "6-4210-robotic-manipulation-fall-2022", + "6-5060-algorithm-engineering-spring-2023", + "6-5830-database-systems-fall-2023", + "6-832-underactuated-robotics-spring-2022", + "6-s062-generative-artificial-intelligence-in-k12-education-fall-2023", + "6-s191-introduction-to-deep-learning-january-iap-2020", + "6-s980-machine-learning-for-inverse-graphics-fall-2022", + "7-00-covid-19-sars-cov-2-and-the-pandemic-fall-2020", + "7-00-covid-19-sars-cov-2-and-the-pandemic-fall-2021", + "8-02-physics-ii-electricity-and-magnetism-spring-2019", + "8-370x-quantum-information-science-i-spring-2018", + "8-371x-quantum-information-science-ii-spring-2018", + "8-s271-nuclear-weapons-history-and-future-prospects-spring-2022", + "hst-936x-global-health-informatics-to-improve-quality-of-care-spring-2020", + "hst-953-collaborative-data-science-for-healthcare-fall-2020", + "res-11-001-cite-reports-fall-2015", + "res-11-003-climate-justice-instructional-toolkit-fall-2023", + "res-11-550-leveraging-urban-mobility-disruptions-to-create-better-cities-spring-2021", + "res-12-002-terrascope-spring-2023", + "res-14-003-the-roosevelt-project-spring-2023", + "res-15-001-mit-sloan-learningedge-fall-2008", + "res-15-005-healthcare-finance-15-482x-spring-2019", + "res-16-002-how-to-cad-almost-anything-january-iap-2024", + "res-17-001-mit-election-data-science-lab-fall-2020", + "res-17-002-mit-governance-lab-spring-2023", + "res-18-003-calculus-for-beginners-and-artists-spring-2005", + "res-21g-3001-teaching-la-princesse-de-cleves-fall-2023", + "res-21g-3002-teaching-marguerite-de-navarres-heptameron-fall-2023", + "res-21g-3003-marguerite-de-roberval-a-web-based-approach-to-teaching-a-renaissance-heroine-fall-2023", + "res-21g-3004-marguerite-de-navarre-society-website-fall-2023", + "res-21g-601-telebridges-russian-conversation-exchange-site-fall-2021", + "res-21h-001-visualizing-the-birth-of-modern-tokyo-spring-2021", + "res-21m-001-heavy-metal-101-january-iap-2023", + "res-7-001-pre-7-01-getting-up-to-speed-in-biology-summer-2019", + "res-7-003-brave-new-planet-fall-2020", + "res-7-004-bionook-online-biology-resources-spring-2021", + "res-7-006-7-03x-genetics", + "res-7-007-7-06x-cell-biology", + "res-7-008-7-28x-molecular-biology", + "res-8-002-a-wikitextbook-for-introductory-mechanics-fall-2009", + "res-8-003-physics-demonstration-videos-spring-2012", + "res-8-008-nuclear-weapons-education-project-spring-2022", + "res-9-004-nancys-brain-talks-fall-2022", + "res-9-005-fmri-bootcamp-fall-2017", + "res-9-006-afni-training-bootcamp-spring-2018", + "res-9-007-meg-workshop-spring-2019", + "res-9-008-brain-and-cognitive-sciences-computational-tutorials", + "res-bloss-blended-learning-open-source-science-or-math-studies-blossoms-spring-2010", + "res-cms-154-launching-innovation-in-schools-spring-2019", + "res-cms-155-design-thinking-for-leading-and-learning-spring-2019", + "res-cms-501-envisioning-the-graduate-of-the-future-spring-2020", + "res-cms-502-competency-based-education-the-why-what-and-how-spring-2020", + "res-cms-504-sorting-truth-from-fiction-civic-online-reasoning-spring-2021", + "res-ec-002-lean-research-skills-for-conducting-interviews-spring-2021", + "res-ec-003-d-lab-student-showcases-spring-2022", + "res-ec-004-energy-needs-assessment-toolkit", + "res-env-004-mit-climate-portal-fall-2020", + "res-env-005-climate-science-risk-solutions-a-climate-primer", + "res-hso-001-mit-haystack-observatory-k12-stem-lesson-plans", + "res-hst-001-mit-little-devices-lab-fall-2021", + "res-ll-001-introduction-to-radar-systems-spring-2007", + "res-ll-002-adaptive-antennas-and-phased-arrays-spring-2010", + "res-mas-001-raise-responsible-ai-for-social-empowerment-and-education-spring-2022", + "res-solvex-business-and-impact-planning-for-social-enterprises-0-solvex-summer-2021", + "res-tl-002-star-software-tools-for-academics-and-researchers-spring-2012", + "res-tll-007-case-studies-in-social-and-ethical-responsibilities-of-computing-fall-2021", +] + + +def populate_delivery(apps, schema_editor): + """ + Populate delivery field. Initial delivery should be identical to learning_format, + with "offline" added for OCW courses except ones in the list above. + """ + LearningResource = apps.get_model("learning_resources", "LearningResource") + urls = [ + urljoin(settings.OCW_BASE_URL, f"courses/{url}/") + for url in OCW_HIDDEN_DOWNLOADS + ] + LearningResource.objects.all().filter(published=True).update( + delivery=models.F("learning_format") + ) + LearningResource.objects.all().filter( + published=True, etl_source=ETLSource.ocw.name + ).exclude(url__in=urls).update( + delivery=[ + LearningResourceDelivery.online.name, + LearningResourceDelivery.offline.name, + ] + ) + + +class Migration(migrations.Migration): + dependencies = [ + ("learning_resources", "0061_learningresource_completeness"), + ] + + operations = [ + migrations.AddField( + model_name="learningresource", + name="delivery", + field=django.contrib.postgres.fields.ArrayField( + base_field=models.CharField( + choices=[ + ("online", "Online"), + ("hybrid", "Hybrid"), + ("in_person", "In person"), + ("offline", "Offline"), + ], + db_index=True, + max_length=24, + ), + default=learning_resources.models.default_learning_format, + size=None, + ), + ), + migrations.RunPython(populate_delivery, migrations.RunPython.noop), + ] diff --git a/learning_resources/models.py b/learning_resources/models.py index 5b9107c704..c38bf5c71e 100644 --- a/learning_resources/models.py +++ b/learning_resources/models.py @@ -16,6 +16,7 @@ from learning_resources.constants import ( Availability, CertificationType, + LearningResourceDelivery, LearningResourceFormat, LearningResourceRelationTypes, LearningResourceType, @@ -422,6 +423,12 @@ class LearningResource(TimestampedModel): choices=((member.name, member.value) for member in Availability), ) completeness = models.FloatField(default=1.0) + delivery = ArrayField( + models.CharField( + max_length=24, db_index=True, choices=LearningResourceDelivery.as_tuple() + ), + default=default_learning_format, + ) @property def audience(self) -> str | None: diff --git a/learning_resources/serializers.py b/learning_resources/serializers.py index 0e7c2c9875..b2be2cc74b 100644 --- a/learning_resources/serializers.py +++ b/learning_resources/serializers.py @@ -15,6 +15,7 @@ from learning_resources.constants import ( LEARNING_MATERIAL_RESOURCE_CATEGORY, CertificationType, + LearningResourceDelivery, LearningResourceFormat, LearningResourceType, LevelType, @@ -236,6 +237,21 @@ def to_representation(self, value): return {"code": value, "name": LearningResourceFormat[value].value} +@extend_schema_field( + { + "type": "object", + "properties": { + "code": {"enum": LearningResourceDelivery.names()}, + "name": {"type": "string"}, + }, + "required": ["code", "name"], + } +) +class LearningResourceDeliverySerializer(serializers.Field): + def to_representation(self, value): + return {"code": value, "name": LearningResourceDelivery[value].value} + + class LearningResourceRunSerializer(serializers.ModelSerializer): """Serializer for the LearningResourceRun model""" @@ -410,6 +426,9 @@ class LearningResourceBaseSerializer(serializers.ModelSerializer, WriteableTopic learning_format = serializers.ListField( child=LearningResourceFormatSerializer(), read_only=True ) + delivery = serializers.ListField( + child=LearningResourceDeliverySerializer(), read_only=True + ) free = serializers.SerializerMethodField() resource_category = serializers.SerializerMethodField() diff --git a/learning_resources/serializers_test.py b/learning_resources/serializers_test.py index 33febe6187..92969fbd8f 100644 --- a/learning_resources/serializers_test.py +++ b/learning_resources/serializers_test.py @@ -12,6 +12,7 @@ from learning_resources.constants import ( LEARNING_MATERIAL_RESOURCE_CATEGORY, CertificationType, + LearningResourceDelivery, LearningResourceFormat, LearningResourceRelationTypes, LearningResourceType, @@ -262,6 +263,10 @@ def test_learning_resource_serializer( # noqa: PLR0913 {"code": lr_format, "name": LearningResourceFormat[lr_format].value} for lr_format in resource.learning_format ], + "delivery": [ + {"code": lr_delivery, "name": LearningResourceDelivery[lr_delivery].value} + for lr_delivery in resource.delivery + ], "next_start_date": resource.next_start_date, "availability": resource.availability, "completeness": 1.0, diff --git a/learning_resources_search/constants.py b/learning_resources_search/constants.py index c0e25dbbcf..c0e0e33677 100644 --- a/learning_resources_search/constants.py +++ b/learning_resources_search/constants.py @@ -76,6 +76,7 @@ class FilterConfig: "platform": FilterConfig("platform.code"), "offered_by": FilterConfig("offered_by.code"), "learning_format": FilterConfig("learning_format.code"), + "delivery": FilterConfig("delivery.code"), "resource_category": FilterConfig("resource_category"), } @@ -121,6 +122,13 @@ class FilterConfig: "name": {"type": "keyword"}, }, }, + "delivery": { + "type": "nested", + "properties": { + "code": {"type": "keyword"}, + "name": {"type": "keyword"}, + }, + }, "readable_id": {"type": "keyword"}, "title": ENGLISH_TEXT_FIELD_WITH_SUGGEST, "description": ENGLISH_TEXT_FIELD_WITH_SUGGEST, diff --git a/learning_resources_search/serializers.py b/learning_resources_search/serializers.py index bcdc4829a5..a26aa7eb52 100644 --- a/learning_resources_search/serializers.py +++ b/learning_resources_search/serializers.py @@ -20,6 +20,7 @@ LEARNING_MATERIAL_RESOURCE_CATEGORY, RESOURCE_CATEGORY_VALUES, CertificationType, + LearningResourceDelivery, LearningResourceFormat, LearningResourceRelationTypes, LearningResourceType, @@ -239,6 +240,7 @@ def to_representation(self, obj): "professional", "free", "learning_format", + "delivery", "resource_category", ] @@ -398,6 +400,15 @@ class LearningResourcesSearchRequestSerializer(SearchRequestSerializer): \n\n{build_choice_description_list(learning_format_choices)}" ), ) + delivery_choices = LearningResourceDelivery.as_list() + delivery = serializers.ListField( + required=False, + child=serializers.ChoiceField(choices=delivery_choices), + help_text=( + f"The delivery options in which the learning resource is offered \ + \n\n{build_choice_description_list(delivery_choices)}" + ), + ) resource_category_choices = [ (value, value.replace("_", " ").title()) for value in RESOURCE_CATEGORY_VALUES ] diff --git a/learning_resources_search/serializers_test.py b/learning_resources_search/serializers_test.py index 9d09f6c0d3..9b0a66c12d 100644 --- a/learning_resources_search/serializers_test.py +++ b/learning_resources_search/serializers_test.py @@ -80,6 +80,16 @@ "name": "Online", } ], + "delivery": [ + { + "code": "online", + "name": "Online", + }, + { + "code": "offline", + "name": "Offline", + }, + ], "professional": True, "certification": "Certificates", "prices": [2250.0], @@ -222,6 +232,16 @@ "name": "Online", } ], + "delivery": [ + { + "code": "online", + "name": "Online", + }, + { + "code": "offline", + "name": "Offline", + }, + ], "professional": True, "certification": "Certificates", "prices": [2250.0], @@ -348,6 +368,16 @@ "name": "Online", } ], + "delivery": [ + { + "code": "online", + "name": "Online", + }, + { + "code": "offline", + "name": "Offline", + }, + ], "prices": [0.00], "last_modified": None, "runs": [], @@ -446,6 +476,17 @@ "buckets": [{"key": 0, "key_as_string": "online", "doc_count": 1}], }, }, + "delivery": { + "doc_count": 1, + "delivery": { + "doc_count_error_upper_bound": 0, + "sum_other_doc_count": 0, + "buckets": [ + {"key": 0, "key_as_string": "online", "doc_count": 1}, + {"key": 0, "key_as_string": "offline", "doc_count": 1}, + ], + }, + }, }, "suggest": { "description.trigram": [ @@ -512,6 +553,16 @@ "name": "Online", } ], + "delivery": [ + { + "code": "online", + "name": "Online", + }, + { + "code": "offline", + "name": "Offline", + }, + ], "prices": [0.00], "last_modified": None, "runs": [], @@ -534,6 +585,10 @@ "certification": [{"key": "false", "doc_count": 1}], "free": [{"key": "false", "doc_count": 1}], "learning_format": [{"key": "online", "doc_count": 1}], + "delivery": [ + {"key": "online", "doc_count": 1}, + {"key": "offline", "doc_count": 1}, + ], }, "suggest": ["broadignite"], }, diff --git a/openapi/specs/v1.yaml b/openapi/specs/v1.yaml index dbf8de09b2..8e9711f615 100644 --- a/openapi/specs/v1.yaml +++ b/openapi/specs/v1.yaml @@ -567,6 +567,38 @@ paths: description: Multiple values may be separated by commas. explode: false style: form + - in: query + name: delivery + schema: + type: array + items: + type: array + items: + enum: + - online + - hybrid + - in_person + - offline + type: string + description: |- + * `online` - Online + * `hybrid` - Hybrid + * `in_person` - In person + * `offline` - Offline + enum: + - hybrid + - in_person + - offline + - online + description: |- + The delivery of course/program resources + + * `online` - Online + * `hybrid` - Hybrid + * `in_person` - In person + * `offline` - Offline + explode: true + style: form - in: query name: department schema: @@ -1167,6 +1199,38 @@ paths: description: Multiple values may be separated by commas. explode: false style: form + - in: query + name: delivery + schema: + type: array + items: + type: array + items: + enum: + - online + - hybrid + - in_person + - offline + type: string + description: |- + * `online` - Online + * `hybrid` - Hybrid + * `in_person` - In person + * `offline` - Offline + enum: + - hybrid + - in_person + - offline + - online + description: |- + The delivery of course/program resources + + * `online` - Online + * `hybrid` - Hybrid + * `in_person` - In person + * `offline` - Offline + explode: true + style: form - in: query name: department schema: @@ -1560,6 +1624,38 @@ paths: description: Multiple values may be separated by commas. explode: false style: form + - in: query + name: delivery + schema: + type: array + items: + type: array + items: + enum: + - online + - hybrid + - in_person + - offline + type: string + description: |- + * `online` - Online + * `hybrid` - Hybrid + * `in_person` - In person + * `offline` - Offline + enum: + - hybrid + - in_person + - offline + - online + description: |- + The delivery of course/program resources + + * `online` - Online + * `hybrid` - Hybrid + * `in_person` - In person + * `offline` - Offline + explode: true + style: form - in: query name: department schema: @@ -2245,6 +2341,7 @@ paths: - professional - free - learning_format + - delivery - resource_category type: string description: |- @@ -2260,6 +2357,7 @@ paths: * `professional` - professional * `free` - free * `learning_format` - learning_format + * `delivery` - delivery * `resource_category` - resource_category description: Show resource counts by category - in: query @@ -2295,6 +2393,25 @@ paths: type: string minLength: 1 description: The course feature. Possible options are at api/v1/course_features/ + - in: query + name: delivery + schema: + type: array + items: + enum: + - online + - hybrid + - in_person + - offline + type: string + description: |- + * `online` - Online + * `hybrid` - Hybrid + * `in_person` - In person + * `offline` - Offline + description: "The delivery options in which the learning resource is offered\ + \ \n\n* `online` - Online\n* `hybrid` - Hybrid\n* `in_person`\ + \ - In person\n* `offline` - Offline" - in: query name: department schema: @@ -2734,6 +2851,7 @@ paths: - professional - free - learning_format + - delivery - resource_category type: string description: |- @@ -2749,6 +2867,7 @@ paths: * `professional` - professional * `free` - free * `learning_format` - learning_format + * `delivery` - delivery * `resource_category` - resource_category description: Show resource counts by category - in: query @@ -2784,6 +2903,25 @@ paths: type: string minLength: 1 description: The course feature. Possible options are at api/v1/course_features/ + - in: query + name: delivery + schema: + type: array + items: + enum: + - online + - hybrid + - in_person + - offline + type: string + description: |- + * `online` - Online + * `hybrid` - Hybrid + * `in_person` - In person + * `offline` - Offline + description: "The delivery options in which the learning resource is offered\ + \ \n\n* `online` - Online\n* `hybrid` - Hybrid\n* `in_person`\ + \ - In person\n* `offline` - Offline" - in: query name: department schema: @@ -3248,6 +3386,7 @@ paths: - professional - free - learning_format + - delivery - resource_category type: string description: |- @@ -3263,6 +3402,7 @@ paths: * `professional` - professional * `free` - free * `learning_format` - learning_format + * `delivery` - delivery * `resource_category` - resource_category description: Show resource counts by category - in: query @@ -3298,6 +3438,25 @@ paths: type: string minLength: 1 description: The course feature. Possible options are at api/v1/course_features/ + - in: query + name: delivery + schema: + type: array + items: + enum: + - online + - hybrid + - in_person + - offline + type: string + description: |- + * `online` - Online + * `hybrid` - Hybrid + * `in_person` - In person + * `offline` - Offline + description: "The delivery options in which the learning resource is offered\ + \ \n\n* `online` - Online\n* `hybrid` - Hybrid\n* `in_person`\ + \ - In person\n* `offline` - Offline" - in: query name: department schema: @@ -3753,6 +3912,7 @@ paths: - professional - free - learning_format + - delivery - resource_category type: string description: |- @@ -3768,6 +3928,7 @@ paths: * `professional` - professional * `free` - free * `learning_format` - learning_format + * `delivery` - delivery * `resource_category` - resource_category description: Show resource counts by category - in: query @@ -3803,6 +3964,25 @@ paths: type: string minLength: 1 description: The course feature. Possible options are at api/v1/course_features/ + - in: query + name: delivery + schema: + type: array + items: + enum: + - online + - hybrid + - in_person + - offline + type: string + description: |- + * `online` - Online + * `hybrid` - Hybrid + * `in_person` - In person + * `offline` - Offline + description: "The delivery options in which the learning resource is offered\ + \ \n\n* `online` - Online\n* `hybrid` - Hybrid\n* `in_person`\ + \ - In person\n* `offline` - Offline" - in: query name: department schema: @@ -4282,6 +4462,38 @@ paths: description: Multiple values may be separated by commas. explode: false style: form + - in: query + name: delivery + schema: + type: array + items: + type: array + items: + enum: + - online + - hybrid + - in_person + - offline + type: string + description: |- + * `online` - Online + * `hybrid` - Hybrid + * `in_person` - In person + * `offline` - Offline + enum: + - hybrid + - in_person + - offline + - online + description: |- + The delivery of course/program resources + + * `online` - Online + * `hybrid` - Hybrid + * `in_person` - In person + * `offline` - Offline + explode: true + style: form - in: query name: department schema: @@ -5000,6 +5212,38 @@ paths: description: Multiple values may be separated by commas. explode: false style: form + - in: query + name: delivery + schema: + type: array + items: + type: array + items: + enum: + - online + - hybrid + - in_person + - offline + type: string + description: |- + * `online` - Online + * `hybrid` - Hybrid + * `in_person` - In person + * `offline` - Offline + enum: + - hybrid + - in_person + - offline + - online + description: |- + The delivery of course/program resources + + * `online` - Online + * `hybrid` - Hybrid + * `in_person` - In person + * `offline` - Offline + explode: true + style: form - in: query name: department schema: @@ -5393,6 +5637,38 @@ paths: description: Multiple values may be separated by commas. explode: false style: form + - in: query + name: delivery + schema: + type: array + items: + type: array + items: + enum: + - online + - hybrid + - in_person + - offline + type: string + description: |- + * `online` - Online + * `hybrid` - Hybrid + * `in_person` - In person + * `offline` - Offline + enum: + - hybrid + - in_person + - offline + - online + description: |- + The delivery of course/program resources + + * `online` - Online + * `hybrid` - Hybrid + * `in_person` - In person + * `offline` - Offline + explode: true + style: form - in: query name: department schema: @@ -5873,6 +6149,38 @@ paths: description: Multiple values may be separated by commas. explode: false style: form + - in: query + name: delivery + schema: + type: array + items: + type: array + items: + enum: + - online + - hybrid + - in_person + - offline + type: string + description: |- + * `online` - Online + * `hybrid` - Hybrid + * `in_person` - In person + * `offline` - Offline + enum: + - hybrid + - in_person + - offline + - online + description: |- + The delivery of course/program resources + + * `online` - Online + * `hybrid` - Hybrid + * `in_person` - In person + * `offline` - Offline + explode: true + style: form - in: query name: department schema: @@ -6656,6 +6964,38 @@ paths: description: Multiple values may be separated by commas. explode: false style: form + - in: query + name: delivery + schema: + type: array + items: + type: array + items: + enum: + - online + - hybrid + - in_person + - offline + type: string + description: |- + * `online` - Online + * `hybrid` - Hybrid + * `in_person` - In person + * `offline` - Offline + enum: + - hybrid + - in_person + - offline + - online + description: |- + The delivery of course/program resources + + * `online` - Online + * `hybrid` - Hybrid + * `in_person` - In person + * `offline` - Offline + explode: true + style: form - in: query name: department schema: @@ -7115,6 +7455,38 @@ paths: description: Multiple values may be separated by commas. explode: false style: form + - in: query + name: delivery + schema: + type: array + items: + type: array + items: + enum: + - online + - hybrid + - in_person + - offline + type: string + description: |- + * `online` - Online + * `hybrid` - Hybrid + * `in_person` - In person + * `offline` - Offline + enum: + - hybrid + - in_person + - offline + - online + description: |- + The delivery of course/program resources + + * `online` - Online + * `hybrid` - Hybrid + * `in_person` - In person + * `offline` - Offline + explode: true + style: form - in: query name: department schema: @@ -7485,6 +7857,7 @@ components: - professional - free - learning_format + - delivery - resource_category type: string description: |- @@ -7500,6 +7873,7 @@ components: * `professional` - professional * `free` - free * `learning_format` - learning_format + * `delivery` - delivery * `resource_category` - resource_category x-enum-descriptions: - resource_type @@ -7514,6 +7888,7 @@ components: - professional - free - learning_format + - delivery - resource_category Article: type: object @@ -7893,6 +8268,23 @@ components: - code - name readOnly: true + delivery: + type: array + items: + type: object + properties: + code: + enum: + - online + - hybrid + - in_person + - offline + name: + type: string + required: + - code + - name + readOnly: true free: type: boolean description: Return true if the resource is free/has a free option @@ -7959,6 +8351,7 @@ components: - certification_type - course - course_feature + - delivery - departments - free - id @@ -8036,6 +8429,23 @@ components: type: string enum: - course + DeliveryEnum: + enum: + - online + - hybrid + - in_person + - offline + type: string + description: |- + * `online` - Online + * `hybrid` - Hybrid + * `in_person` - In person + * `offline` - Offline + x-enum-descriptions: + - Online + - Hybrid + - In person + - Offline DepartmentEnum: enum: - '1' @@ -8317,6 +8727,23 @@ components: - code - name readOnly: true + delivery: + type: array + items: + type: object + properties: + code: + enum: + - online + - hybrid + - in_person + - offline + name: + type: string + required: + - code + - name + readOnly: true free: type: boolean description: Return true if the resource is free/has a free option @@ -8381,6 +8808,7 @@ components: - certification - certification_type - course_feature + - delivery - departments - free - id @@ -9985,6 +10413,13 @@ components: description: "The format(s) in which the learning resource is offered \ \ \n\n* `online` - Online\n* `hybrid` - Hybrid\n* `in_person`\ \ - In person" + delivery: + type: array + items: + $ref: '#/components/schemas/DeliveryEnum' + description: "The delivery options in which the learning resource is offered\ + \ \n\n* `online` - Online\n* `hybrid` - Hybrid\n* `in_person`\ + \ - In person\n* `offline` - Offline" resource_category: type: array items: @@ -10283,6 +10718,23 @@ components: - code - name readOnly: true + delivery: + type: array + items: + type: object + properties: + code: + enum: + - online + - hybrid + - in_person + - offline + name: + type: string + required: + - code + - name + readOnly: true free: type: boolean description: Return true if the resource is free/has a free option @@ -10348,6 +10800,7 @@ components: - certification - certification_type - course_feature + - delivery - departments - free - id @@ -10550,6 +11003,23 @@ components: - code - name readOnly: true + delivery: + type: array + items: + type: object + properties: + code: + enum: + - online + - hybrid + - in_person + - offline + name: + type: string + required: + - code + - name + readOnly: true free: type: boolean description: Return true if the resource is free/has a free option @@ -10615,6 +11085,7 @@ components: - certification - certification_type - course_feature + - delivery - departments - free - id @@ -10947,6 +11418,23 @@ components: - code - name readOnly: true + delivery: + type: array + items: + type: object + properties: + code: + enum: + - online + - hybrid + - in_person + - offline + name: + type: string + required: + - code + - name + readOnly: true free: type: boolean description: Return true if the resource is free/has a free option @@ -11012,6 +11500,7 @@ components: - certification - certification_type - course_feature + - delivery - departments - free - id @@ -11486,6 +11975,23 @@ components: - code - name readOnly: true + delivery: + type: array + items: + type: object + properties: + code: + enum: + - online + - hybrid + - in_person + - offline + name: + type: string + required: + - code + - name + readOnly: true free: type: boolean description: Return true if the resource is free/has a free option @@ -11551,6 +12057,7 @@ components: - certification - certification_type - course_feature + - delivery - departments - free - id @@ -11739,6 +12246,23 @@ components: - code - name readOnly: true + delivery: + type: array + items: + type: object + properties: + code: + enum: + - online + - hybrid + - in_person + - offline + name: + type: string + required: + - code + - name + readOnly: true free: type: boolean description: Return true if the resource is free/has a free option @@ -11804,6 +12328,7 @@ components: - certification - certification_type - course_feature + - delivery - departments - free - id diff --git a/test_json/courses/16-01-unified-engineering-i-ii-iii-iv-fall-2005-spring-2006/data.json b/test_json/courses/16-01-unified-engineering-i-ii-iii-iv-fall-2005-spring-2006/data.json index ca230e9bb8..542e96fb1b 100644 --- a/test_json/courses/16-01-unified-engineering-i-ii-iii-iv-fall-2005-spring-2006/data.json +++ b/test_json/courses/16-01-unified-engineering-i-ii-iii-iv-fall-2005-spring-2006/data.json @@ -97,6 +97,7 @@ "term": "Fall", "year": "2005", "level": ["Undergraduate", "High School"], + "hide_download": false, "image_src": "/courses/16-01-unified-engineering-i-ii-iii-iv-fall-2005-spring-2006/8f56bbb35d0e456dc8b70911bec7cd0d_16-01f05.jpg", "course_image_metadata": { "content_type": "resource", From b64bcb88e60bf66bba225dbc97ba2793f0ac54ec Mon Sep 17 00:00:00 2001 From: Carey P Gumaer Date: Tue, 10 Sep 2024 12:33:36 -0400 Subject: [PATCH 07/18] learning resource drawer list buttons (#1537) * add the buttons to the drawer * add click handlers * fix imports * fix existing tests * add a test that asserts the proper list buttons are shown in the resource drawer given a known authentication status * make click handlers optional * fix typecheck issue and only import user type * test feedback * only check is_learning_path_editor and remove unrealistic test case * better conditional * again, no need to check authentication status here --- .../LearningResourceDrawer.test.tsx | 84 +++++++++++++++++++ .../LearningResourceDrawer.tsx | 35 +++++++- frontends/ol-ckeditor/src/types/settings.d.ts | 1 + .../LearningResourceExpanded/InfoSection.tsx | 61 +++++++++++++- .../LearningResourceExpanded.tsx | 16 +++- 5 files changed, 191 insertions(+), 6 deletions(-) diff --git a/frontends/mit-learn/src/page-components/LearningResourceDrawer/LearningResourceDrawer.test.tsx b/frontends/mit-learn/src/page-components/LearningResourceDrawer/LearningResourceDrawer.test.tsx index 87704724bd..bbfb594b2c 100644 --- a/frontends/mit-learn/src/page-components/LearningResourceDrawer/LearningResourceDrawer.test.tsx +++ b/frontends/mit-learn/src/page-components/LearningResourceDrawer/LearningResourceDrawer.test.tsx @@ -5,6 +5,7 @@ import { renderWithProviders, screen, waitFor, + within, } from "@/test-utils" import LearningResourceDrawer, { useOpenLearningResourceDrawer, @@ -12,6 +13,8 @@ import LearningResourceDrawer, { import { urls, factories, setMockResponse } from "api/test-utils" import { LearningResourceExpanded } from "ol-components" import { RESOURCE_DRAWER_QUERY_PARAM } from "@/common/urls" +import { ResourceTypeEnum } from "api" +import invariant from "tiny-invariant" jest.mock("ol-components", () => { const actual = jest.requireActual("ol-components") @@ -40,6 +43,7 @@ describe("LearningResourceDrawer", () => { ])( "Renders drawer content when resource=id is in the URL and captures the view if PostHog $descriptor", async ({ enablePostHog }) => { + setMockResponse.get(urls.userMe.get(), {}) APP_SETTINGS.POSTHOG = { api_key: enablePostHog ? "test1234" : "", // pragma: allowlist secret } @@ -95,4 +99,84 @@ describe("LearningResourceDrawer", () => { dog: "woof", }) }) + + test.each([ + { + isLearningPathEditor: true, + isAuthenticated: true, + expectAddToLearningPathButton: true, + expectAddToUserListButton: true, + }, + { + isLearningPathEditor: false, + isAuthenticated: true, + expectAddToLearningPathButton: false, + expectAddToUserListButton: true, + }, + { + isLearningPathEditor: false, + isAuthenticated: false, + expectAddToLearningPathButton: false, + expectAddToUserListButton: false, + }, + ])( + "Renders info section list buttons correctly", + async ({ + isLearningPathEditor, + isAuthenticated, + expectAddToLearningPathButton, + expectAddToUserListButton, + }) => { + const resource = factories.learningResources.resource({ + resource_type: ResourceTypeEnum.Course, + runs: [ + factories.learningResources.run({ + languages: ["en-us", "es-es", "fr-fr"], + }), + ], + }) + setMockResponse.get( + urls.learningResources.details({ id: resource.id }), + resource, + ) + + renderWithProviders(, { + url: `?resource=${resource.id}`, + user: { + is_learning_path_editor: isLearningPathEditor, + is_authenticated: isAuthenticated, + }, + }) + + expect(LearningResourceExpanded).toHaveBeenCalled() + + await waitFor(() => { + expectProps(LearningResourceExpanded, { resource }) + }) + + const section = screen + .getByRole("heading", { name: "Info" }) + .closest("section") + invariant(section) + + if (!isAuthenticated) { + const buttons = within(section).queryAllByRole("button") + expect(buttons).toHaveLength(0) + return + } else { + const buttons = within(section).getAllByRole("button") + const expectedButtons = + expectAddToLearningPathButton && expectAddToUserListButton ? 2 : 1 + expect(buttons).toHaveLength(expectedButtons) + expect( + !!within(section).queryByRole("button", { + name: "Add to Learning Path", + }), + ).toBe(expectAddToLearningPathButton) + expect( + !!within(section).queryByRole("button", { name: "Add to User List" }), + ).toBe(expectAddToUserListButton) + } + }, + ) }) diff --git a/frontends/mit-learn/src/page-components/LearningResourceDrawer/LearningResourceDrawer.tsx b/frontends/mit-learn/src/page-components/LearningResourceDrawer/LearningResourceDrawer.tsx index 754b43957b..cc762d7b83 100644 --- a/frontends/mit-learn/src/page-components/LearningResourceDrawer/LearningResourceDrawer.tsx +++ b/frontends/mit-learn/src/page-components/LearningResourceDrawer/LearningResourceDrawer.tsx @@ -1,15 +1,24 @@ -import React, { useEffect, useCallback } from "react" +import React, { useEffect, useCallback, useMemo } from "react" import { RoutedDrawer, LearningResourceExpanded, imgConfigs, } from "ol-components" -import type { RoutedDrawerProps } from "ol-components" +import type { + LearningResourceCardProps, + RoutedDrawerProps, +} from "ol-components" import { useLearningResourcesDetail } from "api/hooks/learningResources" import { useSearchParams, useLocation } from "react-router-dom" import { RESOURCE_DRAWER_QUERY_PARAM } from "@/common/urls" import { usePostHog } from "posthog-js/react" import MetaTags from "@/page-components/MetaTags/MetaTags" +import { useUserMe } from "api/hooks/user" +import NiceModal from "@ebay/nice-modal-react" +import { + AddToLearningPathDialog, + AddToUserListDialog, +} from "../Dialogs/AddToListDialog" const RESOURCE_DRAWER_PARAMS = [RESOURCE_DRAWER_QUERY_PARAM] as const @@ -56,6 +65,25 @@ const DrawerContent: React.FC<{ resourceId: number }> = ({ resourceId }) => { const resource = useLearningResourcesDetail(Number(resourceId)) + const { data: user } = useUserMe() + const handleAddToLearningPathClick: LearningResourceCardProps["onAddToLearningPathClick"] = + useMemo(() => { + if (user?.is_learning_path_editor) { + return (event, resourceId: number) => { + NiceModal.show(AddToLearningPathDialog, { resourceId }) + } + } + return null + }, [user]) + const handleAddToUserListClick: LearningResourceCardProps["onAddToUserListClick"] = + useMemo(() => { + if (user?.is_authenticated) { + return (event, resourceId: number) => { + NiceModal.show(AddToUserListDialog, { resourceId }) + } + } + return null + }, [user]) useCapturePageView(Number(resourceId)) return ( @@ -70,6 +98,9 @@ const DrawerContent: React.FC<{ ) diff --git a/frontends/ol-ckeditor/src/types/settings.d.ts b/frontends/ol-ckeditor/src/types/settings.d.ts index fbb37f3706..237e6ca039 100644 --- a/frontends/ol-ckeditor/src/types/settings.d.ts +++ b/frontends/ol-ckeditor/src/types/settings.d.ts @@ -8,5 +8,6 @@ export declare global { MITOL_API_BASE_URL: string PUBLIC_URL: string SITE_NAME: string + CSRF_COOKIE_NAME: string } } diff --git a/frontends/ol-components/src/components/LearningResourceExpanded/InfoSection.tsx b/frontends/ol-components/src/components/LearningResourceExpanded/InfoSection.tsx index 315f2a3e62..67c767fb42 100644 --- a/frontends/ol-components/src/components/LearningResourceExpanded/InfoSection.tsx +++ b/frontends/ol-components/src/components/LearningResourceExpanded/InfoSection.tsx @@ -13,6 +13,8 @@ import { RiTranslate2, RiAwardLine, RiPresentationLine, + RiMenuAddLine, + RiBookmarkLine, } from "@remixicon/react" import { LearningResource, LearningResourceRun, ResourceTypeEnum } from "api" import { @@ -21,6 +23,9 @@ import { } from "ol-utilities" import { theme } from "../ThemeProvider/ThemeProvider" import Typography from "@mui/material/Typography" +import type { User } from "api/hooks/user" +import { CardActionButton } from "../LearningResourceCard/LearningResourceListCard" +import { LearningResourceCardProps } from "../LearningResourceCard/LearningResourceCard" const InfoItems = styled.section` display: flex; @@ -28,6 +33,17 @@ const InfoItems = styled.section` gap: 16px; ` +const InfoHeader = styled.div` + display: flex; + justify-content: space-between; + align-items: center; +` + +const ListButtonContainer = styled.div` + display: flex; + gap: 8px; +` + const InfoItemContainer = styled.div` display: flex; align-items: flex-start; @@ -223,14 +239,23 @@ const InfoItem = ({ label, Icon, value }: InfoItemProps) => { const InfoSection = ({ resource, run, + user, + onAddToLearningPathClick, + onAddToUserListClick, }: { resource?: LearningResource run?: LearningResourceRun + user?: User + onAddToLearningPathClick?: LearningResourceCardProps["onAddToLearningPathClick"] + onAddToUserListClick?: LearningResourceCardProps["onAddToUserListClick"] }) => { if (!resource) { return null } + const inUserList = !!resource?.user_list_parents?.length + const inLearningPath = !!resource?.learning_path_parents?.length + const infoItems = INFO_ITEMS.map(({ label, Icon, selector }) => ({ label, Icon, @@ -243,9 +268,39 @@ const InfoSection = ({ return ( - - Info - + + + Info + + + {user?.is_learning_path_editor && ( + + onAddToLearningPathClick + ? onAddToLearningPathClick(event, resource.id) + : null + } + > + + + )} + {user?.is_authenticated && ( + + onAddToUserListClick + ? onAddToUserListClick(event, resource.id) + : null + } + > + + + )} + + {infoItems.map((props, index) => ( ))} diff --git a/frontends/ol-components/src/components/LearningResourceExpanded/LearningResourceExpanded.tsx b/frontends/ol-components/src/components/LearningResourceExpanded/LearningResourceExpanded.tsx index a76f386725..8949a85719 100644 --- a/frontends/ol-components/src/components/LearningResourceExpanded/LearningResourceExpanded.tsx +++ b/frontends/ol-components/src/components/LearningResourceExpanded/LearningResourceExpanded.tsx @@ -20,6 +20,8 @@ import type { SimpleSelectProps } from "../SimpleSelect/SimpleSelect" import { EmbedlyCard } from "../EmbedlyCard/EmbedlyCard" import { PlatformLogo, PLATFORMS } from "../Logo/Logo" import InfoSection from "./InfoSection" +import type { User } from "api/hooks/user" +import { LearningResourceCardProps } from "../LearningResourceCard/LearningResourceCard" const Container = styled.div<{ padTop?: boolean }>` display: flex; @@ -138,7 +140,10 @@ const OnPlatform = styled.span` type LearningResourceExpandedProps = { resource?: LearningResource + user?: User imgConfig: EmbedlyConfig + onAddToLearningPathClick?: LearningResourceCardProps["onAddToLearningPathClick"] + onAddToUserListClick?: LearningResourceCardProps["onAddToUserListClick"] } const ImageSection: React.FC<{ @@ -314,7 +319,10 @@ const formatRunDate = ( const LearningResourceExpanded: React.FC = ({ resource, + user, imgConfig, + onAddToLearningPathClick, + onAddToUserListClick, }) => { const [selectedRun, setSelectedRun] = useState(resource?.runs?.[0]) @@ -411,7 +419,13 @@ const LearningResourceExpanded: React.FC = ({ - + ) } From dcf6b4e0c4f3ea76bdcabb830da65741f7005284 Mon Sep 17 00:00:00 2001 From: Nathan Levesque Date: Tue, 10 Sep 2024 16:27:46 -0400 Subject: [PATCH 08/18] Fix data migration erroring if a record exists (#1543) --- data_fixtures/migrations/0014_add_department_SP.py | 6 ++++-- scripts/run-django-dev.sh | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/data_fixtures/migrations/0014_add_department_SP.py b/data_fixtures/migrations/0014_add_department_SP.py index 817bbd8863..4b34bc1f30 100644 --- a/data_fixtures/migrations/0014_add_department_SP.py +++ b/data_fixtures/migrations/0014_add_department_SP.py @@ -30,7 +30,7 @@ def add_sp(apps, schema_editor): Channel = apps.get_model("channels", "Channel") ChannelDepartmentDetail = apps.get_model("channels", "ChannelDepartmentDetail") - department = LearningResourceDepartment.objects.create( + department, _ = LearningResourceDepartment.objects.get_or_create( department_id="SP", name="Special Programs", ) @@ -42,7 +42,9 @@ def add_sp(apps, schema_editor): name=slugify(department.name), title=department.name, ) - ChannelDepartmentDetail.objects.create(channel=channel, department=department) + ChannelDepartmentDetail.objects.get_or_create( + channel=channel, department=department + ) class Migration(migrations.Migration): diff --git a/scripts/run-django-dev.sh b/scripts/run-django-dev.sh index 89820d8a85..d3534c72b9 100755 --- a/scripts/run-django-dev.sh +++ b/scripts/run-django-dev.sh @@ -5,7 +5,7 @@ python3 manage.py collectstatic --noinput --clear python3 manage.py migrate --noinput python3 manage.py createcachetable -RUN_DATA_MIGRATIONS=true python3 manage.py migrate --noinput +python3 manage.py migrate --noinput # load required fixtures on development by default echo "Loading fixtures!" From 6afa145ac9e5947a8715627672acac6cf14a4b70 Mon Sep 17 00:00:00 2001 From: Matt Bertrand Date: Wed, 11 Sep 2024 14:41:14 -0400 Subject: [PATCH 09/18] license_cc and continuing_ed_credits fields (#1544) --- frontends/api/src/generated/v1/api.ts | 180 ++++++++++++++++++ learning_resources/etl/ocw.py | 1 + learning_resources/etl/ocw_test.py | 1 + learning_resources/etl/sloan.py | 1 + learning_resources/etl/sloan_test.py | 5 + learning_resources/etl/xpro.py | 2 + learning_resources/etl/xpro_test.py | 3 + .../0063_continuing_ed_credits_cc_license.py | 33 ++++ learning_resources/models.py | 4 + learning_resources/serializers_test.py | 2 + learning_resources_search/constants.py | 2 + learning_resources_search/serializers_test.py | 8 + openapi/specs/v1.yaml | 105 ++++++++++ test_json/test_sloan_courses.json | 24 +-- test_json/xpro_courses.json | 21 +- test_json/xpro_programs.json | 23 ++- 16 files changed, 389 insertions(+), 26 deletions(-) create mode 100644 learning_resources/migrations/0063_continuing_ed_credits_cc_license.py diff --git a/frontends/api/src/generated/v1/api.ts b/frontends/api/src/generated/v1/api.ts index 4ce15dede9..6c13703ea6 100644 --- a/frontends/api/src/generated/v1/api.ts +++ b/frontends/api/src/generated/v1/api.ts @@ -781,6 +781,18 @@ export interface CourseResource { * @memberof CourseResource */ completeness?: number + /** + * + * @type {boolean} + * @memberof CourseResource + */ + license_cc?: boolean + /** + * + * @type {string} + * @memberof CourseResource + */ + continuing_ed_credits?: string | null } /** @@ -950,6 +962,18 @@ export interface CourseResourceRequest { * @memberof CourseResourceRequest */ completeness?: number + /** + * + * @type {boolean} + * @memberof CourseResourceRequest + */ + license_cc?: boolean + /** + * + * @type {string} + * @memberof CourseResourceRequest + */ + continuing_ed_credits?: string | null } /** @@ -1512,6 +1536,18 @@ export interface LearningPathResource { * @memberof LearningPathResource */ completeness?: number + /** + * + * @type {boolean} + * @memberof LearningPathResource + */ + license_cc?: boolean + /** + * + * @type {string} + * @memberof LearningPathResource + */ + continuing_ed_credits?: string | null } /** @@ -1592,6 +1628,18 @@ export interface LearningPathResourceRequest { * @memberof LearningPathResourceRequest */ completeness?: number + /** + * + * @type {boolean} + * @memberof LearningPathResourceRequest + */ + license_cc?: boolean + /** + * + * @type {string} + * @memberof LearningPathResourceRequest + */ + continuing_ed_credits?: string | null } /** @@ -3379,6 +3427,18 @@ export interface PatchedLearningPathResourceRequest { * @memberof PatchedLearningPathResourceRequest */ completeness?: number + /** + * + * @type {boolean} + * @memberof PatchedLearningPathResourceRequest + */ + license_cc?: boolean + /** + * + * @type {string} + * @memberof PatchedLearningPathResourceRequest + */ + continuing_ed_credits?: string | null } /** @@ -4147,6 +4207,18 @@ export interface PodcastEpisodeResource { * @memberof PodcastEpisodeResource */ completeness?: number + /** + * + * @type {boolean} + * @memberof PodcastEpisodeResource + */ + license_cc?: boolean + /** + * + * @type {string} + * @memberof PodcastEpisodeResource + */ + continuing_ed_credits?: string | null } /** @@ -4227,6 +4299,18 @@ export interface PodcastEpisodeResourceRequest { * @memberof PodcastEpisodeResourceRequest */ completeness?: number + /** + * + * @type {boolean} + * @memberof PodcastEpisodeResourceRequest + */ + license_cc?: boolean + /** + * + * @type {string} + * @memberof PodcastEpisodeResourceRequest + */ + continuing_ed_credits?: string | null } /** @@ -4475,6 +4559,18 @@ export interface PodcastResource { * @memberof PodcastResource */ completeness?: number + /** + * + * @type {boolean} + * @memberof PodcastResource + */ + license_cc?: boolean + /** + * + * @type {string} + * @memberof PodcastResource + */ + continuing_ed_credits?: string | null } /** @@ -4555,6 +4651,18 @@ export interface PodcastResourceRequest { * @memberof PodcastResourceRequest */ completeness?: number + /** + * + * @type {boolean} + * @memberof PodcastResourceRequest + */ + license_cc?: boolean + /** + * + * @type {string} + * @memberof PodcastResourceRequest + */ + continuing_ed_credits?: string | null } /** @@ -5023,6 +5131,18 @@ export interface ProgramResource { * @memberof ProgramResource */ completeness?: number + /** + * + * @type {boolean} + * @memberof ProgramResource + */ + license_cc?: boolean + /** + * + * @type {string} + * @memberof ProgramResource + */ + continuing_ed_credits?: string | null } /** @@ -5103,6 +5223,18 @@ export interface ProgramResourceRequest { * @memberof ProgramResourceRequest */ completeness?: number + /** + * + * @type {boolean} + * @memberof ProgramResourceRequest + */ + license_cc?: boolean + /** + * + * @type {string} + * @memberof ProgramResourceRequest + */ + continuing_ed_credits?: string | null } /** @@ -5830,6 +5962,18 @@ export interface VideoPlaylistResource { * @memberof VideoPlaylistResource */ completeness?: number + /** + * + * @type {boolean} + * @memberof VideoPlaylistResource + */ + license_cc?: boolean + /** + * + * @type {string} + * @memberof VideoPlaylistResource + */ + continuing_ed_credits?: string | null } /** @@ -5910,6 +6054,18 @@ export interface VideoPlaylistResourceRequest { * @memberof VideoPlaylistResourceRequest */ completeness?: number + /** + * + * @type {boolean} + * @memberof VideoPlaylistResourceRequest + */ + license_cc?: boolean + /** + * + * @type {string} + * @memberof VideoPlaylistResourceRequest + */ + continuing_ed_credits?: string | null } /** @@ -6146,6 +6302,18 @@ export interface VideoResource { * @memberof VideoResource */ completeness?: number + /** + * + * @type {boolean} + * @memberof VideoResource + */ + license_cc?: boolean + /** + * + * @type {string} + * @memberof VideoResource + */ + continuing_ed_credits?: string | null } /** @@ -6226,6 +6394,18 @@ export interface VideoResourceRequest { * @memberof VideoResourceRequest */ completeness?: number + /** + * + * @type {boolean} + * @memberof VideoResourceRequest + */ + license_cc?: boolean + /** + * + * @type {string} + * @memberof VideoResourceRequest + */ + continuing_ed_credits?: string | null } /** diff --git a/learning_resources/etl/ocw.py b/learning_resources/etl/ocw.py index 5e2eb55051..12c5a81bbd 100644 --- a/learning_resources/etl/ocw.py +++ b/learning_resources/etl/ocw.py @@ -365,6 +365,7 @@ def transform_course(course_data: dict) -> dict: "unique_field": UNIQUE_FIELD, "availability": Availability.anytime.name, "delivery": parse_delivery(course_data), + "license_cc": True, } diff --git a/learning_resources/etl/ocw_test.py b/learning_resources/etl/ocw_test.py index 1b31ba70b3..b29a7cc00d 100644 --- a/learning_resources/etl/ocw_test.py +++ b/learning_resources/etl/ocw_test.py @@ -239,6 +239,7 @@ def test_transform_course( # noqa: PLR0913 assert transformed_json["runs"][0]["level"] == ["undergraduate", "high_school"] assert transformed_json["runs"][0]["semester"] == (term if term else None) assert transformed_json["runs"][0]["year"] == (year if year else None) + assert transformed_json["license_cc"] is True assert transformed_json["description"] == clean_data( course_json["course_description_html"] ) diff --git a/learning_resources/etl/sloan.py b/learning_resources/etl/sloan.py index fcb3fd289a..1446d9a479 100644 --- a/learning_resources/etl/sloan.py +++ b/learning_resources/etl/sloan.py @@ -226,6 +226,7 @@ def transform_course(course_data: dict, runs_data: dict) -> dict: }, "runs": [transform_run(run, course_data) for run in course_runs_data], "availability": parse_availability(course_runs_data), + "continuing_ed_credits": course_runs_data[0]["Continuing_Ed_Credits"], } return transformed_course if transformed_course.get("url") else None diff --git a/learning_resources/etl/sloan_test.py b/learning_resources/etl/sloan_test.py index b3ae4effc8..d7f4ff408a 100644 --- a/learning_resources/etl/sloan_test.py +++ b/learning_resources/etl/sloan_test.py @@ -144,6 +144,10 @@ def test_transform_course(mock_sloan_courses_data, mock_sloan_runs_data): assert transformed["delivery"] == transformed["learning_format"] assert transformed["image"] == parse_image(course_data) assert transformed["availability"] == parse_availability(course_runs_data) + assert ( + transformed["continuing_ed_credits"] + == course_runs_data[0]["Continuing_Ed_Credits"] + ) assert sorted(transformed.keys()) == sorted( [ "readable_id", @@ -164,6 +168,7 @@ def test_transform_course(mock_sloan_courses_data, mock_sloan_runs_data): "course", "runs", "availability", + "continuing_ed_credits", ] ) diff --git a/learning_resources/etl/xpro.py b/learning_resources/etl/xpro.py index 8090915284..b620473dea 100644 --- a/learning_resources/etl/xpro.py +++ b/learning_resources/etl/xpro.py @@ -155,6 +155,7 @@ def _transform_learning_resource_course(course): "certification": True, "certification_type": CertificationType.professional.name, "availability": course["availability"], + "continuing_ed_credits": course["credits"], } @@ -218,6 +219,7 @@ def transform_programs(programs): "certification": True, "certification_type": CertificationType.professional.name, "availability": program["availability"], + "continuing_ed_credits": program["credits"], } for program in programs ] diff --git a/learning_resources/etl/xpro_test.py b/learning_resources/etl/xpro_test.py index 35af294242..25ffdbbe9d 100644 --- a/learning_resources/etl/xpro_test.py +++ b/learning_resources/etl/xpro_test.py @@ -112,6 +112,7 @@ def test_xpro_transform_programs(mock_xpro_programs_data): "resource_type": LearningResourceType.program.name, "learning_format": transform_delivery(program_data.get("format")), "delivery": transform_delivery(program_data.get("format")), + "continuing_ed_credits": program_data.get("credits"), "runs": [ { "run_id": program_data["readable_id"], @@ -151,6 +152,7 @@ def test_xpro_transform_programs(mock_xpro_programs_data): "availability": Availability.dated.name, "topics": parse_topics(course_data), "resource_type": LearningResourceType.course.name, + "continuing_ed_credits": course_data.get("credits"), "runs": [ { "run_id": course_run_data["courseware_id"], @@ -256,6 +258,7 @@ def test_xpro_transform_courses(mock_xpro_courses_data): }, "certification": True, "certification_type": CertificationType.professional.name, + "continuing_ed_credits": course_data.get("credits"), } for course_data in mock_xpro_courses_data ] diff --git a/learning_resources/migrations/0063_continuing_ed_credits_cc_license.py b/learning_resources/migrations/0063_continuing_ed_credits_cc_license.py new file mode 100644 index 0000000000..e86616d0b7 --- /dev/null +++ b/learning_resources/migrations/0063_continuing_ed_credits_cc_license.py @@ -0,0 +1,33 @@ +# Generated by Django 4.2.15 on 2024-09-10 12:23 + +from django.db import migrations, models + + +def set_ocw_license_cc(apps, schema_editor): + """ + Set license_cc to True for OCW resources. + """ + LearningResource = apps.get_model("learning_resources", "LearningResource") + LearningResource.objects.filter(offered_by__code="ocw").update(license_cc=True) + + +class Migration(migrations.Migration): + dependencies = [ + ("learning_resources", "0062_learningresource_delivery_format_pace"), + ] + + operations = [ + migrations.AddField( + model_name="learningresource", + name="continuing_ed_credits", + field=models.DecimalField( + decimal_places=2, max_digits=5, blank=True, null=True + ), + ), + migrations.AddField( + model_name="learningresource", + name="license_cc", + field=models.BooleanField(default=False), + ), + migrations.RunPython(set_ocw_license_cc, migrations.RunPython.noop), + ] diff --git a/learning_resources/models.py b/learning_resources/models.py index c38bf5c71e..01446e8dd7 100644 --- a/learning_resources/models.py +++ b/learning_resources/models.py @@ -429,6 +429,10 @@ class LearningResource(TimestampedModel): ), default=default_learning_format, ) + license_cc = models.BooleanField(default=False) + continuing_ed_credits = models.DecimalField( + max_digits=5, decimal_places=2, null=True, blank=True + ) @property def audience(self) -> str | None: diff --git a/learning_resources/serializers_test.py b/learning_resources/serializers_test.py index 92969fbd8f..0e8457841d 100644 --- a/learning_resources/serializers_test.py +++ b/learning_resources/serializers_test.py @@ -270,6 +270,8 @@ def test_learning_resource_serializer( # noqa: PLR0913 "next_start_date": resource.next_start_date, "availability": resource.availability, "completeness": 1.0, + "continuing_ed_credits": resource.continuing_ed_credits, + "license_cc": resource.license_cc, } diff --git a/learning_resources_search/constants.py b/learning_resources_search/constants.py index c0e0e33677..1347806875 100644 --- a/learning_resources_search/constants.py +++ b/learning_resources_search/constants.py @@ -261,6 +261,8 @@ class FilterConfig: "resource_age_date": {"type": "date"}, "featured_rank": {"type": "float"}, "completeness": {"type": "float"}, + "license_cc": {"type": "boolean"}, + "continuing_ed_credits": {"type": "float"}, } diff --git a/learning_resources_search/serializers_test.py b/learning_resources_search/serializers_test.py index 9b0a66c12d..938d000f4b 100644 --- a/learning_resources_search/serializers_test.py +++ b/learning_resources_search/serializers_test.py @@ -90,6 +90,8 @@ "name": "Offline", }, ], + "continuing_ed_credits": 2.50, + "license_cc": False, "professional": True, "certification": "Certificates", "prices": [2250.0], @@ -242,6 +244,8 @@ "name": "Offline", }, ], + "continuing_ed_credits": 2.50, + "license_cc": False, "professional": True, "certification": "Certificates", "prices": [2250.0], @@ -378,6 +382,8 @@ "name": "Offline", }, ], + "continuing_ed_credits": None, + "license_cc": True, "prices": [0.00], "last_modified": None, "runs": [], @@ -563,6 +569,8 @@ "name": "Offline", }, ], + "continuing_ed_credits": None, + "license_cc": True, "prices": [0.00], "last_modified": None, "runs": [], diff --git a/openapi/specs/v1.yaml b/openapi/specs/v1.yaml index 8e9711f615..58006cf66b 100644 --- a/openapi/specs/v1.yaml +++ b/openapi/specs/v1.yaml @@ -8346,6 +8346,13 @@ components: completeness: type: number format: double + license_cc: + type: boolean + continuing_ed_credits: + type: string + format: decimal + pattern: ^-?\d{0,3}(?:\.\d{0,2})?$ + nullable: true required: - certification - certification_type @@ -8422,6 +8429,13 @@ components: completeness: type: number format: double + license_cc: + type: boolean + continuing_ed_credits: + type: string + format: decimal + pattern: ^-?\d{0,3}(?:\.\d{0,2})?$ + nullable: true required: - readable_id - title @@ -8804,6 +8818,13 @@ components: completeness: type: number format: double + license_cc: + type: boolean + continuing_ed_credits: + type: string + format: decimal + pattern: ^-?\d{0,3}(?:\.\d{0,2})?$ + nullable: true required: - certification - certification_type @@ -8877,6 +8898,13 @@ components: completeness: type: number format: double + license_cc: + type: boolean + continuing_ed_credits: + type: string + format: decimal + pattern: ^-?\d{0,3}(?:\.\d{0,2})?$ + nullable: true required: - title LearningPathResourceResourceTypeEnum: @@ -10189,6 +10217,13 @@ components: completeness: type: number format: double + license_cc: + type: boolean + continuing_ed_credits: + type: string + format: decimal + pattern: ^-?\d{0,3}(?:\.\d{0,2})?$ + nullable: true PatchedLearningResourceRelationshipRequest: type: object description: CRUD serializer for LearningResourceRelationship @@ -10796,6 +10831,13 @@ components: completeness: type: number format: double + license_cc: + type: boolean + continuing_ed_credits: + type: string + format: decimal + pattern: ^-?\d{0,3}(?:\.\d{0,2})?$ + nullable: true required: - certification - certification_type @@ -10872,6 +10914,13 @@ components: completeness: type: number format: double + license_cc: + type: boolean + continuing_ed_credits: + type: string + format: decimal + pattern: ^-?\d{0,3}(?:\.\d{0,2})?$ + nullable: true required: - readable_id - title @@ -11081,6 +11130,13 @@ components: completeness: type: number format: double + license_cc: + type: boolean + continuing_ed_credits: + type: string + format: decimal + pattern: ^-?\d{0,3}(?:\.\d{0,2})?$ + nullable: true required: - certification - certification_type @@ -11157,6 +11213,13 @@ components: completeness: type: number format: double + license_cc: + type: boolean + continuing_ed_credits: + type: string + format: decimal + pattern: ^-?\d{0,3}(?:\.\d{0,2})?$ + nullable: true required: - readable_id - title @@ -11496,6 +11559,13 @@ components: completeness: type: number format: double + license_cc: + type: boolean + continuing_ed_credits: + type: string + format: decimal + pattern: ^-?\d{0,3}(?:\.\d{0,2})?$ + nullable: true required: - certification - certification_type @@ -11572,6 +11642,13 @@ components: completeness: type: number format: double + license_cc: + type: boolean + continuing_ed_credits: + type: string + format: decimal + pattern: ^-?\d{0,3}(?:\.\d{0,2})?$ + nullable: true required: - readable_id - title @@ -12053,6 +12130,13 @@ components: completeness: type: number format: double + license_cc: + type: boolean + continuing_ed_credits: + type: string + format: decimal + pattern: ^-?\d{0,3}(?:\.\d{0,2})?$ + nullable: true required: - certification - certification_type @@ -12129,6 +12213,13 @@ components: completeness: type: number format: double + license_cc: + type: boolean + continuing_ed_credits: + type: string + format: decimal + pattern: ^-?\d{0,3}(?:\.\d{0,2})?$ + nullable: true required: - readable_id - title @@ -12324,6 +12415,13 @@ components: completeness: type: number format: double + license_cc: + type: boolean + continuing_ed_credits: + type: string + format: decimal + pattern: ^-?\d{0,3}(?:\.\d{0,2})?$ + nullable: true required: - certification - certification_type @@ -12400,6 +12498,13 @@ components: completeness: type: number format: double + license_cc: + type: boolean + continuing_ed_credits: + type: string + format: decimal + pattern: ^-?\d{0,3}(?:\.\d{0,2})?$ + nullable: true required: - readable_id - title diff --git a/test_json/test_sloan_courses.json b/test_json/test_sloan_courses.json index 739cec57be..9004fcea30 100644 --- a/test_json/test_sloan_courses.json +++ b/test_json/test_sloan_courses.json @@ -1,4 +1,16 @@ [ + { + "Title": "Accelerating Digital Transformation with Algorithmic Business Thinking", + "Course_Id": "a056g00000URaaQAAT", + "SourceCreateDate": "2020-02-10T14:56:33.000Z", + "SourceLastModifiedDate": "2024-08-12T21:17:05.000Z", + "Description": "Digital transformation marks a radical rethinking of how technology, people, and processes combine to create and deliver value. This exciting digital transformation education course introduces the concept of Algorithmic Business Thinking as a toolkit, mindset and digital language for uniting and augmenting your human and digital capabilities that helps you discover and uncover new opportunities throughout your digital transformation journey.", + "Topics": "Business: Digital Business/IT", + "Image_Src": "https://executive.mit.edu/on/demandware.static/-/Sites-master-catalog-msee/default/v1d81274c4ad1cd9f5fe6a90da39cc7a906bcee40/images/ABT.jpg", + "BrochureLink": "https://executive.mit.edu/on/demandware.static/-/Sites-master-catalog-msee/default/dw7479c675/brochures/Accelerating%20Digital%20Transformation%20with%20Algorithmic%20Business%20Thinking.pdf", + "URL": "https://executive.mit.edu/course/accelerating-digital-transformation-with-algorithmic-business-thinking/a056g00000URaaQAAT.html", + "Certification_Type": "Digital Business" + }, { "Title": "Building and Scaling Intelligent Enterprises with Artificial Intelligence", "Course_Id": "a054v00000jimlBAAQ", @@ -911,18 +923,6 @@ "URL": "https://executive.mit.edu/course/pricing/a056g00000URaaPAAT.html", "Certification_Type": "Strategy and Innovation" }, - { - "Title": "Accelerating Digital Transformation with Algorithmic Business Thinking", - "Course_Id": "a056g00000URaaQAAT", - "SourceCreateDate": "2020-02-10T14:56:33.000Z", - "SourceLastModifiedDate": "2024-08-12T21:17:05.000Z", - "Description": "Digital transformation marks a radical rethinking of how technology, people, and processes combine to create and deliver value. This exciting digital transformation education course introduces the concept of Algorithmic Business Thinking as a toolkit, mindset and digital language for uniting and augmenting your human and digital capabilities that helps you discover and uncover new opportunities throughout your digital transformation journey.", - "Topics": "Business: Digital Business/IT", - "Image_Src": "https://executive.mit.edu/on/demandware.static/-/Sites-master-catalog-msee/default/v1d81274c4ad1cd9f5fe6a90da39cc7a906bcee40/images/ABT.jpg", - "BrochureLink": "https://executive.mit.edu/on/demandware.static/-/Sites-master-catalog-msee/default/dw7479c675/brochures/Accelerating%20Digital%20Transformation%20with%20Algorithmic%20Business%20Thinking.pdf", - "URL": "https://executive.mit.edu/course/accelerating-digital-transformation-with-algorithmic-business-thinking/a056g00000URaaQAAT.html", - "Certification_Type": "Digital Business" - }, { "Title": "Mastering Negotiation and Influence", "Course_Id": "a056g00000URaaRAAT", diff --git a/test_json/xpro_courses.json b/test_json/xpro_courses.json index ebbc01b851..1cf1fc6266 100644 --- a/test_json/xpro_courses.json +++ b/test_json/xpro_courses.json @@ -11,7 +11,8 @@ "platform": "xPRO", "topics": [{ "name": "Business:Leadership & Organizations" }], "format": "Online", - "availability": "dated" + "availability": "dated", + "credits": "1.25" }, { "id": 20, @@ -39,7 +40,8 @@ "next_run_id": 49, "topics": [{ "name": "Business:Leadership & Organizations" }], "format": "In person", - "availability": "dated" + "availability": "dated", + "credits": "2.25" }, { "id": 19, @@ -68,7 +70,8 @@ "next_run_id": 48, "topics": [{ "name": "Business:Leadership & Organizations" }], "format": "Online", - "availability": "dated" + "availability": "dated", + "credits": "3.25" }, { "id": 21, @@ -81,7 +84,8 @@ "topics": [{ "name": "Business:Leadership & Organizations" }], "platform": "xPRO", "format": "Online", - "availability": "dated" + "availability": "dated", + "credits": "4.25" }, { "id": 30, @@ -131,7 +135,8 @@ ], "next_run_id": 73, "topics": [{ "name": "Business:Leadership & Organizations" }], - "availability": "dated" + "availability": "dated", + "credits": "1.25" }, { "id": 31, @@ -144,7 +149,8 @@ "topics": [{ "name": "Business:Leadership & Organizations" }], "format": "Online", "platform": "xPRO", - "availability": "dated" + "availability": "dated", + "credits": "2.00" }, { "id": 32, @@ -157,6 +163,7 @@ "topics": [{ "name": "Business:Leadership & Organizations" }], "format": "Online", "platform": "xPRO", - "availability": "dated" + "availability": "dated", + "credits": "9.99" } ] diff --git a/test_json/xpro_programs.json b/test_json/xpro_programs.json index e97cfb6ad1..ed1ed88262 100644 --- a/test_json/xpro_programs.json +++ b/test_json/xpro_programs.json @@ -20,6 +20,7 @@ ], "format": "Online", "platform": "xPRO", + "credits": "11.25", "courses": [ { "id": 18, @@ -36,7 +37,8 @@ ], "platform": "xPRO", "format": "Online", - "availability": "dated" + "availability": "dated", + "credits": "11.25" }, { "id": 20, @@ -64,7 +66,8 @@ } ], "next_run_id": 49, - "availability": "dated" + "availability": "dated", + "credits": "11.25" }, { "id": 19, @@ -96,7 +99,8 @@ } ], "next_run_id": 48, - "availability": "dated" + "availability": "dated", + "credits": "11.25" }, { "id": 21, @@ -108,7 +112,8 @@ "next_run_id": null, "topics": [], "platform": "xPRO", - "availability": "dated" + "availability": "dated", + "credits": "11.25" } ], "availability": "dated" @@ -131,6 +136,7 @@ ], "format": "In person", "platform": "xPRO", + "credits": "101.25", "courses": [ { "id": 30, @@ -184,7 +190,8 @@ } ], "next_run_id": 73, - "availability": "dated" + "availability": "dated", + "credits": "11.25" }, { "id": 31, @@ -200,7 +207,8 @@ } ], "platform": "xPRO", - "availability": "dated" + "availability": "dated", + "credits": "11.25" }, { "id": 32, @@ -212,7 +220,8 @@ "next_run_id": null, "topics": [], "platform": "xPRO", - "availability": "dated" + "availability": "dated", + "credits": "11.25" } ], "availability": "dated" From 61c6441306942287eee783105a075d1830e2b011 Mon Sep 17 00:00:00 2001 From: Matt Bertrand Date: Wed, 11 Sep 2024 15:41:34 -0400 Subject: [PATCH 10/18] ETL pipeline for MIT edX programs (#1536) --- app.json | 4 + env/codespaces.env | 1 + learning_resources/etl/conftest.py | 8 + learning_resources/etl/mit_edx.py | 3 +- learning_resources/etl/mit_edx_programs.py | 73 + .../etl/mit_edx_programs_test.py | 13 + learning_resources/etl/openedx.py | 270 ++- learning_resources/etl/openedx_test.py | 184 +- learning_resources/etl/pipelines.py | 14 +- learning_resources/etl/pipelines_test.py | 33 +- learning_resources/tasks.py | 11 +- learning_resources/tasks_test.py | 5 +- main/settings_course_etl.py | 1 + test_json/test_mitx_programs.json | 1535 +++++++++++++++++ 14 files changed, 2091 insertions(+), 64 deletions(-) create mode 100644 learning_resources/etl/mit_edx_programs.py create mode 100644 learning_resources/etl/mit_edx_programs_test.py create mode 100644 test_json/test_mitx_programs.json diff --git a/app.json b/app.json index 9ddc3405dd..52b749d30b 100644 --- a/app.json +++ b/app.json @@ -103,6 +103,10 @@ "description": "S3 prefix for MITx bucket keys", "required": false }, + "EDX_PROGRAMS_API_URL": { + "description": "The catalog url for MITx programs", + "required": false + }, "OPENSEARCH_HTTP_AUTH": { "description": "Basic auth settings for connecting to OpenSearch" }, diff --git a/env/codespaces.env b/env/codespaces.env index ea84a2d1c7..590a225186 100644 --- a/env/codespaces.env +++ b/env/codespaces.env @@ -34,6 +34,7 @@ XPRO_CATALOG_API_URL=https://xpro.mit.edu/api/programs/ XPRO_COURSES_API_URL=https://xpro.mit.edu/api/courses/ EDX_API_ACCESS_TOKEN_URL=https://api.edx.org/oauth2/v1/access_token EDX_API_URL=https://api.edx.org/catalog/v1/catalogs/10/courses +EDX_PROGRAMS_API_URL=https://discovery.edx.org/api/v1/programs/ OCW_BASE_URL=https://ocw.mit.edu/ MICROMASTERS_CATALOG_API_URL=https://micromasters.mit.edu/api/v0/catalog/ MICROMASTERS_COURSE_URL=https://micromasters.mit.edu/api/v0/courseruns/ diff --git a/learning_resources/etl/conftest.py b/learning_resources/etl/conftest.py index 042719c531..12ce7de2ef 100644 --- a/learning_resources/etl/conftest.py +++ b/learning_resources/etl/conftest.py @@ -1,6 +1,7 @@ """Common ETL test fixtures""" import json +from pathlib import Path import pytest @@ -31,3 +32,10 @@ def non_mitx_course_data(): """Catalog data fixture""" with open("./test_json/test_non_mitx_course.json") as f: # noqa: PTH123 yield json.loads(f.read()) + + +@pytest.fixture +def mitx_programs_data(): + """Yield a data fixture for MITx programs""" + with Path.open(Path("./test_json/test_mitx_programs.json")) as f: + yield json.loads(f.read()) diff --git a/learning_resources/etl/mit_edx.py b/learning_resources/etl/mit_edx.py index cc2ebf1f68..4bd4da9de4 100644 --- a/learning_resources/etl/mit_edx.py +++ b/learning_resources/etl/mit_edx.py @@ -5,7 +5,7 @@ from django.conf import settings from toolz import compose, curried -from learning_resources.constants import OfferedBy, PlatformType +from learning_resources.constants import LearningResourceType, OfferedBy, PlatformType from learning_resources.etl.constants import ETLSource from learning_resources.etl.openedx import ( MIT_OWNER_KEYS, @@ -71,6 +71,7 @@ def get_open_edx_config(): PlatformType.edx.name, OfferedBy.mitx.name, ETLSource.mit_edx.name, + LearningResourceType.course.name, ) diff --git a/learning_resources/etl/mit_edx_programs.py b/learning_resources/etl/mit_edx_programs.py new file mode 100644 index 0000000000..5abc881893 --- /dev/null +++ b/learning_resources/etl/mit_edx_programs.py @@ -0,0 +1,73 @@ +"""MIT edX ETL""" + +import logging + +from django.conf import settings +from toolz import compose, curried + +from learning_resources.constants import LearningResourceType, OfferedBy, PlatformType +from learning_resources.etl.constants import ETLSource +from learning_resources.etl.openedx import ( + MIT_OWNER_KEYS, + OpenEdxConfiguration, + openedx_extract_transform_factory, +) + +log = logging.getLogger() + + +def _is_mit_program(program: dict) -> bool: + """ + Helper function to determine if a program is an MIT program + + Args: + program (dict): The JSON object representing the program with all its courses + + Returns: + bool: indicates whether the program is owned by MIT + """ # noqa: D401 + return ( + any( + owner["key"] in MIT_OWNER_KEYS + for owner in program.get("authoring_organizations") + ) + and "micromasters" not in program.get("type", "").lower() + and program.get("status") == "active" + ) + + +def get_open_edx_config(): + """ + Return the program OpenEdxConfiguration for edX. + """ + required_settings = [ + "EDX_API_CLIENT_ID", + "EDX_API_CLIENT_SECRET", + "EDX_API_ACCESS_TOKEN_URL", + "EDX_PROGRAMS_API_URL", + "EDX_BASE_URL", + "EDX_ALT_URL", + ] + for setting in required_settings: + if not getattr(settings, setting): + log.warning("Missing required setting %s", setting) + return OpenEdxConfiguration( + settings.EDX_API_CLIENT_ID, + settings.EDX_API_CLIENT_SECRET, + settings.EDX_API_ACCESS_TOKEN_URL, + settings.EDX_PROGRAMS_API_URL, + settings.EDX_BASE_URL, + settings.EDX_ALT_URL, + PlatformType.edx.name, + OfferedBy.mitx.name, + ETLSource.mit_edx.name, + LearningResourceType.program.name, + ) + + +# use the OpenEdx factory to create our extract and transform funcs +extract, _transform = openedx_extract_transform_factory(get_open_edx_config) + +# modified transform function that filters the program list to ones +# that pass the _is_mit_program() predicate +transform = compose(_transform, curried.filter(_is_mit_program)) diff --git a/learning_resources/etl/mit_edx_programs_test.py b/learning_resources/etl/mit_edx_programs_test.py new file mode 100644 index 0000000000..d34e0e0626 --- /dev/null +++ b/learning_resources/etl/mit_edx_programs_test.py @@ -0,0 +1,13 @@ +"""Tests for mit_edx_programs""" + +import pytest + +from learning_resources.etl.mit_edx_programs import transform + + +@pytest.mark.django_db +def test_mitx_transform_mit_owner(mitx_programs_data): + """Verify that only non-micromasters programs with MIT owners are returned""" + transformed = list(transform(mitx_programs_data)) + assert len(transformed) == 1 + assert transformed[0]["title"] == "Circuits and Electronics" diff --git a/learning_resources/etl/openedx.py b/learning_resources/etl/openedx.py index 1cd24a44a1..fe01dd9678 100644 --- a/learning_resources/etl/openedx.py +++ b/learning_resources/etl/openedx.py @@ -7,6 +7,7 @@ import re from collections import namedtuple from datetime import UTC, datetime +from decimal import Decimal from pathlib import Path import requests @@ -18,6 +19,7 @@ Availability, CertificationType, LearningResourceType, + PlatformType, RunAvailability, ) from learning_resources.etl.constants import COMMON_HEADERS @@ -28,6 +30,8 @@ transform_levels, without_none, ) +from learning_resources.models import LearningResource +from learning_resources.serializers import LearningResourceInstructorSerializer from learning_resources.utils import get_year_and_semester from main.utils import clean_data, now_in_utc @@ -45,6 +49,7 @@ "platform", "offered_by", "etl_source", + "resource_type", ], ) OpenEdxExtractTransform = namedtuple( # noqa: PYI024 @@ -173,17 +178,17 @@ def _get_course_availability(course): return None -def _is_course_or_run_deleted(title): +def _is_resource_or_run_deleted(title: str) -> bool: """ Returns True if '[delete]', 'delete ' (note the ending space character) - exists in a course's title or if the course title equals 'delete' for the - purpose of skipping the course + exists in a resource's title or if the resource title equals 'delete' for the + purpose of skipping the resource/run Args: - title (str): The course.title of the course + title (str): The title of the resource Returns: - bool: True if the course or run should be considered deleted + bool: True if the resource or run should be considered deleted """ # noqa: D401 title = title.strip().lower() @@ -195,9 +200,9 @@ def _is_course_or_run_deleted(title): ) -def _filter_course(course): +def _filter_resource(config, resource): """ - Filter courses to onces that are valid to ingest + Filter resources to onces that are valid to ingest Args: course (dict): the course data @@ -205,25 +210,28 @@ def _filter_course(course): Returns: bool: True if the course should be ingested """ - return not _is_course_or_run_deleted(course.get("title")) and course.get( - "course_runs", [] - ) + if config.resource_type == LearningResourceType.course.name: + return not _is_resource_or_run_deleted(resource.get("title")) and resource.get( + "course_runs", [] + ) + else: + return not _is_resource_or_run_deleted(resource.get("title")) -def _filter_course_run(course_run): +def _filter_resource_run(resource_run): """ - Filter course runs to onces that are valid to ingest + Filter resource runs to ones that are valid to ingest Args: - course_run (dict): the course run data + resource_run (dict): the resource run data Returns: - bool: True if the course run should be ingested + bool: True if the resource run should be ingested """ - return not _is_course_or_run_deleted(course_run.get("title")) + return not _is_resource_or_run_deleted(resource_run.get("title")) -def _transform_image(image_data: dict) -> dict: +def _transform_course_image(image_data: dict) -> dict: """Return the transformed image data if a url is provided""" if image_data and image_data.get("src"): return { @@ -233,6 +241,58 @@ def _transform_image(image_data: dict) -> dict: return None +def _transform_program_image(program_data) -> dict: + """Return the transformed image data if a url is provided""" + url = program_data.get("banner_image", {}).get("medium", {}).get("url") + if url: + return {"url": url, "description": program_data.get("title")} + return None + + +def _parse_course_dates(program, date_field): + """Return all the course run price values for the given date field""" + dates = [] + for course in program.get("courses", []): + if not course["excluded_from_search"]: + dates.extend( + [ + run[date_field] + for run in course["course_runs"] + if run["status"] == "published" and run[date_field] + ] + ) + return dates + + +def _sum_course_prices(program: dict) -> Decimal: + """ + Sum all the course run price values for the program + + Args: + program (dict): the program data + + Returns: + Decimal: the sum of all the course prices + """ + + def _get_course_price(course): + if not course["excluded_from_search"]: + for run in sorted( + course["course_runs"], key=lambda x: x["start"], reverse=False + ): + if run["status"] == "published": + return min( + [ + Decimal(seat["price"]) + for seat in run["seats"] + if seat["price"] != "0.00" + ] + ) + return Decimal(0.00) + + return sum(_get_course_price(course) for course in program.get("courses", [])) + + def _transform_course_run(config, course_run, course_last_modified, marketing_url): """ Transform a course run into the normalized data structure @@ -261,7 +321,7 @@ def _transform_course_run(config, course_run, course_last_modified, marketing_ur "published": _get_run_published(course_run), "enrollment_start": course_run.get("enrollment_start"), "enrollment_end": course_run.get("enrollment_end"), - "image": _transform_image(course_run.get("image")), + "image": _transform_course_image(course_run.get("image")), "availability": course_run.get("availability"), "url": marketing_url or "{}{}/course/".format(config.alt_url, course_run.get("key")), @@ -278,7 +338,96 @@ def _transform_course_run(config, course_run, course_last_modified, marketing_ur } -def _transform_course(config, course): +def _parse_program_instructors_topics(program): + """Get the instructors for each published course in a program""" + instructors = [] + topics = [] + course_ids = [course["key"] for course in program["courses"]] + courses = LearningResource.objects.filter( + readable_id__in=course_ids, + resource_type=LearningResourceType.course.name, + platform=PlatformType.edx.name, + published=True, + ) + for course in courses: + topics.extend([{"name": topic.name} for topic in course.topics.all()]) + run = ( + course.next_run + or course.runs.filter(published=True).order_by("-start_date").first() + ) + if run: + instructors.extend( + [ + LearningResourceInstructorSerializer(instance=instructor).data + for instructor in run.instructors.all() + ] + ) + return ( + sorted(instructors, key=lambda x: x["last_name"] or x["full_name"]), + sorted(topics, key=lambda x: x["name"]), + ) + + +def _transform_program_course(config: OpenEdxConfiguration, course: dict) -> dict: + """ + Transform a program's course dict to a normalized data structure + + Args: + config (OpenEdxConfiguration): configuration for the openedx backend + course (dict): the course data + + Returns: + dict: the transformed course data for the program + + """ + return { + "readable_id": course.get("key"), + "etl_source": config.etl_source, + "platform": config.platform, + "resource_type": LearningResourceType.course.name, + "offered_by": {"code": config.offered_by}, + } + + +def _transform_program_run( + program: dict, program_last_modified: str, image: dict +) -> dict: + """ + Transform program data into the normalized data structure for its run + + Args: + program (dict): the program data + program_last_modified (str): the last modified date string for the program + image (dict): the image data for the program + + Returns: + dict: the transformed program run data + """ + return { + "run_id": program.get("uuid"), + "title": program.get("title"), + "description": program.get("subtitle"), + "full_description": program.get("subtitle"), + "level": transform_levels([program.get("level_type_override")]), + "start_date": min(_parse_course_dates(program, "start"), default=None), + "end_date": max(_parse_course_dates(program, "end"), default=None), + "last_modified": program_last_modified, + "published": True, + "enrollment_start": min( + _parse_course_dates(program, "enrollment_start"), default=None + ), + "enrollment_end": max( + _parse_course_dates(program, "enrollment_end"), default=None + ), + "image": image, + "availability": RunAvailability.current.value, + "url": program.get("marketing_url"), + "prices": [_sum_course_prices(program)], + "instructors": program.pop("instructors", []), + } + + +def _transform_course(config: OpenEdxConfiguration, course: dict) -> dict: """ Filter courses to onces that are valid to ingest @@ -294,7 +443,7 @@ def _transform_course(config, course): runs = [ _transform_course_run(config, course_run, last_modified, marketing_url) for course_run in course.get("course_runs", []) - if _filter_course_run(course_run) + if _filter_resource_run(course_run) ] has_certification = parse_certification(config.offered_by, runs) return { @@ -308,7 +457,7 @@ def _transform_course(config, course): "description": clean_data(course.get("short_description")), "full_description": clean_data(course.get("full_description")), "last_modified": last_modified, - "image": _transform_image(course.get("image")), + "image": _transform_course_image(course.get("image")), "url": marketing_url or "{}{}/course/".format(config.alt_url, course.get("key")), "topics": [ @@ -327,7 +476,70 @@ def _transform_course(config, course): } -def openedx_extract_transform_factory(get_config): +def _transform_program(config: OpenEdxConfiguration, program: dict) -> dict: + """ + Transform raw program data into a normalized data structure + + Args: + config (OpenEdxConfiguration): configuration for the openedx backend + program (dict): the program data + + Returns: + dict: the tranformed program data + """ + last_modified = _parse_openedx_datetime(program.get("data_modified_timestamp")) + marketing_url = program.get("marketing_url") + image = _transform_program_image(program) + instructors, topics = _parse_program_instructors_topics(program) + program["instructors"] = instructors + runs = [_transform_program_run(program, last_modified, image)] + has_certification = parse_certification(config.offered_by, runs) + return { + "readable_id": program.get("uuid"), + "etl_source": config.etl_source, + "platform": config.platform, + "resource_type": LearningResourceType.program.name, + "offered_by": {"code": config.offered_by}, + "title": program.get("title"), + "description": clean_data(program.get("subtitle")), + "full_description": clean_data(program.get("subtitle")), + "last_modified": last_modified, + "image": image, + "url": marketing_url + or "{}{}/course/".format(config.alt_url, program.get("key")), + "topics": topics, + "runs": runs, + "published": any(run["published"] is True for run in runs), + "certification": has_certification, + "certification_type": CertificationType.completion.name + if has_certification + else CertificationType.none.name, + "availability": Availability.anytime.name, + "courses": [ + _transform_program_course(config, course) + for course in program.get("courses", []) + ], + } + + +def _transform_resource(config: OpenEdxConfiguration, resource: dict) -> dict: + """ + Transform the extracted openedx resource data into our normalized data structure + + Args: + config (OpenEdxConfiguration): configuration for the openedx backend + resource (dict): the data for the resource + + Returns: + dict: the tranformed resource data + """ + if config.resource_type == LearningResourceType.course.name: + return _transform_course(config, resource) + else: + return _transform_program(config, resource) + + +def openedx_extract_transform_factory(get_config: callable) -> OpenEdxExtractTransform: """ Factory for generating OpenEdx extract and transform functions based on the configuration @@ -375,26 +587,26 @@ def extract(api_datafile=None): url = config.api_url while url: - courses, url = _get_openedx_catalog_page(url, access_token) - yield from courses + resources, url = _get_openedx_catalog_page(url, access_token) + yield from resources - def transform(courses): + def transform(resources: list[dict]) -> list[dict]: """ Transforms the extracted openedx data into our normalized data structure Args: - list of dict: the merged catalog responses + list of dict: the merged resources responses Returns: - list of dict: the tranformed courses data + list of dict: the tranformed resources data """ # noqa: D401 config = get_config() return [ - _transform_course(config, course) - for course in courses - if _filter_course(course) + _transform_resource(config, resource) + for resource in resources + if _filter_resource(config, resource) ] return OpenEdxExtractTransform( diff --git a/learning_resources/etl/openedx_test.py b/learning_resources/etl/openedx_test.py index cc5f1f0e13..048e17cf49 100644 --- a/learning_resources/etl/openedx_test.py +++ b/learning_resources/etl/openedx_test.py @@ -2,6 +2,7 @@ # pylint: disable=redefined-outer-name from datetime import datetime +from decimal import Decimal from urllib.parse import urlencode import pytest @@ -10,22 +11,33 @@ Availability, CertificationType, LearningResourceType, + OfferedBy, + PlatformType, RunAvailability, ) from learning_resources.etl.constants import COMMON_HEADERS, CourseNumberType from learning_resources.etl.openedx import ( OpenEdxConfiguration, + _filter_resource, openedx_extract_transform_factory, ) +from learning_resources.factories import ( + LearningResourceFactory, + LearningResourceOfferorFactory, + LearningResourcePlatformFactory, + LearningResourceRunFactory, +) +from learning_resources.serializers import LearningResourceInstructorSerializer from main.test_utils import any_instance_of +from main.utils import clean_data ACCESS_TOKEN = "invalid_access_token" # noqa: S105 @pytest.fixture -def openedx_config(): - """Fixture for the openedx config object""" - return OpenEdxConfiguration( +def openedx_common_config(): + """Fixture for the openedx common config object""" + return ( "fake-client-id", "fake-client-secret", "http://localhost/fake-access-token-url/", @@ -39,12 +51,38 @@ def openedx_config(): @pytest.fixture -def openedx_extract_transform(openedx_config): +def openedx_course_config(openedx_common_config): + """Fixture for the openedx config object""" + return OpenEdxConfiguration( + *openedx_common_config, + LearningResourceType.course.name, + ) + + +@pytest.fixture +def openedx_program_config(openedx_common_config): + """Fixture for the openedx config object""" + return OpenEdxConfiguration( + *openedx_common_config, + LearningResourceType.program.name, + ) + + +@pytest.fixture +def openedx_extract_transform_courses(openedx_course_config): """Fixture for generationg an extract/transform pair for the given config""" - return openedx_extract_transform_factory(lambda: openedx_config) + return openedx_extract_transform_factory(lambda: openedx_course_config) -def test_extract(mocked_responses, openedx_config, openedx_extract_transform): +@pytest.fixture +def openedx_extract_transform_programs(openedx_program_config): + """Fixture for generationg an extract/transform pair for the given config""" + return openedx_extract_transform_factory(lambda: openedx_program_config) + + +def test_extract( + mocked_responses, openedx_course_config, openedx_extract_transform_courses +): """Test the generated extract functoin walks the paginated results""" results1 = [1, 2, 3] results2 = [4, 5, 6] @@ -52,19 +90,19 @@ def test_extract(mocked_responses, openedx_config, openedx_extract_transform): mocked_responses.add( mocked_responses.POST, - openedx_config.access_token_url, + openedx_course_config.access_token_url, json={"access_token": ACCESS_TOKEN}, ) mocked_responses.add( mocked_responses.GET, - openedx_config.api_url, + openedx_course_config.api_url, json={"results": results1, "next": next_url}, ) mocked_responses.add( mocked_responses.GET, next_url, json={"results": results2, "next": None} ) - assert openedx_extract_transform.extract() == results1 + results2 + assert openedx_extract_transform_courses.extract() == results1 + results2 for call in mocked_responses.calls: # assert that headers contain our common ones @@ -73,8 +111,8 @@ def test_extract(mocked_responses, openedx_config, openedx_extract_transform): assert mocked_responses.calls[0].request.body == urlencode( { "grant_type": "client_credentials", - "client_id": openedx_config.client_id, - "client_secret": openedx_config.client_secret, + "client_id": openedx_course_config.client_id, + "client_secret": openedx_course_config.client_secret, "token_type": "jwt", } ) @@ -87,12 +125,12 @@ def test_extract(mocked_responses, openedx_config, openedx_extract_transform): @pytest.mark.usefixtures("mocked_responses") @pytest.mark.parametrize("config_arg_idx", range(6)) -def test_extract_disabled(openedx_config, config_arg_idx): +def test_extract_disabled(openedx_course_config, config_arg_idx): """ Verify that extract() exits with no API call if configuration is missing """ - args = list(openedx_config) + args = list(openedx_course_config) args[config_arg_idx] = None config = OpenEdxConfiguration(*args) @@ -123,8 +161,8 @@ def test_extract_disabled(openedx_config, config_arg_idx): ], ) def test_transform_course( # noqa: PLR0913 - openedx_config, - openedx_extract_transform, + openedx_course_config, + openedx_extract_transform_courses, mitx_course_data, has_runs, is_course_deleted, @@ -151,7 +189,7 @@ def test_transform_course( # noqa: PLR0913 if is_run_deleted: run["title"] = f"[delete] {run['title']}" - transformed_courses = openedx_extract_transform.transform(extracted) + transformed_courses = openedx_extract_transform_courses.transform(extracted) if is_course_deleted or not has_runs: assert transformed_courses == [] else: @@ -164,9 +202,9 @@ def test_transform_course( # noqa: PLR0913 "departments": ["15"], "description": "short_description", "full_description": "full description", - "platform": openedx_config.platform, - "etl_source": openedx_config.etl_source, - "offered_by": {"code": openedx_config.offered_by}, + "platform": openedx_course_config.platform, + "etl_source": openedx_course_config.etl_source, + "offered_by": {"code": openedx_course_config.offered_by}, "image": { "url": "https://prod-discovery.edx-cdn.org/media/course/image/ff1df27b-3c97-42ee-a9b3-e031ffd41a4f-747c9c2f216e.small.jpg", "description": "Image description", @@ -267,7 +305,7 @@ def test_transform_course( # noqa: PLR0913 @pytest.mark.parametrize("status", ["published", "other"]) @pytest.mark.parametrize("is_enrollable", [True, False]) def test_transform_course_availability_with_single_run( # noqa: PLR0913 - openedx_extract_transform, + openedx_extract_transform_courses, mitx_course_data, run_overrides, expected_availability, @@ -286,7 +324,7 @@ def test_transform_course_availability_with_single_run( # noqa: PLR0913 "status": status, } extracted[0]["course_runs"] = [run] - transformed_courses = openedx_extract_transform.transform([extracted[0]]) + transformed_courses = openedx_extract_transform_courses.transform([extracted[0]]) if status == "published" and is_enrollable: assert transformed_courses[0]["availability"] == expected_availability @@ -296,7 +334,7 @@ def test_transform_course_availability_with_single_run( # noqa: PLR0913 @pytest.mark.parametrize("has_dated", [True, False]) def test_transform_course_availability_with_multiple_runs( - openedx_extract_transform, mitx_course_data, has_dated + openedx_extract_transform_courses, mitx_course_data, has_dated ): """ Test that if course includes a single run corresponding to availability: "dated", @@ -329,9 +367,109 @@ def test_transform_course_availability_with_multiple_runs( if has_dated: runs.append(run2) extracted[0]["course_runs"] = runs - transformed_courses = openedx_extract_transform.transform([extracted[0]]) + transformed_courses = openedx_extract_transform_courses.transform([extracted[0]]) if has_dated: assert transformed_courses[0]["availability"] == Availability.dated.name else: assert transformed_courses[0]["availability"] is Availability.anytime.name + + +@pytest.mark.django_db +def test_transform_program( + openedx_program_config, + openedx_extract_transform_programs, + mitx_programs_data, +): # pylint: disable=too-many-arguments + """Test that the transform function normalizes and filters out data""" + platform = LearningResourcePlatformFactory.create(code=PlatformType.edx.name) + offeror = LearningResourceOfferorFactory.create(code=OfferedBy.mitx.name) + instructors = [] + topics = [] + for i in range(1, 4): + course = LearningResourceFactory.create( + readable_id=f"MITx+6.002.{i}x", + platform=platform, + offered_by=offeror, + is_course=True, + create_runs=False, + ) + topics.extend([topic.name for topic in course.topics.all()]) + LearningResourceRunFactory.create(learning_resource=course) + for run in course.runs.filter(published=True): + instructors.extend(run.instructors.all()) + extracted = mitx_programs_data + transformed_programs = openedx_extract_transform_programs.transform(extracted) + transformed_program = transformed_programs[0] + assert transformed_program == { + "title": extracted[0]["title"], + "readable_id": extracted[0]["uuid"], + "resource_type": LearningResourceType.program.name, + "description": clean_data(extracted[0]["subtitle"]), + "full_description": clean_data(extracted[0]["subtitle"]), + "platform": openedx_program_config.platform, + "etl_source": openedx_program_config.etl_source, + "offered_by": {"code": openedx_program_config.offered_by}, + "image": { + "url": extracted[0]["banner_image"]["medium"]["url"], + "description": extracted[0]["title"], + }, + "last_modified": any_instance_of(datetime), + "topics": [{"name": topic} for topic in sorted(topics)], + "url": extracted[0]["marketing_url"], + "published": True, + "certification": False, + "certification_type": CertificationType.none.name, + "availability": Availability.anytime.name, + "runs": ( + [ + { + "availability": RunAvailability.current.value, + "run_id": extracted[0]["uuid"], + "start_date": "2019-06-20T15:00:00Z", + "end_date": "2025-05-26T15:00:00Z", + "enrollment_end": "2025-05-17T15:00:00Z", + "enrollment_start": None, + "description": extracted[0]["subtitle"], + "full_description": extracted[0]["subtitle"], + "image": { + "url": extracted[0]["banner_image"]["medium"]["url"], + "description": extracted[0]["title"], + }, + "instructors": [ + LearningResourceInstructorSerializer(instructor).data + for instructor in sorted( + instructors, key=lambda x: x.last_name or x.full_name + ) + ], + "last_modified": any_instance_of(datetime), + "level": [], + "prices": [Decimal("567.00")], + "title": extracted[0]["title"], + "url": extracted[0]["marketing_url"], + "published": True, + } + ] + ), + "courses": [ + { + "etl_source": openedx_program_config.etl_source, + "offered_by": {"code": openedx_program_config.offered_by}, + "platform": openedx_program_config.platform, + "readable_id": f"MITx+6.002.{i}x", + "resource_type": LearningResourceType.course.name, + } + for i in range(1, 4) + ], + } + + +@pytest.mark.parametrize("deleted", [True, False]) +def test_filter_resource(openedx_course_config, openedx_program_config, deleted): + """Test that the filter_resource function filters out resources with DELETE in the title""" + resource = { + "title": "delete" if deleted else "Valid title", + "course_runs": [{"run_id": "id1"}], + } + assert _filter_resource(openedx_course_config, resource) is not deleted + assert _filter_resource(openedx_program_config, resource) is not deleted diff --git a/learning_resources/etl/pipelines.py b/learning_resources/etl/pipelines.py index 793b520904..023263e58b 100644 --- a/learning_resources/etl/pipelines.py +++ b/learning_resources/etl/pipelines.py @@ -11,6 +11,7 @@ loaders, micromasters, mit_edx, + mit_edx_programs, mitxonline, ocw, oll, @@ -44,7 +45,7 @@ micromasters.extract, ) -mit_edx_etl = compose( +mit_edx_courses_etl = compose( load_courses( ETLSource.mit_edx.name, config=CourseLoaderConfig(prune=True), @@ -53,6 +54,17 @@ mit_edx.extract, ) +mit_edx_programs_etl = compose( + load_programs( + ETLSource.mit_edx.name, + config=ProgramLoaderConfig( + courses=CourseLoaderConfig(fetch_only=True), prune=True + ), + ), + mit_edx_programs.transform, + mit_edx_programs.extract, +) + mitxonline_programs_etl = compose( load_programs( ETLSource.mitxonline.name, diff --git a/learning_resources/etl/pipelines_test.py b/learning_resources/etl/pipelines_test.py index cda8889ad9..cc6411c46d 100644 --- a/learning_resources/etl/pipelines_test.py +++ b/learning_resources/etl/pipelines_test.py @@ -35,15 +35,15 @@ def reload_mocked_pipeline(*patchers): reload(pipelines) -def test_mit_edx_etl(): - """Verify that mit edx etl pipeline executes correctly""" +def test_mit_edx_courses_etl(): + """Verify that mit edx courses etl pipeline executes correctly""" with reload_mocked_pipeline( patch("learning_resources.etl.mit_edx.extract", autospec=True), patch("learning_resources.etl.mit_edx.transform", autospec=False), patch("learning_resources.etl.loaders.load_courses", autospec=True), ) as patches: mock_extract, mock_transform, mock_load_courses = patches - result = pipelines.mit_edx_etl() + result = pipelines.mit_edx_courses_etl() mock_extract.assert_called_once_with() @@ -60,6 +60,33 @@ def test_mit_edx_etl(): assert result == mock_load_courses.return_value +def test_mit_edx_programs_etl(): + """Verify that mit edx programs etl pipeline executes correctly""" + with reload_mocked_pipeline( + patch("learning_resources.etl.mit_edx_programs.extract", autospec=True), + patch("learning_resources.etl.mit_edx_programs.transform", autospec=False), + patch("learning_resources.etl.loaders.load_programs", autospec=True), + ) as patches: + mock_extract, mock_transform, mock_load_programs = patches + result = pipelines.mit_edx_programs_etl() + + mock_extract.assert_called_once_with() + + # each of these should be called with the return value of the extract + mock_transform.assert_called_once_with(mock_extract.return_value) + + # load_courses should be called *only* with the return value of transform + mock_load_programs.assert_called_once_with( + ETLSource.mit_edx.name, + mock_transform.return_value, + config=ProgramLoaderConfig( + courses=CourseLoaderConfig(fetch_only=True), prune=True + ), + ) + + assert result == mock_load_programs.return_value + + def test_mitxonline_programs_etl(): """Verify that mitxonline programs etl pipeline executes correctly""" with reload_mocked_pipeline( diff --git a/learning_resources/tasks.py b/learning_resources/tasks.py index d17dbb4a67..b36f70487c 100644 --- a/learning_resources/tasks.py +++ b/learning_resources/tasks.py @@ -55,9 +55,10 @@ def get_mit_edx_data(api_datafile=None) -> int: api_datafile (str): If provided, use this file as the source of API data Otherwise, the API is queried directly. """ - courses = pipelines.mit_edx_etl(api_datafile) + courses = pipelines.mit_edx_courses_etl(api_datafile) + programs = pipelines.mit_edx_programs_etl(api_datafile) clear_search_cache() - return len(courses) + return len(courses) + len(programs) @app.task @@ -66,7 +67,7 @@ def get_mitxonline_data() -> int: courses = pipelines.mitxonline_courses_etl() programs = pipelines.mitxonline_programs_etl() clear_search_cache() - return len(courses + programs) + return len(courses) + len(programs) @app.task @@ -89,7 +90,7 @@ def get_prolearn_data(): courses = pipelines.prolearn_courses_etl() programs = pipelines.prolearn_programs_etl() clear_search_cache() - return len(programs + courses) + return len(courses) + len(programs) @app.task @@ -105,7 +106,7 @@ def get_xpro_data(): courses = pipelines.xpro_courses_etl() programs = pipelines.xpro_programs_etl() clear_search_cache() - return len(courses + programs) + return len(courses) + len(programs) @app.task diff --git a/learning_resources/tasks_test.py b/learning_resources/tasks_test.py index d765ff120e..3e15814c58 100644 --- a/learning_resources/tasks_test.py +++ b/learning_resources/tasks_test.py @@ -90,11 +90,12 @@ def test_get_micromasters_data(mocker): def test_get_mit_edx_data_valid(mocker): - """Verify that the get_mit_edx_data invokes the MIT edX ETL pipeline""" + """Verify that the get_mit_edx_data invokes the MIT edX ETL pipelines""" mock_pipelines = mocker.patch("learning_resources.tasks.pipelines") tasks.get_mit_edx_data.delay() - mock_pipelines.mit_edx_etl.assert_called_once_with(None) + mock_pipelines.mit_edx_courses_etl.assert_called_once_with(None) + mock_pipelines.mit_edx_programs_etl.assert_called_once_with(None) def test_get_mitxonline_data(mocker): diff --git a/main/settings_course_etl.py b/main/settings_course_etl.py index 3167e5037b..48a75dc93a 100644 --- a/main/settings_course_etl.py +++ b/main/settings_course_etl.py @@ -6,6 +6,7 @@ # EDX API Credentials EDX_API_URL = get_string("EDX_API_URL", None) +EDX_PROGRAMS_API_URL = get_string("EDX_PROGRAMS_API_URL", None) EDX_API_ACCESS_TOKEN_URL = get_string("EDX_API_ACCESS_TOKEN_URL", None) EDX_API_CLIENT_ID = get_string("EDX_API_CLIENT_ID", None) EDX_API_CLIENT_SECRET = get_string("EDX_API_CLIENT_SECRET", None) diff --git a/test_json/test_mitx_programs.json b/test_json/test_mitx_programs.json new file mode 100644 index 0000000000..4921e6d03f --- /dev/null +++ b/test_json/test_mitx_programs.json @@ -0,0 +1,1535 @@ +[ + { + "uuid": "927093e3-46ba-4f44-a861-0f8c7aec4f74", + "title": "Circuits and Electronics", + "subtitle": "Learn electronic circuit techniques and applications in designing microchips for smartphones, self-driving cars, computers, and the Internet.", + "type": "XSeries", + "type_attrs": { + "uuid": "a3d34232-2cca-4b35-8810-cef9bb1e0e77", + "slug": "xseries", + "coaching_supported": false + }, + "status": "active", + "marketing_slug": "mitx-circuits-and-electronics", + "marketing_url": "https://www.edx.org/xseries/mitx-circuits-and-electronics", + "banner_image": { + "large": { + "url": "https://prod-discovery.edx-cdn.org/media/programs/banner_images/927093e3-46ba-4f44-a861-0f8c7aec4f74-94e7a300643d.large.png", + "width": 1440, + "height": 480 + }, + "medium": { + "url": "https://prod-discovery.edx-cdn.org/media/programs/banner_images/927093e3-46ba-4f44-a861-0f8c7aec4f74-94e7a300643d.medium.png", + "width": 726, + "height": 242 + }, + "small": { + "url": "https://prod-discovery.edx-cdn.org/media/programs/banner_images/927093e3-46ba-4f44-a861-0f8c7a-ec4f74-94e7a300643d.small.png", + "width": 435, + "height": 145 + }, + "x-small": { + "url": "https://prod-discovery.edx-cdn.org/media/programs/banner_images/927093e3-46ba-4f44-a861-0f8c7aec4f74-94e7a300643d.x-small.png", + "width": 348, + "height": 116 + } + }, + "hidden": false, + "courses": [ + { + "key": "MITx+6.002.1x", + "uuid": "8f9c73ef-73f5-45dd-aca1-7dad3696f743", + "title": "Circuits and Electronics 1: Basic Circuit Analysis", + "course_runs": [ + { + "key": "course-v1:MITx+6.002.1x+2T2019", + "uuid": "4d39edd3-8c7a-4038-80cd-f6edab483b8a", + "title": "Circuits and Electronics 1: Basic Circuit Analysis", + "external_key": "", + "fixed_price_usd": null, + "image": { + "src": "https://prod-discovery.edx-cdn.org/media/course/image/8f9c73ef-73f5-45dd-aca1-7dad3696f743-d417c0e28aed.small.jpg", + "description": null, + "height": null, + "width": null + }, + "short_description": "

Learn techniques that are foundational to the design of microchips used in smartphones, self-driving cars, computers, and the Internet.

", + "marketing_url": "https://www.edx.org/course/circuits-and-electronics-1-basic-circuit-analysis?utm_source=ocwprod-mit-opencourseware&utm_medium=affiliate_partner", + "seats": [ + { + "type": "verified", + "price": "189.00", + "currency": "USD", + "upgrade_deadline": "2025-05-16T23:59:59Z", + "upgrade_deadline_override": null, + "credit_provider": null, + "credit_hours": null, + "sku": "87611B2", + "bulk_sku": "DE4DB23" + }, + { + "type": "audit", + "price": "0.00", + "currency": "USD", + "upgrade_deadline": null, + "upgrade_deadline_override": null, + "credit_provider": null, + "credit_hours": null, + "sku": "8C12550", + "bulk_sku": null + } + ], + "start": "2019-06-20T15:00:00Z", + "end": "2025-05-26T15:00:00Z", + "go_live_date": null, + "enrollment_start": null, + "enrollment_end": "2025-05-17T15:00:00Z", + "weeks_to_complete": 5, + "pacing_type": "self_paced", + "type": "verified", + "restriction_type": null, + "run_type": "df9c20c1-9b54-40a5-bae3-7fda48d84141", + "status": "published", + "is_enrollable": true, + "is_marketable": true, + "availability": "Current", + "variant_id": null + } + ], + "entitlements": [ + { + "mode": "verified", + "price": "189.00", + "currency": "USD", + "sku": "CA912EE", + "expires": null + } + ], + "owners": [ + { + "uuid": "2a73d2ce-c34a-4e08-8223-83bca9d2f01d", + "key": "MITx", + "name": "Massachusetts Institute of Technology", + "auto_generate_course_run_keys": true, + "certificate_logo_image_url": "https://prod-discovery.edx-cdn.org/organization/certificate_logos/2a73d2ce-c34a-4e08-8223-83bca9d2f01d-0d040334059f.png", + "logo_image_url": "https://prod-discovery.edx-cdn.org/organization/logos/2a73d2ce-c34a-4e08-8223-83bca9d2f01d-d4f180052205.png", + "organization_hex_color": null, + "data_modified_timestamp": "2024-07-08T11:50:26.966309Z" + } + ], + "image": { + "src": "https://prod-discovery.edx-cdn.org/media/course/image/8f9c73ef-73f5-45dd-aca1-7dad3696f743-d417c0e28aed.small.jpg", + "description": null, + "height": null, + "width": null + }, + "short_description": "

Learn techniques that are foundational to the design of microchips used in smartphones, self-driving cars, computers, and the Internet.

", + "type": "e85a9f60-2da5-4413-96b7-dcb9b8c0e9f1", + "url_slug": null, + "course_type": "verified-audit", + "enterprise_subscription_inclusion": true, + "excluded_from_seo": false, + "excluded_from_search": false, + "course_run_statuses": ["published"] + }, + { + "key": "MITx+6.002.2x", + "uuid": "f6623bd8-ea35-42b2-880c-77a2f9c744b0", + "title": "Circuits and Electronics 2: Amplification, Speed, and Delay", + "course_runs": [ + { + "key": "course-v1:MITx+6.002.2x+2T2019", + "uuid": "289e51b0-04e7-4979-9ee7-9519af6a16f3", + "title": "Circuits and Electronics 2: Amplification, Speed, and Delay", + "external_key": "", + "fixed_price_usd": null, + "image": { + "src": "https://prod-discovery.edx-cdn.org/media/course/image/f6623bd8-ea35-42b2-880c-77a2f9c744b0-d8369d05cefc.small.jpg", + "description": null, + "height": null, + "width": null + }, + "short_description": "

Learn how to speed up digital circuits and build amplifiers in the design of microchips used in smartphones, self-driving cars, computers, and the Internet.

", + "marketing_url": "https://www.edx.org/course/circuits-and-electronics-2-amplification-speed-and-delay?utm_source=ocwprod-mit-opencourseware&utm_medium=affiliate_partner", + "seats": [ + { + "type": "verified", + "price": "189.00", + "currency": "USD", + "upgrade_deadline": "2025-05-16T23:59:59Z", + "upgrade_deadline_override": null, + "credit_provider": null, + "credit_hours": null, + "sku": "A1A0FA0", + "bulk_sku": "6ED8A2B" + }, + { + "type": "audit", + "price": "0.00", + "currency": "USD", + "upgrade_deadline": null, + "upgrade_deadline_override": null, + "credit_provider": null, + "credit_hours": null, + "sku": "A6BBC32", + "bulk_sku": null + } + ], + "start": "2019-06-20T15:00:00Z", + "end": "2025-05-26T15:00:00Z", + "go_live_date": null, + "enrollment_start": null, + "enrollment_end": "2025-05-17T15:00:00Z", + "weeks_to_complete": 5, + "pacing_type": "self_paced", + "type": "verified", + "restriction_type": null, + "run_type": "df9c20c1-9b54-40a5-bae3-7fda48d84141", + "status": "published", + "is_enrollable": true, + "is_marketable": true, + "availability": "Current", + "variant_id": null + } + ], + "entitlements": [ + { + "mode": "verified", + "price": "189.00", + "currency": "USD", + "sku": "9A03C7B", + "expires": null + } + ], + "owners": [ + { + "uuid": "2a73d2ce-c34a-4e08-8223-83bca9d2f01d", + "key": "MITx", + "name": "Massachusetts Institute of Technology", + "auto_generate_course_run_keys": true, + "certificate_logo_image_url": "https://prod-discovery.edx-cdn.org/organization/certificate_logos/2a73d2ce-c34a-4e08-8223-83bca9d2f01d-0d040334059f.png", + "logo_image_url": "https://prod-discovery.edx-cdn.org/organization/logos/2a73d2ce-c34a-4e08-8223-83bca9d2f01d-d4f180052205.png", + "organization_hex_color": null, + "data_modified_timestamp": "2024-07-08T11:50:26.966309Z" + } + ], + "image": { + "src": "https://prod-discovery.edx-cdn.org/media/course/image/f6623bd8-ea35-42b2-880c-77a2f9c744b0-d8369d05cefc.small.jpg", + "description": null, + "height": null, + "width": null + }, + "short_description": "

Learn how to speed up digital circuits and build amplifiers in the design of microchips used in smartphones, self-driving cars, computers, and the Internet.

", + "type": "e85a9f60-2da5-4413-96b7-dcb9b8c0e9f1", + "url_slug": null, + "course_type": "verified-audit", + "enterprise_subscription_inclusion": true, + "excluded_from_seo": false, + "excluded_from_search": false, + "course_run_statuses": ["published"] + }, + { + "key": "MITx+6.002.3x", + "uuid": "aca3d8f7-8907-474c-9a62-58cca8c6254f", + "title": "Circuits and Electronics 3: Applications", + "course_runs": [ + { + "key": "course-v1:MITx+6.002.3x+2T2019", + "uuid": "5ac8faf4-1147-4ca6-b1ce-96aaf53cce30", + "title": "Circuits and Electronics 3: Applications", + "external_key": "", + "fixed_price_usd": null, + "image": { + "src": "https://prod-discovery.edx-cdn.org/media/course/image/aca3d8f7-8907-474c-9a62-58cca8c6254f-b9bf6f5f5ae8.small.jpg", + "description": null, + "height": null, + "width": null + }, + "short_description": "

Learn about cool applications, op-amps and filters in the design of microchips used in smartphones, self-driving cars, computers, and the internet.

", + "marketing_url": "https://www.edx.org/course/circuits-and-electronics-3-applications?utm_source=ocwprod-mit-opencourseware&utm_medium=affiliate_partner", + "seats": [ + { + "type": "verified", + "price": "189.00", + "currency": "USD", + "upgrade_deadline": "2025-05-16T23:59:59Z", + "upgrade_deadline_override": null, + "credit_provider": null, + "credit_hours": null, + "sku": "62ED93B", + "bulk_sku": "84F2929" + }, + { + "type": "audit", + "price": "0.00", + "currency": "USD", + "upgrade_deadline": null, + "upgrade_deadline_override": null, + "credit_provider": null, + "credit_hours": null, + "sku": "319F510", + "bulk_sku": null + } + ], + "start": "2019-06-20T15:00:00Z", + "end": "2025-05-26T15:00:00Z", + "go_live_date": null, + "enrollment_start": null, + "enrollment_end": "2025-05-17T15:00:00Z", + "weeks_to_complete": 7, + "pacing_type": "self_paced", + "type": "verified", + "restriction_type": null, + "run_type": "df9c20c1-9b54-40a5-bae3-7fda48d84141", + "status": "published", + "is_enrollable": true, + "is_marketable": true, + "availability": "Current", + "variant_id": null + } + ], + "entitlements": [ + { + "mode": "verified", + "price": "189.00", + "currency": "USD", + "sku": "7EF72A8", + "expires": null + } + ], + "owners": [ + { + "uuid": "2a73d2ce-c34a-4e08-8223-83bca9d2f01d", + "key": "MITx", + "name": "Massachusetts Institute of Technology", + "auto_generate_course_run_keys": true, + "certificate_logo_image_url": "https://prod-discovery.edx-cdn.org/organization/certificate_logos/2a73d2ce-c34a-4e08-8223-83bca9d2f01d-0d040334059f.png", + "logo_image_url": "https://prod-discovery.edx-cdn.org/organization/logos/2a73d2ce-c34a-4e08-8223-83bca9d2f01d-d4f180052205.png", + "organization_hex_color": null, + "data_modified_timestamp": "2024-07-08T11:50:26.966309Z" + } + ], + "image": { + "src": "https://prod-discovery.edx-cdn.org/media/course/image/aca3d8f7-8907-474c-9a62-58cca8c6254f-b9bf6f5f5ae8.small.jpg", + "description": null, + "height": null, + "width": null + }, + "short_description": "

Learn about cool applications, op-amps and filters in the design of microchips used in smartphones, self-driving cars, computers, and the internet.

", + "type": "e85a9f60-2da5-4413-96b7-dcb9b8c0e9f1", + "url_slug": null, + "course_type": "verified-audit", + "enterprise_subscription_inclusion": true, + "excluded_from_seo": false, + "excluded_from_search": false, + "course_run_statuses": ["published"] + } + ], + "authoring_organizations": [ + { + "uuid": "2a73d2ce-c34a-4e08-8223-83bca9d2f01d", + "key": "MITx", + "name": "Massachusetts Institute of Technology", + "auto_generate_course_run_keys": true, + "certificate_logo_image_url": "https://prod-discovery.edx-cdn.org/organization/certificate_logos/2a73d2ce-c34a-4e08-8223-83bca9d2f01d-0d040334059f.png", + "logo_image_url": "https://prod-discovery.edx-cdn.org/organization/logos/2a73d2ce-c34a-4e08-8223-83bca9d2f01d-d4f180052205.png", + "organization_hex_color": null, + "data_modified_timestamp": "2024-07-08T11:50:26.966309Z" + } + ], + "card_image_url": "https://prod-discovery.edx-cdn.org/media/programs/card_images/927093e3-46ba-4f44-a861-0f8c7aec4f74-215df4ae5361.jpg", + "is_program_eligible_for_one_click_purchase": true, + "degree": null, + "curricula": [], + "marketing_hook": "Learn about electronic circuit techniques and applications\r", + "total_hours_of_effort": null, + "recent_enrollment_count": 20255, + "organization_short_code_override": "", + "organization_logo_override_url": null, + "primary_subject_override": null, + "level_type_override": null, + "language_override": null, + "labels": [], + "taxi_form": null, + "program_duration_override": null, + "data_modified_timestamp": "2024-08-28T07:14:23.507563Z", + "excluded_from_search": false, + "excluded_from_seo": false, + "has_ofac_restrictions": null, + "ofac_comment": "", + "course_run_statuses": ["published"], + "subscription_eligible": false, + "subscription_prices": [ + { + "price": "79.00", + "currency": "USD" + } + ] + }, + { + "uuid": "84811e48-94e6-4738-8e15-e019b289e374", + "title": "Accounting", + "subtitle": "Learn key concepts and skills in financial accounting, managerial accounting, and tax.", + "type": "MicroMasters", + "type_attrs": { + "uuid": "253dd13d-33a3-401b-bcaa-e0b0f0424777", + "slug": "micromasters", + "coaching_supported": false + }, + "status": "retired", + "marketing_slug": "iux-accounting", + "marketing_url": "https://www.edx.org/micromasters/iux-accounting", + "banner_image": { + "large": { + "url": "https://prod-discovery.edx-cdn.org/media/programs/banner_images/84811e48-94e6-4738-8e15-e019b289e374-c1e0d8478f47.large.png", + "width": 1440, + "height": 480 + }, + "medium": { + "url": "https://prod-discovery.edx-cdn.org/media/programs/banner_images/84811e48-94e6-4738-8e15-e019b289e374-c1e0d8478f47.medium.png", + "width": 726, + "height": 242 + }, + "small": { + "url": "https://prod-discovery.edx-cdn.org/media/programs/banner_images/84811e48-94e6-4738-8e15-e019b289e374-c1e0d8478f47.small.png", + "width": 435, + "height": 145 + }, + "x-small": { + "url": "https://prod-discovery.edx-cdn.org/media/programs/banner_images/84811e48-94e6-4738-8e15-e019b289e374-c1e0d8478f47.x-small.png", + "width": 348, + "height": 116 + } + }, + "hidden": true, + "courses": [ + { + "key": "IUx+BUKD-A500", + "uuid": "33b60d53-f5ac-4f44-8e87-c277289fb576", + "title": "Financial Reporting I", + "course_runs": [ + { + "key": "course-v1:IUx+BUKD-A500+2T2019", + "uuid": "333d356f-09c8-4897-894f-b1fa0b8e039b", + "title": "Financial Reporting I", + "external_key": null, + "fixed_price_usd": null, + "image": { + "src": "https://prod-discovery.edx-cdn.org/media/course/image/33b60d53-f5ac-4f44-8e87-c277289fb576-94c15bb33734.small.jpeg", + "description": null, + "height": null, + "width": null + }, + "short_description": "

Earn a strong foundation in financial reporting concepts and methods, and use your skills to prepare and analyze financial statements.

", + "marketing_url": "https://www.edx.org/course/financial-reporting-i?utm_source=ocwprod-mit-opencourseware&utm_medium=affiliate_partner", + "seats": [ + { + "type": "verified", + "price": "500.00", + "currency": "USD", + "upgrade_deadline": "2019-10-30T23:59:59Z", + "upgrade_deadline_override": null, + "credit_provider": null, + "credit_hours": null, + "sku": "C20EA71", + "bulk_sku": "944FA6E" + }, + { + "type": "audit", + "price": "0.00", + "currency": "USD", + "upgrade_deadline": null, + "upgrade_deadline_override": null, + "credit_provider": null, + "credit_hours": null, + "sku": "DA6810A", + "bulk_sku": null + } + ], + "start": "2019-08-20T05:00:00Z", + "end": "2019-11-09T23:59:00Z", + "go_live_date": null, + "enrollment_start": null, + "enrollment_end": null, + "weeks_to_complete": 12, + "pacing_type": "instructor_paced", + "type": "verified", + "restriction_type": null, + "run_type": "df9c20c1-9b54-40a5-bae3-7fda48d84141", + "status": "unpublished", + "is_enrollable": true, + "is_marketable": false, + "availability": "Archived", + "variant_id": null + }, + { + "key": "course-v1:IUx+BUKD-A500+3T2019", + "uuid": "5a6d79d0-a581-43f8-9e73-d8cbb6663a67", + "title": "Financial Reporting I", + "external_key": null, + "fixed_price_usd": null, + "image": { + "src": "https://prod-discovery.edx-cdn.org/media/course/image/33b60d53-f5ac-4f44-8e87-c277289fb576-94c15bb33734.small.jpeg", + "description": null, + "height": null, + "width": null + }, + "short_description": "

Earn a strong foundation in financial reporting concepts and methods, and use your skills to prepare and analyze financial statements.

", + "marketing_url": "https://www.edx.org/course/financial-reporting-i-3?utm_source=ocwprod-mit-opencourseware&utm_medium=affiliate_partner", + "seats": [ + { + "type": "verified", + "price": "500.00", + "currency": "USD", + "upgrade_deadline": "2020-02-05T23:59:59Z", + "upgrade_deadline_override": null, + "credit_provider": null, + "credit_hours": null, + "sku": "5F72196", + "bulk_sku": "CC08D6C" + }, + { + "type": "audit", + "price": "0.00", + "currency": "USD", + "upgrade_deadline": null, + "upgrade_deadline_override": null, + "credit_provider": null, + "credit_hours": null, + "sku": "4DA39A5", + "bulk_sku": null + } + ], + "start": "2019-11-12T05:00:00Z", + "end": "2020-02-15T05:00:00Z", + "go_live_date": null, + "enrollment_start": null, + "enrollment_end": null, + "weeks_to_complete": 12, + "pacing_type": "instructor_paced", + "type": "verified", + "restriction_type": null, + "run_type": "df9c20c1-9b54-40a5-bae3-7fda48d84141", + "status": "unpublished", + "is_enrollable": true, + "is_marketable": false, + "availability": "Archived", + "variant_id": null + }, + { + "key": "course-v1:IUx+BUKD-A500+2T2020", + "uuid": "80ed1973-1e0d-4df6-9079-a6d59818c523", + "title": "Financial Reporting I", + "external_key": null, + "fixed_price_usd": null, + "image": { + "src": "https://prod-discovery.edx-cdn.org/media/course/image/33b60d53-f5ac-4f44-8e87-c277289fb576-94c15bb33734.small.jpeg", + "description": null, + "height": null, + "width": null + }, + "short_description": "

Earn a strong foundation in financial reporting concepts and methods, and use your skills to prepare and analyze financial statements.

", + "marketing_url": "https://www.edx.org/course/financial-reporting-i-2?utm_source=ocwprod-mit-opencourseware&utm_medium=affiliate_partner", + "seats": [ + { + "type": "verified", + "price": "499.00", + "currency": "USD", + "upgrade_deadline": "2020-07-07T23:59:00Z", + "upgrade_deadline_override": "2020-07-07T23:59:00Z", + "credit_provider": null, + "credit_hours": null, + "sku": "3B035AD", + "bulk_sku": "8F96F45" + }, + { + "type": "audit", + "price": "0.00", + "currency": "USD", + "upgrade_deadline": null, + "upgrade_deadline_override": null, + "credit_provider": null, + "credit_hours": null, + "sku": "5C78228", + "bulk_sku": null + } + ], + "start": "2020-05-19T05:00:00Z", + "end": "2020-08-06T05:00:00Z", + "go_live_date": null, + "enrollment_start": null, + "enrollment_end": null, + "weeks_to_complete": 12, + "pacing_type": "instructor_paced", + "type": "verified", + "restriction_type": null, + "run_type": "df9c20c1-9b54-40a5-bae3-7fda48d84141", + "status": "unpublished", + "is_enrollable": true, + "is_marketable": false, + "availability": "Archived", + "variant_id": null + }, + { + "key": "course-v1:IUx+BUKD-A500+3T2020", + "uuid": "e1ab8996-2d38-432b-b36f-b4c76bbe9cd7", + "title": "Financial Reporting I", + "external_key": "", + "fixed_price_usd": null, + "image": { + "src": "https://prod-discovery.edx-cdn.org/media/course/image/33b60d53-f5ac-4f44-8e87-c277289fb576-94c15bb33734.small.jpeg", + "description": null, + "height": null, + "width": null + }, + "short_description": "

Earn a strong foundation in financial reporting concepts and methods, and use your skills to prepare and analyze financial statements.

", + "marketing_url": "https://www.edx.org/course/financial-reporting-i-course-v1iuxbukd-a5003t2020?utm_source=ocwprod-mit-opencourseware&utm_medium=affiliate_partner", + "seats": [ + { + "type": "audit", + "price": "0.00", + "currency": "USD", + "upgrade_deadline": null, + "upgrade_deadline_override": null, + "credit_provider": null, + "credit_hours": null, + "sku": "468C99E", + "bulk_sku": null + }, + { + "type": "verified", + "price": "499.00", + "currency": "USD", + "upgrade_deadline": "2021-01-21T23:59:00Z", + "upgrade_deadline_override": null, + "credit_provider": null, + "credit_hours": null, + "sku": "A677A5D", + "bulk_sku": "9E6F537" + } + ], + "start": "2020-11-17T05:00:00Z", + "end": "2021-02-19T20:01:00Z", + "go_live_date": "2020-02-26T05:00:00Z", + "enrollment_start": null, + "enrollment_end": "2020-12-15T00:00:00Z", + "weeks_to_complete": 12, + "pacing_type": "instructor_paced", + "type": "verified", + "restriction_type": null, + "run_type": "df9c20c1-9b54-40a5-bae3-7fda48d84141", + "status": "unpublished", + "is_enrollable": false, + "is_marketable": false, + "availability": "Archived", + "variant_id": null + }, + { + "key": "course-v1:IUx+BUKD-A500+2T2021", + "uuid": "e8c274c9-8c27-4fb5-a955-6e69e86c0d45", + "title": "Financial Reporting I", + "external_key": null, + "fixed_price_usd": null, + "image": { + "src": "https://prod-discovery.edx-cdn.org/media/course/image/33b60d53-f5ac-4f44-8e87-c277289fb576-94c15bb33734.small.jpeg", + "description": null, + "height": null, + "width": null + }, + "short_description": "

Earn a strong foundation in financial reporting concepts and methods, and use your skills to prepare and analyze financial statements.

", + "marketing_url": "https://www.edx.org/course/financial-reporting-i-course-v1iuxbukd-a5002t2021?utm_source=ocwprod-mit-opencourseware&utm_medium=affiliate_partner", + "seats": [ + { + "type": "audit", + "price": "0.00", + "currency": "USD", + "upgrade_deadline": null, + "upgrade_deadline_override": null, + "credit_provider": null, + "credit_hours": null, + "sku": "60466B6", + "bulk_sku": null + }, + { + "type": "verified", + "price": "499.00", + "currency": "USD", + "upgrade_deadline": "2021-07-13T23:59:00Z", + "upgrade_deadline_override": "2021-07-13T23:59:00Z", + "credit_provider": null, + "credit_hours": null, + "sku": "C544705", + "bulk_sku": "738E9B4" + } + ], + "start": "2021-05-25T10:00:00Z", + "end": "2021-08-13T20:01:00Z", + "go_live_date": "2020-04-24T04:00:00Z", + "enrollment_start": null, + "enrollment_end": "2021-06-22T01:59:00Z", + "weeks_to_complete": 12, + "pacing_type": "instructor_paced", + "type": "verified", + "restriction_type": null, + "run_type": "df9c20c1-9b54-40a5-bae3-7fda48d84141", + "status": "unpublished", + "is_enrollable": false, + "is_marketable": false, + "availability": "Archived", + "variant_id": null + }, + { + "key": "course-v1:IUx+BUKD-A500+3T2021", + "uuid": "389ba5e9-e337-4a0d-adda-816fbf58efe3", + "title": "Financial Reporting I", + "external_key": null, + "fixed_price_usd": null, + "image": { + "src": "https://prod-discovery.edx-cdn.org/media/course/image/33b60d53-f5ac-4f44-8e87-c277289fb576-94c15bb33734.small.jpeg", + "description": null, + "height": null, + "width": null + }, + "short_description": "

Earn a strong foundation in financial reporting concepts and methods, and use your skills to prepare and analyze financial statements.

", + "marketing_url": "https://www.edx.org/course/financial-reporting-i-course-v1iuxbukd-a5003t2021?utm_source=ocwprod-mit-opencourseware&utm_medium=affiliate_partner", + "seats": [ + { + "type": "audit", + "price": "0.00", + "currency": "USD", + "upgrade_deadline": null, + "upgrade_deadline_override": null, + "credit_provider": null, + "credit_hours": null, + "sku": "10C7C05", + "bulk_sku": null + }, + { + "type": "verified", + "price": "499.00", + "currency": "USD", + "upgrade_deadline": "2022-01-11T23:59:00Z", + "upgrade_deadline_override": "2022-01-11T23:59:00Z", + "credit_provider": null, + "credit_hours": null, + "sku": "6AB677F", + "bulk_sku": "94681A2" + } + ], + "start": "2021-11-16T11:00:00Z", + "end": "2022-02-18T04:59:00Z", + "go_live_date": "2020-11-20T05:00:00Z", + "enrollment_start": null, + "enrollment_end": "2021-12-14T23:59:00Z", + "weeks_to_complete": 12, + "pacing_type": "instructor_paced", + "type": "verified", + "restriction_type": null, + "run_type": "df9c20c1-9b54-40a5-bae3-7fda48d84141", + "status": "unpublished", + "is_enrollable": false, + "is_marketable": false, + "availability": "Archived", + "variant_id": null + }, + { + "key": "course-v1:IUx+BUKD-A500+2T2022", + "uuid": "b63c80d7-b533-4b47-be56-48bfd9899981", + "title": "Financial Reporting I", + "external_key": null, + "fixed_price_usd": null, + "image": { + "src": "https://prod-discovery.edx-cdn.org/media/course/image/33b60d53-f5ac-4f44-8e87-c277289fb576-94c15bb33734.small.jpeg", + "description": null, + "height": null, + "width": null + }, + "short_description": "

Earn a strong foundation in financial reporting concepts and methods, and use your skills to prepare and analyze financial statements.

", + "marketing_url": "https://www.edx.org/course/financial-reporting-i-course-v1iuxbukd-a5002t2022?utm_source=ocwprod-mit-opencourseware&utm_medium=affiliate_partner", + "seats": [ + { + "type": "audit", + "price": "0.00", + "currency": "USD", + "upgrade_deadline": null, + "upgrade_deadline_override": null, + "credit_provider": null, + "credit_hours": null, + "sku": "3272D0A", + "bulk_sku": null + }, + { + "type": "verified", + "price": "499.00", + "currency": "USD", + "upgrade_deadline": "2022-06-08T03:59:00Z", + "upgrade_deadline_override": "2022-06-08T03:59:00Z", + "credit_provider": null, + "credit_hours": null, + "sku": "836E41E", + "bulk_sku": "C7CF0BE" + } + ], + "start": "2022-05-24T10:00:00Z", + "end": "2022-08-13T03:59:00Z", + "go_live_date": "2021-04-14T04:00:00Z", + "enrollment_start": null, + "enrollment_end": "2022-06-08T03:59:00Z", + "weeks_to_complete": 12, + "pacing_type": "instructor_paced", + "type": "verified", + "restriction_type": null, + "run_type": "df9c20c1-9b54-40a5-bae3-7fda48d84141", + "status": "unpublished", + "is_enrollable": false, + "is_marketable": false, + "availability": "Archived", + "variant_id": null + } + ], + "entitlements": [ + { + "mode": "verified", + "price": "499.00", + "currency": "USD", + "sku": "C88D5B7", + "expires": null + } + ], + "owners": [ + { + "uuid": "8946fb40-f288-4e05-9275-9e9b4689bda8", + "key": "IUx", + "name": "Indiana University", + "auto_generate_course_run_keys": true, + "certificate_logo_image_url": "https://prod-discovery.edx-cdn.org/organization/certificate_logos/8946fb40-f288-4e05-9275-9e9b4689bda8-9c4c6df9d3bb.png", + "logo_image_url": "https://prod-discovery.edx-cdn.org/organization/logos/8946fb40-f288-4e05-9275-9e9b4689bda8-e58c545e2296.png", + "organization_hex_color": null, + "data_modified_timestamp": "2023-10-23T13:37:23.725096Z" + } + ], + "image": { + "src": "https://prod-discovery.edx-cdn.org/media/course/image/33b60d53-f5ac-4f44-8e87-c277289fb576-94c15bb33734.small.jpeg", + "description": null, + "height": null, + "width": null + }, + "short_description": "

Earn a strong foundation in financial reporting concepts and methods, and use your skills to prepare and analyze financial statements.

", + "type": "e85a9f60-2da5-4413-96b7-dcb9b8c0e9f1", + "url_slug": null, + "course_type": "verified-audit", + "enterprise_subscription_inclusion": false, + "excluded_from_seo": false, + "excluded_from_search": false, + "course_run_statuses": ["archived"] + } + ], + "authoring_organizations": [ + { + "uuid": "8946fb40-f288-4e05-9275-9e9b4689bda8", + "key": "IUx", + "name": "Indiana University", + "auto_generate_course_run_keys": true, + "certificate_logo_image_url": "https://prod-discovery.edx-cdn.org/organization/certificate_logos/8946fb40-f288-4e05-9275-9e9b4689bda8-9c4c6df9d3bb.png", + "logo_image_url": "https://prod-discovery.edx-cdn.org/organization/logos/8946fb40-f288-4e05-9275-9e9b4689bda8-e58c545e2296.png", + "organization_hex_color": null, + "data_modified_timestamp": "2023-10-23T13:37:23.725096Z" + } + ], + "card_image_url": "https://prod-discovery.edx-cdn.org/media/programs/card_images/84811e48-94e6-4738-8e15-e019b289e374-cdd9850ada79.jpg", + "is_program_eligible_for_one_click_purchase": false, + "degree": null, + "curricula": [], + "marketing_hook": "Gain skills in financial accounting, managerial accounting, and tax", + "total_hours_of_effort": null, + "recent_enrollment_count": -115, + "organization_short_code_override": "", + "organization_logo_override_url": null, + "primary_subject_override": null, + "level_type_override": null, + "language_override": null, + "labels": [], + "taxi_form": null, + "program_duration_override": null, + "data_modified_timestamp": "2024-08-28T07:13:53.011756Z", + "excluded_from_search": false, + "excluded_from_seo": false, + "has_ofac_restrictions": null, + "ofac_comment": "", + "course_run_statuses": ["archived"], + "subscription_eligible": null, + "subscription_prices": [] + }, + { + "uuid": "0445d855-1d24-480a-87ff-01a9186e237a", + "title": "Water Management", + "subtitle": "Explore water management concepts and technologies.", + "type": "XSeries", + "type_attrs": { + "uuid": "a3d34232-2cca-4b35-8810-cef9bb1e0e77", + "slug": "xseries", + "coaching_supported": false + }, + "status": "active", + "marketing_slug": "xseries/delft-university-of-technology-water-management", + "marketing_url": "https://www.edx.org/xseries/delft-university-of-technology-water-management", + "banner_image": { + "large": { + "url": "https://prod-discovery.edx-cdn.org/media/programs/banner_images/0445d855-1d24-480a-87ff-01a9186e237a.large.jpg", + "width": 1440, + "height": 480 + }, + "medium": { + "url": "https://prod-discovery.edx-cdn.org/media/programs/banner_images/0445d855-1d24-480a-87ff-01a9186e237a.medium.jpg", + "width": 726, + "height": 242 + }, + "small": { + "url": "https://prod-discovery.edx-cdn.org/media/programs/banner_images/0445d855-1d24-480a-87ff-01a9186e237a.small.jpg", + "width": 435, + "height": 145 + }, + "x-small": { + "url": "https://prod-discovery.edx-cdn.org/media/programs/banner_images/0445d855-1d24-480a-87ff-01a9186e237a.x-small.jpg", + "width": 348, + "height": 116 + } + }, + "hidden": false, + "courses": [ + { + "key": "Delftx+CTB3300WCx", + "uuid": "8d384724-c109-45d4-9a92-7920d3f74ef5", + "title": "Introduction to Water and Climate", + "course_runs": [ + { + "key": "DelftX/CTB3300WCx/2T2014", + "uuid": "895c2331-d427-4241-aad0-73ba4223f2c0", + "title": "Introduction to Water and Climate", + "external_key": null, + "fixed_price_usd": null, + "image": { + "src": "https://prod-discovery.edx-cdn.org/media/course/image/8d384724-c109-45d4-9a92-7920d3f74ef5-4e859aa4bd33.small.jpg", + "description": null, + "height": null, + "width": null + }, + "short_description": "The basic elements of and the relation between water and climate are highlighted and further discussed together with their mutual coherence.", + "marketing_url": "https://www.edx.org/course/introduction-water-climate-delftx-ctb3300wcx?utm_source=ocwprod-mit-opencourseware&utm_medium=affiliate_partner", + "seats": [ + { + "type": "audit", + "price": "0.00", + "currency": "USD", + "upgrade_deadline": "2014-09-17T08:59:00Z", + "upgrade_deadline_override": null, + "credit_provider": null, + "credit_hours": null, + "sku": "E1FB8DC", + "bulk_sku": null + }, + { + "type": "honor", + "price": "0.00", + "currency": "USD", + "upgrade_deadline": null, + "upgrade_deadline_override": null, + "credit_provider": null, + "credit_hours": null, + "sku": "5DED6EC", + "bulk_sku": null + }, + { + "type": "verified", + "price": "50.00", + "currency": "USD", + "upgrade_deadline": "2014-09-17T08:59:00Z", + "upgrade_deadline_override": null, + "credit_provider": null, + "credit_hours": null, + "sku": "3EC8563", + "bulk_sku": null + } + ], + "start": "2014-08-26T10:00:00Z", + "end": "2014-11-04T11:00:00Z", + "go_live_date": null, + "enrollment_start": null, + "enrollment_end": null, + "weeks_to_complete": 9, + "pacing_type": "instructor_paced", + "type": "verified", + "restriction_type": null, + "run_type": "1028f55f-04ea-4409-8542-6d2802ca4a5f", + "status": "unpublished", + "is_enrollable": true, + "is_marketable": false, + "availability": "Archived", + "variant_id": null + }, + { + "key": "course-v1:Delftx+CTB3300WCx+2015_T3", + "uuid": "a36f5673-6637-11e6-a8e3-22000bdde520", + "title": "Introduction to Water and Climate", + "external_key": null, + "fixed_price_usd": null, + "image": { + "src": "https://prod-discovery.edx-cdn.org/media/course/image/8d384724-c109-45d4-9a92-7920d3f74ef5-4e859aa4bd33.small.jpg", + "description": null, + "height": null, + "width": null + }, + "short_description": "Explore how climate change, water availability, and engineering innovation are key challenges for our planet.", + "marketing_url": "https://www.edx.org/course/introduction-water-climate-delftx-ctb3300wcx-0?utm_source=ocwprod-mit-opencourseware&utm_medium=affiliate_partner", + "seats": [ + { + "type": "honor", + "price": "0.00", + "currency": "USD", + "upgrade_deadline": null, + "upgrade_deadline_override": null, + "credit_provider": null, + "credit_hours": null, + "sku": "1DB5A75", + "bulk_sku": null + }, + { + "type": "verified", + "price": "50.00", + "currency": "USD", + "upgrade_deadline": "2015-10-19T23:59:00Z", + "upgrade_deadline_override": null, + "credit_provider": null, + "credit_hours": null, + "sku": "EFF47EC", + "bulk_sku": null + } + ], + "start": "2015-09-01T12:00:00Z", + "end": "2015-11-04T12:00:00Z", + "go_live_date": null, + "enrollment_start": null, + "enrollment_end": null, + "weeks_to_complete": 4, + "pacing_type": "instructor_paced", + "type": "verified", + "restriction_type": null, + "run_type": "7833c89d-47f7-49bc-ba76-b38cbfa8f903", + "status": "unpublished", + "is_enrollable": true, + "is_marketable": false, + "availability": "Archived", + "variant_id": null + }, + { + "key": "course-v1:Delftx+CTB3300WCx+3T2016", + "uuid": "a36f5692-6637-11e6-a8e3-22000bdde520", + "title": "Introduction to Water and Climate", + "external_key": null, + "fixed_price_usd": null, + "image": { + "src": "https://prod-discovery.edx-cdn.org/media/course/image/8d384724-c109-45d4-9a92-7920d3f74ef5-4e859aa4bd33.small.jpg", + "description": null, + "height": null, + "width": null + }, + "short_description": "Explore how climate change, water availability, and engineering innovation are key challenges for our planet.", + "marketing_url": "https://www.edx.org/course/introduction-water-climate-delftx-ctb3300wcx-1?utm_source=ocwprod-mit-opencourseware&utm_medium=affiliate_partner", + "seats": [ + { + "type": "audit", + "price": "0.00", + "currency": "USD", + "upgrade_deadline": null, + "upgrade_deadline_override": null, + "credit_provider": null, + "credit_hours": null, + "sku": "5547B3C", + "bulk_sku": null + }, + { + "type": "verified", + "price": "50.00", + "currency": "USD", + "upgrade_deadline": "2016-11-28T23:58:00Z", + "upgrade_deadline_override": null, + "credit_provider": null, + "credit_hours": null, + "sku": "C36751E", + "bulk_sku": null + } + ], + "start": "2016-09-06T12:00:00Z", + "end": "2016-11-15T12:00:00Z", + "go_live_date": null, + "enrollment_start": "2016-03-01T12:00:00Z", + "enrollment_end": null, + "weeks_to_complete": 7, + "pacing_type": "instructor_paced", + "type": "verified", + "restriction_type": null, + "run_type": "df9c20c1-9b54-40a5-bae3-7fda48d84141", + "status": "unpublished", + "is_enrollable": true, + "is_marketable": false, + "availability": "Archived", + "variant_id": null + }, + { + "key": "course-v1:Delftx+CTB3300WCx+3T2017", + "uuid": "ce65ed1e-53cf-4888-9ec6-a594cfbfc0a3", + "title": "Introduction to Water and Climate", + "external_key": null, + "fixed_price_usd": null, + "image": { + "src": "https://prod-discovery.edx-cdn.org/media/course/image/8d384724-c109-45d4-9a92-7920d3f74ef5-4e859aa4bd33.small.jpg", + "description": null, + "height": null, + "width": null + }, + "short_description": "Water is a crucial element in climate and for society. Find out about the latest engineering interventions for water management in rivers, coasts and the urban environment.", + "marketing_url": "https://www.edx.org/course/introduction-water-climate-delftx-ctb3300wcx-2?utm_source=ocwprod-mit-opencourseware&utm_medium=affiliate_partner", + "seats": [ + { + "type": "audit", + "price": "0.00", + "currency": "USD", + "upgrade_deadline": null, + "upgrade_deadline_override": null, + "credit_provider": null, + "credit_hours": null, + "sku": "8776217", + "bulk_sku": null + }, + { + "type": "verified", + "price": "50.00", + "currency": "USD", + "upgrade_deadline": null, + "upgrade_deadline_override": null, + "credit_provider": null, + "credit_hours": null, + "sku": "EF51B77", + "bulk_sku": null + } + ], + "start": "2017-09-06T12:00:00Z", + "end": "2017-11-01T12:00:00Z", + "go_live_date": null, + "enrollment_start": null, + "enrollment_end": null, + "weeks_to_complete": 7, + "pacing_type": "instructor_paced", + "type": "verified", + "restriction_type": null, + "run_type": "df9c20c1-9b54-40a5-bae3-7fda48d84141", + "status": "unpublished", + "is_enrollable": true, + "is_marketable": false, + "availability": "Archived", + "variant_id": null + }, + { + "key": "course-v1:Delftx+CTB3300WCx+3T2018", + "uuid": "8ae0f3a9-3fdf-4153-9f06-0929de980bef", + "title": "Introduction to Water and Climate", + "external_key": null, + "fixed_price_usd": null, + "image": { + "src": "https://prod-discovery.edx-cdn.org/media/course/image/8d384724-c109-45d4-9a92-7920d3f74ef5-4e859aa4bd33.small.jpg", + "description": null, + "height": null, + "width": null + }, + "short_description": "

Water is a crucial element in climate and for society. Find out about the latest engineering interventions for water management in rivers, coasts and the urban environment.

", + "marketing_url": "https://www.edx.org/course/introduction-to-water-and-climate?utm_source=ocwprod-mit-opencourseware&utm_medium=affiliate_partner", + "seats": [ + { + "type": "verified", + "price": "50.00", + "currency": "USD", + "upgrade_deadline": "2018-10-20T23:59:59Z", + "upgrade_deadline_override": null, + "credit_provider": null, + "credit_hours": null, + "sku": "51FACDC", + "bulk_sku": "3C3D06B" + }, + { + "type": "audit", + "price": "0.00", + "currency": "USD", + "upgrade_deadline": null, + "upgrade_deadline_override": null, + "credit_provider": null, + "credit_hours": null, + "sku": "9CF1878", + "bulk_sku": null + } + ], + "start": "2018-09-05T12:00:00Z", + "end": "2018-10-30T12:00:00Z", + "go_live_date": null, + "enrollment_start": null, + "enrollment_end": null, + "weeks_to_complete": 7, + "pacing_type": "instructor_paced", + "type": "verified", + "restriction_type": null, + "run_type": "df9c20c1-9b54-40a5-bae3-7fda48d84141", + "status": "unpublished", + "is_enrollable": true, + "is_marketable": false, + "availability": "Archived", + "variant_id": null + }, + { + "key": "course-v1:Delftx+CTB3300WCx+1T2019", + "uuid": "f2fcf0c4-ba9c-4765-8e90-3ad333110813", + "title": "Introduction to Water and Climate", + "external_key": null, + "fixed_price_usd": null, + "image": { + "src": "https://prod-discovery.edx-cdn.org/media/course/image/8d384724-c109-45d4-9a92-7920d3f74ef5-4e859aa4bd33.small.jpg", + "description": null, + "height": null, + "width": null + }, + "short_description": "

Water is a crucial element in climate and for society. Find out about the latest engineering interventions for water management in rivers, coasts and the urban environment.

", + "marketing_url": "https://www.edx.org/course/introduction-to-water-and-climate-0?utm_source=ocwprod-mit-opencourseware&utm_medium=affiliate_partner", + "seats": [ + { + "type": "verified", + "price": "50.00", + "currency": "USD", + "upgrade_deadline": "2019-05-22T23:59:59Z", + "upgrade_deadline_override": null, + "credit_provider": null, + "credit_hours": null, + "sku": "26FDED2", + "bulk_sku": "B045F8E" + }, + { + "type": "audit", + "price": "0.00", + "currency": "USD", + "upgrade_deadline": null, + "upgrade_deadline_override": null, + "credit_provider": null, + "credit_hours": null, + "sku": "669CB6C", + "bulk_sku": null + } + ], + "start": "2019-01-01T12:00:00Z", + "end": "2019-06-01T12:00:00Z", + "go_live_date": null, + "enrollment_start": null, + "enrollment_end": null, + "weeks_to_complete": 10, + "pacing_type": "self_paced", + "type": "verified", + "restriction_type": null, + "run_type": "df9c20c1-9b54-40a5-bae3-7fda48d84141", + "status": "unpublished", + "is_enrollable": true, + "is_marketable": false, + "availability": "Archived", + "variant_id": null + }, + { + "key": "course-v1:Delftx+CTB3300WCx+3T2019", + "uuid": "820730f5-24eb-41f9-960b-adf475bced0d", + "title": "Introduction to Water and Climate", + "external_key": null, + "fixed_price_usd": null, + "image": { + "src": "https://prod-discovery.edx-cdn.org/media/course/image/8d384724-c109-45d4-9a92-7920d3f74ef5-4e859aa4bd33.small.jpg", + "description": null, + "height": null, + "width": null + }, + "short_description": "

Water is a crucial element in climate and for society. Find out about the latest engineering interventions for water management in rivers, coasts and the urban environment.

", + "marketing_url": "https://www.edx.org/course/introduction-to-water-and-climate-2?utm_source=ocwprod-mit-opencourseware&utm_medium=affiliate_partner", + "seats": [ + { + "type": "verified", + "price": "50.00", + "currency": "USD", + "upgrade_deadline": "2021-01-04T23:59:59Z", + "upgrade_deadline_override": null, + "credit_provider": null, + "credit_hours": null, + "sku": "E4CCAD3", + "bulk_sku": "5547ED9" + }, + { + "type": "audit", + "price": "0.00", + "currency": "USD", + "upgrade_deadline": null, + "upgrade_deadline_override": null, + "credit_provider": null, + "credit_hours": null, + "sku": "59AB6FC", + "bulk_sku": null + } + ], + "start": "2019-09-01T00:00:00Z", + "end": "2021-01-14T00:00:00Z", + "go_live_date": null, + "enrollment_start": null, + "enrollment_end": null, + "weeks_to_complete": 10, + "pacing_type": "self_paced", + "type": "verified", + "restriction_type": null, + "run_type": "df9c20c1-9b54-40a5-bae3-7fda48d84141", + "status": "unpublished", + "is_enrollable": true, + "is_marketable": false, + "availability": "Archived", + "variant_id": null + }, + { + "key": "course-v1:Delftx+CTB3300WCx+3T2021", + "uuid": "a7bed6f7-fddb-4979-97bf-544c48880eb6", + "title": "Introduction to Water and Climate", + "external_key": null, + "fixed_price_usd": null, + "image": { + "src": "https://prod-discovery.edx-cdn.org/media/course/image/8d384724-c109-45d4-9a92-7920d3f74ef5-4e859aa4bd33.small.jpg", + "description": null, + "height": null, + "width": null + }, + "short_description": "

Water is a crucial element in climate and for society. Find out about the latest engineering interventions for water management in rivers, coasts and the urban environment.

", + "marketing_url": "https://www.edx.org/course/introduction-to-water-and-climate-course-v1delftxctb3300wcx3t2021?utm_source=ocwprod-mit-opencourseware&utm_medium=affiliate_partner", + "seats": [ + { + "type": "audit", + "price": "0.00", + "currency": "USD", + "upgrade_deadline": null, + "upgrade_deadline_override": null, + "credit_provider": null, + "credit_hours": null, + "sku": "4E77AD3", + "bulk_sku": null + }, + { + "type": "verified", + "price": "139.00", + "currency": "USD", + "upgrade_deadline": "2022-08-21T23:59:59Z", + "upgrade_deadline_override": null, + "credit_provider": null, + "credit_hours": null, + "sku": "42C05D4", + "bulk_sku": "08A08C1" + } + ], + "start": "2021-09-01T10:00:00Z", + "end": "2022-08-31T10:00:00Z", + "go_live_date": "2021-04-27T22:00:00Z", + "enrollment_start": null, + "enrollment_end": null, + "weeks_to_complete": 10, + "pacing_type": "self_paced", + "type": "verified", + "restriction_type": null, + "run_type": "df9c20c1-9b54-40a5-bae3-7fda48d84141", + "status": "unpublished", + "is_enrollable": true, + "is_marketable": false, + "availability": "Archived", + "variant_id": null + }, + { + "key": "course-v1:Delftx+CTB3300WCx+3T2022", + "uuid": "a3822c60-0e37-4bc9-8957-753068d2d662", + "title": "Introduction to Water and Climate", + "external_key": "", + "fixed_price_usd": null, + "image": { + "src": "https://prod-discovery.edx-cdn.org/media/course/image/8d384724-c109-45d4-9a92-7920d3f74ef5-4e859aa4bd33.small.jpg", + "description": null, + "height": null, + "width": null + }, + "short_description": "

Water is a crucial element in climate and for society. Find out about the latest engineering interventions for water management in rivers, coasts and the urban environment.

", + "marketing_url": "https://www.edx.org/course/introduction-to-water-and-climate-course-v1delftxctb3300wcx3t2022?utm_source=ocwprod-mit-opencourseware&utm_medium=affiliate_partner", + "seats": [ + { + "type": "audit", + "price": "0.00", + "currency": "USD", + "upgrade_deadline": null, + "upgrade_deadline_override": null, + "credit_provider": null, + "credit_hours": null, + "sku": "E636C80", + "bulk_sku": null + }, + { + "type": "verified", + "price": "139.00", + "currency": "USD", + "upgrade_deadline": "2023-12-05T23:59:59Z", + "upgrade_deadline_override": null, + "credit_provider": null, + "credit_hours": null, + "sku": "19897AC", + "bulk_sku": "FCB1B8F" + } + ], + "start": "2022-09-01T10:00:00Z", + "end": "2023-12-15T10:00:00Z", + "go_live_date": "2022-04-30T22:00:00Z", + "enrollment_start": null, + "enrollment_end": null, + "weeks_to_complete": 10, + "pacing_type": "self_paced", + "type": "verified", + "restriction_type": null, + "run_type": "df9c20c1-9b54-40a5-bae3-7fda48d84141", + "status": "unpublished", + "is_enrollable": true, + "is_marketable": false, + "availability": "Archived", + "variant_id": null + }, + { + "key": "course-v1:Delftx+CTB3300WCx+1T2024", + "uuid": "b127d07e-3b2d-4bdb-970a-44b871ad6237", + "title": "Introduction to Water and Climate", + "external_key": "", + "fixed_price_usd": null, + "image": { + "src": "https://prod-discovery.edx-cdn.org/media/course/image/8d384724-c109-45d4-9a92-7920d3f74ef5-4e859aa4bd33.small.jpg", + "description": null, + "height": null, + "width": null + }, + "short_description": "

Water is a crucial element in climate and for society. Find out about the latest engineering interventions for water management in rivers, coasts and the urban environment.

", + "marketing_url": "https://www.edx.org/course/introduction-to-water-and-climate-course-v1-delftx-ctb3300wcx-1t2024?utm_source=ocwprod-mit-opencourseware&utm_medium=affiliate_partner", + "seats": [ + { + "type": "audit", + "price": "0.00", + "currency": "USD", + "upgrade_deadline": null, + "upgrade_deadline_override": null, + "credit_provider": null, + "credit_hours": null, + "sku": "007073A", + "bulk_sku": null + }, + { + "type": "verified", + "price": "139.00", + "currency": "USD", + "upgrade_deadline": "2025-05-18T23:59:59Z", + "upgrade_deadline_override": null, + "credit_provider": null, + "credit_hours": null, + "sku": "11EC6DB", + "bulk_sku": "636D2D9" + } + ], + "start": "2024-02-28T11:00:00Z", + "end": "2025-05-28T10:00:00Z", + "go_live_date": "2024-02-08T23:00:00Z", + "enrollment_start": null, + "enrollment_end": null, + "weeks_to_complete": 10, + "pacing_type": "self_paced", + "type": "verified", + "restriction_type": null, + "run_type": "df9c20c1-9b54-40a5-bae3-7fda48d84141", + "status": "published", + "is_enrollable": true, + "is_marketable": true, + "availability": "Current", + "variant_id": null + } + ], + "entitlements": [ + { + "mode": "verified", + "price": "139.00", + "currency": "USD", + "sku": "7ABFFB0", + "expires": null + } + ], + "owners": [ + { + "uuid": "c484a523-d396-4aff-90f4-bb7e82e16bf6", + "key": "DelftX", + "name": "Delft University of Technology", + "auto_generate_course_run_keys": true, + "certificate_logo_image_url": "https://prod-discovery.edx-cdn.org/organization/certificate_logos/c484a523-d396-4aff-90f4-bb7e82e16bf6-c43b6cb39bcc.png", + "logo_image_url": "https://prod-discovery.edx-cdn.org/organization/logos/c484a523-d396-4aff-90f4-bb7e82e16bf6-f9e6cc4a4c94.png", + "organization_hex_color": null, + "data_modified_timestamp": "2024-02-01T15:45:47.314868Z" + } + ], + "image": { + "src": "https://prod-discovery.edx-cdn.org/media/course/image/8d384724-c109-45d4-9a92-7920d3f74ef5-4e859aa4bd33.small.jpg", + "description": null, + "height": null, + "width": null + }, + "short_description": "

Water is a crucial element in climate and for society. Find out about the latest engineering interventions for water management in rivers, coasts and the urban environment.

", + "type": "e85a9f60-2da5-4413-96b7-dcb9b8c0e9f1", + "url_slug": null, + "course_type": "verified-audit", + "enterprise_subscription_inclusion": true, + "excluded_from_seo": false, + "excluded_from_search": false, + "course_run_statuses": ["archived", "published"] + } + ], + "authoring_organizations": [ + { + "uuid": "c484a523-d396-4aff-90f4-bb7e82e16bf6", + "key": "DelftX", + "name": "Delft University of Technology", + "auto_generate_course_run_keys": true, + "certificate_logo_image_url": "https://prod-discovery.edx-cdn.org/organization/certificate_logos/c484a523-d396-4aff-90f4-bb7e82e16bf6-c43b6cb39bcc.png", + "logo_image_url": "https://prod-discovery.edx-cdn.org/organization/logos/c484a523-d396-4aff-90f4-bb7e82e16bf6-f9e6cc4a4c94.png", + "organization_hex_color": null, + "data_modified_timestamp": "2024-02-01T15:45:47.314868Z" + } + ], + "card_image_url": "https://prod-discovery.edx-cdn.org/media/programs/card_images/0445d855-1d24-480a-87ff-01a9186e237a-c1d6687579f0.jpg", + "is_program_eligible_for_one_click_purchase": false, + "degree": null, + "curricula": [], + "marketing_hook": "Explore water management concepts and technologies", + "total_hours_of_effort": null, + "recent_enrollment_count": 3462, + "organization_short_code_override": "", + "organization_logo_override_url": null, + "primary_subject_override": null, + "level_type_override": null, + "language_override": null, + "labels": [], + "taxi_form": null, + "program_duration_override": null, + "data_modified_timestamp": "2024-08-28T07:12:51.701135Z", + "excluded_from_search": false, + "excluded_from_seo": false, + "has_ofac_restrictions": null, + "ofac_comment": "", + "course_run_statuses": ["archived", "published"], + "subscription_eligible": false, + "subscription_prices": [ + { + "price": "79.00", + "currency": "USD" + } + ] + } +] From 3eb1bf3010103eeae4da71d2984b0c9420823868 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 12 Sep 2024 13:40:49 -0400 Subject: [PATCH 11/18] Update docker.elastic.co/elasticsearch/elasticsearch Docker tag to v8 (#1260) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f05a263165..a51f4e43fd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,7 +28,7 @@ jobs: - 6379:6379 elastic: - image: docker.elastic.co/elasticsearch/elasticsearch:7.17.23 + image: docker.elastic.co/elasticsearch/elasticsearch:8.15.1 env: network.host: "0.0.0.0" http.cors.enabled: "true" From abd4d0f792c50dc9a3483c2b6fe030534e45b57c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 12 Sep 2024 14:17:02 -0400 Subject: [PATCH 12/18] Update dependency pytest-cov to v5 (#1549) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- poetry.lock | 12 ++++++------ pyproject.toml | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/poetry.lock b/poetry.lock index fe1026aeda..5d1698572a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -3185,13 +3185,13 @@ testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "no [[package]] name = "pytest-cov" -version = "4.1.0" +version = "5.0.0" description = "Pytest plugin for measuring coverage." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"}, - {file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"}, + {file = "pytest-cov-5.0.0.tar.gz", hash = "sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857"}, + {file = "pytest_cov-5.0.0-py3-none-any.whl", hash = "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652"}, ] [package.dependencies] @@ -3199,7 +3199,7 @@ coverage = {version = ">=5.2.1", extras = ["toml"]} pytest = ">=4.6" [package.extras] -testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] [[package]] name = "pytest-django" @@ -4856,4 +4856,4 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] [metadata] lock-version = "2.0" python-versions = "3.12.5" -content-hash = "09989937bea2537e2d7e1efaa12fcfa0a95298ec2fc3e99859e0df33f4d65d9c" +content-hash = "d4b7f1677ba9a893a290052918eb6192f47b1b86ca36fcc906bdda562e608780" diff --git a/pyproject.toml b/pyproject.toml index a889a84328..5aec2a1cae 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -93,7 +93,7 @@ moto = "^4.1.12" nplusone = "^1.0.0" pdbpp = "^0.10.3" pytest = "^7.3.1" -pytest-cov = "^4.1.0" +pytest-cov = "^5.0.0" pytest-django = "^4.5.2" pytest-env = "^1.0.0" pytest-freezegun = "^0.4.2" From 383886ea9733b9143cb80ff5595204572b8e5cca Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 12 Sep 2024 14:29:09 -0400 Subject: [PATCH 13/18] Update dependency @faker-js/faker to v9 (#1533) --- frontends/api/package.json | 2 +- frontends/api/src/test-utils/factories/user.ts | 5 ++++- frontends/ol-components/package.json | 2 +- frontends/ol-test-utilities/package.json | 2 +- frontends/ol-utilities/package.json | 2 +- frontends/ol-widgets/package.json | 2 +- yarn.lock | 17 ++++++++++++----- 7 files changed, 21 insertions(+), 11 deletions(-) diff --git a/frontends/api/package.json b/frontends/api/package.json index a534c408a5..02ab0da58a 100644 --- a/frontends/api/package.json +++ b/frontends/api/package.json @@ -15,7 +15,7 @@ "react": "18.3.1" }, "devDependencies": { - "@faker-js/faker": "^8.0.0", + "@faker-js/faker": "^9.0.0", "@testing-library/react": "16.0.1", "enforce-unique": "^1.3.0", "jest-when": "^3.6.0", diff --git a/frontends/api/src/test-utils/factories/user.ts b/frontends/api/src/test-utils/factories/user.ts index c244cffcb4..d8defb2296 100644 --- a/frontends/api/src/test-utils/factories/user.ts +++ b/frontends/api/src/test-utils/factories/user.ts @@ -1,6 +1,7 @@ import { faker } from "@faker-js/faker/locale/en" import type { PartialFactory } from "ol-test-utilities" import type { Profile, User } from "../../generated/v0" +import { UniqueEnforcer } from "enforce-unique" const profile: PartialFactory = (overrides = {}): Profile => ({ name: faker.person.fullName(), @@ -21,9 +22,11 @@ const profile: PartialFactory = (overrides = {}): Profile => ({ ...overrides, }) +const enforcerId = new UniqueEnforcer() + const user: PartialFactory = (overrides = {}): User => { const result: User = { - id: faker.helpers.unique(faker.number.int), + id: enforcerId.enforce(faker.number.int), first_name: faker.person.firstName(), last_name: faker.person.lastName(), is_article_editor: false, diff --git a/frontends/ol-components/package.json b/frontends/ol-components/package.json index bf2dd65d29..35c7451f14 100644 --- a/frontends/ol-components/package.json +++ b/frontends/ol-components/package.json @@ -37,7 +37,7 @@ "validator": "^13.11.0" }, "devDependencies": { - "@faker-js/faker": "^8.0.0", + "@faker-js/faker": "^9.0.0", "@storybook/addon-actions": "^8.0.9", "@storybook/addon-essentials": "^8.0.9", "@storybook/addon-interactions": "^8.0.9", diff --git a/frontends/ol-test-utilities/package.json b/frontends/ol-test-utilities/package.json index e548f57a79..a395f4dfb8 100644 --- a/frontends/ol-test-utilities/package.json +++ b/frontends/ol-test-utilities/package.json @@ -7,7 +7,7 @@ "./filemocks/*": "./src/filemocks/*" }, "dependencies": { - "@faker-js/faker": "^8.0.0", + "@faker-js/faker": "^9.0.0", "@testing-library/react": "16.0.1", "css-mediaquery": "^0.1.2", "dom-accessibility-api": "^0.7.0", diff --git a/frontends/ol-utilities/package.json b/frontends/ol-utilities/package.json index c72cee2b83..a81c595f3a 100644 --- a/frontends/ol-utilities/package.json +++ b/frontends/ol-utilities/package.json @@ -13,7 +13,7 @@ "@dnd-kit/core": "^6.0.8", "@dnd-kit/sortable": "^8.0.0", "@dnd-kit/utilities": "^3.2.1", - "@faker-js/faker": "^8.0.0", + "@faker-js/faker": "^9.0.0", "api": "workspace:*", "classnames": "^2.3.2", "decimal.js-light": "^2.5.1", diff --git a/frontends/ol-widgets/package.json b/frontends/ol-widgets/package.json index 3b590ed972..4b1df67f43 100644 --- a/frontends/ol-widgets/package.json +++ b/frontends/ol-widgets/package.json @@ -4,7 +4,7 @@ "private": true, "main": "./src/index.ts", "dependencies": { - "@faker-js/faker": "^8.0.0", + "@faker-js/faker": "^9.0.0", "@remixicon/react": "^4.2.0", "classnames": "^2.3.2", "formik": "^2.4.5", diff --git a/yarn.lock b/yarn.lock index c9ca726d82..0288ed15a1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2911,6 +2911,13 @@ __metadata: languageName: node linkType: hard +"@faker-js/faker@npm:^9.0.0": + version: 9.0.0 + resolution: "@faker-js/faker@npm:9.0.0" + checksum: 10/aedd68affd27065450da3c19a0ea3050e6a16078e7d591faf38402e572dd555f5ae6b402d26456eceb6b793c44b8f83ea7b48804d1fcb841610acd4988c41f91 + languageName: node + linkType: hard + "@floating-ui/core@npm:^1.6.0": version: 1.6.7 resolution: "@floating-ui/core@npm:1.6.7" @@ -6438,7 +6445,7 @@ __metadata: version: 0.0.0-use.local resolution: "api@workspace:frontends/api" dependencies: - "@faker-js/faker": "npm:^8.0.0" + "@faker-js/faker": "npm:^9.0.0" "@lukemorales/query-key-factory": "npm:^1.3.2" "@tanstack/react-query": "npm:^4.36.1" "@testing-library/react": "npm:16.0.1" @@ -15374,7 +15381,7 @@ __metadata: "@dnd-kit/utilities": "npm:^3.2.1" "@emotion/react": "npm:^11.11.1" "@emotion/styled": "npm:^11.11.0" - "@faker-js/faker": "npm:^8.0.0" + "@faker-js/faker": "npm:^9.0.0" "@mui/base": "npm:5.0.0-beta.40" "@mui/lab": "npm:^5.0.0-alpha.172" "@mui/material": "npm:^5.16.1" @@ -15427,7 +15434,7 @@ __metadata: version: 0.0.0-use.local resolution: "ol-test-utilities@workspace:frontends/ol-test-utilities" dependencies: - "@faker-js/faker": "npm:^8.0.0" + "@faker-js/faker": "npm:^9.0.0" "@testing-library/react": "npm:16.0.1" css-mediaquery: "npm:^0.1.2" dom-accessibility-api: "npm:^0.7.0" @@ -15442,7 +15449,7 @@ __metadata: "@dnd-kit/core": "npm:^6.0.8" "@dnd-kit/sortable": "npm:^8.0.0" "@dnd-kit/utilities": "npm:^3.2.1" - "@faker-js/faker": "npm:^8.0.0" + "@faker-js/faker": "npm:^9.0.0" "@testing-library/react": "npm:16.0.1" "@testing-library/user-event": "npm:14.5.2" "@types/validator": "npm:^13.7.6" @@ -15468,7 +15475,7 @@ __metadata: version: 0.0.0-use.local resolution: "ol-widgets@workspace:frontends/ol-widgets" dependencies: - "@faker-js/faker": "npm:^8.0.0" + "@faker-js/faker": "npm:^9.0.0" "@remixicon/react": "npm:^4.2.0" "@testing-library/react": "npm:16.0.1" "@testing-library/user-event": "npm:14.5.2" From f70fb99177dc745e0028ab4959df1c477afc636a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 12 Sep 2024 14:48:17 -0400 Subject: [PATCH 14/18] Update dependency attrs to v24 (#1535) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- poetry.lock | 20 ++++++++++---------- pyproject.toml | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/poetry.lock b/poetry.lock index 5d1698572a..5c4174ea21 100644 --- a/poetry.lock +++ b/poetry.lock @@ -70,22 +70,22 @@ test = ["astroid (>=1,<2)", "astroid (>=2,<4)", "pytest"] [[package]] name = "attrs" -version = "23.2.0" +version = "24.2.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=3.7" files = [ - {file = "attrs-23.2.0-py3-none-any.whl", hash = "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"}, - {file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"}, + {file = "attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2"}, + {file = "attrs-24.2.0.tar.gz", hash = "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346"}, ] [package.extras] -cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] -dev = ["attrs[tests]", "pre-commit"] -docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] -tests = ["attrs[tests-no-zope]", "zope-interface"] -tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"] -tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"] +benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier (<24.7)"] +tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] [[package]] name = "backoff" @@ -4856,4 +4856,4 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] [metadata] lock-version = "2.0" python-versions = "3.12.5" -content-hash = "d4b7f1677ba9a893a290052918eb6192f47b1b86ca36fcc906bdda562e608780" +content-hash = "95cf3b0343e1390880d454b35a21f2b0038ce6c96a1bbdce91108a8544e5d7e3" diff --git a/pyproject.toml b/pyproject.toml index 5aec2a1cae..350aa5351b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ authors = ["MIT ODL"] [tool.poetry.dependencies] python = "3.12.5" -attrs = "^23.1.0" +attrs = "^24.0.0" base36 = "^0.1.1" beautifulsoup4 = "^4.8.2" boto3 = "^1.26.155" From 4ec17e352b23d1b18ad2d13c1a908430db3282eb Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 12 Sep 2024 15:01:45 -0400 Subject: [PATCH 15/18] Update actions/upload-artifact digest to 5076954 (#1528) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a51f4e43fd..a35300027f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -145,7 +145,7 @@ jobs: NODE_ENV: test - name: Upload frontend build - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4 with: name: frontend-build path: frontends/mit-learn/build @@ -301,7 +301,7 @@ jobs: - name: Upload artifact if: always() - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4 with: name: playwright-report path: e2e_testing/playwright-report From af0d5239d5154989d47a47e64942be7116040dd7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 12 Sep 2024 15:28:09 -0400 Subject: [PATCH 16/18] Update dependency faker to v28 (#1550) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- poetry.lock | 8 ++++---- pyproject.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/poetry.lock b/poetry.lock index 5c4174ea21..a375a0e2f4 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1476,13 +1476,13 @@ doc = ["Sphinx", "sphinx-rtd-theme", "sphinxcontrib-spelling"] [[package]] name = "faker" -version = "25.9.2" +version = "28.4.1" description = "Faker is a Python package that generates fake data for you." optional = false python-versions = ">=3.8" files = [ - {file = "Faker-25.9.2-py3-none-any.whl", hash = "sha256:7f8cbd179a7351648bea31f53d021a2bdfdeb59e9b830e121a635916615e0ecd"}, - {file = "Faker-25.9.2.tar.gz", hash = "sha256:ca94843600a4089a91394023fef014bb41fee509f8c4beef1530018373e770fb"}, + {file = "Faker-28.4.1-py3-none-any.whl", hash = "sha256:e59c01d1e8b8e20a83255ab8232c143cb2af3b4f5ab6a3f5ce495f385ad8ab4c"}, + {file = "faker-28.4.1.tar.gz", hash = "sha256:4294d169255a045990720d6f3fa4134b764a4cdf46ef0d3c7553d2506f1adaa1"}, ] [package.dependencies] @@ -4856,4 +4856,4 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] [metadata] lock-version = "2.0" python-versions = "3.12.5" -content-hash = "95cf3b0343e1390880d454b35a21f2b0038ce6c96a1bbdce91108a8544e5d7e3" +content-hash = "6685a9c05a978eb97ae40e72b0589d48390aedb8892d5d84954088477da35259" diff --git a/pyproject.toml b/pyproject.toml index 350aa5351b..c74605b74b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -87,7 +87,7 @@ bpython = "^0.24" ddt = "^1.6.0" django-debug-toolbar = "^4.1.0" factory_boy = "^3.3.0" -faker = "^25.0.0" +faker = "^28.0.0" ipdb = "^0.13.13" moto = "^4.1.12" nplusone = "^1.0.0" From 3bff3818c6e13df49f1d32426835aee0e3333c01 Mon Sep 17 00:00:00 2001 From: Nathan Levesque Date: Thu, 12 Sep 2024 16:02:10 -0400 Subject: [PATCH 17/18] Update docker-compose config to support OS cluster (#1540) --- .devcontainer/devcontainer.json | 2 +- Dockerfile | 4 +- README.md | 12 ++ docker-compose.apps.yml | 68 ++++++++ docker-compose.codespaces.yml | 93 ++--------- docker-compose.opensearch.base.yml | 21 +++ docker-compose.opensearch.cluster.apps.yml | 17 ++ docker-compose.opensearch.cluster.yml | 48 ++++++ ...er-compose.opensearch.single-node.apps.yml | 9 + docker-compose.opensearch.single-node.yml | 17 ++ docker-compose.services.yml | 83 ++++++++++ docker-compose.yml | 155 ++---------------- env/backend.env | 2 +- env/ci.env | 2 +- env/codespaces.env | 2 +- 15 files changed, 304 insertions(+), 231 deletions(-) create mode 100644 docker-compose.apps.yml create mode 100644 docker-compose.opensearch.base.yml create mode 100644 docker-compose.opensearch.cluster.apps.yml create mode 100644 docker-compose.opensearch.cluster.yml create mode 100644 docker-compose.opensearch.single-node.apps.yml create mode 100644 docker-compose.opensearch.single-node.yml create mode 100644 docker-compose.services.yml diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 513c37e424..a26590ed9f 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -4,7 +4,7 @@ "runServices": [ "watch", "web", - "opensearch-node-mitopen", + "opensearch-node-mitopen-1", "db", "tika", "celery", diff --git a/Dockerfile b/Dockerfile index 3a4753e25e..9d3cf6721e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -56,6 +56,6 @@ RUN apt-get clean && apt-get purge USER mitodl -EXPOSE 8063 -ENV PORT 8063 +EXPOSE 8061 +ENV PORT 8061 CMD uwsgi uwsgi.ini diff --git a/README.md b/README.md index 49f5212b47..4c2e992743 100644 --- a/README.md +++ b/README.md @@ -175,6 +175,18 @@ To enable searching the course catalog on opensearch, run through these steps: 3. Once created and with `docker compose up` running, hit this endpoint in your browser to see if the index exists: `http://localhost:9101/discussions_local_all_default/_search` 4. If yes, to run a specific query, make a `POST` request (using `curl`, [Postman](https://www.getpostman.com/downloads/), Python `requests`, etc.) to the above endpoint with a `json` payload. For example, to search for all courses, run a query with Content-Type as `application/json` and with a body `{"query":{"term":{"object_type":"course"}}}` +### Running OpenSearch as a multi-node local cluster + +By default the configuration runs OpenSearch in `single-node` mode. If you'd like to run a 3-node cluster locally you can set the following environment variable in your shell. + +```shell +export OPENSEARCH_CLUSTER_TYPE=cluster +``` + +You should make this permanent by using `direnv` or similar so that all your shell sessions are using the same docker compose config): + +After setting this and running `docker compose up` you'll see this 3 node cluster be created. Note that the volumes used by these containers are separate from the volume used by the single-node setup so you will need to recreate your indicies. This is intentional and critical to being able to switch back and forth between `single-node` and `cluster` setups. + ### Running the app in a notebook This repo includes a config for running a [Jupyter notebook](https://jupyter.org/) in a Docker container. This enables you to do in a Jupyter notebook anything you might otherwise do in a Django shell. To get started: diff --git a/docker-compose.apps.yml b/docker-compose.apps.yml new file mode 100644 index 0000000000..06049cfe6b --- /dev/null +++ b/docker-compose.apps.yml @@ -0,0 +1,68 @@ +include: + - docker-compose.opensearch.${OPENSEARCH_CLUSTER_TYPE:-single-node}.yml + +services: + web: + profiles: + - backend + build: + context: . + dockerfile: Dockerfile + extends: + file: docker-compose.opensearch.${OPENSEARCH_CLUSTER_TYPE:-single-node}.apps.yml + service: web + mem_limit: 1gb + cpus: 2 + command: ./scripts/run-django-dev.sh + stdin_open: true + tty: true + ports: + - "8061:8061" + depends_on: + db: + condition: service_healthy + redis: + condition: service_healthy + volumes: + - .:/src + - django_media:/var/media + + watch: + profiles: + - frontend + working_dir: /src + image: node:20.17 + entrypoint: ["/bin/sh", "-c"] + command: + - | + yarn install --immutable + yarn workspace mit-learn storybook --no-open & + yarn workspace mit-learn watch:docker + ports: + - "8062:8062" + - "6006:6006" + volumes: + - .:/src + + celery: + profiles: + - backend + build: + context: . + dockerfile: Dockerfile + extends: + file: docker-compose.opensearch.${OPENSEARCH_CLUSTER_TYPE:-single-node}.apps.yml + service: web + command: > + /bin/bash -c ' + sleep 3; + celery -A main.celery:app worker -Q default -B -l ${MITOL_LOG_LEVEL:-INFO} & + celery -A main.celery:app worker -Q edx_content,default -l ${MITOL_LOG_LEVEL:-INFO}' + depends_on: + db: + condition: service_healthy + redis: + condition: service_healthy + volumes: + - .:/src + - django_media:/var/media diff --git a/docker-compose.codespaces.yml b/docker-compose.codespaces.yml index 8cfb5b6fdb..965f705a7e 100644 --- a/docker-compose.codespaces.yml +++ b/docker-compose.codespaces.yml @@ -1,90 +1,21 @@ -x-environment: &py-environment +include: + - docker-compose.services.yml services: - db: - extends: - file: docker-compose.yml - service: db - env_file: env/codespaces.env - redis: - extends: - file: docker-compose.yml - service: redis web: - profiles: - - backend - build: - context: . - dockerfile: Dockerfile - mem_limit: 1gb - cpus: 2 + extends: + file: docker-compose.apps.yml + service: web env_file: env/codespaces.env - environment: - PORT: 8061 - command: ./scripts/run-django-dev.sh - stdin_open: true - tty: true - ports: - - "8061:8061" - links: - - db - - opensearch-node-mitopen - - redis - volumes: - - .:/src - - django_media:/var/media + watch: - profiles: - - frontend - working_dir: /src - image: node:20.17 - entrypoint: ["/bin/sh", "-c"] - command: - - | - yarn install --immutable - yarn workspace mit-learn storybook --no-open & - yarn workspace mit-learn watch:docker + extends: + file: docker-compose.apps.yml + service: watch env_file: env/codespaces.env - ports: - - "8062:8062" - - "6006:6006" - volumes: - - .:/src + celery: - profiles: - - backend - build: - context: . - dockerfile: Dockerfile - env_file: env/codespaces.env - command: > - /bin/bash -c ' - sleep 3; - celery -A main.celery:app worker -Q default -B -l ${MITOL_LOG_LEVEL:-INFO} & - celery -A main.celery:app worker -Q edx_content,default -l ${MITOL_LOG_LEVEL:-INFO}' - links: - - db - - opensearch-node-mitopen - - redis - volumes: - - .:/src - - django_media:/var/media - tika: - extends: - file: docker-compose.yml - service: tika - opensearch-node-mitopen: extends: - file: docker-compose.yml - service: opensearch-node-mitopen + file: docker-compose.apps.yml + service: celery env_file: env/codespaces.env - nginx: - extends: - file: docker-compose.yml - service: nginx - -volumes: - opensearch-data1: - django_media: - yarn-cache: - pgdata: diff --git a/docker-compose.opensearch.base.yml b/docker-compose.opensearch.base.yml new file mode 100644 index 0000000000..a5602f6e6e --- /dev/null +++ b/docker-compose.opensearch.base.yml @@ -0,0 +1,21 @@ +services: + opensearch: + image: opensearchproject/opensearch:2.16.0 + environment: + - "cluster.name=opensearch-cluster" + - "bootstrap.memory_lock=true" # along with the memlock settings below, disables swapping + - "OPENSEARCH_JAVA_OPTS=-Xms1024m -Xmx1024m" # Set min and max JVM heap sizes to at least 50% of system RAM + - "DISABLE_INSTALL_DEMO_CONFIG=true" # disables execution of install_demo_configuration.sh bundled with security plugin, which installs demo certificates and security configurations to OpenSearch + - "DISABLE_SECURITY_PLUGIN=true" # disables security plugin entirely in OpenSearch by setting plugins.security.disabled: true in opensearch.yml + healthcheck: + test: curl http://localhost:9200/_cluster/health || exit 1 + interval: 3s + timeout: 3s + retries: 20 + ulimits: + memlock: + soft: -1 + hard: -1 + nofile: + soft: 65536 # maximum number of open files for the OpenSearch user, set to at least 65536 on modern systems + hard: 65536 diff --git a/docker-compose.opensearch.cluster.apps.yml b/docker-compose.opensearch.cluster.apps.yml new file mode 100644 index 0000000000..cd473ffef1 --- /dev/null +++ b/docker-compose.opensearch.cluster.apps.yml @@ -0,0 +1,17 @@ +services: + web: + depends_on: + opensearch-node-mitopen-1: + condition: service_healthy + opensearch-node-mitopen-2: + condition: service_healthy + opensearch-node-mitopen-3: + condition: service_healthy + celery: + depends_on: + opensearch-node-mitopen-1: + condition: service_healthy + opensearch-node-mitopen-2: + condition: service_healthy + opensearch-node-mitopen-3: + condition: service_healthy diff --git a/docker-compose.opensearch.cluster.yml b/docker-compose.opensearch.cluster.yml new file mode 100644 index 0000000000..04021d814b --- /dev/null +++ b/docker-compose.opensearch.cluster.yml @@ -0,0 +1,48 @@ +services: + opensearch-node-mitopen-1: + extends: + file: docker-compose.opensearch.base.yml + service: opensearch + hostname: opensearch-node-mitopen-1 + environment: + - "node.name=opensearch-node-mitopen-1" + - "discovery.seed_hosts=opensearch-node-mitopen-1,opensearch-node-mitopen-2,opensearch-node-mitopen-3" + - "cluster.initial_cluster_manager_nodes=opensearch-node-mitopen-1,opensearch-node-mitopen-2,opensearch-node-mitopen-3" + volumes: + - opensearch-cluster-data1:/usr/share/opensearch/data + ports: + - 9100:9200 # REST API + - 9500:9600 # Performance Analyzer + opensearch-node-mitopen-2: + extends: + file: docker-compose.opensearch.base.yml + service: opensearch + hostname: opensearch-node-mitopen-2 + environment: + - "node.name=opensearch-node-mitopen-2" + - "discovery.seed_hosts=opensearch-node-mitopen-1,opensearch-node-mitopen-2,opensearch-node-mitopen-3" + - "cluster.initial_cluster_manager_nodes=opensearch-node-mitopen-1,opensearch-node-mitopen-2,opensearch-node-mitopen-3" + volumes: + - opensearch-cluster-data2:/usr/share/opensearch/data + ports: + - 9101:9200 # REST API + - 9501:9600 # Performance Analyzer + opensearch-node-mitopen-3: + extends: + file: docker-compose.opensearch.base.yml + service: opensearch + hostname: opensearch-node-mitopen-3 + environment: + - "node.name=opensearch-node-mitopen-3" + - "discovery.seed_hosts=opensearch-node-mitopen-1,opensearch-node-mitopen-2,opensearch-node-mitopen-3" + - "cluster.initial_cluster_manager_nodes=opensearch-node-mitopen-1,opensearch-node-mitopen-2,opensearch-node-mitopen-3" + volumes: + - opensearch-cluster-data3:/usr/share/opensearch/data + ports: + - 9102:9200 # REST API + - 9502:9600 # Performance Analyzer + +volumes: + opensearch-cluster-data1: + opensearch-cluster-data2: + opensearch-cluster-data3: diff --git a/docker-compose.opensearch.single-node.apps.yml b/docker-compose.opensearch.single-node.apps.yml new file mode 100644 index 0000000000..9a66e038f3 --- /dev/null +++ b/docker-compose.opensearch.single-node.apps.yml @@ -0,0 +1,9 @@ +services: + web: + depends_on: + opensearch-node-mitopen-1: + condition: service_healthy + celery: + depends_on: + opensearch-node-mitopen-1: + condition: service_healthy diff --git a/docker-compose.opensearch.single-node.yml b/docker-compose.opensearch.single-node.yml new file mode 100644 index 0000000000..c49b312c75 --- /dev/null +++ b/docker-compose.opensearch.single-node.yml @@ -0,0 +1,17 @@ +services: + opensearch-node-mitopen-1: + extends: + file: docker-compose.opensearch.base.yml + service: opensearch + hostname: opensearch-node-mitopen-1 + environment: + - "node.name=opensearch-node-mitopen-1" + - "discovery.type=single-node" # disables bootstrap checks that are enabled when network.host is set to a non-loopback address + volumes: + - opensearch-data1:/usr/share/opensearch/data + ports: + - 9100:9200 # REST API + - 9500:9600 # Performance Analyzer + +volumes: + opensearch-data1: diff --git a/docker-compose.services.yml b/docker-compose.services.yml new file mode 100644 index 0000000000..f43ce554ea --- /dev/null +++ b/docker-compose.services.yml @@ -0,0 +1,83 @@ +include: + - docker-compose.opensearch.${OPENSEARCH_CLUSTER_TYPE:-single-node}.yml + +services: + db: + profiles: + - backend + image: postgres:12.20 + healthcheck: + test: ["CMD", "pg_isready"] + interval: 3s + timeout: 3s + retries: 10 + ports: + - 5432:5432 + environment: + - PGUSER=postgres + - POSTGRES_USER=postgres + - POSTGRES_PASSWORD=postgres + volumes: + - pgdata:/var/lib/postgresql + + redis: + profiles: + - backend + image: redis:7.4.0 + healthcheck: + test: ["CMD", "redis-cli", "ping", "|", "grep", "PONG"] + interval: 3s + timeout: 3s + retries: 10 + ports: + - "6379" + + nginx: + profiles: + - backend + build: + context: ./nginx + ports: + - "8063:8063" + links: + - web + environment: + PORT: 8063 + NGINX_UWSGI_PASS: "web:8061" + volumes: + - ./config:/etc/nginx/templates + + tika: + profiles: + - backend + image: apache/tika:2.5.0 + ports: + - "9998:9998" + + locust: + image: locustio/locust + ports: + - "8089:8089" + volumes: + - ./load_testing:/mnt/locust + command: -f /mnt/locust/locustfile.py --master -H http://nginx:8063 --class-picker + links: + - nginx + profiles: + - load-testing + + locust-worker: + image: locustio/locust + volumes: + - ./load_testing:/mnt/locust + command: -f /mnt/locust/locustfile.py --worker --master-host locust + links: + - nginx + profiles: + - load-testing + +volumes: + pgdata: + # note: these are here instead of docker-compose.apps.yml because `extends` doesn't pull them in + django_media: + yarn-cache: diff --git a/docker-compose.yml b/docker-compose.yml index 7d9e91625a..584fe7c06c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,72 +1,11 @@ -x-environment: &py-environment +include: + - docker-compose.services.yml services: - db: - profiles: - - backend - image: postgres:12.20 - ports: - - 5432:5432 - environment: - - POSTGRES_PASSWORD=postgres - volumes: - - pgdata:/var/lib/postgresql - - redis: - profiles: - - backend - image: redis:7.4.0 - ports: - - "6379" - - opensearch-node-mitopen: - profiles: - - backend - image: opensearchproject/opensearch:2.16.0 - container_name: opensearch-node-mitopen - environment: - - cluster.name=opensearch-cluster - - node.name=opensearch-node-mitopen - - bootstrap.memory_lock=true # along with the memlock settings below, disables swapping - - "OPENSEARCH_JAVA_OPTS=-Xms1024m -Xmx1024m" # Set min and max JVM heap sizes to at least 50% of system RAM - - "DISABLE_INSTALL_DEMO_CONFIG=true" # disables execution of install_demo_configuration.sh bundled with security plugin, which installs demo certificates and security configurations to OpenSearch - - "DISABLE_SECURITY_PLUGIN=true" # disables security plugin entirely in OpenSearch by setting plugins.security.disabled: true in opensearch.yml - - "discovery.type=single-node" # disables bootstrap checks that are enabled when network.host is set to a non-loopback address - ulimits: - memlock: - soft: -1 - hard: -1 - nofile: - soft: 65536 # maximum number of open files for the OpenSearch user, set to at least 65536 on modern systems - hard: 65536 - volumes: - - opensearch-data1:/usr/share/opensearch/data - ports: - - 9100:9200 - - nginx: - profiles: - - backend - build: - context: ./nginx - ports: - - "8063:8063" - links: - - web - environment: - PORT: 8063 - NGINX_UWSGI_PASS: "web:8061" - volumes: - - ./config:/etc/nginx/templates - web: - profiles: - - backend - build: - context: . - dockerfile: Dockerfile - mem_limit: 1gb - cpus: 2 + extends: + file: docker-compose.apps.yml + service: web env_file: - path: env/shared.env - path: env/shared.local.env @@ -77,30 +16,11 @@ services: # DEPRECATED: legacy .env file at the repo root - path: .env required: false - command: ./scripts/run-django-dev.sh - stdin_open: true - tty: true - ports: - - "8061:8061" - links: - - db - - opensearch-node-mitopen - - redis - volumes: - - .:/src - - django_media:/var/media watch: - profiles: - - frontend - working_dir: /src - image: node:20.17 - entrypoint: ["/bin/sh", "-c"] - command: - - | - yarn install --immutable - yarn workspace mit-learn storybook --no-open & - yarn workspace mit-learn watch:docker + extends: + file: docker-compose.apps.yml + service: watch env_file: - path: env/shared.env - path: env/shared.local.env @@ -111,18 +31,11 @@ services: # DEPRECATED: legacy .env file at the repo root - path: .env required: false - ports: - - "8062:8062" - - "6006:6006" - volumes: - - .:/src celery: - profiles: - - backend - build: - context: . - dockerfile: Dockerfile + extends: + file: docker-compose.apps.yml + service: celery env_file: - path: env/shared.env - path: env/shared.local.env @@ -133,49 +46,3 @@ services: # DEPRECATED: legacy .env file at the repo root - path: .env required: false - command: > - /bin/bash -c ' - sleep 3; - celery -A main.celery:app worker -Q default -B -l ${MITOL_LOG_LEVEL:-INFO} & - celery -A main.celery:app worker -Q edx_content,default -l ${MITOL_LOG_LEVEL:-INFO}' - links: - - db - - opensearch-node-mitopen - - redis - volumes: - - .:/src - - django_media:/var/media - tika: - profiles: - - backend - image: apache/tika:2.5.0 - ports: - - "9998:9998" - - locust: - image: locustio/locust - ports: - - "8089:8089" - volumes: - - ./load_testing:/mnt/locust - command: -f /mnt/locust/locustfile.py --master -H http://nginx:8063 --class-picker - links: - - nginx - profiles: - - load-testing - - locust-worker: - image: locustio/locust - volumes: - - ./load_testing:/mnt/locust - command: -f /mnt/locust/locustfile.py --worker --master-host locust - links: - - nginx - profiles: - - load-testing - -volumes: - opensearch-data1: - django_media: - yarn-cache: - pgdata: diff --git a/env/backend.env b/env/backend.env index e3815cdcef..a82e14ceba 100644 --- a/env/backend.env +++ b/env/backend.env @@ -22,7 +22,7 @@ MITOL_DB_DISABLE_SSL=True MITOL_FEATURES_DEFAULT=True MITOL_SECURE_SSL_REDIRECT=False -OPENSEARCH_URL=opensearch-node-mitopen:9200 +OPENSEARCH_URL=opensearch-node-mitopen-1:9200 OPENSEARCH_INDEX=discussions_local OPENSEARCH_INDEXING_CHUNK_SIZE=100 diff --git a/env/ci.env b/env/ci.env index 58a36aa3a2..f3f22bc430 100644 --- a/env/ci.env +++ b/env/ci.env @@ -5,7 +5,7 @@ DATABASE_URL=postgres://postgres:postgres@db:5432/e2e_postgres # pragma: allowli MITOL_SECURE_SSL_REDIRECT=False MITOL_DB_DISABLE_SSL=True MITOL_FEATURES_DEFAULT=True -OPENSEARCH_URL=opensearch-node-mitopen:9200 +OPENSEARCH_URL=opensearch-node-mitopen-1:9200 CELERY_TASK_ALWAYS_EAGER=False CELERY_BROKER_URL=redis://redis:6379/4 CELERY_RESULT_BACKEND=redis://redis:6379/4 diff --git a/env/codespaces.env b/env/codespaces.env index 590a225186..5ddce03263 100644 --- a/env/codespaces.env +++ b/env/codespaces.env @@ -2,7 +2,7 @@ MITOL_SUPPORT_EMAIL=support@localhost POSTHOG_TIMEOUT_MS=1500 MAILGUN_KEY=test DEBUG=False -OPENSEARCH_URL=opensearch-node-mitopen:9200 +OPENSEARCH_URL=opensearch-node-mitopen-1:9200 OPENSEARCH_INDEX=discussions_local OPENSEARCH_INDEXING_CHUNK_SIZE=100 MAILGUN_RECIPIENT_OVERRIDE= From 6999c4c8ac4b60ba3287c54fe7b7f8e05e49500d Mon Sep 17 00:00:00 2001 From: Doof Date: Thu, 12 Sep 2024 20:29:01 +0000 Subject: [PATCH 18/18] Release 0.19.0 --- RELEASE.rst | 21 +++++++++++++++++++++ main/settings.py | 2 +- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/RELEASE.rst b/RELEASE.rst index 416997387a..5861947def 100644 --- a/RELEASE.rst +++ b/RELEASE.rst @@ -1,6 +1,27 @@ Release Notes ============= +Version 0.19.0 +-------------- + +- Update docker-compose config to support OS cluster (#1540) +- Update dependency faker to v28 (#1550) +- Update actions/upload-artifact digest to 5076954 (#1528) +- Update dependency attrs to v24 (#1535) +- Update dependency @faker-js/faker to v9 (#1533) +- Update dependency pytest-cov to v5 (#1549) +- Update docker.elastic.co/elasticsearch/elasticsearch Docker tag to v8 (#1260) +- ETL pipeline for MIT edX programs (#1536) +- license_cc and continuing_ed_credits fields (#1544) +- Fix data migration erroring if a record exists (#1543) +- learning resource drawer list buttons (#1537) +- Add LearningResource.delivery field (eventually to replace learning_format) (#1510) +- Update mcr.microsoft.com/playwright Docker tag to v1.47.0 (#1531) +- Update dependency ruff to v0.6.4 (#1530) +- Update dependency Django to v4.2.16 (#1529) +- display sub topics on topic channels (#1511) +- Sort news items by publish_date (#1522) + Version 0.18.2 (Released September 12, 2024) -------------- diff --git a/main/settings.py b/main/settings.py index 8465fcf2e4..340de4f3fa 100644 --- a/main/settings.py +++ b/main/settings.py @@ -33,7 +33,7 @@ from main.settings_pluggy import * # noqa: F403 from openapi.settings_spectacular import open_spectacular_settings -VERSION = "0.18.2" +VERSION = "0.19.0" log = logging.getLogger()