Skip to content

Conversation

@ewanek1
Copy link
Owner

@ewanek1 ewanek1 commented Jul 21, 2025

Hello! PR for code review 🙂. Thank you!

@ewanek1 ewanek1 requested a review from WilliamBergamin July 22, 2025 17:47
@ewanek1 ewanek1 marked this pull request as ready for review July 22, 2025 17:48
@ewanek1 ewanek1 changed the title Add files via upload Initial Code Review Jul 22, 2025
Copy link
Collaborator

@WilliamBergamin WilliamBergamin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall this is great work 💯 its an awesome example of how to use the features of Bolt 🚀

Some small things to consider:

When opening PRs, its helpful for reviewers to include things like a testing steps, Demo video/images, a short description of what the changes are etc. For this PR I had a decent amount of context on the app and could follow the README, but it isn't always the case

Including a .gitignore file in your project is useful in order to filter out files that shouldn't be checked into the repositories, for example the node_modules folder or the .env file that contains the values for the environment variables.

Linters and formatters are great tools to facilitate collaboration on projects, they force every maintainer to adhere to a set of rules. In our SDKs we use biome

You've written some great unit tests, leveraging tools like c8 to figure out exactly what sections of your code are getting covered is extremely useful. Trying to reach 100% code coverage is overkill, but these types of tools help highlight "critical" sections that may have been missed

I don't think this is needed for your project but using some sort of continuous integration pipeline to run the linters, unit tests and code coverage on every change introduced in a project can greatly improve the reviewers/maintainers confidence in the change. It helps to ensure that new changes do not create unexpected behaviors. In out SDKs we use github actions to do this.

app.js Outdated
signingSecret: process.env.SLACK_SIGNING_SECRET,
socketMode: true,
appToken: process.env.SLACK_APP_TOKEN,
//logLevel: LogLevel.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this be some code that was intended to be deleted?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes! Thanks for catching that 🫡

@@ -0,0 +1,71 @@
const tzMap = {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes sense but I would like to know why you opted to place this in timeZoneMap.js rather then constants.js?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm I believe since the map was a more crucial part of my code I just separated it out in its own file.

/* The `convertTimeCommandText` function parses
* and converts a time between time zones
*/
function convertTimeCommandText(text) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about adding proper JSdocs here to add information on what text is expected to be and what the outputs of the function is?

As an outsider it can sometimes be hard to determine this just from the code

return {
error: `Sorry, I don't recognize the time zone abbreviation ${fromZoneAbbr}`,
};
} else if (!toZone) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because the if statement returns early I don't think we need the else keyword, I think we could get away with just if blocks 🤔 what dod you think?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I agree! I suppose another technique would be to store the error messages inside an array especially if I want to notify a user if both their to and from zones are invalid.

