Skip to content

Conversation

jakeschurch
Copy link
Contributor

@jakeschurch jakeschurch commented Aug 21, 2025

Description

This PR introduces robust caching and mapping of Slack user details by GitHub login, significantly reducing reliance on Slack and GitHub API calls and mitigating rate limiting issues. It incorporates extensive requested changes and feedback from review:

  • Mapping Support & Caching

    • Add support for providing a GitHub-to-Slack user mapping via environment variable (SLACK_DEPLOY_GITHUB_USERS, formerly SLACK_GITHUB_MAPPING_RAW) or file.
    • Cache Slack user details by GitHub login, skipping API calls when mapping is available.
    • Skip adding users to cache if errors are thrown or if users are deactivated/deleted.
  • Action & Workflow Refactor

    • Split notify and generate-mapping actions into distinct entrypoints.
    • Rename default action workflow to notify in dist output.
    • Introduce .github/actions/generate-mapping/action.yml for generating a JSON mapping of Slack users by GitHub login.
    • Refactor main entry to pass mapping input for author resolution.
  • Code Quality & Feedback

    • Unify mapping shape validation and add type guard tests.
    • Extract Slack user lookup logic to reusable helper function.
    • Reference existing MessageAuthor type for mapping output.
    • Improve code readability, error handling, and caching logic.
    • Use getEnv for environment variable access.
    • Rename variables and inputs for clarity and consistency.
    • Remove redundant error logging and unnecessary print statements.
    • Address parallel API call concerns and clarify rate limiting handling.
  • Documentation

    • Update README to document new environment variable, mapping usage, and benefits.
    • Add examples and clarify usage for custom mapping.

Testing

Reviewer Notes

  • All requested changes from @namoscato have been addressed, including variable naming, error handling, type validation, and code extraction.
  • No merge conflicts; all checks have passed.

NOTES:

- Introduce `.github/actions/generate-mapping/action.yml` for
  generating a JSON mapping of Slack users by GitHub login.

- Add `src/githubToSlackMapping.ts` to fetch GitHub org users,
  match them to Slack users, and output mapping.

- Refactor `src/main.ts` to support `notify` and `generate-mapping`
  modes via `__mode` input.

- Update `action.yml` to include `__mode` input for internal mode
  selection.

Signed-off-by: Jake Schurch <[email protected]>
…er by GitHub login

- Introduce `user_mapping_filepath` input in `action.yml` to optionally
  provide a mapping file for Slack user details by GitHub login.

- Update `getMessageAuthorFactory` and related logic to use the mapping
  file if provided, avoiding Slack and GitHub API calls when possible.

- Add fallback and error handling for mapping file usage.

- Refactor main entry to pass `user_mapping_filepath` to author
  resolution.

Signed-off-by: Jake Schurch <[email protected]>
@jakeschurch jakeschurch self-assigned this Aug 21, 2025
…er by GitHub login

- Introduce `user_mapping_filepath` input in `action.yml` to optionally
  provide a mapping file for Slack user details by GitHub login.

- Update `getMessageAuthorFactory` and related logic to use the mapping
  file if provided, avoiding Slack and GitHub API calls when possible.

- Add fallback and error handling for mapping file usage.

- Refactor main entry to pass `user_mapping_filepath` to author
  resolution.

Signed-off-by: Jake Schurch <[email protected]>
@jakeschurch jakeschurch force-pushed the jakeschurch/sc-30416/cache-slack-users-to-mitigate-api-rate-limiting branch from fc64a95 to 346c9a6 Compare August 21, 2025 15:58
Signed-off-by: Jake Schurch <[email protected]>
Signed-off-by: Jake Schurch <[email protected]>
@jakeschurch jakeschurch force-pushed the jakeschurch/sc-30416/cache-slack-users-to-mitigate-api-rate-limiting branch from 9430cde to ae30272 Compare August 21, 2025 16:58
Signed-off-by: Jake Schurch <[email protected]>
Signed-off-by: Jake Schurch <[email protected]>
- Separate notify and generateMapping logic into distinct entrypoints
  (`src/notify.ts` and new `src/generateMapping.ts`)

