Skip to content

Commit

Permalink
Merge pull request #698 from UTDNebula/develop
Browse files Browse the repository at this point in the history
Sync with develop
  • Loading branch information
akevinge authored Oct 3, 2023
2 parents c80a150 + 23acd81 commit 8aea9eb
Show file tree
Hide file tree
Showing 23 changed files with 425 additions and 229 deletions.
30 changes: 30 additions & 0 deletions .github/workflows/versioning.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: Scheduled Versioning

on:
workflow_dispatch:
schedule:
- cron: '0 0 15 8 *'

jobs:
versioning:
name: Versioning System
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v3

- uses: actions/setup-python@v4
with:
python-version: '3.10'

- name: Install dependencies
working-directory: validator
run: |
pip3 install -r requirements.txt
- name: Execute versioning script
env:
JIRA_API_KEY: ${{ secrets.JIRA_API_KEY }}
working-directory: ./validator/scripts
run: python diff.py
6 changes: 3 additions & 3 deletions public/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,13 @@ header a:hover {
*/

.login {
background-color: #3772ff;
border-color: #3772ff;
background-color: #6266F9;
border-color: #6266F9;
color: #fff !important;
}

.login:hover {
background-color: #2956c5;
background-color: #474bb6;
color: #fff;
}

Expand Down
8 changes: 6 additions & 2 deletions src/components/planner/CourseInfoHoverCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ export const CourseInfoHoverCard: FC<CourseInfoHoverCardProps> = ({
</Link>
</h3>
<CourseDescription description={description} />

<HoverCard.Arrow className="fill-primary" />
</HoverCard.Content>
</HoverCard.Portal>
Expand All @@ -60,10 +59,15 @@ export default CourseInfoHoverCard;
*/
const CourseDescription = ({ description }: { description: string }) => {
const [showMore, setShowMore] = useState(false);
const boldDescription = description.replaceAll(/(\b[A-Z]{2,4} \d{4}\b)/gi, '<b>$1</b>');
return (
<span>
<p className="text-xs">
{!showMore ? `${description.substring(0, 200)}... ` : `${description} `}
<span
dangerouslySetInnerHTML={{
__html: !showMore ? boldDescription.substring(0, 200) + '...' : boldDescription,
}}
/>{' '}
<button
className={`${showMore ? '' : 'inline'} font-medium text-primary`}
onClick={(e) => {
Expand Down
10 changes: 8 additions & 2 deletions src/components/planner/Planner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ export default function Planner({
// Course that is currently being dragged
const [activeCourse, setActiveCourse] = useState<ActiveDragData | null>(null);

// Controls drag locking for the sidebar
const [isCourseDragging, setIsCourseDragging] = useState(false);

// Delay necessary so events inside draggables propagate
// valid sensors: https://github.com/clauderic/dnd-kit/discussions/82#discussioncomment-347608
const sensors = useSensors(
Expand All @@ -116,6 +119,7 @@ export default function Planner({
const handleOnDragStart = ({ active }: { active: Active }) => {
const originData = active.data.current as DragEventOriginData;
setActiveCourse({ from: originData.from, course: originData.course });
setIsCourseDragging(true);
};

const handleOnDragEnd = ({ active, over }: { active: Active; over: Over | null }) => {
Expand Down Expand Up @@ -143,15 +147,16 @@ export default function Planner({
);
}
}

setIsCourseDragging(false);
};

const ref = useRef<HTMLDivElement>(null);
// TODO: Use resizeobserver to change column count based on screen size

return (
<DndContext
// Enabling autoScroll causes odd behavior when dragging outside of a scrollable container (eg. Sidebar)
autoScroll={false}
autoScroll={true}
sensors={sensors}
collisionDetection={pointerWithin}
onDragStart={handleOnDragStart}
Expand Down Expand Up @@ -218,6 +223,7 @@ export default function Planner({
transferCredits={transferCredits}
getSearchedDragId={(course) => `course-list-searched-${course.id}`}
getRequirementDragId={(course) => `course-list-requirement-${course.id}`}
courseDragged={isCourseDragging}
/>
</div>
</DndContext>
Expand Down
6 changes: 5 additions & 1 deletion src/components/planner/Sidebar/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export interface CourseSelectorContainerProps {
transferCredits: string[];
getSearchedDragId: GetDragIdByCourse;
getRequirementDragId: GetDragIdByCourse;
courseDragged: boolean;
}

function CourseSelectorContainer({
Expand All @@ -33,6 +34,7 @@ function CourseSelectorContainer({
transferCredits,
getSearchedDragId,
getRequirementDragId,
courseDragged,
}: CourseSelectorContainerProps) {
const {
data: validationData,
Expand Down Expand Up @@ -119,7 +121,9 @@ function CourseSelectorContainer({
{open ? (
<div
id="tutorial-editor-1"
className="z-0 h-screen w-[30%] min-w-[30%] overflow-x-hidden overflow-y-scroll"
className={`z-0 h-screen w-[30%] min-w-[30%] overflow-x-hidden ${
courseDragged ? 'overflow-y-hidden' : 'overflow-y-scroll'
}`}
>
<div className="flex h-fit min-h-screen w-full flex-col gap-y-4 bg-white p-4">
<div className="flex flex-col">
Expand Down
1 change: 1 addition & 0 deletions src/components/planner/Tiles/SemesterTile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ const DroppableSemesterTile: FC<DroppableSemesterTileProps> = ({
const { setNodeRef } = useDroppable({
id: dropId,
data: { to: 'semester-tile', semester } as DragDataToSemesterTile,
disabled: semester.locked,
});

return (
Expand Down
37 changes: 21 additions & 16 deletions validator/degree_solver.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
from __future__ import annotations
from enum import Enum
from glob import glob
from collections import Counter

from pydantic import Json

from typing import Any

import core
from major.requirements import AbstractRequirement
from major.requirements import AbstractRequirement, FreeElectiveRequirement
from dataclasses import dataclass

from major.requirements.map import REQUIREMENTS_MAP
from major.requirements import loader
import json

from major.requirements.shared import (
Expand All @@ -19,6 +20,7 @@
)
from course import Course

LOADER = loader.Loader()

# Read all degree plan JSON files and store their contents in a hashmap
# This is so that we can avoid reading all the files each time we want to get the data for a certain course
Expand Down Expand Up @@ -144,7 +146,7 @@ def __init__(
requirements: DegreeRequirementsInput,
bypasses: BypassInput,
) -> None:
self.courses = set(courses)
self.courses = set([Course.from_name(course) for course in courses])
self.degree_requirements = self.load_requirements(requirements)
self.solved_core: core.store.AssignmentStore | None = None
self.bypasses = bypasses
Expand Down Expand Up @@ -187,9 +189,7 @@ def load_requirements(

# Add requirements
for req_data in requirements_data:
major_req.requirements.append(
REQUIREMENTS_MAP[req_data["matcher"]].from_json(req_data)
)
major_req.requirements.append(LOADER.requirement_from_json(req_data))
degree_requirements.append(major_req)
# We don't need to check the other JSON files
break
Expand All @@ -201,23 +201,28 @@ def load_requirements(
def solve(self) -> DegreeRequirementsSolver:
# Run for core
core_solver = self.load_core()
self.solved_core = core_solver.solve(
[Course.from_name(course) for course in self.courses], []
)
# Set of the core courses that are fulfilled, so they won't be considered as free electives
used_core_courses = set()
self.solved_core = core_solver.solve(list(self.courses), [])

# Counter of the core courses and their used hours, so they won't be considered as free electives.
used_core_courses: Counter[Course] = Counter()
if self.solved_core is not None:
for req_fill in self.solved_core.reqs_to_courses.values():
used_core_courses.update([course.name for course in req_fill.keys()])
used_core_courses.update(req_fill)

# Run for major
for degree_req in self.degree_requirements:
for course in self.courses:
# Make sure it's not a core course
if course in used_core_courses:
continue
for requirement in degree_req.requirements:
if requirement.attempt_fulfill(course):
# Free elective requirements are special, since they can take left over hours from core courses.
if type(requirement) == FreeElectiveRequirement:
if requirement.attempt_fulfill(
course.name,
available_hours=(
int(course.hours) - used_core_courses[course]
),
):
break
elif requirement.attempt_fulfill(course.name):
break

# Handle requirements bypasses for major
Expand Down
7 changes: 4 additions & 3 deletions validator/gen_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from types import GenericAlias
from typing import Any, ForwardRef
from jsonschema import Draft7Validator
from major.requirements import REQUIREMENTS_MAP
from major.requirements import loader

schema: dict[str, Any] = {
"$schema": Draft7Validator.META_SCHEMA["$id"],
Expand Down Expand Up @@ -89,8 +89,9 @@ def forward_ref_to_schema(ref: ForwardRef) -> dict[str, Any]:
raise Exception("Expected type, got", type(ref_type), ref_type)


for req_name in REQUIREMENTS_MAP:
req = REQUIREMENTS_MAP[req_name]
req_loader = loader.Loader()
for req_name in req_loader.REQUIREMENTS_MAP:
req = req_loader.REQUIREMENTS_MAP[req_name]
requirement_schema_props: dict[str, Any] = {"matcher": {"const": req_name}}

for prop_name, prop_type in req.JSON.__annotations__.items():
Expand Down
2 changes: 1 addition & 1 deletion validator/major/requirements/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
from .base import AbstractRequirement
from .shared import *
from .map import *
from . import loader
2 changes: 1 addition & 1 deletion validator/major/requirements/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from __future__ import annotations
from abc import abstractmethod, ABC
from dataclasses import dataclass

from typing import Any

from pydantic import Json
Expand All @@ -19,6 +18,7 @@ class AbstractRequirement(ABC):
def attempt_fulfill(
self,
course: str,
available_hours: int = 0,
) -> bool:
pass

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import json
from typing import Any, TypedDict
from typing import Any

from pydantic import Json
from major.requirements import loader
from major.requirements.base import AbstractRequirement
from major.requirements.shared import MultiGroupElectiveRequirement
import utils
Expand Down Expand Up @@ -45,13 +46,9 @@ def __init__(

@classmethod
def from_json(cls, json: JSON) -> MultiGroupElectiveRequirement: # type: ignore[override]
from ..map import REQUIREMENTS_MAP

requirements: list[AbstractRequirement] = []
for requirement_data in json["requirements"]:
requirement = REQUIREMENTS_MAP[requirement_data["matcher"]].from_json(
requirement_data
)
requirement = loader.Loader().requirement_from_json(requirement_data)
requirements.append(requirement)

return cls(
Expand Down Expand Up @@ -87,7 +84,7 @@ def to_json(self) -> Json[Any]:
}
)

def attempt_fulfill(self, course: str) -> bool:
def attempt_fulfill(self, course: str, _: int = 0) -> bool:
fulfilled = super().attempt_fulfill(course)
if fulfilled:
if utils.get_level_from_course(course) == 4:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
import json

from pydantic import Json
from major.requirements import AbstractRequirement, map
from major.requirements import AbstractRequirement

from functools import reduce
from typing import Any, TypedDict
from major.requirements.shared import OrRequirement

import utils
from major.requirements import loader

"""
Note: assuming BA 4V90 & BA 4090 cover one of the groups
Expand All @@ -21,7 +21,7 @@ class SomeRequirement(OrRequirement):
NOTE: Allows attempt_filled to work even if is_fulfilled() is true
"""

def attempt_fulfill(self, course: str) -> bool:
def attempt_fulfill(self, course: str, _: int = 0) -> bool:
for requirement in self.requirements:
if requirement.attempt_fulfill(course):
return True
Expand Down Expand Up @@ -66,7 +66,7 @@ def __init__(
self.metadata = metadata
self.override_filled = False

def attempt_fulfill(self, course: str) -> bool:
def attempt_fulfill(self, course: str, _: int = 0) -> bool:
if self.is_fulfilled():
return False

Expand Down Expand Up @@ -146,9 +146,7 @@ def from_json(cls, json: JSON) -> BusinessAdministrationElectiveRequirement:

requirements: list[AbstractRequirement] = []
for requirement_data in json["prefix_groups"]:
requirement = map.REQUIREMENTS_MAP[requirement_data["matcher"]].from_json(
requirement_data
)
requirement = loader.Loader().requirement_from_json(requirement_data)
requirements.append(requirement)

return cls(
Expand Down Expand Up @@ -182,4 +180,4 @@ def __str__(self) -> str:
_______________
Required fulfilled: {self.is_fulfilled()}
"""
return s
return s
Loading

0 comments on commit 8aea9eb

Please sign in to comment.