asana-1.0.1.1: library/Asana/Api/Task.hs
module Asana.Api.Task
( Task (..),
Membership (..),
TaskStatusFilter (..),
ResourceSubtype (..),
PostTask (..),
getTask,
getProjectTasks,
getProjectTasksCompletedSince,
postTask,
addTag,
putCompleted,
taskUrl,
extractNumberField,
extractEnumField,
)
where
import Asana.Api.CustomField
import Asana.Api.Gid
import Asana.Api.Named
import Asana.Api.Prelude
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
{ mProject :: Named,
mSection :: Maybe Named
}
deriving stock (Eq, Generic, Show)
instance FromJSON Membership where
parseJSON = genericParseJSON $ aesonPrefix snakeCase
data ResourceSubtype = DefaultTask | Milestone | Section
deriving stock (Eq, Generic, Show)
instance FromJSON ResourceSubtype where
parseJSON =
genericParseJSON $ defaultOptions {constructorTagModifier = snakeCase}
data Task = Task
{ tAssignee :: Maybe Named,
tName :: Text,
tCompleted :: Bool,
tCompletedAt :: Maybe UTCTime,
tCreatedAt :: UTCTime,
tCustomFields :: CustomFields,
tMemberships :: [Membership],
tGid :: Gid,
tResourceSubtype :: ResourceSubtype,
tNotes :: Text,
tProjects :: [AsanaReference],
tTags :: [Tag]
}
deriving stock (Eq, Generic, Show)
instance FromJSON Task where
parseJSON = genericParseJSON $ aesonPrefix snakeCase
-- | Return all details for a task by id
getTask ::
(MonadUnliftIO m, MonadLogger m, MonadReader env m, HasAsanaAccessKey env) =>
Gid ->
m Task
getTask taskId = getSingle $ "/tasks/" <> T.unpack (gidToText taskId)
data PostTask = PostTask
{ ptProjects :: [Gid],
ptCustomFields :: HashMap Gid Text,
ptName :: Text,
ptNotes :: Text,
ptParent :: Maybe Gid
}
deriving stock (Generic)
instance FromJSON PostTask where
parseJSON = genericParseJSON $ aesonPrefix snakeCase
instance ToJSON PostTask where
toJSON = genericToJSON $ aesonPrefix snakeCase
toEncoding = genericToEncoding $ aesonPrefix snakeCase
-- | Create a new 'Task'
postTask ::
(MonadUnliftIO m, MonadLogger m, MonadReader env m, HasAsanaAccessKey env) =>
PostTask ->
m (Result Task)
postTask body = fmap adData . fromJSON <$> post "/tasks" (ApiData 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 projectId taskStatusFilter = do
now <- liftIO getCurrentTime
getAllParams
(T.unpack $ "/projects/" <> gidToText projectId <> "/tasks")
(completedSince now)
where
completedSince now = case taskStatusFilter of
AllTasks -> []
IncompletedTasks -> [("completed_since", formatISO8601 now)]
data TaskStatusFilter = IncompletedTasks | AllTasks
getProjectTasksCompletedSince ::
(MonadUnliftIO m, MonadLogger m, MonadReader env m, HasAsanaAccessKey env) =>
Gid ->
UTCTime ->
m [Named]
getProjectTasksCompletedSince projectId since =
getAllParams
(T.unpack $ "/projects/" <> gidToText projectId <> "/tasks")
[("completed_since", formatISO8601 since)]
addTag ::
(MonadUnliftIO m, MonadLogger m, MonadReader env m, HasAsanaAccessKey env) =>
Gid ->
-- | Tag
Gid ->
m ()
addTag task tag =
void $
post ("/tasks/" <> T.unpack (gidToText task) <> "/addTag") $
ApiData
(object ["tag" .= tag])
putCompleted ::
(MonadUnliftIO m, MonadLogger m, MonadReader env m, HasAsanaAccessKey env) =>
Gid ->
Bool ->
m ()
putCompleted taskId completed =
void $
put ("/tasks/" <> T.unpack (gidToText taskId)) $
ApiData
(object ["completed" .= completed])
taskUrl :: Task -> Text
taskUrl Task {..} = "https://app.asana.com/0/0/" <> gidToText tGid <> "/f"
extractNumberField :: Text -> Task -> Maybe CustomField
extractNumberField fieldName Task {..} =
listToMaybe $ flip mapMaybe (getCustomFields tCustomFields) $ \case
customField@(CustomNumber _ t _) -> customField <$ guard (t == fieldName)
_ -> Nothing
extractEnumField :: Text -> Task -> Maybe CustomField
extractEnumField fieldName Task {..} =
listToMaybe $ flip mapMaybe (getCustomFields tCustomFields) $ \case
customField@(CustomEnum _ t _ _) ->
if t == fieldName then Just customField else Nothing
_ -> Nothing