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
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,24 @@ on:
- main
workflow_dispatch:

env:
DOTNET_VERSION: '8.0.x' # set this to the dotnet version to use

jobs:
build:
runs-on: windows-latest

steps:
- uses: actions/checkout@v4

- name: Setup DotNet ${{ env.DOTNET_VERSION }} Environment
- name: Set up .NET Core
uses: actions/setup-dotnet@v1
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
dotnet-version: '8.x'
include-prerelease: true

- name: Build with dotnet
run: dotnet build --configuration Release

- name: dotnet publish
run: dotnet publish ProgramInformationV2 -c Release -r linux-x64 --property:PublishDir=${{env.DOTNET_ROOT}}/myapp
run: dotnet publish -c Release -o ${{env.DOTNET_ROOT}}/myapp

- name: Upload artifact for deployment job
uses: actions/upload-artifact@v4
Expand All @@ -54,7 +51,6 @@ jobs:
id: deploy-to-webapp
uses: azure/webapps-deploy@v2
with:
app-name: itp-app-programrepository
package: .
app-name: 'wigg-course-infra-winapp01'
publish-profile: ${{ secrets.AZURE_WEB_PUBLISH_PROFILE_PRODUCTION }}
slot-name: 'deploy'
package: .
57 changes: 57 additions & 0 deletions .github/workflows/wigg-app-staging-course.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Docs for the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy
# More GitHub Actions for Azure: https://github.com/Azure/actions

name: Staging WIGG Build and deploy ASP.Net Core app to Azure Web App

on:
push:
branches:
- develop
workflow_dispatch:

jobs:
build:
runs-on: windows-latest

steps:
- uses: actions/checkout@v4

- name: Set up .NET Core
uses: actions/setup-dotnet@v1
with:
dotnet-version: '8.x'
include-prerelease: true

- name: Build with dotnet
run: dotnet build --configuration Release

- name: dotnet publish
run: dotnet publish -c Release -o ${{env.DOTNET_ROOT}}/myapp

- name: Upload artifact for deployment job
uses: actions/upload-artifact@v4
with:
name: .net-app
path: ${{env.DOTNET_ROOT}}/myapp

deploy:
runs-on: windows-latest
needs: build
environment:
name: 'Production'
url: ${{ steps.deploy-to-webapp.outputs.webapp-url }}

steps:
- name: Download artifact from build job
uses: actions/download-artifact@v4
with:
name: .net-app

- name: Deploy to Azure Web App
id: deploy-to-webapp
uses: azure/webapps-deploy@v2
with:
app-name: 'wigg-course-infra-winapp01'
slot-name: 'staging'
publish-profile: ${{ secrets.AZURE_WEB_PUBLISH_PROFILE_STAGING }}
package: .
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ on:
env:
AZURE_FUNCTIONAPP_PACKAGE_PATH: 'ProgramInformationV2.Function' # set this to the path to your web app project, defaults to the repository root
DOTNET_VERSION: '8.0.x' # set this to the dotnet version to use

jobs:
build-and-deploy:
runs-on: ubuntu-latest
runs-on: windows-latest

steps:
- name: 'Checkout GitHub Action'
Expand All @@ -25,20 +25,18 @@ jobs:
uses: actions/setup-dotnet@v1
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
include-prerelease: true

- name: 'Resolve Project Dependencies Using Dotnet'
shell: pwsh
run: |
pushd './${{ env.AZURE_FUNCTIONAPP_PACKAGE_PATH }}'
dotnet build --configuration Release --output ./output -r linux-x64
dotnet build --configuration Release --output ./output
popd

- name: 'Run Azure Functions Action'
uses: Azure/functions-action@v1
id: fa
with:
app-name: 'itp-function-programrepository'
app-name: 'wigg-course-infra-funapp01'
package: '${{ env.AZURE_FUNCTIONAPP_PACKAGE_PATH }}/output'
publish-profile: ${{ secrets.AZURE_FUNCTION_PUBLISH_PROFILE_PRODUCTION }}
slot-name: 'deploy'
publish-profile: ${{ secrets.AZURE_FUNCTION_PUBLISH_PROFILE_PRODUCTION }}
43 changes: 43 additions & 0 deletions .github/workflows/wigg-fun-staging-course.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Docs for the Azure Web Apps Deploy action: https://github.com/azure/functions-action
# More GitHub Actions for Azure: https://github.com/Azure/actions

name: Staging WIGG Build and deploy ASP.Net Core app to Azure Function App

on:
push:
branches:
- develop
workflow_dispatch:

