mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
Normalizes non-standard code-fence language tags across `docs/**` so a strict highlighter (Shiki, used by Fumadocs) won't fail the build on an unrecognized language, and unifies redundant synonym tags onto one canonical form per language. The current renderer (Speed-Highlight) detects the language from the code content, not the fence label, so this drift wasn't visible until now. ## Changes - `hcl` -> `tf` (199 fences, including indented ones nested in numbered/bulleted lists). Shiki ships `hcl` and `terraform` as two distinct grammars (not aliases); every `hcl`-tagged fence in `docs/**` is actually Terraform resource/data/provider syntax, so the more specific `terraform` grammar is correct for all of them. `tf` is Shiki's own alias for that grammar, and it's also what GitHub's own markdown renderer resolves to the same HCL/Terraform highlighting. - `pwsh`/`powershell` -> `ps1`. Both `ps` and `ps1` are registered PowerShell aliases in Shiki, but on GitHub's renderer only `.ps1` is a registered file extension (`.ps` isn't), so `ps1` renders identically to `powershell` there today while bare `ps` would silently lose highlighting. - `env` -> `dotenv` (a dedicated Shiki grammar for `KEY=VALUE` files) - `text`/`output`/`none`/`url` -> `txt`. Same built-in plain-text fallback either way, just shorter. - `Dockerfile` -> `dockerfile` (lowercase) - `bash`/`shell` -> `sh` (732 fences). Shiki and GitHub both alias all three to a single shell grammar; this was already the style guide's stated preference, just not enforced across the existing corpus until now. - `markdown` -> `md` (4 fences). Alias of the same grammar in both Shiki and GitHub. - `jsonc` -> `json` (1 fence). The block has no comments or trailing commas, so it doesn't need the comments-capable grammar. - `ts` -> `tsx` (2 fences, `docs/about/contributing/frontend.md`). Verified the actual content tokenizes identically under both grammars, and a sibling block in the same file already needs `tsx` for real JSX, so unifying to one tag is safe for this file. Documented a caveat: `tsx` mis-tokenizes the legacy angle-bracket type-assertion syntax (`<Type>value`), which is invalid in real `.tsx` files anyway, so use `value as Type` instead. - `yml` -> `yaml` (1 fence) - Updated `docs/.style/style-guide/formatting.md` to document all canonical tags `promql` (2 fences) and `caddyfile` (2 fences) are left as-is. Shiki doesn't bundle a grammar for either, so they need a custom grammar registration when the site adopts Shiki, rather than degrading to `txt`. Tracked as follow-up work under DOCS-118 and [DOCS-544](https://linear.app/codercom/issue/DOCS-544/vendor-a-local-promql-grammar-for-shiki-syntax-highlighting) (promql). Does not touch `offlinedocs/`. Linear: [DOCS-476](https://linear.app/codercom/issue/DOCS-476/normalize-docs-code-fence-languages-de-risk-shikifumadocs) <details> <summary>How the fence tags were verified</summary> Each tag was tested against a real `shiki@latest` highlighter instance (`codeToHtml`/`codeToTokens`) and cross-checked against GitHub's `@wooorm/starry-night` grammar sources (the renderer that actually displays these `.md` files today, in repo browsing and PR diffs), since that's what determines whether brevity is safe before Shiki adoption: ```text FAIL env -- Language `env` is not included in this bundle. FAIL Dockerfile -- Language `Dockerfile` is not included in this bundle. FAIL promql -- Language `promql` is not included in this bundle. FAIL caddyfile -- Language `caddyfile` is not included in this bundle. FAIL pwsh -- Language `pwsh` is not included in this bundle. FAIL output -- Language `output` is not included in this bundle. ``` `hcl` doesn't error in Shiki, since it's a real grammar, but that's exactly the trap: it was silently rendering every fence with the generic HCL grammar instead of the Terraform-specific one. Every `hcl`-tagged fence in `docs/**` was manually checked against `origin/main` and is genuinely Terraform content. For `ts`/`tsx`, tokenizing the actual doc content confirmed identical output under both grammars; a synthetic test with the legacy angle-bracket cast syntax confirmed `tsx` degrades on that specific construct, which the style guide now calls out. The first normalization pass only matched fence tags at column 0 (`^```tag$`), missing tags indented inside numbered/bulleted lists. A follow-up pass caught the remaining occurrences at any indentation level. </details> --- *This PR description and the underlying changes were prepared with Coder Agents assistance.*
204 lines
6.3 KiB
Markdown
204 lines
6.3 KiB
Markdown
# Slack Notifications
|
|
|
|
[Slack](https://slack.com/) is a popular messaging platform designed for teams
|
|
and businesses, enabling real-time collaboration through channels, direct
|
|
messages, and integrations with external tools. With Coder's integration, you
|
|
can enable automated notifications directly within a self-hosted
|
|
[Slack app](https://api.slack.com/apps), keeping your team updated on key events
|
|
in your Coder environment.
|
|
|
|
Administrators can configure Coder to send notifications via an incoming webhook
|
|
endpoint. These notifications will be delivered as Slack messages direct to the
|
|
user. Routing is based on the user's email address, and this should be
|
|
consistent between Slack and their Coder login.
|
|
|
|
## Requirements
|
|
|
|
Before setting up Slack notifications, ensure that you have the following:
|
|
|
|
- Administrator access to the Slack platform to create apps
|
|
- Coder platform >=v2.16.0
|
|
|
|
## Create Slack Application
|
|
|
|
To integrate Slack with Coder, follow these steps to create a Slack application:
|
|
|
|
1. Go to the [Slack Apps](https://api.slack.com/apps) dashboard and create a new
|
|
Slack App.
|
|
|
|
2. Under "Basic Information," you'll find a "Signing Secret." The Slack
|
|
application uses it to
|
|
[verify requests](https://api.slack.com/authentication/verifying-requests-from-slack)
|
|
coming from Slack.
|
|
|
|
3. Under "OAuth & Permissions", add the following OAuth scopes:
|
|
|
|
- `chat:write`: To send messages as the app.
|
|
- `users:read`: To find the user details.
|
|
- `users:read.email`: To find user emails.
|
|
|
|
4. Install the app to your workspace and note down the **Bot User OAuth Token**
|
|
from the "OAuth & Permissions" section.
|
|
|
|
## Build a Webserver to Receive Webhooks
|
|
|
|
The Slack bot for Coder runs as a _Bolt application_, which is a framework
|
|
designed for building Slack apps using the Slack API.
|
|
[Bolt for JavaScript](https://github.com/slackapi/bolt-js) provides an
|
|
easy-to-use API for responding to events, commands, and interactions from Slack.
|
|
|
|
To build the server to receive webhooks and interact with Slack:
|
|
|
|
1. Initialize your project by running:
|
|
|
|
```sh
|
|
npm init -y
|
|
```
|
|
|
|
2. Install the Bolt library:
|
|
|
|
```sh
|
|
npm install @slack/bolt
|
|
```
|
|
|
|
3. Create and edit the `app.js` file. Below is an example of the basic
|
|
structure:
|
|
|
|
```js
|
|
const { App, LogLevel, ExpressReceiver } = require("@slack/bolt");
|
|
const bodyParser = require("body-parser");
|
|
|
|
const port = process.env.PORT || 6000;
|
|
|
|
// Create a Bolt Receiver
|
|
const receiver = new ExpressReceiver({
|
|
signingSecret: process.env.SLACK_SIGNING_SECRET,
|
|
});
|
|
receiver.router.use(bodyParser.json());
|
|
|
|
// Create the Bolt App, using the receiver
|
|
const app = new App({
|
|
token: process.env.SLACK_BOT_TOKEN,
|
|
logLevel: LogLevel.DEBUG,
|
|
receiver,
|
|
});
|
|
|
|
receiver.router.post("/v1/webhook", async (req, res) => {
|
|
try {
|
|
if (!req.body) {
|
|
return res.status(400).send("Error: request body is missing");
|
|
}
|
|
|
|
const { title, body_markdown } = req.body;
|
|
if (!title || !body_markdown) {
|
|
return res
|
|
.status(400)
|
|
.send('Error: missing fields: "title", or "body_markdown"');
|
|
}
|
|
|
|
const payload = req.body.payload;
|
|
if (!payload) {
|
|
return res.status(400).send('Error: missing "payload" field');
|
|
}
|
|
|
|
const { user_email, actions } = payload;
|
|
if (!user_email || !actions) {
|
|
return res
|
|
.status(400)
|
|
.send('Error: missing fields: "user_email", "actions"');
|
|
}
|
|
|
|
// Get the user ID using Slack API
|
|
const userByEmail = await app.client.users.lookupByEmail({
|
|
email: user_email,
|
|
});
|
|
|
|
const slackMessage = {
|
|
channel: userByEmail.user.id,
|
|
text: body_markdown,
|
|
blocks: [
|
|
{
|
|
type: "header",
|
|
text: { type: "plain_text", text: title },
|
|
},
|
|
{
|
|
type: "section",
|
|
text: { type: "mrkdwn", text: body_markdown },
|
|
},
|
|
],
|
|
};
|
|
|
|
// Add action buttons if they exist
|
|
if (actions && actions.length > 0) {
|
|
slackMessage.blocks.push({
|
|
type: "actions",
|
|
elements: actions.map((action) => ({
|
|
type: "button",
|
|
text: { type: "plain_text", text: action.label },
|
|
url: action.url,
|
|
})),
|
|
});
|
|
}
|
|
|
|
// Post message to the user on Slack
|
|
await app.client.chat.postMessage(slackMessage);
|
|
|
|
res.status(204).send();
|
|
} catch (error) {
|
|
console.error("Error sending message:", error);
|
|
res.status(500).send();
|
|
}
|
|
});
|
|
|
|
// Acknowledge clicks on link_button, otherwise Slack UI
|
|
// complains about missing events.
|
|
app.action("button_click", async ({ body, ack, say }) => {
|
|
await ack(); // no specific action needed
|
|
});
|
|
|
|
// Start the Bolt app
|
|
(async () => {
|
|
await app.start(port);
|
|
console.log("⚡️ Coder Slack bot is running!");
|
|
})();
|
|
```
|
|
|
|
4. Set environment variables to identify the Slack app:
|
|
|
|
```sh
|
|
export SLACK_BOT_TOKEN=xoxb-...
|
|
export SLACK_SIGNING_SECRET=0da4b...
|
|
```
|
|
|
|
5. Start the web application by running:
|
|
|
|
```sh
|
|
node app.js
|
|
```
|
|
|
|
## Enable Interactivity in Slack
|
|
|
|
Slack requires the bot to acknowledge when a user clicks on a URL action button.
|
|
This is handled by setting up interactivity.
|
|
|
|
Under "Interactivity & Shortcuts" in your Slack app settings, set the Request
|
|
URL to match the public URL of your web server's endpoint.
|
|
|
|
You can use any public endpoint that accepts and responds to POST requests with HTTP 200.
|
|
For temporary testing, you can set it to `https://httpbin.org/status/200`.
|
|
|
|
Once this is set, Slack will send interaction payloads to your server, which
|
|
must respond appropriately.
|
|
|
|
## Enable Webhook Integration in Coder
|
|
|
|
To enable webhook integration in Coder, define the POST webhook endpoint
|
|
matching the deployed Slack bot:
|
|
|
|
```sh
|
|
export CODER_NOTIFICATIONS_WEBHOOK_ENDPOINT=http://localhost:6000/v1/webhook`
|
|
```
|
|
|
|
Finally, go to the **Notification Settings** in Coder and switch the notifier to
|
|
**Webhook**.
|