module Asana.Api.Task
  ( Task(..)
  , Membership(..)
  , TaskStatusFilter(..)
  , ResourceSubtype(..)
  , PostTask(..)
  , getTask
  , getProjectTasks
  , getProjectTasksCompletedSince
  , postTask
  , addTag
  , putCompleted
  , taskUrl
  , extractNumberField
  , extractEnumField
  ) where

import Asana.Api.Prelude

import Asana.Api.CustomField
import Asana.Api.Gid
import Asana.Api.Named
import Asana.Api.Request
import Asana.Api.Tag
import Data.Aeson
import Data.Aeson.Casing (aesonPrefix, snakeCase)
import Data.HashMap.Strict (HashMap)
import qualified Data.Text as T
import Data.Time (UTCTime, getCurrentTime)
import Data.Time.ISO8601 (formatISO8601)

data Membership = Membership
  { Membership -> Named
mProject :: Named
  , Membership -> Maybe Named
mSection :: Maybe Named
  }
  deriving stock (Membership -> Membership -> Bool
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: Membership -> Membership -> Bool
$c/= :: Membership -> Membership -> Bool
== :: Membership -> Membership -> Bool
$c== :: Membership -> Membership -> Bool
Eq, forall x. Rep Membership x -> Membership
forall x. Membership -> Rep Membership x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
$cto :: forall x. Rep Membership x -> Membership
$cfrom :: forall x. Membership -> Rep Membership x
Generic, Int -> Membership -> ShowS
[Membership] -> ShowS
Membership -> String
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [Membership] -> ShowS
$cshowList :: [Membership] -> ShowS
show :: Membership -> String
$cshow :: Membership -> String
showsPrec :: Int -> Membership -> ShowS
$cshowsPrec :: Int -> Membership -> ShowS
Show)

instance FromJSON Membership where
  parseJSON :: Value -> Parser Membership
parseJSON = forall a.
(Generic a, GFromJSON Zero (Rep a)) =>
Options -> Value -> Parser a
genericParseJSON forall a b. (a -> b) -> a -> b
$ ShowS -> Options
aesonPrefix ShowS
snakeCase

data ResourceSubtype = DefaultTask | Milestone | Section
  deriving stock (ResourceSubtype -> ResourceSubtype -> Bool
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: ResourceSubtype -> ResourceSubtype -> Bool
$c/= :: ResourceSubtype -> ResourceSubtype -> Bool
== :: ResourceSubtype -> ResourceSubtype -> Bool
$c== :: ResourceSubtype -> ResourceSubtype -> Bool
Eq, forall x. Rep ResourceSubtype x -> ResourceSubtype
forall x. ResourceSubtype -> Rep ResourceSubtype x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
$cto :: forall x. Rep ResourceSubtype x -> ResourceSubtype
$cfrom :: forall x. ResourceSubtype -> Rep ResourceSubtype x
Generic, Int -> ResourceSubtype -> ShowS
[ResourceSubtype] -> ShowS
ResourceSubtype -> String
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [ResourceSubtype] -> ShowS
$cshowList :: [ResourceSubtype] -> ShowS
show :: ResourceSubtype -> String
$cshow :: ResourceSubtype -> String
showsPrec :: Int -> ResourceSubtype -> ShowS
$cshowsPrec :: Int -> ResourceSubtype -> ShowS
Show)

instance FromJSON ResourceSubtype where
  parseJSON :: Value -> Parser ResourceSubtype
parseJSON =
    forall a.
(Generic a, GFromJSON Zero (Rep a)) =>
Options -> Value -> Parser a
genericParseJSON forall a b. (a -> b) -> a -> b
$ Options
defaultOptions { constructorTagModifier :: ShowS
constructorTagModifier = ShowS
snakeCase }

