| 1 | import type { MailJob, Recipient, ThreadData } from "./types.js"; |
| 2 | |
| 3 | /** |
| 4 | * One example of every email Diffinery sends, for the template gallery in site |
| 5 | * administration, the "send me every email" check and the template tests. |
| 6 | */ |
| 7 | export function sampleJobs(pub: string, to: Recipient, me: string, repo = "acme/website"): Array<{ id: string; label: string; job: MailJob }> { |
| 8 | const at = new Date().toISOString(); |
| 9 | const repoId = "sample"; |
| 10 | const thread = (over: Partial<ThreadData>): ThreadData => ({ |
| 11 | repo, |
| 12 | repoId, |
| 13 | number: 42, |
| 14 | kind: "pull", |
| 15 | title: "Cache build artifacts between workflow runs", |
| 16 | event: "opened", |
| 17 | actor: "diffy-dev", |
| 18 | url: `${pub}/${repo}/pull/42`, |
| 19 | base: "main", |
| 20 | head: "cache-artifacts", |
| 21 | ...over, |
| 22 | }); |
| 23 | const body = "This keeps `node_modules` and the build output in the Actions cache, keyed on the lockfile.\n\n- Cold build: **3m 40s**\n- Warm build: **48s**\n\nCloses #37. cc @" + me; |
| 24 | const item = (id: string, label: string, j: Omit<MailJob, "to" | "at">) => ({ id, label, job: { ...j, to, at } as MailJob }); |
| 25 | const exp = new Date(Date.now() + 6 * 86400_000).toISOString(); |
| 26 | return [ |
| 27 | item("pr-opened", "Pull request opened", { kind: "thread", reason: "review_requested", data: thread({ body, intent: "Make CI twice as fast without changing what it tests.", reviewers: [me], commits: [{ sha: "3f9c2a1d8e7b6c5a4f3e2d1c0b9a8f7e6d5c4b3a", message: "Cache node_modules on the lockfile hash" }, { sha: "8a7b6c5d4e3f2a1b0c9d8e7f6a5b4c3d2e1f0a9b", message: "Cache the build output too" }] }) }), |
| 28 | item("issue-opened", "Issue opened (watching)", { kind: "thread", reason: "subscribed", data: thread({ kind: "issue", number: 37, title: "CI takes almost four minutes on every push", url: `${pub}/${repo}/issues/37`, body: "Every push reinstalls all dependencies. Could we cache them?", base: undefined, head: undefined }) }), |
| 29 | item("comment", "New comment (mention)", { kind: "thread", reason: "mention", data: thread({ event: "comment", actor: "rivera", body: `Looks good to me. @${me}, can you double-check the cache key on Windows runners?`, url: `${pub}/${repo}/pull/42#c-sample` }) }), |
| 30 | item("review-approved", "Review: approved", { kind: "thread", reason: "author", data: thread({ event: "review", state: "APPROVED", actor: "rivera", body: "Nice speed-up. Ship it." }) }), |
| 31 | item("review-changes", "Review: changes requested", { kind: "thread", reason: "author", data: thread({ event: "review", state: "CHANGES_REQUESTED", actor: "kim", body: "**.github/workflows/ci.yml:18**\n\nThe key should include the runner OS, or Linux and macOS caches will collide." }) }), |
| 32 | item("review-requested", "Review requested", { kind: "thread", reason: "review_requested", data: thread({ event: "review_requested", reviewers: [me], intent: "Make CI twice as fast without changing what it tests." }) }), |
| 33 | item("assigned", "Assigned", { kind: "thread", reason: "assign", data: thread({ kind: "issue", number: 37, title: "CI takes almost four minutes on every push", event: "assigned", assignees: [me], url: `${pub}/${repo}/issues/37` }) }), |
| 34 | item("pushed", "New commits pushed", { kind: "thread", reason: "comment", data: thread({ event: "pushed", commits: [{ sha: "c0ffee1d8e7b6c5a4f3e2d1c0b9a8f7e6d5c4b3a", message: "Include the runner OS in the cache key" }] }) }), |
| 35 | item("merged", "Pull request merged", { kind: "thread", reason: "author", data: thread({ event: "merged", actor: "kim" }) }), |
| 36 | item("closed", "Issue closed", { kind: "thread", reason: "author", data: thread({ kind: "issue", number: 37, title: "CI takes almost four minutes on every push", event: "closed", stateReason: "completed", url: `${pub}/${repo}/issues/37` }) }), |
| 37 | item("reopened", "Issue reopened", { kind: "thread", reason: "comment", data: thread({ kind: "issue", number: 37, title: "CI takes almost four minutes on every push", event: "reopened", url: `${pub}/${repo}/issues/37` }) }), |
| 38 | item("run-failed", "Workflow run failed", { |
| 39 | kind: "run", |
| 40 | reason: "ci_activity", |
| 41 | data: { |
| 42 | repo, |
| 43 | repoId, |
| 44 | runId: "sample", |
| 45 | runNumber: 318, |
| 46 | workflow: "CI", |
| 47 | title: "Cache node_modules on the lockfile hash", |
| 48 | conclusion: "failure", |
| 49 | event: "push", |
| 50 | branch: "main", |
| 51 | sha: "3f9c2a1d8e7b6c5a4f3e2d1c0b9a8f7e6d5c4b3a", |
| 52 | actor: me, |
| 53 | url: `${pub}/${repo}/actions/runs/sample`, |
| 54 | jobs: [ |
| 55 | { name: "Typecheck and test", conclusion: "failure", url: `${pub}/${repo}/actions/runs/sample/job/1`, failure: "Process completed with exit code 1 (step: Unit tests)" }, |
| 56 | { name: "Lint", conclusion: "success", url: `${pub}/${repo}/actions/runs/sample/job/2` }, |
| 57 | { name: "Deploy", conclusion: "skipped", url: `${pub}/${repo}/actions/runs/sample/job/3` }, |
| 58 | ], |
| 59 | }, |
| 60 | }), |
| 61 | item("signin", "New sign-in", { kind: "signin_new", reason: "security_alert", data: { method: "passkey", device: "Safari on macOS", ip: "203.0.113.24", location: "Istanbul, TR", passkey: "MacBook Touch ID" } }), |
| 62 | item("passkey-added", "Passkey added", { kind: "passkey_added", reason: "security_alert", data: { name: "iPhone", via: "settings" } }), |
| 63 | item("passkey-removed", "Passkey removed", { kind: "passkey_removed", reason: "security_alert", data: { name: "Old YubiKey" } }), |
| 64 | item("token-created", "Access token created", { kind: "token_created", reason: "security_alert", data: { name: "laptop", scopes: ["write"] } }), |
| 65 | item("token-expiring", "Access token expiring", { kind: "token_expiring", reason: "security_alert", data: { name: "ci-deploy", prefix: "dfy_Ab12Cd", expiresAt: exp } }), |
| 66 | item("agent-created", "Agent added", { kind: "agent_created", reason: "security_alert", data: { agent: "review-bot" } }), |
| 67 | item("model-key-added", "Model key added", { kind: "model_key_added", reason: "security_alert", data: { provider: "Anthropic", hint: "x7Qa" } }), |
| 68 | item("org-model-key-added", "Organization model key added", { kind: "model_key_added", reason: "security_alert", data: { provider: "OpenAI", hint: "9fKe", org: "acme", by: me } }), |
| 69 | item("deployment-review", "Deployment waiting for review", { kind: "deployment_review_requested", reason: "review_requested", data: { repo, environment: "production", workflow: "Deploy", runNumber: 128, title: "Release 2.4", branch: "main", sha: "4f1c2e9d0b7a6c5e3f2d1c0b9a8e7d6c5b4a3f2e", requestor: me, url: `${pub}/${repo}/actions/runs/sample` } }), |
| 70 | item("review-limit", "Agent review limit reached", { kind: "review_limit_reached", reason: "your_activity", data: { repo, limit: 50, pr: 42 } }), |
| 71 | item("push-bypassed", "Push protection bypassed", { kind: "push_bypassed", reason: "security_alert", data: { repo, pusher: me, kinds: ["AWS access key"], reason: "Fake key used by a test fixture", refs: ["refs/heads/main"] } }), |
| 72 | item("push-blocked", "Push blocked", { kind: "push_blocked", reason: "security_alert", data: { repo, kinds: ["GitHub token"], refs: ["refs/heads/feature"] } }), |
| 73 | item("commit-blocked", "Commit blocked", { kind: "push_blocked", reason: "security_alert", data: { repo, kinds: ["AWS access key"], paths: ["config.env"] } }), |
| 74 | item("account-disabled", "Account disabled", { kind: "account_status", reason: "account", data: { status: "disabled" } }), |
| 75 | item("account-enabled", "Account enabled", { kind: "account_status", reason: "account", data: { status: "active" } }), |
| 76 | item("account-deleted", "Account deleted", { kind: "account_deleted", reason: "account", data: { login: me, cooldownDays: 90 } }), |
| 77 | item("site-admin-granted", "Made a site administrator", { kind: "site_admin", reason: "security_alert", data: { granted: true, by: "admin" } }), |
| 78 | item("site-admin-revoked", "No longer a site administrator", { kind: "site_admin", reason: "security_alert", data: { granted: false, by: "admin" } }), |
| 79 | item("export-ready", "Account export ready", { kind: "export_ready", reason: "account", data: { url: `${pub}/settings/account`, size: 48_300_000, expiresAt: exp } }), |
| 80 | item("account-suspended", "Account suspended", { kind: "account_status", reason: "account", data: { status: "suspended", reason: "Spam in issues across several repositories" } }), |
| 81 | item("account-unsuspended", "Account unsuspended", { kind: "account_status", reason: "account", data: { status: "unsuspended" } }), |
| 82 | item("invite-org", "Invitation (organization)", { kind: "invite", reason: "invitation", data: { inviter: me, url: `${pub}/invite/sample`, org: repo.split("/")[0], role: "member", expiresAt: exp } }), |
| 83 | item("invite-repo", "Invitation (repository)", { kind: "invite", reason: "invitation", data: { inviter: me, url: `${pub}/invite/sample`, repo, role: "write", expiresAt: exp } }), |
| 84 | item("invite-account", "Invitation (by username)", { kind: "invite", reason: "invitation", data: { inviter: me, url: `${pub}/settings/invitations`, repo, role: "write", expiresAt: exp, existing: true } }), |
| 85 | item("invite-accepted", "Invitation accepted", { kind: "invite_accepted", reason: "account", data: { login: "rivera", email: "rivera@example.com", org: repo.split("/")[0] } }), |
| 86 | item("welcome", "Welcome", { kind: "welcome", reason: "account", data: { login: me } }), |
| 87 | item("signin-link", "Sign-in link", { kind: "signin_link", reason: "account", data: { url: `${pub}/signin#sample`, admin: "admin", expiresAt: new Date(Date.now() + 86400_000).toISOString() } }), |
| 88 | item("signup-code", "Sign-up code", { kind: "signup_code", reason: "signup", data: { code: "042917", expiresAt: new Date(Date.now() + 600_000).toISOString() } }), |
| 89 | item("email-verify", "Confirm an address after a bounce", { kind: "email_verify", reason: "account", data: { email: "diffy@example.com", url: `${pub}/settings/email/verify/sample`, expiresAt: new Date(Date.now() + 86400_000).toISOString() } }), |
| 90 | item("signup-attempt", "Sign-up with an existing address", { kind: "signup_attempt", reason: "account", data: { email: "diffy@example.com" } }), |
| 91 | item("access-request", "Access request", { kind: "access_request", reason: "admin", data: { email: "newcomer@example.com", login: "newcomer", note: "I'm joining the platform team next week." } }), |
| 92 | item("early-access-confirm", "Early access: confirm the request", { kind: "early_access_confirm", reason: "early_access", data: { url: `${pub}/early-access/confirm#sample`, expiresAt: exp } }), |
| 93 | item("early-access-remove", "Early access: remove the request", { kind: "early_access_remove", reason: "early_access", data: { url: `${pub}/early-access/remove/confirm#sample`, expiresAt: exp } }), |
| 94 | item("early-access-waiting", "Early access: already on the list", { kind: "early_access_known", reason: "early_access", data: { variant: "waiting", since: at } }), |
| 95 | item("early-access-invited", "Early access: already invited", { kind: "early_access_known", reason: "early_access", data: { variant: "invited", since: at } }), |
| 96 | item("early-access-account", "Early access: already has an account", { kind: "early_access_known", reason: "early_access", data: { variant: "account", since: at } }), |
| 97 | item("contact-confirm", "Contact: confirm the message", { kind: "contact_confirm", reason: "contact", data: { url: `${pub}/contact/confirm#sample`, expiresAt: exp, topic: "A question about Diffinery" } }), |
| 98 | item("contact-received", "Contact: copy for a signed-in sender", { kind: "contact_received", reason: "account", data: { id: "0123456789abcdef0123456789abcdef", topic: "Running my own Diffinery", message: "Hi! We'd like to run Diffinery for our 40 repositories.\n\nDoes it work with a custom domain on a Cloudflare zone we already have?" } }), |
| 99 | item("contact-message", "Contact: new message for site administrators", { |
| 100 | kind: "contact_message", |
| 101 | reason: "admin", |
| 102 | data: { id: "0123456789abcdef0123456789abcdef", name: "Rivera Kim", email: "rivera@example.com", login: null, topic: "Partnerships and integrations", message: "Hello,\n\nWe build a code review tool and would like to list Diffinery as a supported forge. Who should we talk to?\n\nThanks, Rivera" }, |
| 103 | }), |
| 104 | item("security-report", "Security report", { kind: "security_report", reason: "admin", data: { id: "0123456789abcdef0123456789abcdef", summary: "Stored XSS in release notes" } }), |
| 105 | item("org-member", "Organization membership", { kind: "org_membership", reason: "account", data: { org: repo.split("/")[0]!, role: "admin", previous: "member", by: "diffy-dev" } }), |
| 106 | item("repo-access", "Repository access", { kind: "repo_access", reason: "account", data: { repo, role: "write", previous: null } }), |
| 107 | item("repo-created", "Repository created", { kind: "repo_created", reason: "your_activity", data: { repo, creator: me, visibility: "private", defaultBranch: "main", description: "Marketing site" } }), |
| 108 | item("repo-deleted", "Repository deleted", { kind: "repo_deleted", reason: "admin", data: { repo, actor: "kim" } }), |
| 109 | item("repo-transferred", "Repository transferred", { kind: "repo_transferred", reason: "admin", data: { from: `${me}/${repo.split("/")[1]}`, to: repo, actor: "kim" } }), |
| 110 | item("digest", "Morning digest", { |
| 111 | kind: "digest", |
| 112 | reason: "digest", |
| 113 | data: { |
| 114 | since: new Date(Date.now() - 86400_000).toISOString(), |
| 115 | summary: { |
| 116 | date: new Date().toLocaleDateString("en-US", { weekday: "long", month: "long", day: "numeric", timeZone: "UTC" }), |
| 117 | counts: { needsYou: 2, agents: 1, merged: 3, failed: 1 }, |
| 118 | repositories: 4, |
| 119 | incomplete: false, |
| 120 | sections: [ |
| 121 | { |
| 122 | id: "needs_you", |
| 123 | title: "Needs you", |
| 124 | total: 2, |
| 125 | items: [ |
| 126 | { title: "Cache build artifacts between workflow runs", url: `${pub}/${repo}/pull/42`, where: `${repo}#42`, note: "Review requested · by diffy-dev", mark: "dot" }, |
| 127 | { title: "Retry flaky upload step", url: `${pub}/${repo}/pull/44`, where: `${repo}#44`, note: "Agent PR needs a person's approval · by fixer-bot", mark: "dot" }, |
| 128 | ], |
| 129 | }, |
| 130 | { id: "agents", title: "Agents at work", total: 1, items: [{ title: "Retry flaky upload step", url: `${pub}/${repo}/pull/44`, where: `${repo}#44`, note: "Waiting for approval · fixer-bot", mark: "dot" }] }, |
| 131 | { id: "failures", title: "Failed runs", total: 1, items: [{ title: "Nightly build", url: `${pub}/${repo}/actions/runs/1`, where: repo, note: "nightly on main · " + me, mark: "fail" }] }, |
| 132 | { |
| 133 | id: "landed", |
| 134 | title: "Merged", |
| 135 | total: 3, |
| 136 | items: [ |
| 137 | { title: "Bump the runner image", url: `${pub}/${repo}/pull/40`, where: `${repo}#40`, note: "by fixer-bot (agent), merged by " + me, mark: "ok" }, |
| 138 | { title: "Docs: caching", url: `${pub}/${repo}/pull/41`, where: `${repo}#41`, note: "by rivera", mark: "ok" }, |
| 139 | { title: "Fix the release notes link", url: `${pub}/${repo}/pull/39`, where: `${repo}#39`, note: "by " + me, mark: "ok" }, |
| 140 | ], |
| 141 | }, |
| 142 | ], |
| 143 | }, |
| 144 | }, |
| 145 | }), |
| 146 | item("test", "Test email", { kind: "test", reason: "account", data: { by: me } }), |
| 147 | ]; |
| 148 | } |