env:
AZURE_FUNCTIONAPP_PACKAGE_PATH: 'ProgramInformationV2.Function' # set this to the path to your web app project, defaults to the repository root
DOTNET_VERSION: '8.0.x' # set this to the dotnet version to use

jobs:
build-and-deploy:
runs-on: windows-latest

steps:
- name: 'Checkout GitHub Action'
uses: actions/checkout@v4

- name: Setup DotNet ${{ env.DOTNET_VERSION }} Environment
uses: actions/setup-dotnet@v1
with:
dotnet-version: ${{ env.DOTNET_VERSION }}

- name: 'Resolve Project Dependencies Using Dotnet'
shell: pwsh
run: |
pushd './${{ env.AZURE_FUNCTIONAPP_PACKAGE_PATH }}'
dotnet build --configuration Release --output ./output
popd

- name: 'Run Azure Functions Action'
uses: Azure/functions-action@v1
id: fa
with:
app-name: 'wigg-course-infra-funapp01'
slot-name: 'staging'
package: '${{ env.AZURE_FUNCTIONAPP_PACKAGE_PATH }}/output'
publish-profile: ${{ secrets.AZURE_FUNCTION_PUBLISH_PROFILE_STAGING }}
5 changes: 5 additions & 0 deletions ProgramInformationV2.Data/CourseImport/ScheduleTranslator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ public static Course Translate(ScheduleCourse scheduleCourse, string source, boo
Rubric = scheduleCourse.Rubric,
CourseTitle = scheduleCourse.Title,
ExternalUrl = $"https://courses.illinois.edu/schedule/terms/{scheduleCourse.Rubric}/{scheduleCourse.CourseNumber}",
Url = $"https://courses.illinois.edu/schedule/terms/{scheduleCourse.Rubric}/{scheduleCourse.CourseNumber}",
UrlFull = $"https://courses.illinois.edu/schedule/terms/{scheduleCourse.Rubric}/{scheduleCourse.CourseNumber}",
IsActive = true,
CreditHours = scheduleCourse.CreditHours,
Description = string.Empty,
Expand Down Expand Up @@ -41,6 +43,9 @@ public static Course Translate(ScheduleCourse scheduleCourse, string source, boo
Type = s.Type
})];
}
course.FacultyNameList = course.Sections == null ? [] : course.Sections.SelectMany(s => s.FacultyNameList).Distinct().ToList();
course.Faculty = string.Join(", ", course.FacultyNameList.Select(f => f.Name));
course.Title = string.IsNullOrWhiteSpace(course.Rubric) ? course.CourseTitle : $"{course.Rubric} {course.CourseNumber}: {course.CourseTitle}";
return course;
}

