graphql-engine/server/src-lib/Hasura/RQL/DDL/ScheduledTrigger.hs
Antoine Leblanc e99f9a2f57 Remove MetadataStorageT, clean up error handling.
## Description

This PR removes `MetadataStorageT`, and cleans up all top-level error handling. In short: this PR changes `MonadMetadataStorage` to explicitly return a bunch of `Either QErr a`, instead of relying on the stack providing a `MonadError QErr`. Since we implement that class on the base monad *below any ExceptT*, this removes a lot of very complicated instances that make assumptions about the shape of the stack.

On the back of this, we can remove several layers of ExceptT from the core of the code, including the one in `RunT`, which allows us to remove several instances of `liftEitherM . runExceptT`.

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/7689
GitOrigin-RevId: 97d600154d690f58c0b93fb4cc2d30fd383fd8b8
2023-02-03 01:05:09 +00:00

229 lines
7.0 KiB
Haskell

module Hasura.RQL.DDL.ScheduledTrigger
( runCreateCronTrigger,
runDeleteCronTrigger,
dropCronTriggerInMetadata,
resolveCronTrigger,
runCreateScheduledEvent,
runDeleteScheduledEvent,
runGetScheduledEvents,
runGetScheduledEventInvocations,
populateInitialCronTriggerEvents,
runGetCronTriggers,
)
where
import Data.Aeson qualified as J
import Data.Environment qualified as Env
import Data.HashMap.Strict qualified as Map
import Data.HashMap.Strict.InsOrd qualified as OMap
import Data.Time.Clock qualified as C
import Data.URL.Template (printURLTemplate)
import Hasura.Base.Error
import Hasura.EncJSON
import Hasura.Eventing.ScheduledTrigger
import Hasura.Metadata.Class
import Hasura.Prelude
import Hasura.RQL.DDL.EventTrigger (getHeaderInfosFromConf)
import Hasura.RQL.Types.Common
import Hasura.RQL.Types.EventTrigger
import Hasura.RQL.Types.Metadata
import Hasura.RQL.Types.Metadata.Object
import Hasura.RQL.Types.ScheduledTrigger
import Hasura.RQL.Types.SchemaCache
import Hasura.RQL.Types.SchemaCache.Build
import System.Cron.Types (CronSchedule)
populateInitialCronTriggerEvents ::
( MonadIO m,
MonadError QErr m,
MonadMetadataStorageQueryAPI m
) =>
CronSchedule ->
TriggerName ->
m ()
populateInitialCronTriggerEvents schedule triggerName = do
currentTime <- liftIO C.getCurrentTime
let scheduleTimes = generateScheduleTimes currentTime 100 schedule
liftEitherM $ insertCronEvents $ map (CronEventSeed triggerName) scheduleTimes
-- | runCreateCronTrigger will update a existing cron trigger when the 'replace'
-- value is set to @true@ and when replace is @false@ a new cron trigger will
-- be created
runCreateCronTrigger ::
( MonadError QErr m,
CacheRWM m,
MonadIO m,
MetadataM m,
MonadMetadataStorageQueryAPI m
) =>
CreateCronTrigger ->
m EncJSON
runCreateCronTrigger CreateCronTrigger {..} = do
let q =
CronTriggerMetadata
_cctName
_cctWebhook
_cctCronSchedule
_cctPayload
_cctRetryConf
_cctHeaders
_cctIncludeInMetadata
_cctComment
_cctRequestTransform
_cctResponseTransform
case _cctReplace of
True -> updateCronTrigger q
False -> do
cronTriggersMap <- scCronTriggers <$> askSchemaCache
case Map.lookup (ctName q) cronTriggersMap of
Nothing -> pure ()
Just _ ->
throw400 AlreadyExists $
"cron trigger with name: "
<> triggerNameToTxt (ctName q)
<> " already exists"
let metadataObj = MOCronTrigger _cctName
metadata =
CronTriggerMetadata
_cctName
_cctWebhook
_cctCronSchedule
_cctPayload
_cctRetryConf
_cctHeaders
_cctIncludeInMetadata
_cctComment
_cctRequestTransform
_cctResponseTransform
buildSchemaCacheFor metadataObj $
MetadataModifier $
metaCronTriggers %~ OMap.insert _cctName metadata
populateInitialCronTriggerEvents _cctCronSchedule _cctName
return successMsg
resolveCronTrigger ::
(QErrM m) =>
Env.Environment ->
CronTriggerMetadata ->
m CronTriggerInfo
resolveCronTrigger env CronTriggerMetadata {..} = do
webhookInfo <- resolveWebhook env ctWebhook
headerInfo <- getHeaderInfosFromConf env ctHeaders
let urlTemplate = printURLTemplate $ unInputWebhook ctWebhook
pure $
CronTriggerInfo
ctName
ctSchedule
ctPayload
ctRetryConf
(EnvRecord urlTemplate webhookInfo)
headerInfo
ctComment
ctRequestTransform
ctResponseTransform
updateCronTrigger ::
( MonadError QErr m,
CacheRWM m,
MonadIO m,
MetadataM m,
MonadMetadataStorageQueryAPI m
) =>
CronTriggerMetadata ->
m EncJSON
updateCronTrigger cronTriggerMetadata = do
let triggerName = ctName cronTriggerMetadata
checkExists triggerName
buildSchemaCacheFor (MOCronTrigger triggerName) $
MetadataModifier $
metaCronTriggers %~ OMap.insert triggerName cronTriggerMetadata
liftEitherM $ dropFutureCronEvents $ SingleCronTrigger triggerName
currentTime <- liftIO C.getCurrentTime
let scheduleTimes = generateScheduleTimes currentTime 100 $ ctSchedule cronTriggerMetadata
liftEitherM $ insertCronEvents $ map (CronEventSeed triggerName) scheduleTimes
pure successMsg
runDeleteCronTrigger ::
( MonadError QErr m,
CacheRWM m,
MetadataM m,
MonadMetadataStorageQueryAPI m
) =>
ScheduledTriggerName ->
m EncJSON
runDeleteCronTrigger (ScheduledTriggerName stName) = do
checkExists stName
withNewInconsistentObjsCheck $
buildSchemaCache $
dropCronTriggerInMetadata stName
liftEitherM $ dropFutureCronEvents $ SingleCronTrigger stName
return successMsg
dropCronTriggerInMetadata :: TriggerName -> MetadataModifier
dropCronTriggerInMetadata name =
MetadataModifier $ metaCronTriggers %~ OMap.delete name
runCreateScheduledEvent ::
(MonadError QErr m, MonadMetadataStorageQueryAPI m) =>
CreateScheduledEvent ->
m EncJSON
runCreateScheduledEvent scheduledEvent = do
eid <- liftEitherM $ createOneOffScheduledEvent scheduledEvent
pure $ encJFromJValue $ J.object ["message" J..= J.String "success", "event_id" J..= eid]
checkExists :: (CacheRM m, MonadError QErr m) => TriggerName -> m ()
checkExists name = do
cronTriggersMap <- scCronTriggers <$> askSchemaCache
void $
onNothing (Map.lookup name cronTriggersMap) $
throw400 NotExists $
"cron trigger with name: " <> triggerNameToTxt name <> " does not exist"
runDeleteScheduledEvent ::
(MonadMetadataStorageQueryAPI m, MonadError QErr m) => DeleteScheduledEvent -> m EncJSON
runDeleteScheduledEvent DeleteScheduledEvent {..} = do
liftEitherM $ dropEvent _dseEventId _dseType
pure successMsg
runGetScheduledEvents ::
( MonadError QErr m,
CacheRM m,
MonadMetadataStorageQueryAPI m
) =>
GetScheduledEvents ->
m EncJSON
runGetScheduledEvents gse = do
case _gseScheduledEvent gse of
SEOneOff -> pure ()
SECron name -> checkExists name
encJFromJValue <$> liftEitherM (fetchScheduledEvents gse)
runGetScheduledEventInvocations ::
( MonadError QErr m,
CacheRM m,
MonadMetadataStorageQueryAPI m
) =>
GetScheduledEventInvocations ->
m EncJSON
runGetScheduledEventInvocations getEventInvocations@GetScheduledEventInvocations {..} = do
case _geiInvocationsBy of
GIBEventId _ _ -> pure ()
GIBEvent event -> case event of
SEOneOff -> pure ()
SECron name -> checkExists name
WithOptionalTotalCount countMaybe invocations <- liftEitherM $ fetchScheduledEventInvocations getEventInvocations
pure $
encJFromJValue $
J.object $
("invocations" J..= invocations) : (maybe mempty (\count -> ["count" J..= count]) countMaybe)
-- | Metadata API handler to retrieve all the cron triggers from the metadata
runGetCronTriggers :: MetadataM m => m EncJSON
runGetCronTriggers = do
cronTriggers <- toList . _metaCronTriggers <$> getMetadata
pure $
encJFromJValue $
J.object
["cron_triggers" J..= cronTriggers]