From 63bd6409e642b969c6b42687c09ee95bbd5ff84f Mon Sep 17 00:00:00 2001 From: Karthikeyan Chinnakonda Date: Tue, 25 Feb 2020 17:43:48 +0530 Subject: [PATCH 01/12] add a new column utc_offset in the hdb_scheduled_trigger table --- server/src-lib/Hasura/RQL/DDL/Metadata.hs | 7 ++++--- server/src-lib/Hasura/RQL/DDL/ScheduledTrigger.hs | 6 +++--- server/src-lib/Hasura/RQL/DDL/Schema/Cache.hs | 1 + server/src-lib/Hasura/RQL/Types/Catalog.hs | 1 + server/src-lib/Hasura/RQL/Types/ScheduledTrigger.hs | 11 +++++++++++ server/src-rsr/catalog_metadata.sql | 3 ++- server/src-rsr/initialise.sql | 1 + server/src-rsr/migrations/32_to_33.sql | 1 + 8 files changed, 24 insertions(+), 7 deletions(-) diff --git a/server/src-lib/Hasura/RQL/DDL/Metadata.hs b/server/src-lib/Hasura/RQL/DDL/Metadata.hs index 0997d72f6ba82..8faa61eaf755b 100644 --- a/server/src-lib/Hasura/RQL/DDL/Metadata.hs +++ b/server/src-lib/Hasura/RQL/DDL/Metadata.hs @@ -407,19 +407,20 @@ fetchMetadata = do fetchScheduledTriggers = map uncurrySchedule <$> Q.listQE defaultTxErrorHandler [Q.sql| - SELECT st.name, st.webhook_conf, st.schedule_conf, st.payload, st.retry_conf, st.header_conf + SELECT st.name, st.webhook_conf, st.schedule_conf, st.payload, st.retry_conf, st.header_conf,st.utc_offset FROM hdb_catalog.hdb_scheduled_trigger st WHERE include_in_metadata |] () False where - uncurrySchedule (n, wc, sc, p, rc, hc) = + uncurrySchedule (n, wc, sc, p, rc, hc, offset) = CreateScheduledTrigger { stName = n, stWebhook = Q.getAltJ wc, stSchedule = Q.getAltJ sc, stPayload = Q.getAltJ <$> p, stRetryConf = Q.getAltJ rc, - stHeaders = Q.getAltJ hc + stHeaders = Q.getAltJ hc, + stUtcOffset = offset } fetchCustomTypes :: Q.TxE QErr CustomTypes diff --git a/server/src-lib/Hasura/RQL/DDL/ScheduledTrigger.hs b/server/src-lib/Hasura/RQL/DDL/ScheduledTrigger.hs index dc7dae82d6fcb..f45223f19cda1 100644 --- a/server/src-lib/Hasura/RQL/DDL/ScheduledTrigger.hs +++ b/server/src-lib/Hasura/RQL/DDL/ScheduledTrigger.hs @@ -44,10 +44,10 @@ addScheduledTriggerToCatalog CreateScheduledTrigger {..} = liftTx $ do Q.unitQE defaultTxErrorHandler [Q.sql| INSERT into hdb_catalog.hdb_scheduled_trigger - (name, webhook_conf, schedule_conf, payload, retry_conf, header_conf) - VALUES ($1, $2, $3, $4, $5, $6) + (name, webhook_conf, schedule_conf, payload, retry_conf, header_conf, utc_offset) + VALUES ($1, $2, $3, $4, $5, $6, $7) |] (stName, Q.AltJ stWebhook, Q.AltJ stSchedule, Q.AltJ <$> stPayload, Q.AltJ stRetryConf - ,Q.AltJ stHeaders) False + ,Q.AltJ stHeaders, stUtcOffset) False case stSchedule of AdHoc (Just timestamp) -> Q.unitQE defaultTxErrorHandler [Q.sql| diff --git a/server/src-lib/Hasura/RQL/DDL/Schema/Cache.hs b/server/src-lib/Hasura/RQL/DDL/Schema/Cache.hs index 5020e2e0e411a..3c698391a5de4 100644 --- a/server/src-lib/Hasura/RQL/DDL/Schema/Cache.hs +++ b/server/src-lib/Hasura/RQL/DDL/Schema/Cache.hs @@ -287,6 +287,7 @@ buildSchemaCacheRule = proc (catalogMetadata, invalidationKeys) -> do _cstPayload (fromMaybe defaultRetryConfST _cstRetryConf) (fromMaybe [] _cstHeaderConf) + _cstUtcOffset definition = toJSON q triggerName = triggerNameToTxt _cstName metadataObject = MetadataObject (MOScheduledTrigger _cstName) definition diff --git a/server/src-lib/Hasura/RQL/Types/Catalog.hs b/server/src-lib/Hasura/RQL/Types/Catalog.hs index 14309a9cd7476..413114f40d351 100644 --- a/server/src-lib/Hasura/RQL/Types/Catalog.hs +++ b/server/src-lib/Hasura/RQL/Types/Catalog.hs @@ -151,6 +151,7 @@ data CatalogScheduledTrigger , _cstPayload :: !(Maybe Value) , _cstRetryConf :: !(Maybe RetryConfST) , _cstHeaderConf :: !(Maybe [HeaderConf]) + , _cstUtcOffset :: !(Maybe UtcOffset) } deriving (Show, Eq, Generic) instance NFData CatalogScheduledTrigger instance Cacheable CatalogScheduledTrigger diff --git a/server/src-lib/Hasura/RQL/Types/ScheduledTrigger.hs b/server/src-lib/Hasura/RQL/Types/ScheduledTrigger.hs index 53f229ac0b876..d6c98714b99bb 100644 --- a/server/src-lib/Hasura/RQL/Types/ScheduledTrigger.hs +++ b/server/src-lib/Hasura/RQL/Types/ScheduledTrigger.hs @@ -8,6 +8,7 @@ module Hasura.RQL.Types.ScheduledTrigger , RetryConfST(..) , formatTime' , defaultRetryConfST + , UtcOffset(..) ) where import Data.Time.Clock @@ -19,10 +20,14 @@ import Data.Aeson.TH import Hasura.Prelude import System.Cron.Types import Hasura.Incremental +import Language.Haskell.TH.Syntax (Lift) +import Hasura.RQL.Types.Common (NonEmptyText (..)) +import Hasura.SQL.Types import qualified Data.Text as T import qualified Data.Aeson as J import qualified Hasura.RQL.Types.EventTrigger as ET +import qualified Database.PG.Query as Q data RetryConfST = RetryConfST @@ -66,6 +71,9 @@ instance ToJSON ScheduleType where toJSON (AdHoc (Just ts)) = object ["type" .= String "adhoc", "value" .= toJSON ts] toJSON (AdHoc Nothing) = object ["type" .= String "adhoc"] +newtype UtcOffset = UtcOffset { unUtcOffset :: NonEmptyText } + deriving (Show, Eq, Hashable, Lift, DQuote, FromJSON, ToJSON, ToJSONKey, Q.FromCol, Q.ToPrepArg, Generic, Arbitrary, NFData, Cacheable) + data CreateScheduledTrigger = CreateScheduledTrigger { stName :: !ET.TriggerName @@ -74,6 +82,7 @@ data CreateScheduledTrigger , stPayload :: !(Maybe J.Value) , stRetryConf :: !RetryConfST , stHeaders :: ![ET.HeaderConf] + , stUtcOffset :: !(Maybe UtcOffset) } deriving (Show, Eq, Generic) instance NFData CreateScheduledTrigger @@ -88,6 +97,8 @@ instance FromJSON CreateScheduledTrigger where stSchedule <- o .: "schedule" stRetryConf <- o .:? "retry_conf" .!= defaultRetryConfST stHeaders <- o .:? "headers" .!= [] + stUtcOffset <- o .:? "utc_offset" + pure CreateScheduledTrigger {..} $(deriveToJSON (aesonDrop 2 snakeCase){omitNothingFields=True} ''CreateScheduledTrigger) diff --git a/server/src-rsr/catalog_metadata.sql b/server/src-rsr/catalog_metadata.sql index c16f86be5d0b6..5223713c47408 100644 --- a/server/src-rsr/catalog_metadata.sql +++ b/server/src-rsr/catalog_metadata.sql @@ -217,7 +217,8 @@ from 'schedule_conf', schedule_conf :: json, 'payload', payload :: json, 'retry_conf', retry_conf :: json, - 'header_conf', header_conf :: json + 'header_conf', header_conf :: json, + 'utc_offset', utc_offset :: text ) ), '[]' diff --git a/server/src-rsr/initialise.sql b/server/src-rsr/initialise.sql index f8b8a5069f0e9..1a865132bc94b 100644 --- a/server/src-rsr/initialise.sql +++ b/server/src-rsr/initialise.sql @@ -727,6 +727,7 @@ CREATE TABLE hdb_catalog.hdb_scheduled_trigger payload JSON, retry_conf JSON, header_conf JSON, + utc_offset text, include_in_metadata BOOLEAN NOT NULL DEFAULT FALSE ); diff --git a/server/src-rsr/migrations/32_to_33.sql b/server/src-rsr/migrations/32_to_33.sql index 78ed54491ee65..c4067ff5087bd 100644 --- a/server/src-rsr/migrations/32_to_33.sql +++ b/server/src-rsr/migrations/32_to_33.sql @@ -6,6 +6,7 @@ CREATE TABLE hdb_catalog.hdb_scheduled_trigger payload JSON, retry_conf JSON, header_conf JSON, + utc_offset text, include_in_metadata BOOLEAN NOT NULL DEFAULT FALSE ); From 909c8f8eebd21c5a65f7f930962ae92c611fd1af Mon Sep 17 00:00:00 2001 From: Karthikeyan Chinnakonda Date: Tue, 25 Feb 2020 18:54:49 +0530 Subject: [PATCH 02/12] change the logic of ST to incorporate IST and UTC time difference - the value is currently hard-coded(19800), it should be calculated from the utcOffset --- server/src-lib/Hasura/RQL/DDL/ScheduledTrigger.hs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/server/src-lib/Hasura/RQL/DDL/ScheduledTrigger.hs b/server/src-lib/Hasura/RQL/DDL/ScheduledTrigger.hs index f45223f19cda1..e26faac82b753 100644 --- a/server/src-lib/Hasura/RQL/DDL/ScheduledTrigger.hs +++ b/server/src-lib/Hasura/RQL/DDL/ScheduledTrigger.hs @@ -57,8 +57,10 @@ addScheduledTriggerToCatalog CreateScheduledTrigger {..} = liftTx $ do |] (stName, timestamp) False Cron cron -> do currentTime <- liftIO C.getCurrentTime - let scheduleTimes = generateScheduleTimes currentTime 100 cron -- generate next 100 events - events = map (ScheduledEventSeed stName) scheduleTimes + let timeWithOffset = C.addUTCTime 19800 currentTime + scheduleTimesWithOffset = generateScheduleTimes timeWithOffset 100 cron -- generate next 100 events + scheduleTimesinUtc = map (\t -> C.addUTCTime (-19800) t) scheduleTimesWithOffset + events = map (ScheduledEventSeed stName) scheduleTimesinUtc insertScheduledEvents events _ -> pure () From 422c8480754bbcb0e77028ca3ee17fedcd5ce46e Mon Sep 17 00:00:00 2001 From: Karthikeyan Chinnakonda Date: Thu, 27 Feb 2020 16:34:46 +0530 Subject: [PATCH 03/12] add instances for Data.Time.LocalTime - FromJSON,ToJSON,Cacheable,Q.ToPrepArg,Q.FromCol --- .../Hasura/Incremental/Internal/Dependency.hs | 3 ++ server/src-lib/Hasura/RQL/Types/Catalog.hs | 5 ++- .../Hasura/RQL/Types/ScheduledTrigger.hs | 34 ++++++++++++++++--- 3 files changed, 37 insertions(+), 5 deletions(-) diff --git a/server/src-lib/Hasura/Incremental/Internal/Dependency.hs b/server/src-lib/Hasura/Incremental/Internal/Dependency.hs index e69503ba49bcf..d19121115f296 100644 --- a/server/src-lib/Hasura/Incremental/Internal/Dependency.hs +++ b/server/src-lib/Hasura/Incremental/Internal/Dependency.hs @@ -22,6 +22,7 @@ import Data.Vector (Vector) import GHC.Generics ((:*:) (..), (:+:) (..), Generic (..), K1 (..), M1 (..), U1 (..), V1) import System.Cron.Types +import Data.Time.LocalTime (TimeZone(..)) import Hasura.Incremental.Select @@ -166,6 +167,8 @@ instance Cacheable N.URIAuth where unchanged _ = (==) instance Cacheable DiffTime where unchanged _ = (==) instance Cacheable NominalDiffTime where unchanged _ = (==) instance Cacheable UTCTime where unchanged _ = (==) +instance Cacheable TimeZone where unchanged _ = (==) + -- instances for CronSchedule from package `cron` instance Cacheable StepField diff --git a/server/src-lib/Hasura/RQL/Types/Catalog.hs b/server/src-lib/Hasura/RQL/Types/Catalog.hs index 413114f40d351..629cda607347a 100644 --- a/server/src-lib/Hasura/RQL/Types/Catalog.hs +++ b/server/src-lib/Hasura/RQL/Types/Catalog.hs @@ -22,6 +22,7 @@ import qualified Data.HashMap.Strict as M import Data.Aeson import Data.Aeson.Casing import Data.Aeson.TH +import Data.Time.LocalTime (TimeZone(..)) import Hasura.Incremental (Cacheable) import Hasura.RQL.DDL.ComputedField @@ -38,6 +39,8 @@ import Hasura.RQL.Types.SchemaCache import Hasura.RQL.Types.ScheduledTrigger import Hasura.SQL.Types +import qualified Database.PG.Query as Q + newtype CatalogForeignKey = CatalogForeignKey { unCatalogForeignKey :: ForeignKey @@ -151,7 +154,7 @@ data CatalogScheduledTrigger , _cstPayload :: !(Maybe Value) , _cstRetryConf :: !(Maybe RetryConfST) , _cstHeaderConf :: !(Maybe [HeaderConf]) - , _cstUtcOffset :: !(Maybe UtcOffset) + , _cstUtcOffset :: !(Maybe TimeZone) } deriving (Show, Eq, Generic) instance NFData CatalogScheduledTrigger instance Cacheable CatalogScheduledTrigger diff --git a/server/src-lib/Hasura/RQL/Types/ScheduledTrigger.hs b/server/src-lib/Hasura/RQL/Types/ScheduledTrigger.hs index d6c98714b99bb..1000d0450c325 100644 --- a/server/src-lib/Hasura/RQL/Types/ScheduledTrigger.hs +++ b/server/src-lib/Hasura/RQL/Types/ScheduledTrigger.hs @@ -8,7 +8,6 @@ module Hasura.RQL.Types.ScheduledTrigger , RetryConfST(..) , formatTime' , defaultRetryConfST - , UtcOffset(..) ) where import Data.Time.Clock @@ -17,12 +16,14 @@ import Data.Time.Format import Data.Aeson import Data.Aeson.Casing import Data.Aeson.TH +import Data.Char import Hasura.Prelude import System.Cron.Types import Hasura.Incremental import Language.Haskell.TH.Syntax (Lift) import Hasura.RQL.Types.Common (NonEmptyText (..)) import Hasura.SQL.Types +import Data.Time.LocalTime (TimeZone(..),minutesToTimeZone) import qualified Data.Text as T import qualified Data.Aeson as J @@ -71,8 +72,33 @@ instance ToJSON ScheduleType where toJSON (AdHoc (Just ts)) = object ["type" .= String "adhoc", "value" .= toJSON ts] toJSON (AdHoc Nothing) = object ["type" .= String "adhoc"] -newtype UtcOffset = UtcOffset { unUtcOffset :: NonEmptyText } - deriving (Show, Eq, Hashable, Lift, DQuote, FromJSON, ToJSON, ToJSONKey, Q.FromCol, Q.ToPrepArg, Generic, Arbitrary, NFData, Cacheable) +convertUtcOffsetToTimeZone :: String -> Either String TimeZone +convertUtcOffsetToTimeZone offset + | length offset == 4 = convertUtcOffsetToTimeZone ('+':offset) +convertUtcOffsetToTimeZone ('+':h1:h2:m1:m2:"") + | and [(isDigit h1),(isDigit h2),(isDigit m1),(isDigit m2)] = + let mins = (10 * (digitToInt h1) + (digitToInt h2)) * 60 + + (10 * (digitToInt m1) + (digitToInt m2)) + in Right $ TimeZone mins False ('+':h1:h2:m1:m2:"") + | otherwise = Left "Invalid TimeZone Format" +convertUtcOffsetToTimeZone ('-':h1:h2:m1:m2:"") = + case convertUtcOffsetToTimeZone ('+':h1:h2:m1:m2:"") of + Left msg -> Left msg + Right (TimeZone mins False offset) -> Right (TimeZone (-1 * mins) False offset) +convertUtcOffsetToTimeZone _ = Left "Invalid TimeZone Format" + +instance FromJSON TimeZone where + parseJSON = withText "TimeZone" $ \o -> + either fail pure $ convertUtcOffsetToTimeZone $ T.unpack o + +instance ToJSON TimeZone where + toJSON (TimeZone _ _ offset) = String . T.pack $ offset + +instance Q.ToPrepArg TimeZone where + toPrepVal tz = Q.toPrepVal tz + +-- instance Q.FromCol TimeZone where +-- fromCol = \o -> data CreateScheduledTrigger = CreateScheduledTrigger @@ -82,7 +108,7 @@ data CreateScheduledTrigger , stPayload :: !(Maybe J.Value) , stRetryConf :: !RetryConfST , stHeaders :: ![ET.HeaderConf] - , stUtcOffset :: !(Maybe UtcOffset) + , stUtcOffset :: !(Maybe TimeZone) } deriving (Show, Eq, Generic) instance NFData CreateScheduledTrigger From b97d1243e0ef5f6e8abc00bc432da4b7a4c686c0 Mon Sep 17 00:00:00 2001 From: Karthikeyan Chinnakonda Date: Thu, 27 Feb 2020 18:01:26 +0530 Subject: [PATCH 04/12] include the utc-offset field in the schedule type conf field --- .../Hasura/Eventing/ScheduledTrigger.hs | 3 ++- server/src-lib/Hasura/RQL/DDL/Metadata.hs | 7 +++--- .../Hasura/RQL/DDL/ScheduledTrigger.hs | 22 +++++++++++++------ server/src-lib/Hasura/RQL/DDL/Schema/Cache.hs | 1 - server/src-lib/Hasura/RQL/Types/Catalog.hs | 1 - .../Hasura/RQL/Types/ScheduledTrigger.hs | 15 ++++--------- server/src-rsr/catalog_metadata.sql | 3 +-- server/src-rsr/initialise.sql | 1 - server/src-rsr/migrations/32_to_33.sql | 1 - 9 files changed, 25 insertions(+), 29 deletions(-) diff --git a/server/src-lib/Hasura/Eventing/ScheduledTrigger.hs b/server/src-lib/Hasura/Eventing/ScheduledTrigger.hs index dcddfa85b7518..baa8708ef091b 100644 --- a/server/src-lib/Hasura/Eventing/ScheduledTrigger.hs +++ b/server/src-lib/Hasura/Eventing/ScheduledTrigger.hs @@ -206,7 +206,8 @@ generateScheduledEventsFrom startTime ScheduledTriggerInfo{..} = let events = case stiSchedule of AdHoc _ -> empty -- ad-hoc scheduled events are created through 'create_scheduled_event' API - Cron cron -> generateScheduleTimes startTime 100 cron -- by default, generate next 100 events + Cron cron (Just tz) -> generateScheduleTimes startTime 100 cron -- by default, generate next 100 events + Cron cron Nothing -> generateScheduleTimes startTime 100 cron -- by default, in map (ScheduledEventSeed stiName) events -- | Generates next @n events starting @from according to 'CronSchedule' diff --git a/server/src-lib/Hasura/RQL/DDL/Metadata.hs b/server/src-lib/Hasura/RQL/DDL/Metadata.hs index 8faa61eaf755b..0997d72f6ba82 100644 --- a/server/src-lib/Hasura/RQL/DDL/Metadata.hs +++ b/server/src-lib/Hasura/RQL/DDL/Metadata.hs @@ -407,20 +407,19 @@ fetchMetadata = do fetchScheduledTriggers = map uncurrySchedule <$> Q.listQE defaultTxErrorHandler [Q.sql| - SELECT st.name, st.webhook_conf, st.schedule_conf, st.payload, st.retry_conf, st.header_conf,st.utc_offset + SELECT st.name, st.webhook_conf, st.schedule_conf, st.payload, st.retry_conf, st.header_conf FROM hdb_catalog.hdb_scheduled_trigger st WHERE include_in_metadata |] () False where - uncurrySchedule (n, wc, sc, p, rc, hc, offset) = + uncurrySchedule (n, wc, sc, p, rc, hc) = CreateScheduledTrigger { stName = n, stWebhook = Q.getAltJ wc, stSchedule = Q.getAltJ sc, stPayload = Q.getAltJ <$> p, stRetryConf = Q.getAltJ rc, - stHeaders = Q.getAltJ hc, - stUtcOffset = offset + stHeaders = Q.getAltJ hc } fetchCustomTypes :: Q.TxE QErr CustomTypes diff --git a/server/src-lib/Hasura/RQL/DDL/ScheduledTrigger.hs b/server/src-lib/Hasura/RQL/DDL/ScheduledTrigger.hs index e26faac82b753..3277e3bf8746c 100644 --- a/server/src-lib/Hasura/RQL/DDL/ScheduledTrigger.hs +++ b/server/src-lib/Hasura/RQL/DDL/ScheduledTrigger.hs @@ -12,6 +12,8 @@ module Hasura.RQL.DDL.ScheduledTrigger , resolveScheduledTrigger ) where +import Data.Time.LocalTime (TimeZone(..)) + import Hasura.Db import Hasura.EncJSON import Hasura.Prelude @@ -44,10 +46,10 @@ addScheduledTriggerToCatalog CreateScheduledTrigger {..} = liftTx $ do Q.unitQE defaultTxErrorHandler [Q.sql| INSERT into hdb_catalog.hdb_scheduled_trigger - (name, webhook_conf, schedule_conf, payload, retry_conf, header_conf, utc_offset) - VALUES ($1, $2, $3, $4, $5, $6, $7) + (name, webhook_conf, schedule_conf, payload, retry_conf, header_conf) + VALUES ($1, $2, $3, $4, $5, $6) |] (stName, Q.AltJ stWebhook, Q.AltJ stSchedule, Q.AltJ <$> stPayload, Q.AltJ stRetryConf - ,Q.AltJ stHeaders, stUtcOffset) False + ,Q.AltJ stHeaders) False case stSchedule of AdHoc (Just timestamp) -> Q.unitQE defaultTxErrorHandler [Q.sql| @@ -55,11 +57,17 @@ addScheduledTriggerToCatalog CreateScheduledTrigger {..} = liftTx $ do (name, scheduled_time) VALUES ($1, $2) |] (stName, timestamp) False - Cron cron -> do + Cron cron Nothing -> do + currentTime <- liftIO C.getCurrentTime + let scheduleTimes = generateScheduleTimes currentTime 100 cron -- generate next 100 events + events = map (ScheduledEventSeed stName) scheduleTimes + insertScheduledEvents events + Cron cron (Just (TimeZone mins _ _)) -> do currentTime <- liftIO C.getCurrentTime - let timeWithOffset = C.addUTCTime 19800 currentTime - scheduleTimesWithOffset = generateScheduleTimes timeWithOffset 100 cron -- generate next 100 events - scheduleTimesinUtc = map (\t -> C.addUTCTime (-19800) t) scheduleTimesWithOffset + let secsOffset = realToFrac $ (mins * 60) + currentTimeWithOffset = C.addUTCTime secsOffset currentTime + scheduleTimesWithOffset = generateScheduleTimes currentTimeWithOffset 100 cron -- generate next 100 events + scheduleTimesinUtc = map (\t -> C.addUTCTime (-1 * secsOffset) t) scheduleTimesWithOffset events = map (ScheduledEventSeed stName) scheduleTimesinUtc insertScheduledEvents events _ -> pure () diff --git a/server/src-lib/Hasura/RQL/DDL/Schema/Cache.hs b/server/src-lib/Hasura/RQL/DDL/Schema/Cache.hs index 3c698391a5de4..5020e2e0e411a 100644 --- a/server/src-lib/Hasura/RQL/DDL/Schema/Cache.hs +++ b/server/src-lib/Hasura/RQL/DDL/Schema/Cache.hs @@ -287,7 +287,6 @@ buildSchemaCacheRule = proc (catalogMetadata, invalidationKeys) -> do _cstPayload (fromMaybe defaultRetryConfST _cstRetryConf) (fromMaybe [] _cstHeaderConf) - _cstUtcOffset definition = toJSON q triggerName = triggerNameToTxt _cstName metadataObject = MetadataObject (MOScheduledTrigger _cstName) definition diff --git a/server/src-lib/Hasura/RQL/Types/Catalog.hs b/server/src-lib/Hasura/RQL/Types/Catalog.hs index 629cda607347a..8256b8917c4c2 100644 --- a/server/src-lib/Hasura/RQL/Types/Catalog.hs +++ b/server/src-lib/Hasura/RQL/Types/Catalog.hs @@ -154,7 +154,6 @@ data CatalogScheduledTrigger , _cstPayload :: !(Maybe Value) , _cstRetryConf :: !(Maybe RetryConfST) , _cstHeaderConf :: !(Maybe [HeaderConf]) - , _cstUtcOffset :: !(Maybe TimeZone) } deriving (Show, Eq, Generic) instance NFData CatalogScheduledTrigger instance Cacheable CatalogScheduledTrigger diff --git a/server/src-lib/Hasura/RQL/Types/ScheduledTrigger.hs b/server/src-lib/Hasura/RQL/Types/ScheduledTrigger.hs index 1000d0450c325..4af46cf8a1ce3 100644 --- a/server/src-lib/Hasura/RQL/Types/ScheduledTrigger.hs +++ b/server/src-lib/Hasura/RQL/Types/ScheduledTrigger.hs @@ -52,7 +52,7 @@ defaultRetryConfST = , rcstTolerance = 21600 -- 6 hours } -data ScheduleType = Cron CronSchedule | AdHoc (Maybe UTCTime) +data ScheduleType = Cron CronSchedule (Maybe TimeZone) | AdHoc (Maybe UTCTime) deriving (Show, Eq, Generic) instance NFData ScheduleType @@ -63,12 +63,13 @@ instance FromJSON ScheduleType where withObject "ScheduleType" $ \o -> do type' <- o .: "type" case type' of - String "cron" -> Cron <$> o .: "value" + String "cron" -> Cron <$> o .: "value" <*> o .:? "utc-offset" String "adhoc" -> AdHoc <$> o .:? "value" _ -> fail "expected type to be cron or adhoc" instance ToJSON ScheduleType where - toJSON (Cron cs) = object ["type" .= String "cron", "value" .= toJSON cs] + toJSON (Cron cs (Just offset)) = object ["type" .= String "cron", "value" .= toJSON cs, "utc-offset" .= (show offset)] + toJSON (Cron cs Nothing) = object ["type" .= String "cron", "value" .= toJSON cs] toJSON (AdHoc (Just ts)) = object ["type" .= String "adhoc", "value" .= toJSON ts] toJSON (AdHoc Nothing) = object ["type" .= String "adhoc"] @@ -94,12 +95,6 @@ instance FromJSON TimeZone where instance ToJSON TimeZone where toJSON (TimeZone _ _ offset) = String . T.pack $ offset -instance Q.ToPrepArg TimeZone where - toPrepVal tz = Q.toPrepVal tz - --- instance Q.FromCol TimeZone where --- fromCol = \o -> - data CreateScheduledTrigger = CreateScheduledTrigger { stName :: !ET.TriggerName @@ -108,7 +103,6 @@ data CreateScheduledTrigger , stPayload :: !(Maybe J.Value) , stRetryConf :: !RetryConfST , stHeaders :: ![ET.HeaderConf] - , stUtcOffset :: !(Maybe TimeZone) } deriving (Show, Eq, Generic) instance NFData CreateScheduledTrigger @@ -123,7 +117,6 @@ instance FromJSON CreateScheduledTrigger where stSchedule <- o .: "schedule" stRetryConf <- o .:? "retry_conf" .!= defaultRetryConfST stHeaders <- o .:? "headers" .!= [] - stUtcOffset <- o .:? "utc_offset" pure CreateScheduledTrigger {..} diff --git a/server/src-rsr/catalog_metadata.sql b/server/src-rsr/catalog_metadata.sql index 5223713c47408..c16f86be5d0b6 100644 --- a/server/src-rsr/catalog_metadata.sql +++ b/server/src-rsr/catalog_metadata.sql @@ -217,8 +217,7 @@ from 'schedule_conf', schedule_conf :: json, 'payload', payload :: json, 'retry_conf', retry_conf :: json, - 'header_conf', header_conf :: json, - 'utc_offset', utc_offset :: text + 'header_conf', header_conf :: json ) ), '[]' diff --git a/server/src-rsr/initialise.sql b/server/src-rsr/initialise.sql index 1a865132bc94b..f8b8a5069f0e9 100644 --- a/server/src-rsr/initialise.sql +++ b/server/src-rsr/initialise.sql @@ -727,7 +727,6 @@ CREATE TABLE hdb_catalog.hdb_scheduled_trigger payload JSON, retry_conf JSON, header_conf JSON, - utc_offset text, include_in_metadata BOOLEAN NOT NULL DEFAULT FALSE ); diff --git a/server/src-rsr/migrations/32_to_33.sql b/server/src-rsr/migrations/32_to_33.sql index c4067ff5087bd..78ed54491ee65 100644 --- a/server/src-rsr/migrations/32_to_33.sql +++ b/server/src-rsr/migrations/32_to_33.sql @@ -6,7 +6,6 @@ CREATE TABLE hdb_catalog.hdb_scheduled_trigger payload JSON, retry_conf JSON, header_conf JSON, - utc_offset text, include_in_metadata BOOLEAN NOT NULL DEFAULT FALSE ); From 86777ab2367ba721957dd6c47884412f6dccc432 Mon Sep 17 00:00:00 2001 From: Karthikeyan Chinnakonda Date: Thu, 27 Feb 2020 18:24:32 +0530 Subject: [PATCH 05/12] include the timezone logic while generating the events every time --- .../src-lib/Hasura/Eventing/ScheduledTrigger.hs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/server/src-lib/Hasura/Eventing/ScheduledTrigger.hs b/server/src-lib/Hasura/Eventing/ScheduledTrigger.hs index baa8708ef091b..6edd7c07ec83d 100644 --- a/server/src-lib/Hasura/Eventing/ScheduledTrigger.hs +++ b/server/src-lib/Hasura/Eventing/ScheduledTrigger.hs @@ -17,7 +17,6 @@ The delivery mechanism is similar to Event Triggers; see "Hasura.Eventing.EventT module Hasura.Eventing.ScheduledTrigger ( processScheduledQueue , runScheduledEventsGenerator - , ScheduledEventSeed(..) , generateScheduleTimes , insertScheduledEvents @@ -29,6 +28,7 @@ import Data.Has import Data.Int (Int64) import Data.List (unfoldr) import Data.Time.Clock +import Data.Time.LocalTime (TimeZone(..)) import Hasura.Eventing.HTTP import Hasura.Prelude import Hasura.RQL.DDL.Headers @@ -206,9 +206,17 @@ generateScheduledEventsFrom startTime ScheduledTriggerInfo{..} = let events = case stiSchedule of AdHoc _ -> empty -- ad-hoc scheduled events are created through 'create_scheduled_event' API - Cron cron (Just tz) -> generateScheduleTimes startTime 100 cron -- by default, generate next 100 events - Cron cron Nothing -> generateScheduleTimes startTime 100 cron -- by default, - in map (ScheduledEventSeed stiName) events + + Cron cron Nothing -> generateScheduleTimes startTime 100 cron -- by default,generate next 100 events + + Cron cron (Just (TimeZone mins _ _)) -> do + let secsOffset = realToFrac $ (mins * 60) + startTimeWithOffset = addUTCTime secsOffset startTime + scheduleTimesWithOffset = + generateScheduleTimes startTimeWithOffset 100 cron + map (\t -> addUTCTime (-1 * secsOffset) t) scheduleTimesWithOffset + + in map (ScheduledEventSeed stiName) events -- | Generates next @n events starting @from according to 'CronSchedule' generateScheduleTimes :: UTCTime -> Int -> CronSchedule -> [UTCTime] From 9d8c13f14ff24c7705cd251f8db85a7851b00eb0 Mon Sep 17 00:00:00 2001 From: Karthikeyan Chinnakonda Date: Thu, 27 Feb 2020 18:39:40 +0530 Subject: [PATCH 06/12] add documentation explaining the scheduled triggers with timezone --- server/src-lib/Hasura/RQL/DDL/ScheduledTrigger.hs | 6 +++++- server/src-lib/Hasura/RQL/Types/ScheduledTrigger.hs | 5 +++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/server/src-lib/Hasura/RQL/DDL/ScheduledTrigger.hs b/server/src-lib/Hasura/RQL/DDL/ScheduledTrigger.hs index 3277e3bf8746c..f7d4ed5ab4dc4 100644 --- a/server/src-lib/Hasura/RQL/DDL/ScheduledTrigger.hs +++ b/server/src-lib/Hasura/RQL/DDL/ScheduledTrigger.hs @@ -57,6 +57,7 @@ addScheduledTriggerToCatalog CreateScheduledTrigger {..} = liftTx $ do (name, scheduled_time) VALUES ($1, $2) |] (stName, timestamp) False + -- when no timezone, then generate events keeping UTC as seed time Cron cron Nothing -> do currentTime <- liftIO C.getCurrentTime let scheduleTimes = generateScheduleTimes currentTime 100 cron -- generate next 100 events @@ -66,7 +67,10 @@ addScheduledTriggerToCatalog CreateScheduledTrigger {..} = liftTx $ do currentTime <- liftIO C.getCurrentTime let secsOffset = realToFrac $ (mins * 60) currentTimeWithOffset = C.addUTCTime secsOffset currentTime - scheduleTimesWithOffset = generateScheduleTimes currentTimeWithOffset 100 cron -- generate next 100 events + -- generate the schedule times with `currentTimeWithOffset` + -- and then while inserting it into the db, convert it back into UTC. + scheduleTimesWithOffset = generateScheduleTimes currentTimeWithOffset 100 cron + -- by default,generate next 100 events scheduleTimesinUtc = map (\t -> C.addUTCTime (-1 * secsOffset) t) scheduleTimesWithOffset events = map (ScheduledEventSeed stName) scheduleTimesinUtc insertScheduledEvents events diff --git a/server/src-lib/Hasura/RQL/Types/ScheduledTrigger.hs b/server/src-lib/Hasura/RQL/Types/ScheduledTrigger.hs index 4af46cf8a1ce3..7efe50d4ed7f8 100644 --- a/server/src-lib/Hasura/RQL/Types/ScheduledTrigger.hs +++ b/server/src-lib/Hasura/RQL/Types/ScheduledTrigger.hs @@ -73,6 +73,11 @@ instance ToJSON ScheduleType where toJSON (AdHoc (Just ts)) = object ["type" .= String "adhoc", "value" .= toJSON ts] toJSON (AdHoc Nothing) = object ["type" .= String "adhoc"] +-- convertUtcOffsetToTimeZone can take an offset in any one of +-- the following formats: +-- HHMM,HH:MM,(+/-)HHMM +-- If the length of the offset is 4, then it's assumed that it's a +-- positive offset. convertUtcOffsetToTimeZone :: String -> Either String TimeZone convertUtcOffsetToTimeZone offset | length offset == 4 = convertUtcOffsetToTimeZone ('+':offset) From a344b6b67fd7975238c8a42b668ef04337b90adf Mon Sep 17 00:00:00 2001 From: Karthikeyan Chinnakonda Date: Fri, 28 Feb 2020 11:21:26 +0530 Subject: [PATCH 07/12] handle the adding and subtracting of TZ offset in one place --- .../Hasura/Eventing/ScheduledTrigger.hs | 30 ++++++++++++------- .../Hasura/RQL/DDL/ScheduledTrigger.hs | 15 ++-------- 2 files changed, 21 insertions(+), 24 deletions(-) diff --git a/server/src-lib/Hasura/Eventing/ScheduledTrigger.hs b/server/src-lib/Hasura/Eventing/ScheduledTrigger.hs index 6edd7c07ec83d..1da4d24960ebe 100644 --- a/server/src-lib/Hasura/Eventing/ScheduledTrigger.hs +++ b/server/src-lib/Hasura/Eventing/ScheduledTrigger.hs @@ -28,7 +28,7 @@ import Data.Has import Data.Int (Int64) import Data.List (unfoldr) import Data.Time.Clock -import Data.Time.LocalTime (TimeZone(..)) +import Data.Time.LocalTime (TimeZone(..),minutesToTimeZone) import Hasura.Eventing.HTTP import Hasura.Prelude import Hasura.RQL.DDL.Headers @@ -207,22 +207,30 @@ generateScheduledEventsFrom startTime ScheduledTriggerInfo{..} = case stiSchedule of AdHoc _ -> empty -- ad-hoc scheduled events are created through 'create_scheduled_event' API - Cron cron Nothing -> generateScheduleTimes startTime 100 cron -- by default,generate next 100 events - - Cron cron (Just (TimeZone mins _ _)) -> do - let secsOffset = realToFrac $ (mins * 60) - startTimeWithOffset = addUTCTime secsOffset startTime - scheduleTimesWithOffset = - generateScheduleTimes startTimeWithOffset 100 cron - map (\t -> addUTCTime (-1 * secsOffset) t) scheduleTimesWithOffset + Cron cron tz -> generateScheduleTimes startTime tz 100 cron in map (ScheduledEventSeed stiName) events +addOffsetToUTCTime :: UTCTime -> TimeZone -> UTCTime +addOffsetToUTCTime ut (TimeZone mins _ _) = + addUTCTime (secsOffset mins) ut + where + secsOffset :: Int -> NominalDiffTime + secsOffset mins = realToFrac $ (mins * 60) + -- | Generates next @n events starting @from according to 'CronSchedule' -generateScheduleTimes :: UTCTime -> Int -> CronSchedule -> [UTCTime] -generateScheduleTimes from n cron = take n $ go from +-- When Timezone is not Nothing, the offset will be added to the `from` value +-- then the cron schedules are generated and then the offset will be subtracted +-- from the generated timestamps. +generateScheduleTimes :: UTCTime -> Maybe TimeZone -> Int -> CronSchedule -> [UTCTime] +generateScheduleTimes from tz n cron = + case tz of + Nothing -> take n $ go from + Just tz@(TimeZone mins _ _) -> + map (\t -> addOffsetToUTCTime t (inverseTimeZone mins)) $ take n $ go $ addOffsetToUTCTime from tz where go = unfoldr (fmap dup . nextMatch cron) + inverseTimeZone mins = minutesToTimeZone (-1 * mins) processScheduledQueue :: HasVersion diff --git a/server/src-lib/Hasura/RQL/DDL/ScheduledTrigger.hs b/server/src-lib/Hasura/RQL/DDL/ScheduledTrigger.hs index f7d4ed5ab4dc4..608ff86adb775 100644 --- a/server/src-lib/Hasura/RQL/DDL/ScheduledTrigger.hs +++ b/server/src-lib/Hasura/RQL/DDL/ScheduledTrigger.hs @@ -58,22 +58,11 @@ addScheduledTriggerToCatalog CreateScheduledTrigger {..} = liftTx $ do VALUES ($1, $2) |] (stName, timestamp) False -- when no timezone, then generate events keeping UTC as seed time - Cron cron Nothing -> do + Cron cron tz -> do currentTime <- liftIO C.getCurrentTime - let scheduleTimes = generateScheduleTimes currentTime 100 cron -- generate next 100 events + let scheduleTimes = generateScheduleTimes currentTime tz 100 cron -- generate next 100 events events = map (ScheduledEventSeed stName) scheduleTimes insertScheduledEvents events - Cron cron (Just (TimeZone mins _ _)) -> do - currentTime <- liftIO C.getCurrentTime - let secsOffset = realToFrac $ (mins * 60) - currentTimeWithOffset = C.addUTCTime secsOffset currentTime - -- generate the schedule times with `currentTimeWithOffset` - -- and then while inserting it into the db, convert it back into UTC. - scheduleTimesWithOffset = generateScheduleTimes currentTimeWithOffset 100 cron - -- by default,generate next 100 events - scheduleTimesinUtc = map (\t -> C.addUTCTime (-1 * secsOffset) t) scheduleTimesWithOffset - events = map (ScheduledEventSeed stName) scheduleTimesinUtc - insertScheduledEvents events _ -> pure () resolveScheduledTrigger From 2bd205aa16a64550c6caade54ee0dc99c8cd2bd1 Mon Sep 17 00:00:00 2001 From: Karthikeyan Chinnakonda Date: Fri, 28 Feb 2020 11:32:36 +0530 Subject: [PATCH 08/12] refactor the code to avoid the warnings --- server/src-lib/Hasura/Eventing/ScheduledTrigger.hs | 9 +++------ server/src-lib/Hasura/RQL/DDL/ScheduledTrigger.hs | 2 -- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/server/src-lib/Hasura/Eventing/ScheduledTrigger.hs b/server/src-lib/Hasura/Eventing/ScheduledTrigger.hs index 1da4d24960ebe..684682e68fa71 100644 --- a/server/src-lib/Hasura/Eventing/ScheduledTrigger.hs +++ b/server/src-lib/Hasura/Eventing/ScheduledTrigger.hs @@ -213,18 +213,15 @@ generateScheduledEventsFrom startTime ScheduledTriggerInfo{..} = addOffsetToUTCTime :: UTCTime -> TimeZone -> UTCTime addOffsetToUTCTime ut (TimeZone mins _ _) = - addUTCTime (secsOffset mins) ut - where - secsOffset :: Int -> NominalDiffTime - secsOffset mins = realToFrac $ (mins * 60) + addUTCTime (realToFrac $ (mins * 60)) ut -- | Generates next @n events starting @from according to 'CronSchedule' -- When Timezone is not Nothing, the offset will be added to the `from` value -- then the cron schedules are generated and then the offset will be subtracted -- from the generated timestamps. generateScheduleTimes :: UTCTime -> Maybe TimeZone -> Int -> CronSchedule -> [UTCTime] -generateScheduleTimes from tz n cron = - case tz of +generateScheduleTimes from timezone n cron = + case timezone of Nothing -> take n $ go from Just tz@(TimeZone mins _ _) -> map (\t -> addOffsetToUTCTime t (inverseTimeZone mins)) $ take n $ go $ addOffsetToUTCTime from tz diff --git a/server/src-lib/Hasura/RQL/DDL/ScheduledTrigger.hs b/server/src-lib/Hasura/RQL/DDL/ScheduledTrigger.hs index 608ff86adb775..786b1b69f54db 100644 --- a/server/src-lib/Hasura/RQL/DDL/ScheduledTrigger.hs +++ b/server/src-lib/Hasura/RQL/DDL/ScheduledTrigger.hs @@ -12,8 +12,6 @@ module Hasura.RQL.DDL.ScheduledTrigger , resolveScheduledTrigger ) where -import Data.Time.LocalTime (TimeZone(..)) - import Hasura.Db import Hasura.EncJSON import Hasura.Prelude From ef75d6589d6a7de687cdc169406e37471e228f08 Mon Sep 17 00:00:00 2001 From: Karthikeyan Chinnakonda Date: Fri, 28 Feb 2020 13:12:22 +0530 Subject: [PATCH 09/12] add test for creating scheduled trigger with utc-offset --- .../Hasura/RQL/DDL/ScheduledTrigger.hs | 1 - server/tests-py/test_scheduled_triggers.py | 79 +++++++++++++++++-- 2 files changed, 72 insertions(+), 8 deletions(-) diff --git a/server/src-lib/Hasura/RQL/DDL/ScheduledTrigger.hs b/server/src-lib/Hasura/RQL/DDL/ScheduledTrigger.hs index 786b1b69f54db..7349b40a6fd2f 100644 --- a/server/src-lib/Hasura/RQL/DDL/ScheduledTrigger.hs +++ b/server/src-lib/Hasura/RQL/DDL/ScheduledTrigger.hs @@ -55,7 +55,6 @@ addScheduledTriggerToCatalog CreateScheduledTrigger {..} = liftTx $ do (name, scheduled_time) VALUES ($1, $2) |] (stName, timestamp) False - -- when no timezone, then generate events keeping UTC as seed time Cron cron tz -> do currentTime <- liftIO C.getCurrentTime let scheduleTimes = generateScheduleTimes currentTime tz 100 cron -- generate next 100 events diff --git a/server/tests-py/test_scheduled_triggers.py b/server/tests-py/test_scheduled_triggers.py index 72efe11ec8005..57166db9bbfcd 100644 --- a/server/tests-py/test_scheduled_triggers.py +++ b/server/tests-py/test_scheduled_triggers.py @@ -5,6 +5,7 @@ from croniter import croniter from validate import validate_event_webhook,validate_event_headers from queue import Empty +from pytz import timezone import time def stringify_datetime(dt): @@ -25,9 +26,72 @@ def get_events_of_scheduled_trigger(hge_ctx,trigger_name): class TestScheduledTriggerCron(object): cron_trigger_name = "a_scheduled_trigger" + cron_trigger_with_offset = cron_trigger_name + "_offset" webhook_payload = {"foo":"baz"} webhook_path = "/hello" url = '/v1/query' + utc_offset = "+0530" + + def test_create_cron_schedule_triggers_with_offset(self,hge_ctx): + # setting the test to be after 30 mins, to make sure that + # any of the events are not triggered. + local_now = datetime.now().astimezone(timezone('Asia/Kolkata')) + min_after_30_mins = (local_now + timedelta(minutes=30)).minute + TestScheduledTriggerCron.cron_schedule = "{} * * * *".format(min_after_30_mins) + + cron_st_api_query = { + "type":"create_scheduled_trigger", + "args":{ + "name":self.cron_trigger_with_offset, + "webhook":"http://127.0.0.1:5594" + "/foo", + "schedule":{ + "type":"cron", + "value":self.cron_schedule, + "utc-offset":"+0530" + }, + "headers":[ + { + "name":"foo", + "value":"baz" + } + ], + "payload":self.webhook_payload + } + } + headers = {} + if hge_ctx.hge_key is not None: + headers['X-Hasura-Admin-Secret'] = hge_ctx.hge_key + cron_st_code,cron_st_resp,_ = hge_ctx.anyq(self.url,cron_st_api_query,headers) + TestScheduledTriggerCron.init_time_with_offset = datetime.now().astimezone(timezone('Asia/Kolkata')) # the cron events will be generated based on the current time, they will not be exactly the same though(the server now and now here) + assert cron_st_code == 200 + assert cron_st_resp['message'] == 'success' + + def test_check_generated_cron_scheduled_events_with_offset(self,hge_ctx): + expected_schedule_timestamps = [] + iter = croniter(self.cron_schedule,self.init_time_with_offset) + for i in range(100): + dt = iter.next(datetime) + expected_schedule_timestamps.append(datetime.timestamp(dt)) + sql = ''' + select timezone('Asia/Kolkata',scheduled_time) as scheduled_time + from hdb_catalog.hdb_scheduled_events where + name = '{}' order by scheduled_time asc; + ''' + q = { + "type":"run_sql", + "args":{ + "sql":sql.format(self.cron_trigger_with_offset) + } + } + st,resp = hge_ctx.v1q(q) + assert st == 200 + ts_resp = resp['result'][1:] + assert len(ts_resp) == 100 # 100 events are generated in a cron ST + db_timestamps = [] + for ts in ts_resp: + datetime_ts = datetime.strptime(ts[0],"%Y-%m-%d %H:%M:%S") + db_timestamps.append(datetime.timestamp(datetime_ts)) + assert db_timestamps == expected_schedule_timestamps def test_create_cron_schedule_triggers(self,hge_ctx): # setting the test to be after 30 mins, to make sure that @@ -90,14 +154,15 @@ def test_check_generated_cron_scheduled_events(self,hge_ctx): assert future_schedule_timestamps == scheduled_events_ts def test_delete_cron_scheduled_trigger(self,hge_ctx): - q = { - "type":"delete_scheduled_trigger", - "args":{ - "name":self.cron_trigger_name + for trigger_name in [self.cron_trigger_name,self.cron_trigger_with_offset]: + q = { + "type":"delete_scheduled_trigger", + "args":{ + "name":trigger_name + } } - } - st,resp = hge_ctx.v1q(q) - assert st == 200,resp + st,resp = hge_ctx.v1q(q) + assert st == 200,resp class ScheduledEventNotFound(Exception): pass From f80ba573652064b28c361cc4695e6676748a5714 Mon Sep 17 00:00:00 2001 From: Karthikeyan Chinnakonda Date: Fri, 28 Feb 2020 13:22:29 +0530 Subject: [PATCH 10/12] refactor the scheduled triggers tests --- server/tests-py/test_scheduled_triggers.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/server/tests-py/test_scheduled_triggers.py b/server/tests-py/test_scheduled_triggers.py index 57166db9bbfcd..101ecfa171db3 100644 --- a/server/tests-py/test_scheduled_triggers.py +++ b/server/tests-py/test_scheduled_triggers.py @@ -30,12 +30,13 @@ class TestScheduledTriggerCron(object): webhook_payload = {"foo":"baz"} webhook_path = "/hello" url = '/v1/query' - utc_offset = "+0530" + timezone_region = "Asia/Kolkata" + offset_at_timezone_region = "+0530" def test_create_cron_schedule_triggers_with_offset(self,hge_ctx): # setting the test to be after 30 mins, to make sure that # any of the events are not triggered. - local_now = datetime.now().astimezone(timezone('Asia/Kolkata')) + local_now = datetime.now().astimezone(timezone(self.timezone_region)) min_after_30_mins = (local_now + timedelta(minutes=30)).minute TestScheduledTriggerCron.cron_schedule = "{} * * * *".format(min_after_30_mins) @@ -47,7 +48,7 @@ def test_create_cron_schedule_triggers_with_offset(self,hge_ctx): "schedule":{ "type":"cron", "value":self.cron_schedule, - "utc-offset":"+0530" + "utc-offset":self.offset_at_timezone_region }, "headers":[ { @@ -62,7 +63,7 @@ def test_create_cron_schedule_triggers_with_offset(self,hge_ctx): if hge_ctx.hge_key is not None: headers['X-Hasura-Admin-Secret'] = hge_ctx.hge_key cron_st_code,cron_st_resp,_ = hge_ctx.anyq(self.url,cron_st_api_query,headers) - TestScheduledTriggerCron.init_time_with_offset = datetime.now().astimezone(timezone('Asia/Kolkata')) # the cron events will be generated based on the current time, they will not be exactly the same though(the server now and now here) + TestScheduledTriggerCron.init_time_with_offset = datetime.now().astimezone(timezone(self.timezone_region)) # the cron events will be generated based on the current time, they will not be exactly the same though(the server now and now here) assert cron_st_code == 200 assert cron_st_resp['message'] == 'success' @@ -73,14 +74,14 @@ def test_check_generated_cron_scheduled_events_with_offset(self,hge_ctx): dt = iter.next(datetime) expected_schedule_timestamps.append(datetime.timestamp(dt)) sql = ''' - select timezone('Asia/Kolkata',scheduled_time) as scheduled_time + select timezone('{}',scheduled_time) as scheduled_time from hdb_catalog.hdb_scheduled_events where name = '{}' order by scheduled_time asc; ''' q = { "type":"run_sql", "args":{ - "sql":sql.format(self.cron_trigger_with_offset) + "sql":sql.format(self.timezone_region, self.cron_trigger_with_offset) } } st,resp = hge_ctx.v1q(q) From 7a723d722c5f03b4159cfb3a9e0de8b004800662 Mon Sep 17 00:00:00 2001 From: Karthikeyan Chinnakonda Date: Fri, 28 Feb 2020 13:42:56 +0530 Subject: [PATCH 11/12] parse utc-offset if it's in the format of (+/-)?HH:MM format --- .../Hasura/RQL/Types/ScheduledTrigger.hs | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/server/src-lib/Hasura/RQL/Types/ScheduledTrigger.hs b/server/src-lib/Hasura/RQL/Types/ScheduledTrigger.hs index 7efe50d4ed7f8..4d590b4b75205 100644 --- a/server/src-lib/Hasura/RQL/Types/ScheduledTrigger.hs +++ b/server/src-lib/Hasura/RQL/Types/ScheduledTrigger.hs @@ -20,9 +20,6 @@ import Data.Char import Hasura.Prelude import System.Cron.Types import Hasura.Incremental -import Language.Haskell.TH.Syntax (Lift) -import Hasura.RQL.Types.Common (NonEmptyText (..)) -import Hasura.SQL.Types import Data.Time.LocalTime (TimeZone(..),minutesToTimeZone) import qualified Data.Text as T @@ -73,29 +70,33 @@ instance ToJSON ScheduleType where toJSON (AdHoc (Just ts)) = object ["type" .= String "adhoc", "value" .= toJSON ts] toJSON (AdHoc Nothing) = object ["type" .= String "adhoc"] --- convertUtcOffsetToTimeZone can take an offset in any one of +-- convertUTCOffsetToTimeZone can take an offset in any one of -- the following formats: -- HHMM,HH:MM,(+/-)HHMM -- If the length of the offset is 4, then it's assumed that it's a -- positive offset. -convertUtcOffsetToTimeZone :: String -> Either String TimeZone -convertUtcOffsetToTimeZone offset - | length offset == 4 = convertUtcOffsetToTimeZone ('+':offset) -convertUtcOffsetToTimeZone ('+':h1:h2:m1:m2:"") +convertUTCOffsetToTimeZone :: String -> Either String TimeZone +convertUTCOffsetToTimeZone offset + | length offset == 4 = convertUTCOffsetToTimeZone ('+':offset) +convertUTCOffsetToTimeZone (h1:h2:':':m1:m2:"") = + convertUTCOffsetToTimeZone('+':h1:h2:m1:m2:"") +convertUTCOffsetToTimeZone (p:h1:h2:':':m1:m2:"") = + convertUTCOffsetToTimeZone(p:h1:h2:m1:m2:"") +convertUTCOffsetToTimeZone ('+':h1:h2:m1:m2:"") | and [(isDigit h1),(isDigit h2),(isDigit m1),(isDigit m2)] = let mins = (10 * (digitToInt h1) + (digitToInt h2)) * 60 + (10 * (digitToInt m1) + (digitToInt m2)) in Right $ TimeZone mins False ('+':h1:h2:m1:m2:"") | otherwise = Left "Invalid TimeZone Format" -convertUtcOffsetToTimeZone ('-':h1:h2:m1:m2:"") = - case convertUtcOffsetToTimeZone ('+':h1:h2:m1:m2:"") of +convertUTCOffsetToTimeZone ('-':h1:h2:m1:m2:"") = + case convertUTCOffsetToTimeZone ('+':h1:h2:m1:m2:"") of Left msg -> Left msg - Right (TimeZone mins False offset) -> Right (TimeZone (-1 * mins) False offset) -convertUtcOffsetToTimeZone _ = Left "Invalid TimeZone Format" + Right (TimeZone mins isSummerOnly offset) -> Right (TimeZone (-1 * mins) isSummerOnly offset) +convertUTCOffsetToTimeZone _ = Left "Invalid TimeZone Format" instance FromJSON TimeZone where parseJSON = withText "TimeZone" $ \o -> - either fail pure $ convertUtcOffsetToTimeZone $ T.unpack o + either fail pure $ convertUTCOffsetToTimeZone $ T.unpack o instance ToJSON TimeZone where toJSON (TimeZone _ _ offset) = String . T.pack $ offset From e399937dfea97ed29c143145094595cb5100fb63 Mon Sep 17 00:00:00 2001 From: Karthikeyan Chinnakonda Date: Fri, 28 Feb 2020 13:55:10 +0530 Subject: [PATCH 12/12] fix bug which stores negative offsets as positive in the db --- server/src-lib/Hasura/RQL/Types/ScheduledTrigger.hs | 2 +- server/tests-py/test_scheduled_triggers.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src-lib/Hasura/RQL/Types/ScheduledTrigger.hs b/server/src-lib/Hasura/RQL/Types/ScheduledTrigger.hs index 4d590b4b75205..9f433dc9ea061 100644 --- a/server/src-lib/Hasura/RQL/Types/ScheduledTrigger.hs +++ b/server/src-lib/Hasura/RQL/Types/ScheduledTrigger.hs @@ -91,7 +91,7 @@ convertUTCOffsetToTimeZone ('+':h1:h2:m1:m2:"") convertUTCOffsetToTimeZone ('-':h1:h2:m1:m2:"") = case convertUTCOffsetToTimeZone ('+':h1:h2:m1:m2:"") of Left msg -> Left msg - Right (TimeZone mins isSummerOnly offset) -> Right (TimeZone (-1 * mins) isSummerOnly offset) + Right (TimeZone mins isSummerOnly ('+':offset)) -> Right (TimeZone (-1 * mins) isSummerOnly ('-':offset)) convertUTCOffsetToTimeZone _ = Left "Invalid TimeZone Format" instance FromJSON TimeZone where diff --git a/server/tests-py/test_scheduled_triggers.py b/server/tests-py/test_scheduled_triggers.py index 101ecfa171db3..6078d1c21decd 100644 --- a/server/tests-py/test_scheduled_triggers.py +++ b/server/tests-py/test_scheduled_triggers.py @@ -31,7 +31,7 @@ class TestScheduledTriggerCron(object): webhook_path = "/hello" url = '/v1/query' timezone_region = "Asia/Kolkata" - offset_at_timezone_region = "+0530" + offset_at_timezone_region = "+05:30" def test_create_cron_schedule_triggers_with_offset(self,hge_ctx): # setting the test to be after 30 mins, to make sure that