forked from filipg/gonito
Merge branch 'dashboard' into 'master'
Dashboard See merge request gonito/gonito!1
This commit is contained in:
commit
5ade94b225
@ -54,6 +54,7 @@ import Handler.EditSubmission
|
||||
import Handler.Achievements
|
||||
import Handler.Score
|
||||
import Handler.ExtraPoints
|
||||
import Handler.Dashboard
|
||||
|
||||
-- This line actually creates our YesodDispatch instance. It is the second half
|
||||
-- of the call to mkYesodData which occurs in Foundation.hs. Please see the
|
||||
|
@ -126,6 +126,8 @@ instance Yesod App where
|
||||
isAuthorized (EditAchievementR _) _ = isAdmin
|
||||
isAuthorized ExtraPointsR _ = isAdmin
|
||||
|
||||
isAuthorized DashboardR _ = return Authorized
|
||||
|
||||
isAuthorized (ShowChallengeR _) _ = return Authorized
|
||||
isAuthorized (ChallengeReadmeR _) _ = return Authorized
|
||||
isAuthorized (ChallengeAllSubmissionsR _) _ = return Authorized
|
||||
|
@ -11,7 +11,6 @@ import Handler.Shared
|
||||
|
||||
import Handler.AchievementUtils
|
||||
|
||||
import Data.Time.Clock
|
||||
import Data.Time.LocalTime
|
||||
|
||||
import Data.Text
|
||||
@ -103,7 +102,7 @@ $if canGiveUpWorkingOn
|
||||
|
||||
getSubmissionForAchievementR :: SubmissionId -> WorkingOnId -> Handler Html
|
||||
getSubmissionForAchievementR submissionId workingOnId = do
|
||||
(Entity userId user) <- requireAuth
|
||||
(Entity userId _) <- requireAuth
|
||||
submission <- runDB $ get404 submissionId
|
||||
workingOn <- runDB $ get404 workingOnId
|
||||
if submissionSubmitter submission == userId && workingOnUser workingOn == userId
|
||||
@ -151,7 +150,7 @@ getStartWorkingOnR achievementId = do
|
||||
|
||||
getGiveUpWorkingOnR :: AchievementId -> Handler Html
|
||||
getGiveUpWorkingOnR achievementId = do
|
||||
(Entity userId user) <- requireAuth
|
||||
(Entity userId _) <- requireAuth
|
||||
|
||||
alreadyWorkingOn <- runDB $ selectList [WorkingOnUser ==. userId,
|
||||
WorkingOnAchievement ==. achievementId,
|
||||
@ -171,11 +170,11 @@ getGiveUpWorkingOnR achievementId = do
|
||||
|
||||
|
||||
determineWhetherCanStartWorkingOn Nothing _ _ = False
|
||||
determineWhetherCanStartWorkingOn (Just (Entity userId user)) peopleWorkingOn maxWinners =
|
||||
determineWhetherCanStartWorkingOn (Just (Entity userId _)) peopleWorkingOn maxWinners =
|
||||
(Import.all (\e -> (userId /= entityKey e)) peopleWorkingOn) && (checkLimit peopleWorkingOn maxWinners)
|
||||
|
||||
determineWhetherCanGiveUpWorkingOn Nothing _ = False
|
||||
determineWhetherCanGiveUpWorkingOn (Just (Entity userId user)) peopleWorkingOn =
|
||||
determineWhetherCanGiveUpWorkingOn (Just (Entity userId _)) peopleWorkingOn =
|
||||
(Import.any (\e -> (userId == entityKey e)) peopleWorkingOn)
|
||||
|
||||
checkLimit _ Nothing = True
|
||||
@ -230,7 +229,6 @@ getEditAchievementR achievementId = do
|
||||
postEditAchievementR :: AchievementId -> Handler Html
|
||||
postEditAchievementR achievementId = do
|
||||
tagsAvailableAsJSON <- runDB $ getAvailableTagsAsJSON
|
||||
achievement <- runDB $ get404 achievementId
|
||||
((result, formWidget), formEnctype) <- runFormPost (achievementForm Nothing Nothing)
|
||||
mUser <- maybeAuth
|
||||
|
||||
|
212
Handler/Dashboard.hs
Normal file
212
Handler/Dashboard.hs
Normal file
@ -0,0 +1,212 @@
|
||||
|
||||
|
||||
module Handler.Dashboard where
|
||||
|
||||
import Import
|
||||
|
||||
import Data.Time.LocalTime
|
||||
import Handler.Shared
|
||||
import Handler.Common (checkIfAdmin)
|
||||
|
||||
import Yesod.Form.Bootstrap3 (BootstrapFormLayout (..), renderBootstrap3, bfs)
|
||||
import qualified Yesod.Table as Table
|
||||
|
||||
import qualified Data.Text as T
|
||||
|
||||
import Handler.Tables (timestampCell)
|
||||
|
||||
data IndicatorEntry = IndicatorEntry {
|
||||
indicatorEntryIndicator :: Entity Indicator,
|
||||
indicatorEntryTest :: Entity Test,
|
||||
indicatorEntryChallenge :: Entity Challenge,
|
||||
indicatorEntryFilterCondition :: Maybe Text,
|
||||
indicatorEntryTargetCondition :: Maybe Text,
|
||||
indicatorEntryTargets :: [Entity Target]
|
||||
}
|
||||
|
||||
getDashboardR :: Handler Html
|
||||
getDashboardR = do
|
||||
(formWidget, formEnctype) <- generateFormPost targetForm
|
||||
mUser <- maybeAuth
|
||||
doDashboard mUser formWidget formEnctype
|
||||
|
||||
postDashboardR :: Handler Html
|
||||
postDashboardR = do
|
||||
((result, formWidget), formEnctype) <- runFormPost targetForm
|
||||
mUser <- maybeAuth
|
||||
when (checkIfAdmin mUser) $ do
|
||||
case result of
|
||||
FormSuccess (testId, filterCondition, targetCondition, deadlineDay, deadlineTime, value) -> do
|
||||
targetId <- runDB $ insert $ Indicator testId filterCondition targetCondition
|
||||
_ <- runDB $ insert $ Target targetId (UTCTime { utctDay = deadlineDay, utctDayTime = timeOfDayToTime deadlineTime }) value
|
||||
return ()
|
||||
_ -> do
|
||||
return ()
|
||||
doDashboard mUser formWidget formEnctype
|
||||
|
||||
getDeleteIndicatorR :: IndicatorId -> Handler Html
|
||||
getDeleteIndicatorR indicatorId = do
|
||||
(formWidget, formEnctype) <- generateFormPost targetForm
|
||||
mUser <- maybeAuth
|
||||
when (checkIfAdmin mUser) $ runDB $ do
|
||||
targets <- selectList [TargetIndicator ==. indicatorId] []
|
||||
mapM_ delete $ map entityKey targets
|
||||
delete indicatorId
|
||||
setMessage $ toHtml (("Indicator deleted along with its targets!" :: Text))
|
||||
doDashboard mUser formWidget formEnctype
|
||||
|
||||
getEditIndicatorR :: IndicatorId -> Handler Html
|
||||
getEditIndicatorR indicatorId = do
|
||||
indicator <- runDB $ get404 indicatorId
|
||||
(formWidget, formEnctype) <- generateFormPost (indicatorForm indicator)
|
||||
mUser <- maybeAuth
|
||||
doEditIndicator mUser indicatorId formWidget formEnctype
|
||||
|
||||
postEditIndicatorR :: IndicatorId -> Handler Html
|
||||
postEditIndicatorR indicatorId = do
|
||||
indicator <- runDB $ get404 indicatorId
|
||||
((result, formWidget), formEnctype) <- runFormPost (indicatorForm indicator)
|
||||
mUser <- maybeAuth
|
||||
|
||||
when (checkIfAdmin mUser) $ do
|
||||
case result of
|
||||
FormSuccess changedIndicator -> do
|
||||
runDB $ replace indicatorId changedIndicator
|
||||
return ()
|
||||
_ -> do
|
||||
return ()
|
||||
|
||||
doEditIndicator mUser indicatorId formWidget formEnctype
|
||||
|
||||
doEditIndicator mUser indicatorId formWidget formEnctype = do
|
||||
(addTargetformWidget, addTargetFormEnctype) <- generateFormPost addTargetForm
|
||||
|
||||
indicator <- runDB $ get404 indicatorId
|
||||
indicatorEntry <- indicatorToEntry (Entity indicatorId indicator)
|
||||
defaultLayout $ do
|
||||
setTitle "Dashboard"
|
||||
$(widgetFile "edit-indicator")
|
||||
|
||||
postAddTargetR :: IndicatorId -> Handler Html
|
||||
postAddTargetR indicatorId = do
|
||||
((result, _), _) <- runFormPost addTargetForm
|
||||
mUser <- maybeAuth
|
||||
when (checkIfAdmin mUser) $ runDB $ do
|
||||
case result of
|
||||
FormSuccess (deadlineDay, deadlineTime, value) -> do
|
||||
_ <- insert $ Target indicatorId (UTCTime { utctDay = deadlineDay, utctDayTime = timeOfDayToTime deadlineTime }) value
|
||||
return ()
|
||||
_ -> do
|
||||
return ()
|
||||
getEditIndicatorR indicatorId
|
||||
|
||||
|
||||
getDeleteTargetR :: TargetId -> Handler Html
|
||||
getDeleteTargetR targetId = do
|
||||
(formWidget, formEnctype) <- generateFormPost targetForm
|
||||
mUser <- maybeAuth
|
||||
target <- runDB $ get404 targetId
|
||||
when (checkIfAdmin mUser) $ runDB $ do
|
||||
delete targetId
|
||||
setMessage $ toHtml (("Target deleted!" :: Text))
|
||||
doEditIndicator mUser (targetIndicator target) formWidget formEnctype
|
||||
|
||||
|
||||
doDashboard mUser formWidget formEnctype = do
|
||||
indicators <- runDB $ selectList [] [Asc IndicatorId]
|
||||
|
||||
indicatorEntries <- mapM indicatorToEntry indicators
|
||||
|
||||
defaultLayout $ do
|
||||
setTitle "Dashboard"
|
||||
$(widgetFile "dashboard")
|
||||
|
||||
indicatorToEntry :: (BaseBackend (YesodPersistBackend site) ~ SqlBackend, PersistQueryRead (YesodPersistBackend site), YesodPersist site) => Entity Indicator -> HandlerFor site IndicatorEntry
|
||||
indicatorToEntry indicatorEnt@(Entity indicatorId indicator) = runDB $ do
|
||||
let theTestId = indicatorTest indicator
|
||||
test <- get404 theTestId
|
||||
|
||||
let theChallengeId = testChallenge test
|
||||
challenge <- get404 theChallengeId
|
||||
|
||||
targets <- selectList [TargetIndicator ==. indicatorId] [Asc TargetDeadline]
|
||||
|
||||
return $ IndicatorEntry {
|
||||
indicatorEntryIndicator = indicatorEnt,
|
||||
indicatorEntryTest = Entity theTestId test,
|
||||
indicatorEntryChallenge = Entity theChallengeId challenge,
|
||||
indicatorEntryFilterCondition = indicatorFilterCondition indicator,
|
||||
indicatorEntryTargetCondition = indicatorTargetCondition indicator,
|
||||
indicatorEntryTargets = targets
|
||||
}
|
||||
|
||||
targetForm :: Form (TestId, Maybe Text, Maybe Text, Day, TimeOfDay, Double)
|
||||
targetForm = renderBootstrap3 BootstrapBasicForm $ (,,,,,)
|
||||
<$> testSelectFieldList Nothing
|
||||
<*> aopt textField (fieldWithTooltip MsgFilterCondition MsgFilterConditionTooltip) Nothing
|
||||
<*> aopt textField (fieldWithTooltip MsgTargetCondition MsgTargetConditionTooltip) Nothing
|
||||
<*> areq dayField (bfs MsgTargetDeadlineDay) Nothing
|
||||
<*> areq timeFieldTypeTime (bfs MsgTargetDeadlineTime) Nothing
|
||||
<*> areq doubleField (bfs MsgTargetValue) Nothing
|
||||
|
||||
indicatorForm :: Indicator -> Form Indicator
|
||||
indicatorForm indicator = renderBootstrap3 BootstrapBasicForm $ Indicator
|
||||
<$> testSelectFieldList (Just $ indicatorTest indicator)
|
||||
<*> aopt textField (fieldWithTooltip MsgFilterCondition MsgFilterConditionTooltip) (Just $ indicatorFilterCondition indicator)
|
||||
<*> aopt textField (fieldWithTooltip MsgTargetCondition MsgTargetConditionTooltip) (Just $ indicatorTargetCondition indicator)
|
||||
|
||||
addTargetForm :: Form (Day, TimeOfDay, Double)
|
||||
addTargetForm = renderBootstrap3 BootstrapBasicForm $ (,,)
|
||||
<$> areq dayField (bfs MsgTargetDeadlineDay) Nothing
|
||||
<*> areq timeFieldTypeTime (bfs MsgTargetDeadlineTime) Nothing
|
||||
<*> areq doubleField (bfs MsgTargetValue) Nothing
|
||||
|
||||
indicatorTable :: Maybe (Entity User) -> Table.Table App (IndicatorEntry)
|
||||
indicatorTable mUser = mempty
|
||||
++ Table.text "indicator" prettyIndicatorEntry
|
||||
++ Table.text "filter condition" ((fromMaybe T.empty) . indicatorEntryFilterCondition)
|
||||
++ Table.text "target condition" ((fromMaybe T.empty) . indicatorEntryTargetCondition)
|
||||
++ Table.text "targets" formatTargets
|
||||
++ indicatorStatusCell mUser
|
||||
|
||||
targetTable :: Maybe (Entity User) -> Table.Table App (Entity Target)
|
||||
targetTable mUser = mempty
|
||||
++ Table.text "target value" (T.pack . show . targetValue . entityVal)
|
||||
++ timestampCell "deadline" (targetDeadline . entityVal)
|
||||
++ targetStatusCell mUser
|
||||
|
||||
prettyIndicatorEntry :: IndicatorEntry -> Text
|
||||
prettyIndicatorEntry entry = prettyTestTitle (entityVal $ indicatorEntryTest entry)
|
||||
(entityVal $ indicatorEntryChallenge entry)
|
||||
|
||||
formatTargets :: IndicatorEntry -> Text
|
||||
formatTargets = T.intercalate ", " . (map formatTarget) . indicatorEntryTargets
|
||||
|
||||
formatTarget :: Entity Target -> Text
|
||||
formatTarget (Entity _ target) = (T.pack $ show $ targetValue target) <> " (" <> (T.pack $ show $ targetDeadline target) ++ ")"
|
||||
|
||||
indicatorStatusCell :: Maybe (Entity User) -> Table.Table App IndicatorEntry
|
||||
indicatorStatusCell mUser = Table.widget "" (indicatorStatusCellWidget mUser)
|
||||
|
||||
indicatorStatusCellWidget :: Maybe (Entity User) -> IndicatorEntry -> WidgetFor App ()
|
||||
indicatorStatusCellWidget mUser indicatorEntry = $(widgetFile "indicator-status")
|
||||
where indicatorId = entityKey $ indicatorEntryIndicator indicatorEntry
|
||||
|
||||
targetStatusCell :: Maybe (Entity User) -> Table.Table App (Entity Target)
|
||||
targetStatusCell mUser = Table.widget "" (targetStatusCellWidget mUser)
|
||||
|
||||
targetStatusCellWidget :: Maybe (Entity User) -> Entity Target -> WidgetFor App ()
|
||||
targetStatusCellWidget mUser targetEnt = $(widgetFile "target-status")
|
||||
where targetId = entityKey $ targetEnt
|
||||
|
||||
testSelectFieldList :: (BaseBackend (YesodPersistBackend site) ~ SqlBackend, RenderMessage site AppMessage, RenderMessage site FormMessage, PersistQueryRead (YesodPersistBackend site), YesodPersist site) => Maybe TestId -> AForm (HandlerFor site) (Key Test)
|
||||
testSelectFieldList mTestId = areq (selectField tests) (bfs MsgTest) mTestId
|
||||
where
|
||||
tests = do
|
||||
testEnts <- runDB $ selectList [] [Asc TestName]
|
||||
challenges <- runDB $ mapM (\(Entity _ val) -> get404 (testChallenge val)) testEnts
|
||||
let items = Import.map (\(t, ch) -> (prettyTestTitle (entityVal t) ch, entityKey t)) $ zip testEnts challenges
|
||||
optionsPairs $ sortBy (\a b -> fst a `compare` fst b) items
|
||||
|
||||
prettyTestTitle :: Test -> Challenge -> Text
|
||||
prettyTestTitle t ch = (challengeTitle ch) ++ " / " ++ (testName t) ++ " / " ++ (pack $ show $ testMetric t)
|
1
build.sh
1
build.sh
@ -1,5 +1,6 @@
|
||||
#!/bin/bash -xe
|
||||
|
||||
rm -rf ../geval
|
||||
git clone git://gonito.net/geval
|
||||
mv geval ..
|
||||
stack install
|
||||
|
@ -98,6 +98,7 @@ SubmissionTag
|
||||
tag TagId
|
||||
accepted Bool Maybe
|
||||
UniqueSubmissionTag submission tag
|
||||
-- for machine learning courses
|
||||
Achievement
|
||||
name Text
|
||||
challenge ChallengeId
|
||||
@ -133,4 +134,13 @@ Participant
|
||||
user UserId
|
||||
course CourseId
|
||||
UniqueUserCourse user course
|
||||
-- for "KPI" dashboard
|
||||
Indicator
|
||||
test TestId
|
||||
filterCondition Text Maybe
|
||||
targetCondition Text Maybe
|
||||
Target
|
||||
indicator IndicatorId
|
||||
deadline UTCTime
|
||||
value Double
|
||||
-- By default this file is used in Model.hs (which is imported by Foundation.hs)
|
||||
|
@ -52,6 +52,12 @@
|
||||
/score/#UserId ScoreR GET
|
||||
/my-score MyScoreR GET
|
||||
|
||||
/dashboard DashboardR GET POST
|
||||
/edit-indicator/#IndicatorId EditIndicatorR GET POST
|
||||
/delete-indicator/#IndicatorId DeleteIndicatorR GET
|
||||
/delete-target/#TargetId DeleteTargetR GET
|
||||
/add-target/#IndicatorId AddTargetR POST
|
||||
|
||||
/edit-submission/#SubmissionId EditSubmissionR GET POST
|
||||
/edit-submission-and-variant/#SubmissionId/#VariantId EditSubmissionAndVariantR GET POST
|
||||
/add-variant-param/#SubmissionId/#VariantId AddVariantParamR POST
|
||||
|
@ -52,6 +52,7 @@ library
|
||||
Handler.AchievementUtils
|
||||
Handler.ExtraPoints
|
||||
Handler.Runner
|
||||
Handler.Dashboard
|
||||
|
||||
if flag(dev) || flag(library-only)
|
||||
cpp-options: -DDEVELOPMENT
|
||||
|
@ -66,3 +66,12 @@ ParameterName: parameter name
|
||||
ParameterValue: parameter value
|
||||
ChallengeName: computer-friendly name ("slug")
|
||||
ChallengeNameTooltip: to be used in the URLs (only lower-case letters, digits or hyphens, must start with a lower-case letter), once set cannot be changed!
|
||||
FilterCondition: filter condition
|
||||
FilterConditionTooltip: the condition used to pre-select all submission — an extra graph will be drawed for all submissions matching this condition (not necessarily the target condition)
|
||||
TargetCondition: target condition
|
||||
TargetConditionTooltip: the condition which is required for a submission to be considered for realising the target
|
||||
TargetDeadlineDay: target day
|
||||
TargetDeadlineTime: target time
|
||||
TargetValue: target value to be reached before the target date
|
||||
Test: test
|
||||
Dashboard: dashboard
|
||||
|
13
templates/dashboard.hamlet
Normal file
13
templates/dashboard.hamlet
Normal file
@ -0,0 +1,13 @@
|
||||
<h1>Dashboard
|
||||
|
||||
^{Table.buildBootstrap (indicatorTable mUser) indicatorEntries}
|
||||
|
||||
<hr>
|
||||
|
||||
$if (checkIfAdmin mUser)
|
||||
<h2>Create a new target
|
||||
|
||||
<form method=post action=@{DashboardR}#form enctype=#{formEnctype}>
|
||||
^{formWidget}
|
||||
<button .btn .btn-primary type="submit">
|
||||
_{MsgSubmit} <span class="glyphicon glyphicon-upload"></span>
|
@ -19,6 +19,7 @@
|
||||
<li><a href="@{ListChallengesR}">_{MsgListChallenges}</a>
|
||||
<li><a href="@{TagsR}">_{MsgListTags}</a>
|
||||
<li><a href="@{AchievementsR}">_{MsgAchievements}</a>
|
||||
<li><a href="@{DashboardR}">_{MsgDashboard}</a>
|
||||
$if userIsAdmin $ entityVal user
|
||||
<li class="dropdown">
|
||||
<a id="admin" href="#" class="dropdown-toggle" data-toggle="dropdown">_{MsgManage}<span class="caret"></span>
|
||||
@ -39,6 +40,7 @@
|
||||
<li><a href="@{Presentation4RealR}">_{MsgAbout}</a>
|
||||
<li><a href="@{ListChallengesR}">_{MsgListChallenges}</a>
|
||||
<li><a href="@{TagsR}">_{MsgListTags}</a>
|
||||
<li><a href="@{DashboardR}">_{MsgDashboard}</a>
|
||||
|
||||
<ul class="nav navbar-nav navbar-collapse collapse navbar-right">
|
||||
<li><a href="@{AuthR LoginR}">log in</a>
|
||||
|
21
templates/edit-indicator.hamlet
Normal file
21
templates/edit-indicator.hamlet
Normal file
@ -0,0 +1,21 @@
|
||||
<h2>#{prettyIndicatorEntry indicatorEntry}
|
||||
|
||||
<form method=post action=@{EditIndicatorR indicatorId}#form enctype=#{formEnctype}>
|
||||
^{formWidget}
|
||||
<button .btn .btn-primary type="submit">
|
||||
_{MsgSubmit} <span class="glyphicon glyphicon-upload"></span>
|
||||
|
||||
<h4>Targets
|
||||
|
||||
^{Table.buildBootstrap (targetTable mUser) (indicatorEntryTargets indicatorEntry)}
|
||||
|
||||
$if (checkIfAdmin mUser)
|
||||
<h4>Add a new target
|
||||
<form method=post action=@{AddTargetR indicatorId}#form enctype=#{addTargetFormEnctype}>
|
||||
^{addTargetformWidget}
|
||||
<button .btn .btn-primary type="submit">
|
||||
_{MsgSubmit} <span class="glyphicon glyphicon-upload"></span>
|
||||
|
||||
<hr>
|
||||
|
||||
<p><a href=@{DashboardR}>Back to Dashboard</p>
|
6
templates/indicator-status.hamlet
Normal file
6
templates/indicator-status.hamlet
Normal file
@ -0,0 +1,6 @@
|
||||
$if (checkIfAdmin mUser)
|
||||
<a href="@{EditIndicatorR indicatorId}">
|
||||
<span class="glyphicon glyphicon-pencil" title="click to edit the submission" aria-hidden="true">
|
||||
|
||||
<a href="@{DeleteIndicatorR indicatorId}">
|
||||
<span class="glyphicon glyphicon-remove" title="click to remove the submission" aria-hidden="true">
|
3
templates/target-status.hamlet
Normal file
3
templates/target-status.hamlet
Normal file
@ -0,0 +1,3 @@
|
||||
$if (checkIfAdmin mUser)
|
||||
<a href="@{DeleteTargetR targetId}">
|
||||
<span class="glyphicon glyphicon-remove" title="click to remove the submission" aria-hidden="true">
|
Loading…
Reference in New Issue
Block a user