Skip to content

Refactoring the Graph Methods #1566

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
- Added dependency groups to Dependabot configuration
- Added tests for the index function in `Controllers/Graph`
- Added `Models` folder and a new `Course.hs` module within it
- Added the `Graph.hs` module in the `App/Models`
- Updated eslint configuration for eslint v9

## [0.6.0] - 2024-06-24
Expand Down
22 changes: 17 additions & 5 deletions app/Controllers/Graph.hs
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
module Controllers.Graph (graphResponse, index, getGraphJSON, graphImageResponse) where
module Controllers.Graph (graphResponse, index, getGraphJSON, graphImageResponse, saveGraphJSON) where

import Control.Monad.IO.Class (liftIO)
import Data.Aeson (object, (.=))
import Data.Aeson (decode, object, (.=))
import Data.Maybe (fromMaybe)
import Happstack.Server (Response, ServerPart, look, lookText', ok, toResponse)
import Happstack.Server (Response, ServerPart, look, lookBS, lookText', ok, toResponse)
import MasterTemplate (header, masterTemplate)
import Scripts (graphScripts)
import Text.Blaze ((!))
import qualified Text.Blaze.Html5 as H
import qualified Text.Blaze.Html5.Attributes as A

import Config (runDb)
import Database.CourseQueries (getGraph)
import Database.Persist.Sqlite (Entity, SelectOpt (Asc), SqlPersistM, selectList, (==.))
import Database.Tables as Tables (EntityField (GraphDynamic, GraphTitle), Graph, Text)
import Database.Tables as Tables (EntityField (GraphDynamic, GraphTitle), Graph, SvgJSON, Text)
import Export.GetImages (getActiveGraphImage)
import Models.Graph (getGraph, insertGraph)
import Response.Image (returnImageData)
import Util.Happstack (createJSONResponse)

Expand Down Expand Up @@ -53,3 +53,15 @@ graphImageResponse = do
graphInfo <- look "JsonLocalStorageObj"
(svgFilename, imageFilename) <- liftIO $ getActiveGraphImage graphInfo
liftIO $ returnImageData svgFilename imageFilename

-- | Inserts SVG graph data into Texts, Shapes, and Paths tables
saveGraphJSON :: ServerPart Response
saveGraphJSON = do
jsonStr <- lookBS "jsonData"
nameStr <- lookText' "nameData"
let jsonObj = decode jsonStr :: Maybe SvgJSON
case jsonObj of
Nothing -> return $ toResponse ("Error" :: String)
Just svg -> do
_ <- liftIO $ runDb $ insertGraph nameStr svg
return $ toResponse ("Success" :: String)
30 changes: 3 additions & 27 deletions app/Database/CourseInsertion.hs
Original file line number Diff line number Diff line change
Expand Up @@ -8,36 +8,12 @@ into the database. These functions are used as helpers for the WebParsing module
-}

module Database.CourseInsertion
(insertCourse,
saveGraphJSON) where
(insertCourse) where

import Config (runDb)
import Control.Monad.IO.Class (liftIO)
import qualified Data.Aeson as Aeson
import qualified Data.Text as T
import Database.Persist.Class (selectKeysList)
import Database.Persist.Sqlite (SqlPersistM, insert, insertMany_, insert_, selectFirst, (==.))
import Database.Tables hiding (breadth, distribution, paths, shapes, texts)
import Happstack.Server (Response, ServerPart, lookBS, lookText', toResponse)

-- | Inserts SVG graph data into Texts, Shapes, and Paths tables
saveGraphJSON :: ServerPart Response
saveGraphJSON = do
jsonStr <- lookBS "jsonData"
nameStr <- lookText' "nameData"
let jsonObj = Aeson.decode jsonStr :: Maybe SvgJSON
case jsonObj of
Nothing -> return $ toResponse ("Error" :: String)
Just (SvgJSON texts shapes paths) -> do
_ <- liftIO $ runDb $ insertGraph nameStr texts shapes paths
return $ toResponse ("Success" :: String)
where
insertGraph :: T.Text -> [Text] -> [Shape] -> [Path] -> SqlPersistM ()
insertGraph nameStr_ texts shapes paths = do
gId <- insert $ Graph nameStr_ 256 256 False
insertMany_ $ map (\text -> text {textGraph = gId}) texts
insertMany_ $ map (\shape -> shape {shapeGraph = gId}) shapes
insertMany_ $ map (\path -> path {pathGraph = gId}) paths
import Database.Persist.Sqlite (SqlPersistM, insert_, selectFirst, (==.))
import Database.Tables hiding (breadth, distribution)

--contains' :: PersistEntity m => T.Text -> SqlPersistM m
--contains field query = Filter field (Left $ T.concat ["%", query, "%"]) (BackendSpecificFilter "LIKE")
Expand Down
55 changes: 2 additions & 53 deletions app/Database/CourseQueries.hs
Original file line number Diff line number Diff line change
Expand Up @@ -13,26 +13,20 @@ module Database.CourseQueries
reqsForPost,
prereqsForCourse,
returnMeeting,
getGraph,
getMeetingTime,
buildTime,
getDeptCourses,
) where

import Config (runDb)
import Control.Monad.IO.Class (MonadIO, liftIO)
import Data.Aeson (Value, object, toJSON)
import Data.Char (isAlpha, isAlphaNum, isDigit, isPunctuation)
import Data.List (partition)
import Data.Maybe (fromJust, fromMaybe)
import qualified Data.Text as T (Text, append, isPrefixOf, snoc, tail, toUpper, unpack)
import Database.DataType (ShapeType (BoolNode, Hybrid, Node))
import Database.Persist.Sqlite (Entity, PersistEntity, PersistValue (PersistInt64, PersistText),
SqlPersistM, entityKey, entityVal, keyToValues, rawSql, selectFirst,
selectList, (<-.), (==.))
import Database.Persist.Sqlite (Entity, PersistValue (PersistText), SqlPersistM, entityKey,
entityVal, rawSql, selectFirst, selectList, (<-.), (==.))
import Database.Tables as Tables
import Models.Course (buildCourse, buildMeetTimes)
import Svg.Builder (buildEllipses, buildPath, buildRect, intersectsWithShape)

-- | Queries the database for information about the post then returns the post value
returnPost :: T.Text -> IO (Maybe Post)
Expand Down Expand Up @@ -72,51 +66,6 @@ returnMeeting lowerStr sect session = do

-- ** Other queries

getGraph :: T.Text -> IO (Maybe Value)
getGraph graphName = runDb $ do
graphEnt :: (Maybe (Entity Graph)) <- selectFirst [GraphTitle ==. graphName] []
case graphEnt of
Nothing -> return Nothing
Just graph -> do
let gId = entityKey graph
sqlTexts :: [Entity Text] <- selectList [TextGraph ==. gId] []
sqlRects :: [Entity Shape] <- selectList
[ShapeType_ <-. [Node, Hybrid],
ShapeGraph ==. gId] []
sqlEllipses :: [Entity Shape] <- selectList
[ShapeType_ ==. BoolNode,
ShapeGraph ==. gId] []
sqlPaths :: [Entity Path] <- selectList [PathGraph ==. gId] []

let
keyAsInt :: PersistEntity a => Entity a -> Integer
keyAsInt = fromIntegral . (\(PersistInt64 x) -> x) . head . keyToValues . entityKey

graphtexts = map entityVal sqlTexts
rects = zipWith (buildRect graphtexts)
(map entityVal sqlRects)
(map keyAsInt sqlRects)
ellipses = zipWith (buildEllipses graphtexts)
(map entityVal sqlEllipses)
(map keyAsInt sqlEllipses)
graphpaths = zipWith (buildPath rects ellipses)
(map entityVal sqlPaths)
(map keyAsInt sqlPaths)
(regions, _) = partition pathIsRegion graphpaths
regionTexts = filter (not .
intersectsWithShape (rects ++ ellipses))
graphtexts

response = object [
("texts", toJSON $ graphtexts ++ regionTexts),
("shapes", toJSON $ rects ++ ellipses),
("paths", toJSON $ graphpaths ++ regions),
("width", toJSON $ graphWidth $ entityVal graph),
("height", toJSON $ graphHeight $ entityVal graph)
]

return (Just response)

-- | Retrieves the prerequisites for a course (code) as a string.
-- Also retrieves the actual course code in the database in case
-- the one the user inputs doesn't match it exactly
Expand Down
2 changes: 1 addition & 1 deletion app/DynamicGraphs/WriteRunDot.hs
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ import Data.List (sort)
import Data.Maybe (fromMaybe)
import qualified Data.Text as T
import Data.Text.Encoding (decodeUtf8)
import Database.CourseQueries (getGraph)
import DynamicGraphs.GraphGenerator (coursesToPrereqGraph, coursesToPrereqGraphExcluding,
graphProfileHash)
import DynamicGraphs.GraphOptions (CourseGraphOptions (..), GraphOptions (..))
import Happstack.Server (ServerPart, askRq)
import Happstack.Server.SimpleHTTP (Response)
import Happstack.Server.Types (takeRequestBody, unBody)
import Models.Graph (getGraph)
import Svg.Parser (parseDynamicSvg)
import System.Directory (createDirectoryIfMissing)
import System.FilePath (combine, normalise)
Expand Down
66 changes: 66 additions & 0 deletions app/Models/Graph.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
module Models.Graph
(getGraph,
insertGraph) where

import Config (runDb)
import Data.Aeson (Value, object, toJSON)
import Data.List (partition)
import qualified Data.Text as T (Text)
import Database.DataType (ShapeType (BoolNode, Hybrid, Node))
import Database.Persist.Sqlite (Entity, PersistEntity, PersistValue (PersistInt64), SqlPersistM,
entityKey, entityVal, insert, insertMany_, keyToValues, selectFirst,
selectList, (<-.), (==.))
import Database.Tables hiding (paths, shapes, texts)
import Svg.Builder (buildEllipses, buildPath, buildRect, intersectsWithShape)

getGraph :: T.Text -> IO (Maybe Value)
getGraph graphName = runDb $ do
graphEnt :: (Maybe (Entity Graph)) <- selectFirst [GraphTitle ==. graphName] []
case graphEnt of
Nothing -> return Nothing
Just graph -> do
let gId = entityKey graph
sqlTexts :: [Entity Text] <- selectList [TextGraph ==. gId] []
sqlRects :: [Entity Shape] <- selectList
[ShapeType_ <-. [Node, Hybrid],
ShapeGraph ==. gId] []
sqlEllipses :: [Entity Shape] <- selectList
[ShapeType_ ==. BoolNode,
ShapeGraph ==. gId] []
sqlPaths :: [Entity Path] <- selectList [PathGraph ==. gId] []

let
keyAsInt :: PersistEntity a => Entity a -> Integer
keyAsInt = fromIntegral . (\(PersistInt64 x) -> x) . head . keyToValues . entityKey

graphtexts = map entityVal sqlTexts
rects = zipWith (buildRect graphtexts)
(map entityVal sqlRects)
(map keyAsInt sqlRects)
ellipses = zipWith (buildEllipses graphtexts)
(map entityVal sqlEllipses)
(map keyAsInt sqlEllipses)
graphpaths = zipWith (buildPath rects ellipses)
(map entityVal sqlPaths)
(map keyAsInt sqlPaths)
(regions, _) = partition pathIsRegion graphpaths
regionTexts = filter (not .
intersectsWithShape (rects ++ ellipses))
graphtexts

response = object [
("texts", toJSON $ graphtexts ++ regionTexts),
("shapes", toJSON $ rects ++ ellipses),
("paths", toJSON $ graphpaths ++ regions),
("width", toJSON $ graphWidth $ entityVal graph),
("height", toJSON $ graphHeight $ entityVal graph)
]

return (Just response)

insertGraph :: T.Text -> SvgJSON -> SqlPersistM ()
insertGraph nameStr_ (SvgJSON texts shapes paths) = do
gId <- insert $ Graph nameStr_ 256 256 False
insertMany_ $ map (\text -> text {textGraph = gId}) texts
insertMany_ $ map (\shape -> shape {shapeGraph = gId}) shapes
insertMany_ $ map (\path -> path {pathGraph = gId}) paths
3 changes: 1 addition & 2 deletions app/Routes.hs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@ import Control.Monad (MonadPlus (mplus), msum)
import Controllers.Course as CoursesController (courseInfo, index, retrieveCourse)
import Controllers.Generate as GenerateController (findAndSavePrereqsResponse, generateResponse)
import Controllers.Graph as GraphsController (getGraphJSON, graphImageResponse, graphResponse,
index)
index, saveGraphJSON)
import Controllers.Timetable as TimetableController
import Database.CourseInsertion (saveGraphJSON)
import Happstack.Server (Browsing (DisableBrowsing), Response, ServerPart, ServerPartT,
ToMessage (toResponse), dir, noTrailingSlash, nullDir, seeOther,
serveDirectory)
Expand Down
2 changes: 2 additions & 0 deletions courseography.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ library
Export.TimetableImageCreator,
MasterTemplate,
Models.Course,
Models.Graph,
Response.Image,
Scripts,
Svg.Builder,
Expand Down Expand Up @@ -167,6 +168,7 @@ executable courseography
Export.TimetableImageCreator,
MasterTemplate,
Models.Course,
Models.Graph,
Response,
Response.About,
Response.Draw,
Expand Down