data Task = Task
  { Task -> Maybe Named
tAssignee :: Maybe Named
  , Task -> Text
tName :: Text
  , Task -> Bool
tCompleted :: Bool
  , Task -> Maybe UTCTime
tCompletedAt :: Maybe UTCTime
  , Task -> UTCTime
tCreatedAt :: UTCTime
  , Task -> CustomFields
tCustomFields :: CustomFields
  , Task -> [Membership]
tMemberships :: [Membership]
  , Task -> Gid
tGid :: Gid
  , Task -> ResourceSubtype
tResourceSubtype :: ResourceSubtype
  , Task -> Text
tNotes :: Text
  , Task -> [AsanaReference]
tProjects :: [AsanaReference]
  , Task -> [Tag]
tTags :: [Tag]
  }
  deriving stock (Task -> Task -> Bool
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: Task -> Task -> Bool
$c/= :: Task -> Task -> Bool
== :: Task -> Task -> Bool
$c== :: Task -> Task -> Bool
Eq, forall x. Rep Task x -> Task
forall x. Task -> Rep Task x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
$cto :: forall x. Rep Task x -> Task
$cfrom :: forall x. Task -> Rep Task x
Generic, Int -> Task -> ShowS
[Task] -> ShowS
Task -> String
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [Task] -> ShowS
$cshowList :: [Task] -> ShowS
show :: Task -> String
$cshow :: Task -> String
showsPrec :: Int -> Task -> ShowS
$cshowsPrec :: Int -> Task -> ShowS
Show)

instance FromJSON Task where
  parseJSON :: Value -> Parser Task
parseJSON = forall a.
(Generic a, GFromJSON Zero (Rep a)) =>
Options -> Value -> Parser a
genericParseJSON forall a b. (a -> b) -> a -> b
$ ShowS -> Options
aesonPrefix ShowS
snakeCase

-- | Return all details for a task by id
getTask
  :: (MonadUnliftIO m, MonadLogger m, MonadReader env m, HasAsanaAccessKey env)
  => Gid
  -> m Task
getTask :: forall (m :: * -> *) env.
(MonadUnliftIO m, MonadLogger m, MonadReader env m,
 HasAsanaAccessKey env) =>
Gid -> m Task
getTask Gid
taskId = forall (m :: * -> *) env a.
(MonadUnliftIO m, MonadLogger m, MonadReader env m,
 HasAsanaAccessKey env, FromJSON a) =>
String -> m a
getSingle forall a b. (a -> b) -> a -> b
$ String
"/tasks/" forall a. Semigroup a => a -> a -> a
<> Text -> String
T.unpack (Gid -> Text
gidToText Gid
taskId)

data PostTask = PostTask
  { PostTask -> [Gid]
ptProjects :: [Gid]
  , PostTask -> HashMap Gid Text
ptCustomFields :: HashMap Gid Text
  , PostTask -> Text
ptName :: Text
  , PostTask -> Text
ptNotes :: Text
  , PostTask -> Maybe Gid
ptParent :: Maybe Gid
  }
  deriving stock forall x. Rep PostTask x -> PostTask
forall x. PostTask -> Rep PostTask x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
$cto :: forall x. Rep PostTask x -> PostTask
$cfrom :: forall x. PostTask -> Rep PostTask x
Generic

instance FromJSON PostTask where
  parseJSON :: Value -> Parser PostTask
parseJSON = forall a.
(Generic a, GFromJSON Zero (Rep a)) =>
Options -> Value -> Parser a
genericParseJSON forall a b. (a -> b) -> a -> b
$ ShowS -> Options
aesonPrefix ShowS
snakeCase

instance ToJSON PostTask where
  toJSON :: PostTask -> Value