- Remove internal `__mode` input from action.yml files

- Update action.yml and generate-mapping/action.yml to reference new
  dist entrypoints

- Refactor package.json exports and scripts for new structure

- Improve maintainability and clarity by decoupling action
  responsibilities

refs: [sc-30416]

Signed-off-by: Jake Schurch <[email protected]>
…g to file

- Change output from file path to raw JSON string in GitHub Action
  output (`raw_mapping_json`)

- Update `generateGithubToSlackMapping` to return JSON string instead of
  writing to disk

- Remove file system operations from mapping generation logic

- Update action input/output and usage to reflect new output format

Signed-off-by: Jake Schurch <[email protected]>
…Slack user mapping

- Add details to README about the optional SLACK_GITHUB_MAPPING_RAW
  environment variable, allowing users to provide raw JSON or YAML
  mappings of GitHub logins to Slack user details.

- Explains usage, example formats, and benefits of avoiding API calls
  for user mapping.

refs: sc-30416

Signed-off-by: Jake Schurch <[email protected]>
Signed-off-by: Jake Schurch <[email protected]>
Signed-off-by: Jake Schurch <[email protected]>
Signed-off-by: Jake Schurch <[email protected]>
Signed-off-by: Jake Schurch <[email protected]>
@jakeschurch jakeschurch force-pushed the jakeschurch/sc-30416/cache-slack-users-to-mitigate-api-rate-limiting branch from fdef784 to 251c8b8 Compare September 9, 2025 18:43
@jakeschurch jakeschurch marked this pull request as ready for review September 9, 2025 18:59
Copy link
Collaborator

@namoscato namoscato left a comment

Choose a reason for hiding this comment

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

Thanks for your work on this, @jakeschurch – this is looking great!

I might take a pass through the README once the dust settles.

… guard tests

- Refactor mapping extraction to use `githubUserMapping` instead of
  `rawMapping`
- Validate mapping shape using new `isMessageAuthor` type guard
- Extract and use `isMemberWithProfile` and `isMessageAuthor` type
  guards across codebase
- Add tests for type guards in
  `src/slack/__tests__/types.test.ts`
- Update environment variable to `SLACK_DEPLOY_GITHUB_USERS`
- Remove redundant debug logging and simplify Slack user matching logic
- Update README

refs: [sc-30416]

Signed-off-by: Jake Schurch <[email protected]>
Signed-off-by: Jake Schurch <[email protected]>
…gic to helper

- Move Slack user lookup by GitHub name to new
  `getSlackUserFromGithubName` function for reuse and clarity.
- Replace inline Slack user matching logic in `getMessageAuthorFactory`
  and `generateGithubToSlackMapping` with the new helper.
- Update `isMemberWithProfile` type guard to remove `real_name` null
  check, relying only on `display_name` and `image_48` presence.
- Minor option default value change in `getMessageAuthorFactory`
  (`githubUserMapping: undefined`).

Signed-off-by: Jake Schurch <[email protected]>
Signed-off-by: Jake Schurch <[email protected]>
@jakeschurch jakeschurch changed the title [sc-30416]: cache slack user details by github user login to mitigate api rate limiting [sc-30416]: Cache Slack user details by GitHub user login to mitigate API rate limiting Sep 23, 2025
- move to separate module
- rename from getSlackUserFromGithubName
- filter MemberWithProfile in SlackClient
- remove unnecessary reduce
- surface empty set short circuits; move to warning
- preserve not found `null` values
- fix GitHubUser type
Copy link
Collaborator

@namoscato namoscato left a comment

Choose a reason for hiding this comment

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

This is close! I pushed up a few commits and have a few more comments before I do some manual testing on my end. I'm happy to help with some of the loose ends – just let me know.

}
}