Expand Down
5 changes: 4 additions & 1 deletion ProgramInformationV2.Data/CourseImport/XmlImporter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public static class XmlImporter {
private static readonly int _yearsLookBack = 5;
private static readonly int _yearsLookForward = 1;

public static IEnumerable<CourseUrl> GetAllCoursesBySemester(string rubric, string courseNumber) {
public static IEnumerable<CourseUrl> GetAllCoursesBySemester(string rubric, string courseNumber, bool shortCircuit = false) {
var returnValue = new List<CourseUrl>();

var urls = new List<Tuple<string, string, int>>();
Expand All @@ -26,6 +26,9 @@ public static IEnumerable<CourseUrl> GetAllCoursesBySemester(string rubric, stri
var courseUrl = new CourseUrl { CourseNumber = node.Attributes("id").First().Value, Rubric = rubric.ToUpperInvariant(), Semester = url.Item2, Url = FixUrl(node.Attributes("href").First().Value), Year = url.Item3 };
if (courseUrl != null && courseUrl.CourseNumber == courseNumber) {
returnValue.Add(courseUrl);
if (shortCircuit) {
return returnValue;
}
}
}
Console.WriteLine($"Found {url.Item1}");
Expand Down
22 changes: 11 additions & 11 deletions ProgramInformationV2.Data/DataHelpers/FilterHelper.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
using Microsoft.EntityFrameworkCore;
using System.Text.Json;
using Microsoft.EntityFrameworkCore;
using ProgramInformationV2.Data.DataContext;
using ProgramInformationV2.Data.DataModels;
using ProgramInformationV2.Search.Helpers;
using ProgramInformationV2.Search.JsonThinModels;
using System.Text.Json;

namespace ProgramInformationV2.Data.DataHelpers {

Expand All @@ -16,20 +16,20 @@ public async Task<IEnumerable<IGrouping<TagType, TagSource>>> GetAllFilters(stri

public async Task<List<TagList>> GetFilterListForExport(string source) => [.. (await GetAllFilters(source)).Select(t => new TagList {
Title = t.Key.ToString(),
List = [.. t.Select(l => l.Title)],
List = [.. t.OrderBy(l => l.Order).Select(l => l.Title)],
})];

public async Task<string> GetFilterJsonForExport(string source) => JsonSerializer.Serialize(await GetFilterListForExport(source));

public async Task<string> ImportFiltersFromJson(string source, string json) {
List<TagList>? importedTags = JsonSerializer.Deserialize<List<TagList>>(json);
Source? sourceEntity = await _programRepository.ReadAsync(c => c.Sources.FirstOrDefault(s => s.Code == source));
var importedTags = JsonSerializer.Deserialize<List<TagList>>(json);
var sourceEntity = await _programRepository.ReadAsync(c => c.Sources.FirstOrDefault(s => s.Code == source));
if (sourceEntity == null || importedTags == null) {
return "No tags loaded";
}
foreach (TagList tagList in importedTags) {
foreach (var tagList in importedTags) {
var count = 1;
TagType tagType = Enum.TryParse<TagType>(tagList.Title, out TagType parsedTagType) ? parsedTagType : TagType.None;
var tagType = Enum.TryParse<TagType>(tagList.Title, out var parsedTagType) ? parsedTagType : TagType.None;
if (tagType != TagType.None) {
foreach (var tag in tagList.List) {
var newTag = new TagSource {
Expand All @@ -46,9 +46,9 @@ public async Task<string> ImportFiltersFromJson(string source, string json) {
}

public async Task<(List<TagSource> TagSources, int SourceId)> GetFilters(string source, TagType tagType) {
List<TagSource> returnValue = await _programRepository.ReadAsync(c => c.TagSources.Include(c => c.Source).Where(ts => ts.Source != null && ts.Source.Code == source && ts.TagType == tagType).OrderBy(ts => ts.Order).ToList());
var returnValue = await _programRepository.ReadAsync(c => c.TagSources.Include(c => c.Source).Where(ts => ts.Source != null && ts.Source.Code == source && ts.TagType == tagType).OrderBy(ts => ts.Order).ToList());
var sourceId = 0;
foreach (TagSource? item in returnValue) {
foreach (var item in returnValue) {
item.OldTitle = item.Title;
sourceId = item.SourceId;
}
Expand All @@ -60,7 +60,7 @@ public async Task<string> ImportFiltersFromJson(string source, string json) {

public async Task<bool> SaveFilters(IEnumerable<TagSource> tags, IEnumerable<TagSource> tagsForDeletion, string sourceName) {
var i = 1;
foreach (TagSource tag in tags) {
foreach (var tag in tags) {
tag.Order = i++;
if (tag.Id == 0) {
_ = await _programRepository.CreateAsync(tag);
Expand All @@ -72,7 +72,7 @@ public async Task<bool> SaveFilters(IEnumerable<TagSource> tags, IEnumerable<Tag
}
}
if (_bulkEditor != null) {
foreach (TagSource tag in tagsForDeletion) {
foreach (var tag in tagsForDeletion) {
_ = await _programRepository.DeleteAsync(tag);
_ = await _bulkEditor.DeleteTags(sourceName, tag.TagTypeSourceName, tag.OldTitle);
}
Expand Down
41 changes: 41 additions & 0 deletions ProgramInformationV2.Function/GetCampusCourse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Attributes;
using Microsoft.Extensions.Logging;
using Microsoft.OpenApi.Models;
using ProgramInformationV2.Data.CourseImport;
using ProgramInformationV2.Function.Helper;
using ProgramInformationV2.Search.Models;
using System.Net;

namespace ProgramInformationV2.Function;

public class GetCampusCourse(ILogger<GetCampusCourse> logger) {
private readonly ILogger<GetCampusCourse> _logger = logger;

[Function("CampusCourse")]
[OpenApiOperation(operationId: "CampusCourse", tags: "Get Course Information", Description = "Get a course from the campus tools. This is using the same course object, but the source and many other fields not supported by the campus tools will be blank. Note that this is doing a lot of calls to see if the course exists in a particular semester, so it may take some time to run as the only way to check is to validate timeout.")]
[OpenApiParameter(name: "rubric", In = ParameterLocation.Query, Required = true, Type = typeof(string), Description = "The rubric.")]
[OpenApiParameter(name: "coursenumber", In = ParameterLocation.Query, Required = true, Type = typeof(string), Description = "The course number.")]
[OpenApiParameter(name: "singlesemester", In = ParameterLocation.Query, Required = true, Type = typeof(bool), Description = "If set to true, then only query latest semester -- used if you just need the description. Defaults to false.")]
[OpenApiParameter(name: "courseonly", In = ParameterLocation.Query, Required = true, Type = typeof(bool), Description = "If set to true, then do not include section information. Defaults to false.")]
[OpenApiResponseWithBody(statusCode: HttpStatusCode.OK, contentType: "text/plain", bodyType: typeof(List<Course>), Description = "The list of courses")]
public async Task<HttpResponseData> Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestData req) {
_logger.LogInformation("Called CampusCourse.");
var requestHelper = RequestHelperFactory.Create();
requestHelper.Initialize(req);
var rubric = requestHelper.GetRequest(req, "rubric");
var coursenumber = requestHelper.GetRequest(req, "coursenumber");
var singlesemester = requestHelper.GetBoolean(req, "singlesemester");
var courseonly = requestHelper.GetBoolean(req, "courseonly");
requestHelper.Validate();

var itemGroups = XmlImporter.GetAllCoursesBySemester(rubric, coursenumber, singlesemester);
var scheduledCourse = XmlImporter.GetCourse(itemGroups ?? []);
var course = ScheduleTranslator.Translate(scheduledCourse, "", !courseonly);
var response = req.CreateResponse(HttpStatusCode.OK);
await response.WriteAsJsonAsync(course);
return response;
}
}
4 changes: 2 additions & 2 deletions ProgramInformationV2.Function/GetCourses.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,8 @@
[OpenApiParameter(name: "formats", In = ParameterLocation.Query, Required = false, Type = typeof(string), Description = "Either 'On-Campus', 'Online', 'Off-Campus', or 'Hybrid'. Can choose multiple by separating them with the characters '[-]'")]
[OpenApiParameter(name: "rubric", In = ParameterLocation.Query, Required = false, Type = typeof(string), Description = "The course rubric.")]
[OpenApiParameter(name: "terms", In = ParameterLocation.Query, Required = false, Type = typeof(string), Description = "Either 'Fall', 'Spring', 'Summer', or 'Winter'. Can choose multiple by separating them with the characters '[-]'")]
[OpenApiParameter(name: "take", In = ParameterLocation.Query, Required = false, Type = typeof(int), Description = "How many courses do you want? Defaults to 0.")]
[OpenApiParameter(name: "skip", In = ParameterLocation.Query, Required = false, Type = typeof(int), Description = "A skip value to help with pagination. Defaults to 1000.")]
[OpenApiParameter(name: "take", In = ParameterLocation.Query, Required = false, Type = typeof(int), Description = "How many courses do you want? Defaults to 1000.")]
[OpenApiParameter(name: "skip", In = ParameterLocation.Query, Required = false, Type = typeof(int), Description = "A skip value to help with pagination. Defaults to 0.")]
[OpenApiResponseWithBody(statusCode: HttpStatusCode.OK, contentType: "text/plain", bodyType: typeof(IEnumerable<Program>), Description = "All programs that meet the search criteria. If you filter by credentials, it will filter the credential list for each program.")]
public async Task<HttpResponseData> Search([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequestData req) {
_logger.LogInformation("Called CourseSearch.");
Expand Down Expand Up @@ -117,7 +117,7 @@
[OpenApiParameter(name: "q", In = ParameterLocation.Query, Required = false, Type = typeof(string), Description = "A full text search string for autocomplete.")]
[OpenApiParameter(name: "take", In = ParameterLocation.Query, Required = false, Type = typeof(int), Description = "How many suggestions do you want? Defaults to 10.")]
[OpenApiResponseWithBody(statusCode: HttpStatusCode.OK, contentType: "text/plain", bodyType: typeof(IEnumerable<string>), Description = "An array of strings")]
public async Task<HttpResponseData> Suggest([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequestData req) {

Check warning on line 120 in ProgramInformationV2.Function/GetCourses.cs

View workflow job for this annotation

GitHub Actions / build-and-deploy

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Check warning on line 120 in ProgramInformationV2.Function/GetCourses.cs

View workflow job for this annotation

GitHub Actions / build-and-deploy

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Check warning on line 120 in ProgramInformationV2.Function/GetCourses.cs

View workflow job for this annotation

GitHub Actions / build

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.
_logger.LogInformation("Called ProgramSuggest.");
var requestHelper = RequestHelperFactory.Create();
requestHelper.Initialize(req);
Expand Down
Loading
Loading