toJSON = forall a.
(Generic a, GToJSON' Value Zero (Rep a)) =>
Options -> a -> Value
genericToJSON forall a b. (a -> b) -> a -> b
$ ShowS -> Options
aesonPrefix ShowS
snakeCase
  toEncoding :: PostTask -> Encoding
toEncoding = forall a.
(Generic a, GToJSON' Encoding Zero (Rep a)) =>
Options -> a -> Encoding
genericToEncoding forall a b. (a -> b) -> a -> b
$ ShowS -> Options
aesonPrefix ShowS
snakeCase

-- | Create a new 'Task'
postTask
  :: (MonadUnliftIO m, MonadLogger m, MonadReader env m, HasAsanaAccessKey env)
  => PostTask
  -> m (Result Task)
postTask :: forall (m :: * -> *) env.
(MonadUnliftIO m, MonadLogger m, MonadReader env m,
 HasAsanaAccessKey env) =>
PostTask -> m (Result Task)
postTask PostTask
body = forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap forall a. ApiData a -> a
adData forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. FromJSON a => Value -> Result a
fromJSON forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> forall (m :: * -> *) env a.
(MonadUnliftIO m, MonadLogger m, MonadReader env m,
 HasAsanaAccessKey env, ToJSON a) =>
String -> a -> m Value
post String
"/tasks" (forall a. a -> ApiData a
ApiData PostTask
body)

-- | Return compact task details for a project
--
-- Iterating ourselves and returning @['Task']@ is a better interface but
-- precludes us logging things each time we request an element. So we return
-- @'Named'@ for now and let the caller use @'getTask'@ themselves.
--
getProjectTasks
  :: (MonadUnliftIO m, MonadLogger m, MonadReader env m, HasAsanaAccessKey env)
  => Gid
  -> TaskStatusFilter
  -> m [Named]
getProjectTasks :: forall (m :: * -> *) env.
(MonadUnliftIO m, MonadLogger m, MonadReader env m,
 HasAsanaAccessKey env) =>
Gid -> TaskStatusFilter -> m [Named]
getProjectTasks Gid
projectId TaskStatusFilter
taskStatusFilter = do
  UTCTime
now <- forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO IO UTCTime
getCurrentTime
  forall (m :: * -> *) env a.
(MonadUnliftIO m, MonadLogger m, MonadReader env m,
 HasAsanaAccessKey env, FromJSON a) =>
String -> [(String, String)] -> m [a]
getAllParams
    (Text -> String
T.unpack forall a b. (a -> b) -> a -> b
$ Text
"/projects/" forall a. Semigroup a => a -> a -> a
<> Gid -> Text
gidToText Gid
projectId forall a. Semigroup a => a -> a -> a
<> Text
"/tasks")
    (UTCTime -> [(String, String)]
completedSince UTCTime
now)

 where
  completedSince :: UTCTime -> [(String, String)]
completedSince UTCTime
now = case TaskStatusFilter
taskStatusFilter of
    TaskStatusFilter
AllTasks -> []
    TaskStatusFilter
IncompletedTasks -> [(String
"completed_since", UTCTime -> String
formatISO8601 UTCTime
now)]

data TaskStatusFilter = IncompletedTasks | AllTasks

getProjectTasksCompletedSince
  :: (MonadUnliftIO m, MonadLogger m, MonadReader env m, HasAsanaAccessKey env)
  => Gid
  -> UTCTime
  -> m [Named]
getProjectTasksCompletedSince :: forall (m :: * -> *) env.
(MonadUnliftIO m, MonadLogger m, MonadReader env m,
 HasAsanaAccessKey env) =>
Gid -> UTCTime -> m [Named]
getProjectTasksCompletedSince Gid
projectId UTCTime
since = forall (m :: * -> *) env a.
(MonadUnliftIO m, MonadLogger m, MonadReader env m,
 HasAsanaAccessKey env, FromJSON a) =>
String -> [(String, String)] -> m [a]
getAllParams
  (Text -> String
T.unpack forall a b. (a -> b) -> a -> b
$ Text
"/projects/" forall a. Semigroup a => a -> a -> a
<> Gid -> Text
gidToText Gid
projectId forall a. Semigroup a => a -> a -> a
<> Text
"/tasks")
  [(String
"completed_since", UTCTime -> String
formatISO8601 UTCTime
since)]

addTag
  :: (MonadUnliftIO m, MonadLogger m, MonadReader env m, HasAsanaAccessKey env)
  => Gid
  -> Gid -- ^ Tag
  -> m ()
addTag :: forall (m :: * -> *) env.
(MonadUnliftIO m, MonadLogger m, MonadReader env m,
 HasAsanaAccessKey env) =>
Gid -> Gid -> m ()
addTag Gid
task Gid
tag =
  forall (f :: * -> *) a. Functor f => f a -> f ()
void forall a b. (a -> b) -> a -> b
$ forall (m :: * -> *) env a.
(MonadUnliftIO m, MonadLogger m, MonadReader env m,
 HasAsanaAccessKey env, ToJSON a) =>
String -> a -> m Value
post (String
"/tasks/" forall a. Semigroup a => a -> a -> a
<> Text -> String
T.unpack (Gid -> Text
gidToText Gid
task) forall a. Semigroup a => a -> a -> a
<> String
"/addTag") forall a b. (a -> b) -> a -> b
$ forall a. a -> ApiData a
ApiData
    ([Pair] -> Value
object [Key
"tag" forall kv v. (KeyValue kv, ToJSON v) => Key -> v -> kv
.= Gid
tag])

putCompleted
  :: (MonadUnliftIO m, MonadLogger m, MonadReader env m, HasAsanaAccessKey env)
  => Gid
  -> Bool
  -> m ()
putCompleted :: forall (m :: * -> *) env.
(MonadUnliftIO m, MonadLogger m, MonadReader env m,
 HasAsanaAccessKey env) =>
Gid -> Bool -> m ()
putCompleted Gid
taskId Bool
completed =
  forall (f :: * -> *) a. Functor f => f a -> f ()
void forall a b. (a -> b) -> a -> b
$ forall (m :: * -> *) env a.
(MonadUnliftIO m, MonadLogger m, MonadReader env m,
 HasAsanaAccessKey env, ToJSON a) =>
String -> a -> m Value
put (String
"/tasks/" forall a. Semigroup a => a -> a -> a
<> Text -> String
T.unpack (Gid -> Text
gidToText Gid
taskId)) forall a b. (a -> b) -> a -> b
$ forall a. a -> ApiData a
ApiData
    ([Pair] -> Value
object [Key
"completed" forall kv v. (KeyValue kv, ToJSON v) => Key -> v -> kv
.= Bool
completed])

taskUrl :: Task -> Text
taskUrl :: Task -> Text
taskUrl Task {Bool
[AsanaReference]
[Tag]
[Membership]
Maybe UTCTime
Maybe Named
UTCTime
Text
Gid
CustomFields
ResourceSubtype
tTags :: [Tag]
tProjects :: [AsanaReference]
tNotes :: Text
tResourceSubtype :: ResourceSubtype
tGid :: Gid
tMemberships :: [Membership]
tCustomFields :: CustomFields
tCreatedAt :: UTCTime
tCompletedAt :: Maybe UTCTime
tCompleted :: Bool
tName :: Text
tAssignee :: Maybe Named
tTags :: Task -> [Tag]
tProjects :: Task -> [AsanaReference]
tNotes :: Task -> Text
tResourceSubtype :: Task -> ResourceSubtype
tGid :: Task -> Gid
tMemberships :: Task -> [Membership]
tCustomFields :: Task -> CustomFields
tCreatedAt :: Task -> UTCTime
tCompletedAt :: Task -> Maybe UTCTime
tCompleted :: Task -> Bool
tName :: Task -> Text
tAssignee :: Task -> Maybe Named
..} = Text
"https://app.asana.com/0/0/" forall a. Semigroup a => a -> a -> a
<> Gid -> Text
gidToText Gid
tGid forall a. Semigroup a => a -> a -> a
<> Text
"/f"

extractNumberField :: Text -> Task -> Maybe CustomField
extractNumberField :: Text -> Task -> Maybe CustomField
extractNumberField Text
fieldName Task {Bool
[AsanaReference]
[Tag]
[Membership]
Maybe UTCTime
Maybe Named
UTCTime
Text
Gid
CustomFields
ResourceSubtype
tTags :: [Tag]
tProjects :: [AsanaReference]
tNotes :: Text
tResourceSubtype :: ResourceSubtype
tGid :: Gid
tMemberships :: [Membership]
tCustomFields :: CustomFields
tCreatedAt :: UTCTime
tCompletedAt :: Maybe UTCTime
tCompleted :: Bool
tName :: Text
tAssignee :: Maybe Named
tTags :: Task -> [Tag]
tProjects :: Task -> [AsanaReference]
tNotes :: Task -> Text
tResourceSubtype :: Task -> ResourceSubtype
tGid :: Task -> Gid
tMemberships :: Task -> [Membership]
tCustomFields :: Task -> CustomFields
tCreatedAt :: Task -> UTCTime
tCompletedAt :: Task -> Maybe UTCTime
tCompleted :: Task -> Bool
tName :: Task -> Text
tAssignee :: Task -> Maybe Named
..} =
  forall a. [a] -> Maybe a
listToMaybe forall a b. (a -> b) -> a -> b
$ forall a b c. (a -> b -> c) -> b -> a -> c
flip forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe (CustomFields -> [CustomField]
getCustomFields CustomFields
tCustomFields) forall a b. (a -> b) -> a -> b
$ \case
    customField :: CustomField
customField@(CustomNumber Gid
_ Text
t Maybe Scientific
_) -> CustomField
customField forall (f :: * -> *) a b. Functor f => a -> f b -> f a
<$ forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Text
t forall a. Eq a => a -> a -> Bool
== Text
fieldName)
    CustomField
_ -> forall a. Maybe a
Nothing

extractEnumField :: Text -> Task -> Maybe CustomField
extractEnumField :: Text -> Task -> Maybe CustomField
extractEnumField Text
fieldName Task {Bool
[AsanaReference]
[Tag]
[Membership]
Maybe UTCTime
Maybe Named
UTCTime
Text
Gid
CustomFields
ResourceSubtype
tTags :: [Tag]
tProjects :: [AsanaReference]
tNotes :: Text
tResourceSubtype :: ResourceSubtype
tGid :: Gid
tMemberships :: [Membership]
tCustomFields :: CustomFields
tCreatedAt :: UTCTime
tCompletedAt :: Maybe UTCTime
tCompleted :: Bool
tName :: Text
tAssignee :: Maybe Named
tTags :: Task -> [Tag]
tProjects :: Task -> [AsanaReference]
tNotes :: Task -> Text
tResourceSubtype :: Task -> ResourceSubtype
tGid :: Task -> Gid
tMemberships :: Task -> [Membership]
tCustomFields :: Task -> CustomFields
tCreatedAt :: Task -> UTCTime
tCompletedAt :: Task -> Maybe UTCTime
tCompleted :: Task -> Bool
tName :: Task -> Text
tAssignee :: Task -> Maybe Named
..} =
  forall a. [a] -> Maybe a
listToMaybe forall a b. (a -> b) -> a -> b
$ forall a b c. (a -> b -> c) -> b -> a -> c
flip forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe (CustomFields -> [CustomField]
getCustomFields CustomFields
tCustomFields) forall a b. (a -> b) -> a -> b
$ \case
    customField :: CustomField
customField@(CustomEnum Gid
_ Text
t [EnumOption]
_ Maybe Text
_) ->
      if Text
t forall a. Eq a => a -> a -> Bool
== Text
fieldName then forall a. a -> Maybe a
Just CustomField
customField else forall a. Maybe a
Nothing
    CustomField
_ -> forall a. Maybe a
Nothing