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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion ProgramInformationV2.Data/CourseImport/ScheduleTranslator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ public static Course Translate(ScheduleCourse scheduleCourse, string source, boo
Description = string.Empty,
Information = string.Empty,
ScheduleInformation = string.Empty,
Prerequisite = string.Empty
Prerequisite = string.Empty,
PlatformType = PlatformTypes.Campus
};
course = course.AddDescription(scheduleCourse.Description);

Expand Down
18 changes: 18 additions & 0 deletions ProgramInformationV2.Data/CourseraImport/CourseraCourse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace ProgramInformationV2.Data.CourseraImport {
public class CourseraCourse {
public string Title { get; set; } = "";
public string Description { get; set; } = "";
public string Url { get; set; } = "";
public string ImageUrl { get; set; } = "";
public string ImageAltText { get; set; } = "";

public bool IsCourseFree { get; set; }

public bool IsCreditEligible { get; set; }

public string Id { get; set; } = "";
public List<string> Skills { get; set; } = [];
public List<string> Instructors { get; set; } = [];

}
}
106 changes: 106 additions & 0 deletions ProgramInformationV2.Data/CourseraImport/CourseraGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
using System.Text;
using System.Text.Json;

namespace ProgramInformationV2.Data.CourseraImport {
public class CourseraGenerator {
public async Task<CourseraCourse> GetCourse(string id) {
var url = "https://www.coursera.org/graphql-gateway?opname=Search";
var payload = """
[{
"operationName": "Search",
"variables": {
"requests": [
{
"entityType": "PRODUCTS",
"limit": 10000,
"enableAutoAppliedFilters": false,
"requestOrigin": {
"pageType": "EQP",
"segmentType": "CONSUMER"
},
"facetFilters":[["partners:University of Illinois Urbana-Champaign"],["productTypeDescription:Courses"]],
"maxValuesPerFacet":2000,
"cursor": "0",
"query": ""
}
]
},
"query": "query Search($requests: [Search_Request!]!) {\n SearchResult {\n search(requests: $requests) {\n ...SearchResult\n __typename\n }\n __typename\n }\n}\n\nfragment SearchResult on Search_Result {\n elements {\n ...SearchHit\n __typename\n }\n }\n\nfragment SearchHit on Search_Hit {\n ...SearchArticleHit\n ...SearchProductHit\n ...SearchSuggestionHit\n __typename\n}\n\nfragment SearchArticleHit on Search_ArticleHit {\n aeName\n careerField\n category\n createdByName\n firstPublishedAt\n id\n internalContentEpic\n internalProductLine\n internalTargetKw\n introduction\n islocalized\n lastPublishedAt\n localizedCountryCd\n localizedLanguageCd\n name\n subcategory\n topics\n url\n skill: skills\n __typename\n}\n\nfragment SearchProductHit on Search_ProductHit {\n avgProductRating\n cobrandingEnabled\n completions\n duration\n id\n imageUrl\n isCourseFree\n isCreditEligible\n isNewContent\n isPartOfCourseraPlus\n name\n numProductRatings\n parentCourseName\n parentLessonName\n partnerLogos\n partners\n productCard {\n ...SearchProductCard\n __typename\n }\n productDifficultyLevel\n productDuration\n productType\n skills\n url\n videosInLesson\n translatedName\n translatedSkills\n translatedParentCourseName\n translatedParentLessonName\n tagline\n fullyTranslatedLanguages\n subtitlesOnlyLanguages\n __typename\n}\n\nfragment SearchSuggestionHit on Search_SuggestionHit {\n id\n name\n score\n __typename\n}\n\nfragment SearchProductCard on ProductCard_ProductCard {\n id\n canonicalType\n marketingProductType\n badges\n productTypeAttributes {\n ... on ProductCard_Specialization {\n ...SearchProductCardSpecialization\n __typename\n }\n ... on ProductCard_Course {\n ...SearchProductCardCourse\n __typename\n }\n ... on ProductCard_Clip {\n ...SearchProductCardClip\n __typename\n }\n ... on ProductCard_Degree {\n ...SearchProductCardDegree\n __typename\n }\n __typename\n }\n __typename\n}\n\nfragment SearchProductCardSpecialization on ProductCard_Specialization {\n isPathwayContent\n __typename\n}\n\nfragment SearchProductCardCourse on ProductCard_Course {\n isPathwayContent\n rating\n reviewCount\n __typename\n}\n\nfragment SearchProductCardClip on ProductCard_Clip {\n canonical {\n id\n __typename\n }\n __typename\n}\n\nfragment SearchProductCardDegree on ProductCard_Degree {\n canonical {\n id\n __typename\n }\n __typename\n}\n"
}]
""";

using var request = new HttpRequestMessage(HttpMethod.Post, url);

request.Content = new StringContent(payload, Encoding.UTF8, "application/json");
request.Headers.Add("apollographql-client-name", "seo-entity-page");
request.Headers.Add("apollographql-client-version", "3741b28900f73cb04ed39fe2210b2b9774b1d446");
request.Headers.Add("operation-name", "Search");

using var httpClient = new HttpClient();
var response = await httpClient.SendAsync(request);
using var doc = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
var json = doc.RootElement.EnumerateArray().First();
var items = json.GetProperty("data").GetProperty("SearchResult").GetProperty("search").EnumerateArray().First();
var element = items.GetProperty("elements");
var item = element.EnumerateArray().FirstOrDefault(e => e.GetProperty("id").ToString() == id);
if (item.GetProperty("id").ToString() == id) {
return new CourseraCourse() {
Id = item.GetProperty("id").ToString(),
Title = item.GetProperty("name").ToString(),
Url = item.GetProperty("url").ToString(),
ImageUrl = item.GetProperty("imageUrl").ToString(),
Skills = [.. item.GetProperty("skills").EnumerateArray().Select(s => s.ToString())],
IsCourseFree = item.GetProperty("isCourseFree").GetBoolean(),
IsCreditEligible = item.GetProperty("isCreditEligible").GetBoolean(),
};
}
return new CourseraCourse();
}

public async Task<Dictionary<string, string>> GetCourses() {
var url = "https://www.coursera.org/graphql-gateway?opname=Search";
var payload = """
[{
"operationName": "Search",
"variables": {
"requests": [
{
"entityType": "PRODUCTS",
"limit": 10000,
"enableAutoAppliedFilters": false,
"requestOrigin": {
"pageType": "EQP",
"segmentType": "CONSUMER"
},
"facetFilters":[["partners:University of Illinois Urbana-Champaign"],["productTypeDescription:Courses"]],
"maxValuesPerFacet":2000,
"cursor": "0",
"query": ""
}
]
},
"query": "query Search($requests: [Search_Request!]!) {\n SearchResult {\n search(requests: $requests) {\n ...SearchResult\n __typename\n }\n __typename\n }\n}\n\nfragment SearchResult on Search_Result {\n elements {\n ...SearchHit\n __typename\n }\n }\n\nfragment SearchHit on Search_Hit {\n ...SearchArticleHit\n ...SearchProductHit\n ...SearchSuggestionHit\n __typename\n}\n\nfragment SearchArticleHit on Search_ArticleHit {\n aeName\n careerField\n category\n createdByName\n firstPublishedAt\n id\n internalContentEpic\n internalProductLine\n internalTargetKw\n introduction\n islocalized\n lastPublishedAt\n localizedCountryCd\n localizedLanguageCd\n name\n subcategory\n topics\n url\n skill: skills\n __typename\n}\n\nfragment SearchProductHit on Search_ProductHit {\n avgProductRating\n cobrandingEnabled\n completions\n duration\n id\n imageUrl\n isCourseFree\n isCreditEligible\n isNewContent\n isPartOfCourseraPlus\n name\n numProductRatings\n parentCourseName\n parentLessonName\n partnerLogos\n partners\n productCard {\n ...SearchProductCard\n __typename\n }\n productDifficultyLevel\n productDuration\n productType\n skills\n url\n videosInLesson\n translatedName\n translatedSkills\n translatedParentCourseName\n translatedParentLessonName\n tagline\n fullyTranslatedLanguages\n subtitlesOnlyLanguages\n __typename\n}\n\nfragment SearchSuggestionHit on Search_SuggestionHit {\n id\n name\n score\n __typename\n}\n\nfragment SearchProductCard on ProductCard_ProductCard {\n id\n canonicalType\n marketingProductType\n badges\n productTypeAttributes {\n ... on ProductCard_Specialization {\n ...SearchProductCardSpecialization\n __typename\n }\n ... on ProductCard_Course {\n ...SearchProductCardCourse\n __typename\n }\n ... on ProductCard_Clip {\n ...SearchProductCardClip\n __typename\n }\n ... on ProductCard_Degree {\n ...SearchProductCardDegree\n __typename\n }\n __typename\n }\n __typename\n}\n\nfragment SearchProductCardSpecialization on ProductCard_Specialization {\n isPathwayContent\n __typename\n}\n\nfragment SearchProductCardCourse on ProductCard_Course {\n isPathwayContent\n rating\n reviewCount\n __typename\n}\n\nfragment SearchProductCardClip on ProductCard_Clip {\n canonical {\n id\n __typename\n }\n __typename\n}\n\nfragment SearchProductCardDegree on ProductCard_Degree {\n canonical {\n id\n __typename\n }\n __typename\n}\n"
}]
""";

using var request = new HttpRequestMessage(HttpMethod.Post, url);

request.Content = new StringContent(payload, Encoding.UTF8, "application/json");
request.Headers.Add("apollographql-client-name", "seo-entity-page");
request.Headers.Add("apollographql-client-version", "3741b28900f73cb04ed39fe2210b2b9774b1d446");
request.Headers.Add("operation-name", "Search");

using var httpClient = new HttpClient();
var response = await httpClient.SendAsync(request);
using var doc = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
var json = doc.RootElement.EnumerateArray().First();
var items = json.GetProperty("data").GetProperty("SearchResult").GetProperty("search").EnumerateArray().First();
var element = items.GetProperty("elements");
var returnValue = new Dictionary<string, string>();
foreach (var item in element.EnumerateArray()) {
returnValue.Add(item.GetProperty("id").ToString(), item.GetProperty("name").ToString());
}
return returnValue;
}
}
}
39 changes: 39 additions & 0 deletions ProgramInformationV2.Data/CourseraImport/CourseraImportManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using ProgramInformationV2.Search.Models;