output_message = "Invalid input. Please use `/teamclock` or `/teamclock @usergroup`";
}
} catch (error) {
console.error("Error fetching user list:", error);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might be overkill for the project but this function could accept some sort of context input parameter that contains a logger, this way your entire project could use the Bolt logger 💡

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Edit: Heh I realize now I'm making similar comments elsewhere - using the context value is a very interesting idea though! 🧠 ✨

@@ -0,0 +1,105 @@
const { convertTimeCommandText } = require("../commands/convertTimeLogic.js");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice 💯

/* Formats a moment object into a time string and its timezone abbreviation
* Example: 3pm EST
*/
function formatTimeWithAbbr(momentObject) {
Copy link
Collaborator

@WilliamBergamin WilliamBergamin Jul 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sweet 💯

app.js Outdated
// Initializing Bolt app with Socket Mode
const app = new App({
token: process.env.SLACK_BOT_TOKEN,
signingSecret: process.env.SLACK_SIGNING_SECRET,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💭 Socket mode apps do not need the signing secret it is used to validate HTTP requests coming from Slack

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ohh thanks for that!

README.md Outdated
Example: `/convert 3pm est pst`
Converts a time between two time zones. Inputs are not case sensitive.

- **`/time [time] @user`**
Copy link
Collaborator

@WilliamBergamin WilliamBergamin Jul 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be /timefor instead of /time?

Does this command only accept one parameter?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes thanks for catching this. I'll fix it. I initially wrote this as /time @user. But I thought about it some more and figured a person would use their current time.

if (parts.length !== 1) {
return {
error:
"Invalid input format. Please use the `/time_for @user`\nExample: `/time_for @username`",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there might be a typo in the error message 🤔 how do you think we could improve this so that we maintain consistency across the project?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it - timefor not time_for.

Copy link
Collaborator

@zimeg zimeg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ewanek1 This is an impressive setup all around!

I left a handful of comments building off of what @WilliamBergamin shared, but I do want to callout a few thoughts I was having:

  • Reducing dependencies: I'm not sure if the native date handling of javascript can substitute the "moment" package, but for ongoing maintenance that might be optimal if so! Also, we might want to use @slack/bolt as the interface for all Slack interactions in this app.
  • Separating handlers: Listeners and logic and handlers alike are found in similar places which is nice for fast context but can cause confusion in later enhancements. Some ideas on changes to this were left in comment!
  • Error handling: Generous use of try/catch would move this app towards production readiness! Logging error cases might follow that, but I'm hoping most to avoid unexpected crashes.

Overall though, this is very solid development with a great grasp on the event driven setups of bolt! 🤖

Comment on lines 7 to 14
const { error, result } = convertTimeCommandText(command.text);

if (error) {
await respond({ text: error });
return;
}

await respond({ text: result });
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 I'm a big fan of treating errors as values, but this might miss an unexpected throw from calling code, or even from a failing ack above!

Some examples in documentation might not show this at the moment, but a nice pattern to use can be found in this sample:

https://github.com/slack-samples/bolt-js-starter-template/blob/main/listeners/commands/sample-command.js

Changing the returning error to a throw might take additional logic to parse and be excessive, but I do think a try/catch at the listener level is most useful to avoid a crashed program 🤓

* Expects input: '/teamclock @usergroup' for usergroup
*/

const moment = require("moment-timezone");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⏳ The time happenings included can no doubt use functions from elsewhere and I'm wondering if the Intl.DateTimeFormat class was considered instead of moment-timezone?

AFAICT it might be a suitable replacement, albeit with not as concise syntax... But I do tend to prefer fewer dependencies when possible!

Comment on lines 13 to 15
{ start: "17:00", end: "20:00", message: "After hours 🌙" },
{ start: "20:00", end: "24:00", message: "After hours 🌙" },
{ start: "00:00", end: "09:00", message: "Sleeping 😴" },
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔 Some of these time ranges have overlap at the end time which was confusing to me at a glance.

I'm somewhat curious if an ordered status list could be used instead to avoid some of the special case boundary checking below?

const timeRanges = [
   { start: "00:00", message: "Sleeping 😴" },
   { start: "09:00", message: "Available ✅" },
   { start: "16:00", message: "Getting late 🕓" },
   { start: "17:00", message: "After hours 🌙" },
];

A following check can iterate in reverse here (or somehow else) to find the closest start time here for the corresponding message, though I admit that's makes the ordering important.

const status = teamClockCommandText(curTime);

// Format output message line
messages.push(`${user.real_name}: ${curTimeFormatted} ⇔ ${status}`);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👁️‍🗨️ IIRC the real_name value isn't guaranteed here - formatting this to prefer the <@${user.id}> might be most safe for all outputs!

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you 😊

@@ -0,0 +1,76 @@
const { WebClient } = require("@slack/web-api");
const { convertTimeBetweenUsers } = require("./timeForLogic.js");
const client = new WebClient(process.env.SLACK_BOT_TOKEN);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const client = new WebClient(process.env.SLACK_BOT_TOKEN);

🪓 Passing the app.client to these functions might be a best practice to ensure the same client "options" are kept throughout the app.

I don't believe @slack/bolt changes these much, but if retry logic was changed during app initialization we'd want to make sure it matches here too!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👁️‍🗨️ I like that logic for each command is separated, but the separation between teamClock and teamClockLogic isn't so clear to me and I might favor a single file for all of this?

The listeners pattern in samples can be useful too once projects gain enough functionalities ✨

manifest.json Outdated
"org_deploy_enabled": true,
"socket_mode_enabled": true,
"token_rotation_enabled": false,
"hermes_app_type": "remote",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"hermes_app_type": "remote",

🪓 I believe this is leftover but the same as function_runtime...

package.json Outdated
},
"dependencies": {
"@slack/bolt": "^4.4.0",
"@slack/web-api": "^7.9.3",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"@slack/web-api": "^7.9.3",

We might have access to the client from @slack/bolt!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗣️ IMHO utils often hints at the need for another directory!

The time details that follow, and perhaps timezone specific logic for various listeners, could be moved into paths like so?

app.js
- listeners
  - commands
  - functions # for workflow steps
- tests
- timezones

The exact logic splits might not be right, but I do think similar organization can make the separation of Slack logic and timezone handling more clear 👾

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mmm I see. Thank you!

Comment on lines +96 to +99
module.exports = {
handleTimeZoneConversionWF,
handleUserConversionWF,
}; No newline at end of file
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Exporting these functions into separate files might make future additions more obvious since fewer questions of where something should go might appear.

The shared logic within complicates that a smidge, but I think for app setups it might still be preferred.

ewanek1 and others added 3 commits July 25, 2025 11:11
Fixes the typo in the `timefor` command and removes the necessity for a signing secret since Chrono uses socket mode.
Removes signing secret because Chrono uses socket mode.
- Removed timefor slash command typos.
- Removed signing secret mentionings since socket mode is used.
- Removed the timeZoneMap.js file and moved contents into constants.js for better organization.
- Removed the 'else' in the if/else statement in convertTimeLogic.js.
- Addressed the overlap in the timeRanges array.
- Handled cases where real_name is not guaranteed in teamClockLogic.js.
Copy link
Collaborator

@WilliamBergamin WilliamBergamin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great app 💯 most comments seem to be address 🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants