Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions packages/docs/src/content/docs/concepts/automations.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,16 @@ whenever a new issue is created in Linear team SRE, investigate it and comment f
when someone posts a new message in this channel, answer it if you can, otherwise tag me
```

An event automation can also run for every resource of a plugin, with no exact resource. Filters then narrow it:
An event automation selects its events with filters. Filters such as `repo` and `pullRequest` select one resource. Fewer filters run it for more resources:

```text
whenever any Sentry metric alert for project javascript goes critical, summarize it here
whenever a pull request opens in getsentry/junior, review it
```

Each plugin decides which events can run this way, and each plugin page lists them. Some events need a filter. For example, GitHub issue, pull request, and release events need a `repo` filter, and Linear issue events need a `teamKey` or `issueIdentifier` filter. Junior names the missing filter when it cannot save the automation. Junior saves filter values in the case that event data uses, so `getsentry/Junior` matches `getsentry/junior`. One event runs the automation once, even when the plugin publishes the event for more than one resource, such as each project of a metric alert. Slack channel-message automations always need their own channel.
Automations that were saved with an exact resource keep it. Junior does not change it when you edit them.

Each plugin decides which events can run for every resource, and each plugin page lists them. Some events need a filter. For example, GitHub issue, pull request, and release events need a `repo` filter, and Linear issue events need a `teamKey` or `issueIdentifier` filter. Junior names the missing filter when it cannot save the automation. Junior saves filter values in the case that event data uses, so `getsentry/Junior` matches `getsentry/junior`. One event runs the automation once, even when the plugin publishes the event for more than one resource, such as each project of a metric alert. Slack channel-message automations always need their own channel.

A required filter limits how much an automation covers and how noisy it is. It is not access control. Junior does not check that the person who creates the automation can see the repository, project, or team in the filter. An automation with no exact resource posts what it receives to its destination channel. For example, a broad Sentry trigger or GitHub deployment trigger can post private project or repository names in that channel. Create broad automations only in channels where everyone can see those resources.

Expand Down
6 changes: 4 additions & 2 deletions packages/docs/src/content/docs/extend/github-plugin.md
Original file line number Diff line number Diff line change
Expand Up @@ -326,8 +326,10 @@ repository automation to one environment or one workflow. For example, an
automation on `getsentry/toolkit` for `deployment.failed` can match
`environment: "production"` and `workflowPath: ".github/workflows/deploy.yml"`.

An event automation can also run for every repository, with no identifier.
Deployment events (`deployment.created`, `deployment.queued`,
An event automation selects one item with filters instead: `repo` and
`issue` or `pullRequest`, `repo` and `tag`, or `repo`, `commitSha`, and
`environment`. Lookup tools return these filters as `match`. With fewer filters,
it runs for more items. Deployment events (`deployment.created`, `deployment.queued`,
`deployment.pending`, `deployment.in_progress`, `deployment.succeeded`,
`deployment.failed`, `deployment.error`) can run that way with no filter.
Release, issue, and pull request events need a `repo` filter, because they
Expand Down
6 changes: 3 additions & 3 deletions packages/docs/src/content/docs/extend/linear-plugin.md
Original file line number Diff line number Diff line change
Expand Up @@ -185,14 +185,14 @@ Create the watch or event automation before the issue arrives. Junior does not r

Identifiers are normalized to uppercase. Prefer team-scoped event automations for monitor workflows that create many new issues.

An event automation can also run for every team, with no identifier.
`issue.created` can run that way only with a `teamKey` or an
An event automation selects a team or an issue with filters instead of an
identifier. `issue.created` needs a `teamKey` or an
`issueIdentifier` filter, because an issue carries its title and description
and a team can be private. Junior saves both in uppercase. The filter does not
check that the creator can see the team. See
[Automations](/concepts/automations/).

Optional `match` values come from the resource type. For Linear issue and team events, `teamKey` is the uppercase team key such as `SRE`. Junior drops events that do not match before it wakes the agent. Prefer a team identifier for “all new issues in SRE”. Use `match.teamKey` when an issue-scoped watch or event automation needs an extra team guard.
Optional `match` values come from the resource type. For Linear issue and team events, `teamKey` is the uppercase team key such as `SRE`. Junior drops events that do not match before it wakes the agent. For “all new issues in SRE”, a watch uses the team identifier and an event automation uses `match.teamKey`.

## Verify

Expand Down
4 changes: 2 additions & 2 deletions packages/docs/src/content/docs/extend/sentry-plugin.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,8 +230,8 @@ The alert resolved.

Junior publishes each state once for each alert incident.

An event automation can also run for every project in the Sentry org, with no
identifier. `issue.created`, `metric_alert.critical`, `metric_alert.warning`,
An event automation selects Sentry events with filters instead of an
identifier, and can run for every project in the Sentry org. `issue.created`, `metric_alert.critical`, `metric_alert.warning`,
and `metric_alert.resolved` can run that way with no filter. Use the
`projects` filter to limit one to some projects. Junior saves project slugs in
lowercase. Junior does not check that the creator can see the projects, so a
Expand Down
4 changes: 2 additions & 2 deletions packages/docs/src/content/docs/extend/vercel-plugin.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,9 +154,9 @@ The deployment was canceled.

</details>

An event automation can also run for every project, with no identifier.
An event automation selects deployments with filters instead of an identifier.
`deployment.succeeded`, `deployment.error`, and `deployment.canceled` can run
that way with no filter. Use the `projectId` or `target` filter to limit one.
for every project with no filter. Use the `projectId` or `target` filter to limit one.
Junior does not check that the creator can see the projects, so a broad
automation can post private project names in its channel. See
[Automations](/concepts/automations/).
Expand Down
5 changes: 1 addition & 4 deletions packages/junior-evals/evals/guardian/scheduled-work.eval.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,8 +204,6 @@ describeEval("Guardian Scheduled Work Snapshots", guardianEvals, (it) => {
trigger: {
type: "event",
namespace: "github",
identifier: "getsentry/junior#208",
resourceType: "issue",
label: "GitHub issue getsentry/junior#208",
events: ["issue.reopened"],
},
Expand All @@ -232,10 +230,9 @@ describeEval("Guardian Scheduled Work Snapshots", guardianEvals, (it) => {
trigger: {
type: "event",
namespace: "github",
resourceType: "pull_request",
identifier: "getsentry/junior#691",
label: "getsentry/junior#691",
events: ["pull_request.review.changes_requested"],
match: { repo: "getsentry/junior", pullRequest: 691 },
},
outcomes: [
{ action: "send_message", destination: "current_conversation" },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ describe("Event automation credentials", () => {
type: "event",
events: ["pull_request.review.changes_requested"],
namespace: "github",
identifier: "getsentry/junior#691",
match: { repo: "getsentry/junior", pullRequest: 691 },
},
});
expect([undefined, "creator"]).toContain(credentialMode(creates[0]));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,8 @@ describe("Event automation management", () => {
trigger: {
type: "event",
namespace: "github",
identifier: "getsentry/junior#691",
resourceType: "pull_request",
events: ["pull_request.review.changes_requested"],
match: { repo: "getsentry/junior", pullRequest: 691 },
},
});
const names = conversation.toolCalls.map((call) => call.name);
Expand Down Expand Up @@ -143,8 +142,7 @@ describe("Event automation management", () => {
trigger: {
type: "event",
namespace: "github",
identifier: "getsentry/junior#208",
resourceType: "issue",
match: { repo: "getsentry/junior", issue: 208 },
},
});
expect(new Set(triggerEvents(creates[0]!))).toEqual(
Expand Down Expand Up @@ -180,13 +178,18 @@ describe("Event automation management", () => {
const creates = completedToolCalls("createAutomation", conversation);
expect(creates).toHaveLength(1);
expect(creates[0]!.input).toMatchObject({
trigger: {
type: "event",
namespace: "github",
identifier: "getsentry/junior",
resourceType: "repository",
},
trigger: { type: "event", namespace: "github" },
});
expect(toolOutput(creates[0]!)).toMatchObject({
automation: { trigger: { match: { repo: "getsentry/junior" } } },
});
expect(
(
toolOutput(creates[0]!) as {
automation: { trigger: { match: object } };
}
).automation.trigger.match,
).not.toHaveProperty("issue");
expect(new Set(triggerEvents(creates[0]!))).toEqual(
new Set(["issue.closed", "issue.reopened"]),
);
Expand Down Expand Up @@ -272,7 +275,7 @@ describe("Event automation management", () => {
automationId: id,
trigger: { type: "event", events: ["issue.reopened"] },
});
// The update can omit identifier and resourceType to keep the saved ones.
// A saved trigger with an identifier keeps it when the tools edit it.
expect(toolOutput(updates[0]!)).toMatchObject({
automation: {
id,
Expand Down
13 changes: 12 additions & 1 deletion packages/junior-github/src/events/deployment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@ import {
eventMatchFieldsSchema,
type SubscribableResource,
} from "@sentry/junior-plugin-api";
import { gitHubCommitMatchFields, gitHubRepoMatchFields } from "./scope.js";
import {
gitHubCommitMatchFields,
gitHubCommitShaDataSchema,
gitHubRepoDataSchema,
gitHubRepoMatchFields,
} from "./scope.js";

export const GITHUB_DEPLOYMENT_EVENTS = [
"deployment.created",
Expand Down Expand Up @@ -89,8 +94,14 @@ export function gitHubDeploymentSourceSubscribable(input: {
repo: string;
}): SubscribableResource | undefined {
if (!input.canSubscribe) return undefined;
const environment = input.environment?.trim().toLowerCase();
return {
...gitHubDeploymentSourceResource(input),
match: {
repo: gitHubRepoDataSchema.parse(input.repo),
commitSha: gitHubCommitShaDataSchema.parse(input.commitSha),
...(environment ? { environment } : undefined),
},
suggestedEvents: GITHUB_DEPLOYMENT_SUGGESTED_EVENTS,
supportedEvents: [...GITHUB_DEPLOYMENT_EVENTS],
type: "deployment_source",
Expand Down
6 changes: 5 additions & 1 deletion packages/junior-github/src/events/issue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
type SubscribableResource,
} from "@sentry/junior-plugin-api";
import { gitHubCommentMatchFields } from "./comment.js";
import { gitHubRepoMatchFields } from "./scope.js";
import { gitHubRepoDataSchema, gitHubRepoMatchFields } from "./scope.js";

export const GITHUB_ISSUE_EVENTS = [
"issue.comment.created",
Expand Down Expand Up @@ -63,6 +63,10 @@ export function gitHubIssueSubscribable(input: {
if (!input.canSubscribe) return undefined;
return {
...gitHubIssueResource(input),
match: {
repo: gitHubRepoDataSchema.parse(input.repo),
issue: input.number,
},
suggestedEvents: GITHUB_ISSUE_SUGGESTED_EVENTS,
supportedEvents: GITHUB_ISSUE_EVENTS,
type: "issue",
Expand Down
10 changes: 9 additions & 1 deletion packages/junior-github/src/events/pull-request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ import {
type SubscribableResource,
} from "@sentry/junior-plugin-api";
import { gitHubCommentMatchFields } from "./comment.js";
import { gitHubCommitMatchFields, gitHubRepoMatchFields } from "./scope.js";
import {
gitHubCommitMatchFields,
gitHubRepoDataSchema,
gitHubRepoMatchFields,
} from "./scope.js";

export const GITHUB_PULL_REQUEST_EVENTS = [
"pull_request.checks.failed",
Expand Down Expand Up @@ -230,6 +234,10 @@ export function gitHubPullRequestSubscribable(input: {
);
return {
...gitHubPullRequestResource(input),
match: {
repo: gitHubRepoDataSchema.parse(input.repo),
pullRequest: input.number,
},
...(suggestedEvents.length > 0
? { suggestedEvents: [...suggestedEvents] }
: undefined),
Expand Down
7 changes: 6 additions & 1 deletion packages/junior-github/src/events/release.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
type SubscribableResource,
} from "@sentry/junior-plugin-api";
import { z } from "zod";
import { gitHubRepoMatchFields } from "./scope.js";
import { gitHubRepoDataSchema, gitHubRepoMatchFields } from "./scope.js";

export const GITHUB_RELEASE_EVENTS = ["release.published"] as const;

Expand Down Expand Up @@ -69,8 +69,13 @@ export function gitHubReleaseSourceSubscribable(input: {
tag?: string;
}): SubscribableResource | undefined {
if (!input.canSubscribe) return undefined;
const tag = input.tag?.trim();
return {
...gitHubReleaseSourceResource(input),
match: {
repo: gitHubRepoDataSchema.parse(input.repo),
...(tag ? { tag: gitHubReleaseTagDataSchema.parse(tag) } : undefined),
},
suggestedEvents: GITHUB_RELEASE_SUGGESTED_EVENTS,
supportedEvents: [...GITHUB_RELEASE_EVENTS],
type: "release_source",
Expand Down
2 changes: 2 additions & 0 deletions packages/junior-github/src/events/repository.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { SubscribableResource } from "@sentry/junior-plugin-api";
import { gitHubRepoDataSchema } from "./scope.js";
import { GITHUB_DEPLOYMENT_EVENTS } from "./deployment.js";
import { GITHUB_ISSUE_EVENTS, GITHUB_ISSUE_SUGGESTED_EVENTS } from "./issue.js";
import {
Expand Down Expand Up @@ -50,6 +51,7 @@ export function gitHubRepositorySubscribable(input: {
if (!input.canSubscribe) return undefined;
return {
...gitHubRepositoryResource(input),
match: { repo: gitHubRepoDataSchema.parse(input.repo) },
suggestedEvents: [...GITHUB_REPOSITORY_SUGGESTED_EVENTS],
supportedEvents: [...GITHUB_REPOSITORY_EVENTS],
type: "repository",
Expand Down
Loading
Loading