namespace ProgramInformationV2.Data.CourseraImport {
public class CourseraImportManager(CourseraGenerator courseraGenerator) {
private readonly CourseraGenerator _courseraGenerator = courseraGenerator;

public async Task<Dictionary<string, string>> GetCourses(string s) {
return (await _courseraGenerator.GetCourses()).Where(c => c.Value.ToLowerInvariant().Contains(s.ToLowerInvariant()) || s == "").OrderBy(d => d.Value).ToDictionary(d => d.Key, d => d.Value);
}

public async Task<Course> GetCourse(string source, string id) {
var courseraCourse = await _courseraGenerator.GetCourse(id);
var course = new Course {
Source = source,
Title = courseraCourse.Title,
Url = "https://www.coursera.com" + courseraCourse.Url,
Id = source + "-" + courseraCourse.Id,
PlatformType = PlatformTypes.Coursera,
ImageUrl = courseraCourse.ImageUrl,
SkillList = courseraCourse.Skills,
IsActive = true,
CourseTitle = courseraCourse.Title,
Sections = [
new Section {
BeginDate = DateTime.MinValue,
EndDate = DateTime.MaxValue,
IsActive = true,
Term = Terms.Ongoing,
FormatType = FormatType.Online,
SectionCode = courseraCourse.Id
}
]
};
course.CleanHtmlFields();
return course;
}

}
}
3 changes: 2 additions & 1 deletion ProgramInformationV2.Data/PageList/PageGroup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,8 @@ public static class PageGroup {
},
{ SidebarEnum.Courses, [
new("Courses", "/courses"),
new("Import Courses", "/courses/import")
new("Import Campus Courses", "/courses/import"),
new("Import Coursera Courses", "/courses/courseraimport")
]
},
{ SidebarEnum.Course, [
Expand Down
1 change: 1 addition & 0 deletions ProgramInformationV2.Data/ProgramInformationV2.Data.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
<PackageReference Include="Markdig" Version="1.1.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.14" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.14" />
<PackageReference Include="System.Collections" Version="4.3.0" />
</ItemGroup>

<ItemGroup>
Expand Down
7 changes: 5 additions & 2 deletions ProgramInformationV2.Search/Models/Course.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
using System.Text.Json;
using OpenSearch.Client;
using OpenSearch.Client;
using System.Text.Json;

namespace ProgramInformationV2.Search.Models {

Expand Down Expand Up @@ -55,6 +55,9 @@ public Course() {
public string Length { get; set; } = "";
public int MaximumCreditHours { get; set; }
public int MinimumCreditHours { get; set; }

public PlatformTypes PlatformType { get; set; }

public string Prerequisite { get; set; } = "";

[Keyword]
Expand Down
2 changes: 2 additions & 0 deletions ProgramInformationV2.Search/Models/ExtensionTypes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ public enum Terms { None, Fall, Spring, Summer, Summer1, Summer2, Winter, Ongoin

public enum UrlTypes { Programs, Courses, RequirementSets }

public enum PlatformTypes { None, Campus, Coursera, Custom, Moodle }

public enum NoteTemplateTypes { Programs = 1, Credentials = 2, Courses = 3 }

public static class ExtensionTypes {
Expand Down
56 changes: 56 additions & 0 deletions ProgramInformationV2/Components/Pages/Course/CourseraImport.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
@page "/courses/courseraimport"
@layout SidebarLayout

<PageTitle>Import a Coursera Course</PageTitle>
<style>
.container {
padding: 20px;
border: 1px solid var(--il-blue);
}
</style>
<ilw-content>
<h1>Import a Coursera Course</h1>
@if (_useCourses.HasValue && _useCourses.Value)
{
<p>Note that this will overwrite any changes you made to the existing item.</p>
<div class="ils-input-entry">
<label for="search">Search Term</label>
<input id="search" type="text" @bind="SearchTerm" class="ils-input-long" />
</div>
<button class="ilw-button" @onclick="Search">Search for Courses</button>
@if (ListOfCourseraCourses != null && ListOfCourseraCoursesSelected != null)
{
<div class="container">
<div class="ils-input-entry">
<label for="options">Coursera Courses</label>
<select id="options" type="text" @bind="ListOfCourseraCourseId" size="10" style="width: 100%;">
@foreach (var course in ListOfCourseraCourses)
{
<option value="@course.Key">@course.Value</option>
}
</select>
<div style="display: flex;">
<button class="ilw-button" @onclick="Transfer">Add To Selected List</button>
<button class="ilw-button" @onclick="TransferAll">Add All</button>
</div>
</div>
</div>
<div class="container">
<div class="ils-input-entry">
<label for="optionsselected">Selected Courses</label>
<select id="optionsselected" type="text" @bind="ListOfCourseraCourseIdSelected" size="10" style="width: 100%;">
@foreach (var course in ListOfCourseraCoursesSelected)
{
<option value="@course.Key">@course.Value</option>
}
</select>
<div style="display: flex;">
<button class="ilw-button" @onclick="Remove">Remove From Selected List</button>
<button class="ilw-button" @onclick="RemoveAll">Remove All</button>
</div>
</div>
</div>
<button class="ilw-button ilw-theme-orange-solid" @onclick="SendImport" @onblur="Layout.RemoveMessage">Import Courses</button>
}
}
</ilw-content>
Loading
Loading