Skip to content

Commit bd48796

Browse files
[Fix] Slight refactor of render part (#56)
Changes: 1. Remove button from section builders as it's never needed; 2. Add separate datatype for sender flag instead of plain Bool; 3. Replace plain text with markdown in the Slack section block; 4. Add functions for converting to offset time/back; 5. Unite fields of TimeReferenceToUtcResult for convenience.
1 parent 66f169b commit bd48796

File tree

7 files changed

+112
-95
lines changed

7 files changed

+112
-95
lines changed

src/TzBot/Feedback/Save.hs

+7-8
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import Data.Time.Zones.All (toTZName)
1919
import Text.Interpolation.Nyan (int, rmode')
2020

2121
import TzBot.Logger
22-
import TzBot.Render (TranslationPairs, renderSlackBlocks)
22+
import TzBot.Render (TranslationPairs, asForOthersS, renderSlackBlocks)
2323
import TzBot.RunMonad
2424
import TzBot.Slack (sendMessage)
2525
import TzBot.Slack.API
@@ -53,22 +53,21 @@ saveFeedbackSlack entry channelId = sendMessage req
5353
req = do
5454
-- We always render the translation for other users (not author),
5555
-- so the author can see how his message is translated for others
56-
let forSender = False
57-
pmrChannel = channelId
56+
let pmrChannel = channelId
5857
pmrText = "New user feedback"
5958
pmrBlocks = NE.nonEmpty $
6059
[ BHeader (Header "Message")
61-
, BSection (textSection (PlainText $ feMessageText entry) Nothing)
60+
, BSection $ markdownSection (Mrkdwn $ feMessageText entry)
6261
, BDivider divider
6362
, BHeader (Header "Time translation")
64-
] <> renderSlackBlocks forSender (feTimeTranslation entry)
63+
] <> renderSlackBlocks asForOthersS (feTimeTranslation entry)
6564
<>
6665
[ BDivider divider
6766
, BHeader (Header "Details")
68-
, BSection $ fieldsSection Nothing Nothing $
67+
, BSection $ fieldsSection Nothing $
6968
("Message timestamp", show $ feMessageTimestamp entry) :|
70-
[ ("Sender timezone", PlainText $ cs $ toTZName $ feSenderTimezone entry)
71-
, ("User report", PlainText $ feUserReport entry)
69+
[ ("Sender timezone", Mrkdwn $ cs $ toTZName $ feSenderTimezone entry)
70+
, ("User report", Mrkdwn $ feUserReport entry)
7271
]
7372
]
7473
PostMessageReq {..}

src/TzBot/ProcessEvents/Message.hs

+6-6
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ processMessageEvent evt =
124124

125125
let now = meTs evt
126126
channelId = meChannel evt
127-
let sendAction :: Bool -> TranslationPairs -> UserId -> BotM ()
127+
let sendAction :: SenderFlag -> TranslationPairs -> UserId -> BotM ()
128128
sendAction toSender transl userId = do
129129
let req = PostEphemeralReq
130130
{ perUser = userId
@@ -133,7 +133,7 @@ processMessageEvent evt =
133133
, perText = joinTranslationPairs toSender transl
134134
, perBlocks = NE.nonEmpty $
135135
renderSlackBlocks toSender (Just transl) <>
136-
[ BSection $ markdownSection (Mrkdwn Fixtures.helpUsage) Nothing
136+
[ BSection $ markdownSection (Mrkdwn Fixtures.helpUsage)
137137
| whetherToShowHelpCmd
138138
]
139139
}
@@ -152,15 +152,15 @@ processMessageEvent evt =
152152
let ephemeralMessage = renderAllForOthersTP sender ephemeralTemplate
153153
logInfo [int||Received message from the DM, sending translation \
154154
to the author|]
155-
sendAction False ephemeralMessage (uId sender)
155+
sendAction asForOthersS ephemeralMessage (uId sender)
156156
_ -> do
157157
usersInChannelIds <- getChannelMembersCached channelId
158158

159159
whenJust (renderErrorsForSenderTP ephemeralTemplate) $ \errorsMsg -> do
160160
logInfo
161161
[int||Found invalid time references, \
162162
sending an ephemeral with them to the message sender|]
163-
sendAction True errorsMsg (uId sender)
163+
sendAction asForSenderS errorsMsg (uId sender)
164164

165165
let notBotAndSameTimeZone u = not (uIsBot u) && uTz u /= uTz sender
166166
notSender userId = userId /= uId sender
@@ -172,7 +172,7 @@ processMessageEvent evt =
172172
then do
173173
userInChannel <- getUserCached userInChannelId
174174
let ephemeralMessage = renderAllForOthersTP userInChannel ephemeralTemplate
175-
sendAction False ephemeralMessage (uId userInChannel)
175+
sendAction asForOthersS ephemeralMessage (uId userInChannel)
176176
pure True
177177
else do
178178
let whenT :: (Monad m) => Bool -> m Bool -> m Bool
@@ -181,7 +181,7 @@ processMessageEvent evt =
181181
userInChannel <- getUserCached userInChannelId
182182
whenT (notBotAndSameTimeZone userInChannel) $ do
183183
let ephemeralMessage = renderAllForOthersTP userInChannel ephemeralTemplate
184-
sendAction False ephemeralMessage (uId userInChannel)
184+
sendAction asForOthersS ephemeralMessage (uId userInChannel)
185185
pure True
186186

187187
let failedMsg = "Ephemeral sending failed" :: Builder

src/TzBot/Render.hs

+32-19
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ module TzBot.Render
2222

2323
-- * General template
2424
, renderTemplate
25+
26+
-- * Flags
27+
, SenderFlag
28+
, asForSenderS
29+
, asForOthersS
2530
) where
2631

2732
import Universum
@@ -38,7 +43,7 @@ import Data.Time.Zones.All (TZLabel)
3843
import Text.Interpolation.Nyan (int, rmode')
3944

4045
import TzBot.Instances ()
41-
import TzBot.Slack.API (Mrkdwn(Mrkdwn), PlainText(..), User(uTz))
46+
import TzBot.Slack.API (Mrkdwn(Mrkdwn), User(uTz))
4247
import TzBot.Slack.API.Block
4348
import TzBot.TimeReference
4449
import TzBot.Util
@@ -66,10 +71,20 @@ type TranslationPairs = NE.NonEmpty TranslationPair
6671

6772
newtype Template = Template { unTemplate :: NE.NonEmpty EitherTemplateUnit }
6873

69-
chooseNote :: Bool -> TranslationPair -> Maybe Text
70-
chooseNote sender = if sender then tuNoteForSender else tuNoteForOthers
74+
--------
75+
-- Notes
76+
-- | Used for choosing between two notes
77+
newtype SenderFlag = SenderFlag Bool
78+
79+
asForSenderS, asForOthersS :: SenderFlag
80+
asForSenderS = SenderFlag True
81+
asForOthersS = SenderFlag False
82+
83+
chooseNote :: SenderFlag -> TranslationPair -> Maybe Text
84+
chooseNote (SenderFlag sender) = if sender then tuNoteForSender else tuNoteForOthers
7185

72-
concatTranslationPair :: Bool -> TranslationPair -> Builder
86+
--------
87+
concatTranslationPair :: SenderFlag -> TranslationPair -> Builder
7388
concatTranslationPair sender t@TranslationPair {..} = do
7489
let rightNote = chooseNote sender t
7590
let note = maybe "" (("\n" <>) . fromText) rightNote
@@ -89,33 +104,31 @@ noRefsFoundMsg = "No time references found."
89104

90105
-- Render text
91106
renderErrorsForSender :: Template -> Maybe Text
92-
renderErrorsForSender template = do
93-
let sender = True
94-
joinTranslationPairs sender <$> renderErrorsForSenderTP template
107+
renderErrorsForSender template =
108+
joinTranslationPairs asForSenderS <$> renderErrorsForSenderTP template
95109

96110
renderAllForOthers :: User -> Template -> Text
97-
renderAllForOthers user = do
98-
let sender = False
99-
joinTranslationPairs sender . renderAllForOthersTP user
111+
renderAllForOthers user =
112+
joinTranslationPairs asForOthersS . renderAllForOthersTP user
100113

101-
joinTranslationPairs :: Bool -> TranslationPairs -> Text
114+
joinTranslationPairs :: SenderFlag -> TranslationPairs -> Text
102115
joinTranslationPairs sender =
103116
T.toStrict . toLazyText . fold . NE.toList
104117
. NE.map ((<> singleton '\n') . concatTranslationPair sender)
105118

106119
-- Render Slack block
107-
renderSlackBlocks :: Bool -> Maybe TranslationPairs -> [Block]
120+
renderSlackBlocks :: SenderFlag -> Maybe TranslationPairs -> [Block]
108121
renderSlackBlocks forSender =
109122
maybe [noRefsFoundSection]
110123
(intercalate [BDivider divider] . NE.toList . NE.map mkTranslationBlocks)
111124
where
112-
noRefsFoundSection = BSection $ textSection (PlainText noRefsFoundMsg) Nothing
125+
noRefsFoundSection = BSection $ markdownSection $ Mrkdwn noRefsFoundMsg
113126
mkTranslationBlocks :: TranslationPair -> [Block]
114127
mkTranslationBlocks timeRef = do
115-
let t = (PlainText $ tuTimeRef timeRef, PlainText $ tuTranslation timeRef)
128+
let t = (Mrkdwn $ tuTimeRef timeRef, Mrkdwn $ tuTranslation timeRef)
116129
mbNote = chooseNote forSender timeRef
117-
translationBlock = BSection $ fieldsSection Nothing Nothing $ NE.singleton t
118-
mkNoteBlock note = BSection $ markdownSection (Mrkdwn note) Nothing
130+
translationBlock = BSection $ fieldsSection Nothing $ NE.singleton t
131+
mkNoteBlock note = BSection $ markdownSection (Mrkdwn note)
119132
withMaybe mbNote [translationBlock] $ \note -> [translationBlock, mkNoteBlock note]
120133

121134
renderTemplate :: UTCTime -> User -> NE.NonEmpty TimeReference -> Template
@@ -132,7 +145,7 @@ renderEphemeralMessageTranslationPair
132145
-> (TimeReference, TimeReferenceToUTCResult)
133146
-> EitherTemplateUnit
134147
renderEphemeralMessageTranslationPair now sender (timeRef, result) = case result of
135-
TRTUSuccess utcTime _offsetInfo -> do
148+
TRTUSuccess (TimeRefSuccess utcTime _offsetInfo) -> do
136149
let mbSenderTimeZone =
137150
guard (isNothing $ trLocationRef timeRef)
138151
$> (uTz sender) :: Maybe TZLabel
@@ -145,7 +158,7 @@ renderEphemeralMessageTranslationPair now sender (timeRef, result) = case result
145158
[int||#{renderedUserTime} in #{userTzLabel}|]
146159
Nothing
147160
Nothing
148-
TRTUAmbiguous implicitSenderTimezone tzLabel -> do
161+
TRTUAmbiguous (TimeShiftErrorInfo implicitSenderTimezone tzLabel) -> do
149162
let shownTZ = shownTimezone implicitSenderTimezone tzLabel
150163
Left $ TranslationPair
151164
{ tuTimeRef = trText timeRef
@@ -159,7 +172,7 @@ renderEphemeralMessageTranslationPair now sender (timeRef, result) = case result
159172
time, and this particular timestamp can be possible with \
160173
different offsets._|]
161174
}
162-
TRTUInvalid implicitSenderTimezone tzLabel -> do
175+
TRTUInvalid (TimeShiftErrorInfo implicitSenderTimezone tzLabel) -> do
163176
let shownTZ = shownTimezone implicitSenderTimezone tzLabel
164177
Left $ TranslationPair
165178
{ tuTimeRef = trText timeRef

src/TzBot/Slack/API/Block.hs

+8-12
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ module TzBot.Slack.API.Block
1212
, ActionId(..)
1313
, BlockId(..)
1414
, Section
15-
, textSection
1615
, markdownSection
1716
, fieldsSection
1817
, Divider(..)
@@ -27,7 +26,7 @@ import Data.List.NonEmpty qualified as NE
2726
import Formatting (Buildable)
2827

2928
import TzBot.Instances ()
30-
import TzBot.Slack.API.Common (Mrkdwn, PlainText, TextObject(TOMarkdownText, TOPlainText))
29+
import TzBot.Slack.API.Common (Mrkdwn(..), PlainText)
3130
import TzBot.Util (SumWrapper(..), TypedWrapper(..), neConcatMap)
3231

3332
newtype ActionId = ActionId { unActionId :: Text }
@@ -51,21 +50,18 @@ data Block
5150

5251
-- | See https://api.slack.com/reference/block-kit/blocks#section
5352
data Section = Section
54-
{ sText :: Maybe TextObject
53+
{ sText :: Maybe Mrkdwn
5554
, sAccessory :: Maybe Button
56-
, sFields :: Maybe (NE.NonEmpty PlainText)
55+
, sFields :: Maybe (NE.NonEmpty Mrkdwn)
5756
} deriving stock (Eq, Show, Generic)
5857
deriving ToJSON via TypedWrapper Section
5958

60-
textSection :: PlainText -> Maybe Button -> Section
61-
textSection text mbButton = Section (Just $ TOPlainText text) mbButton Nothing
59+
markdownSection :: Mrkdwn -> Section
60+
markdownSection markdown = Section (Just markdown) Nothing Nothing
6261

63-
markdownSection :: Mrkdwn -> Maybe Button -> Section
64-
markdownSection markdown mbButton = Section (Just $ TOMarkdownText markdown) mbButton Nothing
65-
66-
fieldsSection :: Maybe TextObject -> Maybe Button -> NE.NonEmpty (PlainText, PlainText) -> Section
67-
fieldsSection mbText mbButton fields =
68-
Section mbText mbButton $ Just $ neConcatMap (\(x, y) -> x :| [y]) fields
62+
fieldsSection :: Maybe Mrkdwn -> NE.NonEmpty (Mrkdwn, Mrkdwn) -> Section
63+
fieldsSection mbText fields =
64+
Section mbText Nothing $ Just $ neConcatMap (\(x, y) -> x :| [y]) fields
6965

7066
-- | See https://api.slack.com/reference/block-kit/blocks#divider
7167
data Divider = Divider

src/TzBot/Slack/Modal.hs

+3-4
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import Data.List (singleton)
1010

1111
import TzBot.Feedback.Dialog.Types
1212
import TzBot.Instances ()
13-
import TzBot.Render (TranslationPairs, renderSlackBlocks)
13+
import TzBot.Render (TranslationPairs, asForOthersS, renderSlackBlocks)
1414
import TzBot.Slack.API
1515
import TzBot.Slack.Fixtures qualified as Fixtures
1616

@@ -64,13 +64,12 @@ mkBlocks :: Text -> Maybe TranslationPairs -> Block -> [Block]
6464
mkBlocks shownMessageText translatedMessage block =
6565
-- We always render the translation for other users (not author),
6666
-- so the author can see how his message is translated for others
67-
let forSender = False in
6867
[ BHeader Header { hText = PlainText "Message text" }
69-
, BSection $ textSection (PlainText shownMessageText) Nothing
68+
, BSection $ markdownSection (Mrkdwn shownMessageText)
7069
, BDivider divider
7170
, BHeader Header { hText = PlainText "Time references" }
7271
]
73-
<> renderSlackBlocks forSender translatedMessage
72+
<> renderSlackBlocks asForOthersS translatedMessage
7473
<> [ BDivider divider
7574
, block
7675
]

src/TzBot/TimeReference.hs

+33-19
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,14 @@ utcToUtcLocalTime = utcToLocalTime utc
8484
utcLocalTimeToUTC :: LocalTime -> UTCTime
8585
utcLocalTimeToUTC = localTimeToUTC utc
8686

87+
convertUtcToOffsetTime :: Offset -> UTCTime -> LocalTime
88+
convertUtcToOffsetTime offset utcTime =
89+
addLocalTime (offsetToNominalDiffTime offset) (utcToUtcLocalTime utcTime)
90+
91+
convertOffsetTimeToUtc :: Offset -> LocalTime -> UTCTime
92+
convertOffsetTimeToUtc offset localTime =
93+
utcLocalTimeToUTC $ addLocalTime (negate $ offsetToNominalDiffTime offset) localTime
94+
8795
{- | Converts a time reference to a moment in time (expressed in UTC).
8896
8997
If the time reference contains a timezone abbreviation, and if that abbreviation\
@@ -105,29 +113,29 @@ timeReferenceToUTC sendersTZLabel eventTimestamp TimeReference {..} =
105113
-- In the case of rigid offset we don't need the `modifyLocal` from
106114
-- from the `tztime` package because there are no timeshifts that
107115
-- we should take into account. So we just use plain LocalTime.
108-
let eventTimeUTC = utcToUtcLocalTime eventTimestamp
109-
offsetNominal = offsetToNominalDiffTime offset
110-
let refTimeUTC = eventTimeUTC & (
111-
addLocalTime offsetNominal
116+
let refTimeUTC = eventTimestamp & (
117+
convertUtcToOffsetTime offset
112118
>>> dayTransition
113119
>>> TZT.atTimeOfDay trTimeOfDay
114-
>>> addLocalTime (negate offsetNominal)
120+
>>> convertOffsetTimeToUtc offset
115121
)
116-
TRTUSuccess (utcLocalTimeToUTC refTimeUTC) (Right offset)
122+
TRTUSuccess $ TimeRefSuccess refTimeUTC (Right offset)
117123
Right (Left (tzLabel, implicitSenderTimezone)) -> do
118124
let eventLocalTime = TZT.fromUTC (TZI.fromLabel tzLabel) eventTimestamp
119125
let eithRefTime = eventLocalTime & TZT.modifyLocalStrict (
120126
dayTransition >>> TZT.atTimeOfDay trTimeOfDay
121127
)
122128
case eithRefTime of
123129
Left err -> tzErrorToResult implicitSenderTimezone tzLabel err
124-
Right refTime -> TRTUSuccess (TZT.toUTC refTime) (Left tzLabel)
130+
Right refTime -> TRTUSuccess $ TimeRefSuccess (TZT.toUTC refTime) (Left tzLabel)
125131

126132
where
127133
tzErrorToResult :: Bool -> TZLabel -> TZT.TZError -> TimeReferenceToUTCResult
128134
tzErrorToResult implicitSenderTimezone tzLabel = \case
129-
TZT.TZOverlap {} -> TRTUAmbiguous implicitSenderTimezone tzLabel
130-
TZT.TZGap {} -> TRTUInvalid implicitSenderTimezone tzLabel
135+
TZT.TZOverlap {} -> TRTUAmbiguous $
136+
TimeShiftErrorInfo implicitSenderTimezone tzLabel
137+
TZT.TZGap {} -> TRTUInvalid $
138+
TimeShiftErrorInfo implicitSenderTimezone tzLabel
131139

132140
-- This doesn't include setting time, only date changes
133141
dayTransition :: LocalTime -> LocalTime
@@ -209,21 +217,27 @@ chooseBestYear dayOfMonth monthOfYear now = do
209217
sortedCandidates = NE.sortBy (compare `on` calcWeight) candidates
210218
NE.head sortedCandidates
211219

212-
type IsImplicitSenderTimezone = Bool
220+
data TimeRefSuccess = TimeRefSuccess
221+
{ trsUtcResult :: UTCTime
222+
-- ^ The result of the conversion.
223+
, trsEithTzOffset :: Either TZLabel Offset
224+
-- ^ The timezone or offset that this TimeReference is related to.
225+
-- When the `TimeReference` does not explicitly mention a timezone/offset,
226+
-- we assume it's related to the sender's timezone.
227+
} deriving stock (Eq, Show)
228+
229+
data TimeShiftErrorInfo = TimeShiftErrorInfo
230+
{ tseiIsImplicitSenderTimezone :: Bool
231+
, tseiRefTimeZone :: TZLabel
232+
} deriving stock (Eq, Show)
213233

214234
data TimeReferenceToUTCResult
215-
= TRTUSuccess
235+
= TRTUSuccess TimeRefSuccess
216236
-- ^ Conversion succeeded.
217-
UTCTime
218-
-- ^ The result of the conversion.
219-
(Either TZLabel Offset)
220-
-- ^ The timezone or offset that this TimeReference is related to.
221-
-- When the `TimeReference` does not explicitly mention a timezone/offset,
222-
-- we assume it's related to the sender's timezone.
223-
| TRTUAmbiguous IsImplicitSenderTimezone TZLabel
237+
| TRTUAmbiguous TimeShiftErrorInfo
224238
-- ^ The time reference was ambiguous (e.g. due to a time ocurring twice in the same timezone during DST changes).
225239
-- See [Edge cases & pitfalls](https://github.com/serokell/tzbot/blob/main/docs/pitfalls.md#ambiguous-times).
226-
| TRTUInvalid IsImplicitSenderTimezone TZLabel
240+
| TRTUInvalid TimeShiftErrorInfo
227241
-- ^ The time reference was invalid (e.g. due to a time being skipped in a timezone during DST changes).
228242
-- See [Edge cases & pitfalls](https://github.com/serokell/tzbot/blob/main/docs/pitfalls.md#invalid-times).
229243
| TRTUInvalidTimeZoneAbbrev TimeZoneAbbreviation

0 commit comments

Comments
 (0)