Skip to content

Commit 9299e45

Browse files
authored
Updated generate form to search for programs (#1525)
1 parent 128acf1 commit 9299e45

File tree

10 files changed

+316
-47
lines changed

10 files changed

+316
-47
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
- Added text warnings and validations for each field in `GenerateForm`
1111
- Redesigned generate form UI
1212
- Allow option to log server request accesses to a file
13+
- Added search by program to create graphs on Generate Page
1314

1415
### 🐛 Bug fixes
1516

app/Controllers/Generate.hs

+27-6
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,16 @@ import Scripts
77
import Text.Blaze ((!))
88
import qualified Text.Blaze.Html5 as H
99
import qualified Text.Blaze.Html5.Attributes as A
10+
import Control.Monad()
11+
import Control.Monad.IO.Class (liftIO)
12+
import Data.Aeson (decode, object, (.=))
13+
import Data.List (nub)
14+
import Data.Maybe (fromJust, isNothing, mapMaybe)
15+
import qualified Data.Text.Lazy as TL
16+
import Database.CourseQueries (returnPost, reqsForPost)
1017
import DynamicGraphs.WriteRunDot (getBody, generateAndSavePrereqResponse)
1118
import DynamicGraphs.GraphOptions (CourseGraphOptions (..), GraphOptions (..))
12-
import Data.Maybe (fromJust)
13-
import Data.Aeson (decode)
14-
import qualified Data.Text.Lazy as TL
15-
import Control.Monad.IO.Class (liftIO)
19+
import Util.Happstack (createJSONResponse)
1620

1721
generateResponse :: ServerPart Response
1822
generateResponse =
@@ -37,11 +41,28 @@ findAndSavePrereqsResponse = do
3741
method PUT
3842
requestBody <- getBody
3943
let coursesOptions :: CourseGraphOptions = fromJust $ decode requestBody
44+
45+
postResults <- liftIO $ mapM (\code -> do
46+
post <- returnPost (TL.toStrict code)
47+
return (TL.toStrict code, post))
48+
(programs coursesOptions)
49+
50+
let invalidPrograms = map fst $ filter (isNothing . snd) postResults
51+
validPrograms = mapMaybe snd postResults
52+
53+
allCourses <- liftIO $ nub <$>
54+
if all (== TL.empty) (courses coursesOptions)
55+
then return $ map TL.pack (concatMap reqsForPost validPrograms)
56+
else return $ courses coursesOptions
57+
4058
let updatedCoursesOptions = coursesOptions
41-
{ courses = map TL.toUpper (courses coursesOptions)
59+
{ courses = map TL.toUpper allCourses
4260
, graphOptions = (graphOptions coursesOptions)
4361
{ taken = map TL.toUpper (taken (graphOptions coursesOptions))
4462
, departments = map TL.toUpper (departments (graphOptions coursesOptions))
4563
}
4664
}
47-
liftIO $ generateAndSavePrereqResponse updatedCoursesOptions
65+
66+
if all (== TL.empty) (courses coursesOptions) && not (null invalidPrograms)
67+
then return $ createJSONResponse $ object ["error" .= object ["invalidPrograms" .= invalidPrograms]]
68+
else liftIO $ generateAndSavePrereqResponse updatedCoursesOptions

app/Database/CourseQueries.hs

+22-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ and serve the information back to the client.
1010

1111
module Database.CourseQueries
1212
(retrievePost,
13+
returnPost,
14+
reqsForPost,
1315
returnCourse,
1416
prereqsForCourse,
1517
returnMeeting,
@@ -23,9 +25,10 @@ module Database.CourseQueries
2325
import Config (runDb)
2426
import Control.Monad.IO.Class (MonadIO, liftIO)
2527
import Data.Aeson (object, toJSON, Value)
28+
import Data.Char (isAlphaNum, isPunctuation, isAlpha, isDigit)
2629
import Data.List (partition)
2730
import Data.Maybe (fromJust, fromMaybe)
28-
import qualified Data.Text as T (Text, append, tail, isPrefixOf, toUpper, filter, snoc, take)
31+
import qualified Data.Text as T (Text, append, tail, isPrefixOf, toUpper, filter, snoc, take, unpack)
2932
import Database.DataType ( ShapeType( Node ) , ShapeType( Hybrid ), ShapeType( BoolNode ))
3033
import Database.Persist.Sqlite (Entity, PersistEntity, SqlPersistM, PersistValue( PersistInt64 ), selectList,
3134
entityKey, entityVal, selectFirst, (==.), (<-.), get, keyToValues, PersistValue( PersistText ),
@@ -90,6 +93,24 @@ returnPost code = runDb $ do
9093
Nothing -> return Nothing
9194
Just post -> return $ Just $ entityVal post
9295

96+
-- | Retrieves the course requirements for a Post as a list of course codes
97+
reqsForPost :: Post -> [String]
98+
reqsForPost post = do
99+
let requirementsText = T.unpack $ postRequirements post
100+
cleaned = filter (`notElem` ("<>" :: String)) $ filter (not . isPunctuation) requirementsText
101+
potentialCodes = words cleaned
102+
filter isCourseCode potentialCodes
103+
where
104+
-- | TODO: change function to use a regex
105+
isCourseCode :: String -> Bool
106+
isCourseCode codeStr =
107+
length codeStr == 8 &&
108+
all isAlphaNum codeStr &&
109+
all isAlpha (take 3 codeStr) &&
110+
all isDigit (take 3 (drop 3 codeStr)) &&
111+
isAlpha (codeStr !! 6) &&
112+
isDigit (codeStr !! 7)
113+
93114
-- | Queries the database for all information regarding a specific meeting for
94115
-- a @course@, returns a Meeting.
95116
returnMeeting :: T.Text -> T.Text -> T.Text -> SqlPersistM (Entity Meeting)

app/DynamicGraphs/GraphOptions.hs

+3-2
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ data GraphOptions =
1515
includeGrades :: Bool -- True to include grade nodes
1616
} deriving (Show)
1717

18-
data CourseGraphOptions = CourseGraphOptions { courses :: [T.Text], graphOptions :: GraphOptions }
18+
data CourseGraphOptions = CourseGraphOptions { courses :: [T.Text], programs :: [T.Text], graphOptions :: GraphOptions }
1919
deriving (Show)
2020

2121
defaultGraphOptions :: GraphOptions
@@ -33,6 +33,7 @@ defaultGraphOptions =
3333
instance FromJSON CourseGraphOptions where
3434
parseJSON = withObject "Expected Object for GraphOptions" $ \o -> do
3535
rootCourses <- o .:? "courses" .!= []
36+
rootPrograms <- o.:? "programs" .!= []
3637
takenCourses <- o .:? "taken" .!= []
3738
dept <- o .:? "departments" .!= []
3839
excludedCourseDepth <- o .:? "excludedDepth" .!= 0
@@ -51,4 +52,4 @@ instance FromJSON CourseGraphOptions where
5152
includedLocation
5253
incRaws
5354
incGrades
54-
return $ CourseGraphOptions rootCourses options
55+
return $ CourseGraphOptions rootCourses rootPrograms options
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
{-|
2+
Description: CourseQueries module tests.
3+
4+
Module that contains the tests for the functions in the CourseQueries module.
5+
6+
-}
7+
8+
module Database.CourseQueriesTests
9+
( courseQueriesTestSuite
10+
) where
11+
12+
import Config (runDb)
13+
import Control.Monad.IO.Class (liftIO)
14+
import qualified Data.Text as T
15+
import Data.Time (getCurrentTime)
16+
import Database.CourseQueries (reqsForPost)
17+
import Database.DataType (PostType(..))
18+
import Database.Persist.Sqlite (insert_)
19+
import Database.Tables (Post(..))
20+
import Test.HUnit (Test(..), assertEqual)
21+
import TestHelpers (clearDatabase)
22+
23+
-- | List of test cases as (label, requirements to insert, input program, expected output)
24+
reqsForPostTestCases :: [(String, T.Text, T.Text, String)]
25+
reqsForPostTestCases =
26+
[ ("No program", "", "", "[]")
27+
, ("Valid program", "/CSC199H1/", "ASMAJ1689", "[\"CSC199H1\"]")
28+
, ("Invalid program", "", "ABCDE1234", "[]")
29+
]
30+
31+
-- | Run a test case (case, requirements, input, expected output) on the reqsForPost function.
32+
runReqsForPostTest :: String -> T.Text -> T.Text -> String -> Test
33+
runReqsForPostTest label reqsToInsert program expected =
34+
TestLabel label $ TestCase $ do
35+
currentTime <- liftIO getCurrentTime
36+
let testPost = Post Major "Computer Science" program "Sample post description" reqsToInsert currentTime currentTime
37+
38+
runDb $ do
39+
clearDatabase
40+
insert_ testPost
41+
42+
let requirements = reqsForPost testPost
43+
let actual = show requirements
44+
assertEqual ("Unexpected response body for " ++ label) expected actual
45+
46+
-- | Run all the reqsForPost test cases
47+
runReqsForPostTests :: [Test]
48+
runReqsForPostTests = map (\(label, reqsToInsert, program, expected) -> runReqsForPostTest label reqsToInsert program expected) reqsForPostTestCases
49+
50+
-- | Test suite for CourseQueries Module
51+
courseQueriesTestSuite :: Test
52+
courseQueriesTestSuite = TestLabel "Course Queries tests" $ TestList runReqsForPostTests
+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{-|
2+
Description: Database test suites module.
3+
4+
Module that contains the test suites for all the database functions.
5+
6+
-}
7+
8+
module Database.DatabaseTests
9+
( databaseTests ) where
10+
11+
import Test.HUnit (Test (..))
12+
import Database.CourseQueriesTests (courseQueriesTestSuite)
13+
14+
-- Single test encompassing all database test suites
15+
databaseTests :: Test
16+
databaseTests = TestList [courseQueriesTestSuite]

backend-test/Main.hs

+2-1
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,12 @@ import qualified System.Exit as Exit
1717
import Test.HUnit (Test (..), failures, runTestTT)
1818
import RequirementTests.RequirementTests (requirementTests)
1919
import Controllers.ControllerTests (controllerTests)
20+
import Database.DatabaseTests (databaseTests)
2021
import SvgTests.SvgTests (svgTests)
2122

2223
tests :: IO Test
2324
tests = do
24-
return $ TestList [requirementTests, controllerTests, svgTests]
25+
return $ TestList [requirementTests, controllerTests, svgTests, databaseTests]
2526

2627
main :: IO ()
2728
main = do

courseography.cabal

+2-1
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,8 @@ test-suite Tests
106106
test-framework,
107107
test-framework-hunit,
108108
test-framework-quickcheck2,
109-
text
109+
text,
110+
time
110111
default-extensions:
111112
OverloadedStrings,
112113
ScopedTypeVariables

0 commit comments

Comments
 (0)