Timeline.purs
module Aftok.Api.Timeline where
import Prelude
import Control.Alt ((<|>))
import Control.Monad.Error.Class (throwError)
import Control.Monad.Except.Trans (withExceptT, runExceptT)
import Data.Array (head)
import Data.Argonaut.Core (Json)
import Data.Argonaut.Decode (class DecodeJson, JsonDecodeError(..), decodeJson, (.:), (.:?))
import Data.DateTime (DateTime)
import Data.DateTime.Instant (Instant)
import Data.Either (Either, note)
import Data.Foldable (class Foldable, foldMapDefaultR, intercalate, foldr, foldl)
import Data.JSDate as JD
import Data.Maybe (Maybe(..))
import Data.Newtype (class Newtype)
import Data.Traversable (class Traversable, traverse)
import Data.UUID as UUID
import Foreign.Object (Object)
-- import Type.Proxy (Proxy(..))
-- import Text.Format as F -- (format, zeroFill, width)
import Effect.Aff (Aff)
import Effect.Class (liftEffect)
import Affjax (get, post)
import Affjax.RequestBody as RB
import Affjax.ResponseFormat as RF
import Data.Argonaut.Encode (encodeJson)
import Aftok.Types (ProjectId(..), pidStr)
import Aftok.Api.Types (APIError)
import Aftok.Api.Json (decompose, parseDatedResponse)
data TimelineError
= LogFailure (APIError)
| Unexpected String
instance showTimelineError :: Show TimelineError where
show = case _ of
LogFailure e -> show e
Unexpected t -> t
data Event t
= StartEvent t
| StopEvent t
eventTime :: forall i. Event i -> i
eventTime = case _ of
StartEvent t -> t
StopEvent t -> t
instance showEvent :: (Show i) => Show (Event i) where
show = case _ of
StartEvent t -> "Start " <> show t
StopEvent t -> "Stop " <> show t
derive instance eventFunctor :: Functor Event
instance eventFoldable :: Foldable Event where
foldr f b = case _ of
StartEvent a -> f a b
StopEvent a -> f a b
foldl f b = case _ of
StartEvent a -> f b a
StopEvent a -> f b a
foldMap = foldMapDefaultR
instance eventTraversable :: Traversable Event where
traverse f = case _ of
StartEvent a -> StartEvent <$> f a
StopEvent a -> StopEvent <$> f a
sequence = traverse identity
parseEventFields :: Object Json -> Either JsonDecodeError (Event String)
parseEventFields obj = do
ev <- obj .: "event"
start' <- traverse (_ .: "eventTime") =<< ev .:? "start"
stop' <- traverse (_ .: "eventTime") =<< ev .:? "stop"
note (TypeMismatch "Only 'stop' and 'start' events are supported.")
$ (StartEvent <$> start')
<|> (StopEvent <$> stop')
instance eventDecodeJSON :: DecodeJson (Event String) where
decodeJson = parseEventFields <=< decodeJson
newtype KeyedEvent i
= KeyedEvent
{ eventId :: String
, event :: Event i
}
keyedEvent :: forall i. String -> Event i -> KeyedEvent i
keyedEvent eid ev = KeyedEvent { eventId: eid, event: ev }
eventId :: forall i. KeyedEvent i -> String
eventId (KeyedEvent xs) = xs.eventId
event :: forall i. KeyedEvent i -> Event i
event (KeyedEvent xs) = xs.event
derive instance keyedEventFunctor :: Functor KeyedEvent
instance keyedEventFoldable :: Foldable KeyedEvent where
foldr f b = foldr f b <<< event
foldl f b = foldl f b <<< event
foldMap = foldMapDefaultR
instance keyedEventTraversable :: Traversable KeyedEvent where
traverse f (KeyedEvent xs) = (\ev -> KeyedEvent { eventId: xs.eventId, event: ev }) <$> traverse f xs.event
sequence = traverse identity
instance keyedEventDecodeJson :: DecodeJson (KeyedEvent String) where
decodeJson json = do
obj <- decodeJson json
keyedEvent <$> obj .: "eventId" <*> parseEventFields obj
newtype Interval i
= Interval
{ start :: i
, end :: i
}
derive instance intervalEq :: (Eq i) => Eq (Interval i)
derive instance intervalNewtype :: Newtype (Interval i) _
instance showInterval :: Show i => Show (Interval i) where
show (Interval i) = "Interval {start: " <> show i.start <> ", end: " <> show i.end <> "}"
type TimeInterval
= Interval Instant
derive instance intervalFunctor :: Functor Interval
instance intervalFoldable :: Foldable Interval where
foldr f b (Interval i) = f i.start (f i.end b)
foldl f b (Interval i) = f (f b i.start) i.end
foldMap = foldMapDefaultR
instance intervalTraversable :: Traversable Interval where
traverse f (Interval i) = interval <$> f i.start <*> f i.end
sequence = traverse identity
instance intervalDecodeJSON :: DecodeJson i => DecodeJson (Interval i) where
decodeJson json = do
obj <- decodeJson json
interval <$> obj .: "start" <*> obj .: "end"
interval :: forall i. i -> i -> Interval i
interval s e = Interval { start: s, end: e }
start :: forall i. Interval i -> i
start (Interval i) = i.start
end :: forall i. Interval i -> i
end (Interval i) = i.end
data TimeSpan' t
= Before t
| During (Interval t)
| After t
type TimeSpan
= TimeSpan' DateTime
derive instance timeSpanFunctor :: Functor TimeSpan'
instance timeSpanFoldable :: Foldable TimeSpan' where
foldr f b = case _ of
Before a -> f a b
During x -> foldr f b x
After a -> f a b
foldl f b = case _ of
Before a -> f b a
During x -> foldl f b x
After a -> f b a
foldMap = foldMapDefaultR
instance timeSpanTraversable :: Traversable TimeSpan' where
traverse f = case _ of
Before a -> Before <$> f a
During x -> During <$> traverse f x
After a -> After <$> f a
sequence = traverse identity
apiLogStart :: ProjectId -> Aff (Either TimelineError (KeyedEvent Instant))
apiLogStart (ProjectId pid) = do
let
requestBody = Just <<< RB.Json <<< encodeJson $ { schemaVersion: "2.0" }
response <- post RF.json ("/api/user/projects/" <> UUID.toString pid <> "/logStart") requestBody
liftEffect <<< runExceptT
$ do
kev <- withExceptT LogFailure $ parseDatedResponse decodeJson response
case event kev of
StartEvent _ -> pure kev
StopEvent _ -> throwError <<< Unexpected $ "Expected start event, got stop."
apiLogEnd :: ProjectId -> Aff (Either TimelineError (KeyedEvent Instant))
apiLogEnd (ProjectId pid) = do
let
requestBody = Just <<< RB.Json <<< encodeJson $ { schemaVersion: "2.0" }
response <- post RF.json ("/api/user/projects/" <> UUID.toString pid <> "/logEnd") requestBody
liftEffect <<< runExceptT
$ do
kev <- withExceptT LogFailure $ parseDatedResponse decodeJson response
case event kev of
StartEvent _ -> throwError <<< Unexpected $ "Expected stop event, got start."
StopEvent _ -> pure kev
newtype ListIntervalsResponse a
= ListIntervalsResponse
{ workIndex :: Array ({ intervals :: Array a })
}
derive instance listIntervalsResponseNewtype :: Newtype (ListIntervalsResponse a) _
derive instance listIntervalsResponseFunctor :: Functor ListIntervalsResponse
instance listIntervalsResponseFoldable :: Foldable ListIntervalsResponse where
foldr f b (ListIntervalsResponse r) = foldr f b (r.workIndex >>= _.intervals)
foldl f b (ListIntervalsResponse r) = foldl f b (r.workIndex >>= _.intervals)
foldMap = foldMapDefaultR
instance listIntervalsResponseTraversable :: Traversable ListIntervalsResponse where
traverse f (ListIntervalsResponse r) =
let
traverseCreditRow r' = ({ intervals: _ }) <$> traverse f r'.intervals
in
(ListIntervalsResponse <<< ({ workIndex: _ })) <$> traverse traverseCreditRow r.workIndex
sequence = traverse identity
instance listIntervalsResponseDecodeJson :: DecodeJson a => DecodeJson (ListIntervalsResponse a) where
decodeJson = map ListIntervalsResponse <<< decodeJson
apiListIntervals :: ProjectId -> TimeSpan -> Aff (Either TimelineError (Array (Interval (KeyedEvent Instant))))
apiListIntervals pid ts = do
ts' <- liftEffect $ traverse (JD.toISOString <<< JD.fromDateTime) ts
let
queryElements = case ts' of
Before t -> [ "before=" <> t, "limit=100" ]
During (Interval x) -> [ "after=" <> x.start, "before=" <> x.end, "limit=100" ]
After t -> [ "after=" <> t, "limit=100" ]
response <- get RF.json ("/api/user/projects/" <> pidStr pid <> "/workIndex?" <> intercalate "&" queryElements)
liftEffect
<<< runExceptT
<<< map (\(ListIntervalsResponse r) -> r.workIndex >>= (_.intervals))
<<< map (map decompose <<< decompose)
<<< withExceptT LogFailure
$ parseDatedResponse decodeJson response
apiLatestEvent :: ProjectId -> Aff (Either TimelineError (Maybe (KeyedEvent Instant)))
apiLatestEvent pid = do
response <- get RF.json ("/api/user/projects/" <> pidStr pid <> "/events")
liftEffect
<<< runExceptT
<<< map head
<<< map decompose
<<< withExceptT LogFailure
$ parseDatedResponse decodeJson response