async function generateMapping(
Copy link
Collaborator

Choose a reason for hiding this comment

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

@jakeschurch, would you be open to making this name more specific? I'd be happy to make the change, if so.

Comment on lines 40 to 41
channel,
errorReaction
Copy link
Collaborator

Choose a reason for hiding this comment

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

@jakeschurch, any objections to making these channel / errorReaction dependencies optional?

Comment on lines 7 to 49
/**
* Return all GitHub usernames from the specified `github_org`, mapped to their Slack user.
*
* The users are matched by full name. If a match is not found, the value will be `null`.
*/
export async function githubToSlackMapping(
octokit: OctokitClient,
slack: SlackClient,
github_org: string
): Promise<Record<string, MessageAuthor | null>> {
const githubUsers = await fetchGitHubUsers(octokit, github_org)
if (githubUsers.length === 0) {
warning('No GitHub members found.')
return {}
}

info('Fetching Slack users')
const slackUsers = await slack.getRealUsers()
if (slackUsers.length === 0) {
warning('No Slack users found.')
return {}
}

const result: Record<string, MessageAuthor | null> = {}

for (const githubUser of githubUsers) {
try {
const slackUser = getSlackUserFromName(slackUsers, githubUser.name)

result[githubUser.login] = {
slack_user_id: slackUser.id,
username: slackUser.profile.display_name,
icon_url: slackUser.profile.image_48
}
} catch (err) {
warning(err instanceof Error ? err.message : String(err))

result[githubUser.login] = null
}
}

return result
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

4edb065 simplified this implementation a bit.

Also notably, I widened the return interface to include null values, signifying a Slack user could not be matched. I was thinking that could enable a nice getMessageAuthor optimization – essentially skipping the expensive API calls when we already know a user will not match (via null) while still falling back to them for a presumably new / unknown user (via undefined).

Thoughts on that proposed behavior? I didn't update getMessageAuthor yet but happy to do so if you're onboard.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Updated getMessageAuthor in 4732a6a.

- collapse getMessageAuthorFromGithubUserMapping into maybeGetMessageAuthorFromGithubUserMapping
- tighten type predicate
- improve error handling
- introduce GitHubUserMapping type
- notify -> notifySlack
- generateMapping -> generateUserMapping
- move non-entrypoints to utils/ namespace
channel and errorReaction are unnecessary in the generateUserMapping context
Copy link
Collaborator

@namoscato namoscato left a comment

Choose a reason for hiding this comment

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

Alright I think this is pretty close to code-complete. @jakeschurch, would you mind glancing at where this is landing?

I just need to do some manual testing and would like to take a pass at documentation.

}
}

async function generateMapping(
Copy link
Collaborator

Choose a reason for hiding this comment

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

Renamed to generateUserMapping in b1f2cb5.

I also renamed notifynotifySlack.

Comment on lines 7 to 49
/**
* Return all GitHub usernames from the specified `github_org`, mapped to their Slack user.
*
* The users are matched by full name. If a match is not found, the value will be `null`.
*/
export async function githubToSlackMapping(
octokit: OctokitClient,
slack: SlackClient,
github_org: string
): Promise<Record<string, MessageAuthor | null>> {
const githubUsers = await fetchGitHubUsers(octokit, github_org)
if (githubUsers.length === 0) {
warning('No GitHub members found.')
return {}
}

info('Fetching Slack users')
const slackUsers = await slack.getRealUsers()
if (slackUsers.length === 0) {
warning('No Slack users found.')
return {}
}

const result: Record<string, MessageAuthor | null> = {}

for (const githubUser of githubUsers) {
try {
const slackUser = getSlackUserFromName(slackUsers, githubUser.name)

result[githubUser.login] = {
slack_user_id: slackUser.id,
username: slackUser.profile.display_name,
icon_url: slackUser.profile.image_48
}
} catch (err) {
warning(err instanceof Error ? err.message : String(err))

result[githubUser.login] = null
}
}

return result
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Updated getMessageAuthor in 4732a6a.

Comment on lines 40 to 41
channel,
errorReaction
Copy link
Collaborator

Choose a reason for hiding this comment

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

Updated in 942c96a.

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.

2 participants