diff --git a/apps/docs/components/icons.tsx b/apps/docs/components/icons.tsx index 6614c9f603..4662b1c0d3 100644 --- a/apps/docs/components/icons.tsx +++ b/apps/docs/components/icons.tsx @@ -6387,6 +6387,41 @@ export function RipplingIcon(props: SVGProps) { ) } +export function RootlyIcon(props: SVGProps) { + return ( + + + + + + + + + + ) +} + export function HexIcon(props: SVGProps) { return ( diff --git a/apps/docs/components/ui/icon-mapping.ts b/apps/docs/components/ui/icon-mapping.ts index ef92dc123f..b8edc84d2b 100644 --- a/apps/docs/components/ui/icon-mapping.ts +++ b/apps/docs/components/ui/icon-mapping.ts @@ -139,6 +139,7 @@ import { ResendIcon, RevenueCatIcon, RipplingIcon, + RootlyIcon, S3Icon, SalesforceIcon, SearchIcon, @@ -320,6 +321,7 @@ export const blockTypeToIconMap: Record = { resend: ResendIcon, revenuecat: RevenueCatIcon, rippling: RipplingIcon, + rootly: RootlyIcon, s3: S3Icon, salesforce: SalesforceIcon, search: SearchIcon, diff --git a/apps/docs/content/docs/en/tools/meta.json b/apps/docs/content/docs/en/tools/meta.json index 20dd3a4bf9..6071d7c7a7 100644 --- a/apps/docs/content/docs/en/tools/meta.json +++ b/apps/docs/content/docs/en/tools/meta.json @@ -134,6 +134,7 @@ "resend", "revenuecat", "rippling", + "rootly", "s3", "salesforce", "search", diff --git a/apps/docs/content/docs/en/tools/rippling.mdx b/apps/docs/content/docs/en/tools/rippling.mdx index 31945d94ac..88160e37d2 100644 --- a/apps/docs/content/docs/en/tools/rippling.mdx +++ b/apps/docs/content/docs/en/tools/rippling.mdx @@ -2201,7 +2201,7 @@ Create a new object category ### `rippling_update_object_category` -Update a object category +Update an object category #### Input @@ -2224,7 +2224,7 @@ Update a object category ### `rippling_delete_object_category` -Delete a object category +Delete an object category #### Input diff --git a/apps/docs/content/docs/en/tools/rootly.mdx b/apps/docs/content/docs/en/tools/rootly.mdx new file mode 100644 index 0000000000..58b3cb1e90 --- /dev/null +++ b/apps/docs/content/docs/en/tools/rootly.mdx @@ -0,0 +1,510 @@ +--- +title: Rootly +description: Manage incidents, alerts, and on-call with Rootly +--- + +import { BlockInfoCard } from "@/components/ui/block-info-card" + + + +{/* MANUAL-CONTENT-START:intro */} +[Rootly](https://rootly.com/) is an incident management platform that helps teams respond to, mitigate, and learn from incidents — all without leaving Slack or your existing tools. Rootly automates on-call alerting, incident workflows, status page updates, and retrospectives so engineering teams can resolve issues faster and reduce toil. + +**Why Rootly?** +- **End-to-End Incident Management:** Create, track, update, and resolve incidents with full lifecycle support — from initial triage through retrospective. +- **On-Call Alerting:** Create and manage alerts with deduplication, routing, and escalation to ensure the right people are notified immediately. +- **Timeline Events:** Add structured timeline events to incidents for clear, auditable incident narratives. +- **Service Catalog:** Maintain a catalog of services and map them to incidents for precise impact tracking. +- **Severity & Prioritization:** Use configurable severity levels to prioritize incidents and drive appropriate response urgency. +- **Retrospectives:** Access post-incident retrospectives to identify root causes, capture learnings, and drive reliability improvements. + +**Using Rootly in Sim** + +Sim's Rootly integration connects your agentic workflows directly to your Rootly account using an API key. With operations spanning incidents, alerts, services, severities, teams, environments, functionalities, incident types, and retrospectives, you can build powerful incident management automations without writing backend code. + +**Key benefits of using Rootly in Sim:** +- **Automated incident creation:** Trigger incident creation from monitoring alerts, customer reports, or anomaly detection workflows with full metadata including severity, services, and teams. +- **Incident lifecycle automation:** Automatically update incident status, add timeline events, and attach mitigation or resolution messages as your response progresses. +- **Alert management:** Create and list alerts with deduplication support to integrate Rootly into your existing monitoring and notification pipelines. +- **Organizational awareness:** Query services, severities, teams, environments, functionalities, and incident types to build context-aware incident workflows. +- **Retrospective insights:** List and filter retrospectives to feed post-incident learnings into continuous improvement workflows. + +Whether you're automating incident response, building on-call alerting pipelines, or driving post-incident learning, Rootly in Sim gives you direct, secure access to the Rootly API — no middleware required. Simply configure your API key, select the operation you need, and let Sim handle the rest. +{/* MANUAL-CONTENT-END */} + + +## Usage Instructions + +Integrate Rootly incident management into workflows. Create and manage incidents, alerts, services, severities, and retrospectives. + + + +## Tools + +### `rootly_create_incident` + +Create a new incident in Rootly with optional severity, services, and teams. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rootly API key | +| `title` | string | No | The title of the incident \(auto-generated if not provided\) | +| `summary` | string | No | A summary of the incident | +| `severityId` | string | No | Severity ID to attach to the incident | +| `status` | string | No | Incident status \(in_triage, started, detected, acknowledged, mitigated, resolved, closed, cancelled, scheduled, in_progress, completed\) | +| `kind` | string | No | Incident kind \(normal, normal_sub, test, test_sub, example, example_sub, backfilled, scheduled, scheduled_sub\) | +| `serviceIds` | string | No | Comma-separated service IDs to attach | +| `environmentIds` | string | No | Comma-separated environment IDs to attach | +| `groupIds` | string | No | Comma-separated team/group IDs to attach | +| `incidentTypeIds` | string | No | Comma-separated incident type IDs to attach | +| `functionalityIds` | string | No | Comma-separated functionality IDs to attach | +| `labels` | string | No | Labels as JSON object, e.g. \{"platform":"osx","version":"1.29"\} | +| `private` | boolean | No | Create as a private incident \(cannot be undone\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `incident` | object | The created incident | +| ↳ `id` | string | Unique incident ID | +| ↳ `sequentialId` | number | Sequential incident number | +| ↳ `title` | string | Incident title | +| ↳ `slug` | string | Incident slug | +| ↳ `kind` | string | Incident kind | +| ↳ `summary` | string | Incident summary | +| ↳ `status` | string | Incident status | +| ↳ `private` | boolean | Whether the incident is private | +| ↳ `url` | string | URL to the incident | +| ↳ `shortUrl` | string | Short URL to the incident | +| ↳ `severityName` | string | Severity name | +| ↳ `severityId` | string | Severity ID | +| ↳ `createdAt` | string | Creation date | +| ↳ `updatedAt` | string | Last update date | +| ↳ `startedAt` | string | Start date | +| ↳ `mitigatedAt` | string | Mitigation date | +| ↳ `resolvedAt` | string | Resolution date | +| ↳ `closedAt` | string | Closed date | + +### `rootly_get_incident` + +Retrieve a single incident by ID from Rootly. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rootly API key | +| `incidentId` | string | Yes | The ID of the incident to retrieve | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `incident` | object | The incident details | +| ↳ `id` | string | Unique incident ID | +| ↳ `sequentialId` | number | Sequential incident number | +| ↳ `title` | string | Incident title | +| ↳ `slug` | string | Incident slug | +| ↳ `kind` | string | Incident kind | +| ↳ `summary` | string | Incident summary | +| ↳ `status` | string | Incident status | +| ↳ `private` | boolean | Whether the incident is private | +| ↳ `url` | string | URL to the incident | +| ↳ `shortUrl` | string | Short URL to the incident | +| ↳ `severityName` | string | Severity name | +| ↳ `severityId` | string | Severity ID | +| ↳ `createdAt` | string | Creation date | +| ↳ `updatedAt` | string | Last update date | +| ↳ `startedAt` | string | Start date | +| ↳ `mitigatedAt` | string | Mitigation date | +| ↳ `resolvedAt` | string | Resolution date | +| ↳ `closedAt` | string | Closed date | + +### `rootly_update_incident` + +Update an existing incident in Rootly (status, severity, summary, etc.). + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rootly API key | +| `incidentId` | string | Yes | The ID of the incident to update | +| `title` | string | No | Updated incident title | +| `summary` | string | No | Updated incident summary | +| `severityId` | string | No | Updated severity ID | +| `status` | string | No | Updated status \(in_triage, started, detected, acknowledged, mitigated, resolved, closed, cancelled, scheduled, in_progress, completed\) | +| `kind` | string | No | Incident kind \(normal, normal_sub, test, test_sub, example, example_sub, backfilled, scheduled, scheduled_sub\) | +| `private` | boolean | No | Set incident as private \(cannot be undone\) | +| `serviceIds` | string | No | Comma-separated service IDs | +| `environmentIds` | string | No | Comma-separated environment IDs | +| `groupIds` | string | No | Comma-separated team/group IDs | +| `incidentTypeIds` | string | No | Comma-separated incident type IDs to attach | +| `functionalityIds` | string | No | Comma-separated functionality IDs to attach | +| `labels` | string | No | Labels as JSON object, e.g. \{"platform":"osx","version":"1.29"\} | +| `mitigationMessage` | string | No | How was the incident mitigated? | +| `resolutionMessage` | string | No | How was the incident resolved? | +| `cancellationMessage` | string | No | Why was the incident cancelled? | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `incident` | object | The updated incident | +| ↳ `id` | string | Unique incident ID | +| ↳ `sequentialId` | number | Sequential incident number | +| ↳ `title` | string | Incident title | +| ↳ `slug` | string | Incident slug | +| ↳ `kind` | string | Incident kind | +| ↳ `summary` | string | Incident summary | +| ↳ `status` | string | Incident status | +| ↳ `private` | boolean | Whether the incident is private | +| ↳ `url` | string | URL to the incident | +| ↳ `shortUrl` | string | Short URL to the incident | +| ↳ `severityName` | string | Severity name | +| ↳ `severityId` | string | Severity ID | +| ↳ `createdAt` | string | Creation date | +| ↳ `updatedAt` | string | Last update date | +| ↳ `startedAt` | string | Start date | +| ↳ `mitigatedAt` | string | Mitigation date | +| ↳ `resolvedAt` | string | Resolution date | +| ↳ `closedAt` | string | Closed date | + +### `rootly_list_incidents` + +List incidents from Rootly with optional filtering by status, severity, and more. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rootly API key | +| `status` | string | No | Filter by status \(in_triage, started, detected, acknowledged, mitigated, resolved, closed, cancelled, scheduled, in_progress, completed\) | +| `severity` | string | No | Filter by severity slug | +| `search` | string | No | Search term to filter incidents | +| `services` | string | No | Filter by service slugs \(comma-separated\) | +| `teams` | string | No | Filter by team slugs \(comma-separated\) | +| `environments` | string | No | Filter by environment slugs \(comma-separated\) | +| `sort` | string | No | Sort order \(e.g., -created_at, created_at, -started_at\) | +| `pageSize` | number | No | Number of items per page \(default: 20\) | +| `pageNumber` | number | No | Page number for pagination | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `incidents` | array | List of incidents | +| ↳ `id` | string | Unique incident ID | +| ↳ `sequentialId` | number | Sequential incident number | +| ↳ `title` | string | Incident title | +| ↳ `slug` | string | Incident slug | +| ↳ `kind` | string | Incident kind | +| ↳ `summary` | string | Incident summary | +| ↳ `status` | string | Incident status | +| ↳ `private` | boolean | Whether the incident is private | +| ↳ `url` | string | URL to the incident | +| ↳ `shortUrl` | string | Short URL to the incident | +| ↳ `severityName` | string | Severity name | +| ↳ `severityId` | string | Severity ID | +| ↳ `createdAt` | string | Creation date | +| ↳ `updatedAt` | string | Last update date | +| ↳ `startedAt` | string | Start date | +| ↳ `mitigatedAt` | string | Mitigation date | +| ↳ `resolvedAt` | string | Resolution date | +| ↳ `closedAt` | string | Closed date | +| `totalCount` | number | Total number of incidents returned | + +### `rootly_create_alert` + +Create a new alert in Rootly for on-call notification and routing. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rootly API key | +| `summary` | string | Yes | The summary of the alert | +| `description` | string | No | A detailed description of the alert | +| `source` | string | Yes | The source of the alert \(e.g., api, manual, datadog, pagerduty\) | +| `status` | string | No | Alert status on creation \(open, triggered\) | +| `serviceIds` | string | No | Comma-separated service IDs to attach | +| `groupIds` | string | No | Comma-separated team/group IDs to attach | +| `environmentIds` | string | No | Comma-separated environment IDs to attach | +| `externalId` | string | No | External ID for the alert | +| `externalUrl` | string | No | External URL for the alert | +| `deduplicationKey` | string | No | Alerts sharing the same deduplication key are treated as a single alert | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `alert` | object | The created alert | +| ↳ `id` | string | Unique alert ID | +| ↳ `summary` | string | Alert summary | +| ↳ `description` | string | Alert description | +| ↳ `source` | string | Alert source | +| ↳ `status` | string | Alert status | +| ↳ `externalId` | string | External ID | +| ↳ `externalUrl` | string | External URL | +| ↳ `deduplicationKey` | string | Deduplication key | +| ↳ `createdAt` | string | Creation date | +| ↳ `updatedAt` | string | Last update date | + +### `rootly_list_alerts` + +List alerts from Rootly with optional filtering by status, source, and services. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rootly API key | +| `status` | string | No | Filter by status \(open, triggered, acknowledged, resolved\) | +| `source` | string | No | Filter by source \(e.g., api, datadog, pagerduty\) | +| `services` | string | No | Filter by service slugs \(comma-separated\) | +| `environments` | string | No | Filter by environment slugs \(comma-separated\) | +| `groups` | string | No | Filter by team/group slugs \(comma-separated\) | +| `pageSize` | number | No | Number of items per page \(default: 20\) | +| `pageNumber` | number | No | Page number for pagination | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `alerts` | array | List of alerts | +| ↳ `id` | string | Unique alert ID | +| ↳ `summary` | string | Alert summary | +| ↳ `description` | string | Alert description | +| ↳ `source` | string | Alert source | +| ↳ `status` | string | Alert status | +| ↳ `externalId` | string | External ID | +| ↳ `externalUrl` | string | External URL | +| ↳ `deduplicationKey` | string | Deduplication key | +| ↳ `createdAt` | string | Creation date | +| ↳ `updatedAt` | string | Last update date | +| `totalCount` | number | Total number of alerts returned | + +### `rootly_add_incident_event` + +Add a timeline event to an existing incident in Rootly. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rootly API key | +| `incidentId` | string | Yes | The ID of the incident to add the event to | +| `event` | string | Yes | The summary/description of the event | +| `visibility` | string | No | Event visibility \(internal or external\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `eventId` | string | The ID of the created event | +| `event` | string | The event summary | +| `visibility` | string | Event visibility \(internal or external\) | +| `occurredAt` | string | When the event occurred | +| `createdAt` | string | Creation date | +| `updatedAt` | string | Last update date | + +### `rootly_list_services` + +List services from Rootly with optional search filtering. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rootly API key | +| `search` | string | No | Search term to filter services | +| `pageSize` | number | No | Number of items per page \(default: 20\) | +| `pageNumber` | number | No | Page number for pagination | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `services` | array | List of services | +| ↳ `id` | string | Unique service ID | +| ↳ `name` | string | Service name | +| ↳ `slug` | string | Service slug | +| ↳ `description` | string | Service description | +| ↳ `color` | string | Service color | +| ↳ `createdAt` | string | Creation date | +| ↳ `updatedAt` | string | Last update date | +| `totalCount` | number | Total number of services returned | + +### `rootly_list_severities` + +List severity levels configured in Rootly. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rootly API key | +| `search` | string | No | Search term to filter severities | +| `pageSize` | number | No | Number of items per page \(default: 20\) | +| `pageNumber` | number | No | Page number for pagination | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `severities` | array | List of severity levels | +| ↳ `id` | string | Unique severity ID | +| ↳ `name` | string | Severity name | +| ↳ `slug` | string | Severity slug | +| ↳ `description` | string | Severity description | +| ↳ `severity` | string | Severity level \(critical, high, medium, low\) | +| ↳ `color` | string | Severity color | +| ↳ `position` | number | Display position | +| ↳ `createdAt` | string | Creation date | +| ↳ `updatedAt` | string | Last update date | +| `totalCount` | number | Total number of severities returned | + +### `rootly_list_teams` + +List teams (groups) configured in Rootly. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rootly API key | +| `search` | string | No | Search term to filter teams | +| `pageSize` | number | No | Number of items per page \(default: 20\) | +| `pageNumber` | number | No | Page number for pagination | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `teams` | array | List of teams | +| ↳ `id` | string | Unique team ID | +| ↳ `name` | string | Team name | +| ↳ `slug` | string | Team slug | +| ↳ `description` | string | Team description | +| ↳ `color` | string | Team color | +| ↳ `createdAt` | string | Creation date | +| ↳ `updatedAt` | string | Last update date | +| `totalCount` | number | Total number of teams returned | + +### `rootly_list_environments` + +List environments configured in Rootly. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rootly API key | +| `search` | string | No | Search term to filter environments | +| `pageSize` | number | No | Number of items per page \(default: 20\) | +| `pageNumber` | number | No | Page number for pagination | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `environments` | array | List of environments | +| ↳ `id` | string | Unique environment ID | +| ↳ `name` | string | Environment name | +| ↳ `slug` | string | Environment slug | +| ↳ `description` | string | Environment description | +| ↳ `color` | string | Environment color | +| ↳ `createdAt` | string | Creation date | +| ↳ `updatedAt` | string | Last update date | +| `totalCount` | number | Total number of environments returned | + +### `rootly_list_incident_types` + +List incident types configured in Rootly. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rootly API key | +| `search` | string | No | Filter incident types by name | +| `pageSize` | number | No | Number of items per page \(default: 20\) | +| `pageNumber` | number | No | Page number for pagination | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `incidentTypes` | array | List of incident types | +| ↳ `id` | string | Unique incident type ID | +| ↳ `name` | string | Incident type name | +| ↳ `slug` | string | Incident type slug | +| ↳ `description` | string | Incident type description | +| ↳ `color` | string | Incident type color | +| ↳ `createdAt` | string | Creation date | +| ↳ `updatedAt` | string | Last update date | +| `totalCount` | number | Total number of incident types returned | + +### `rootly_list_functionalities` + +List functionalities configured in Rootly. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rootly API key | +| `search` | string | No | Search term to filter functionalities | +| `pageSize` | number | No | Number of items per page \(default: 20\) | +| `pageNumber` | number | No | Page number for pagination | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `functionalities` | array | List of functionalities | +| ↳ `id` | string | Unique functionality ID | +| ↳ `name` | string | Functionality name | +| ↳ `slug` | string | Functionality slug | +| ↳ `description` | string | Functionality description | +| ↳ `color` | string | Functionality color | +| ↳ `createdAt` | string | Creation date | +| ↳ `updatedAt` | string | Last update date | +| `totalCount` | number | Total number of functionalities returned | + +### `rootly_list_retrospectives` + +List incident retrospectives (post-mortems) from Rootly. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rootly API key | +| `status` | string | No | Filter by status \(draft, published\) | +| `search` | string | No | Search term to filter retrospectives | +| `pageSize` | number | No | Number of items per page \(default: 20\) | +| `pageNumber` | number | No | Page number for pagination | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `retrospectives` | array | List of retrospectives | +| ↳ `id` | string | Unique retrospective ID | +| ↳ `title` | string | Retrospective title | +| ↳ `status` | string | Status \(draft or published\) | +| ↳ `url` | string | URL to the retrospective | +| ↳ `startedAt` | string | Incident start date | +| ↳ `mitigatedAt` | string | Mitigation date | +| ↳ `resolvedAt` | string | Resolution date | +| ↳ `createdAt` | string | Creation date | +| ↳ `updatedAt` | string | Last update date | +| `totalCount` | number | Total number of retrospectives returned | + + diff --git a/apps/sim/app/(landing)/integrations/data/icon-mapping.ts b/apps/sim/app/(landing)/integrations/data/icon-mapping.ts index 7b9b36c977..26162d9a58 100644 --- a/apps/sim/app/(landing)/integrations/data/icon-mapping.ts +++ b/apps/sim/app/(landing)/integrations/data/icon-mapping.ts @@ -139,6 +139,7 @@ import { ResendIcon, RevenueCatIcon, RipplingIcon, + RootlyIcon, S3Icon, SalesforceIcon, SearchIcon, @@ -320,6 +321,7 @@ export const blockTypeToIconMap: Record = { resend: ResendIcon, revenuecat: RevenueCatIcon, rippling: RipplingIcon, + rootly: RootlyIcon, s3: S3Icon, salesforce: SalesforceIcon, search: SearchIcon, diff --git a/apps/sim/app/(landing)/integrations/data/integrations.json b/apps/sim/app/(landing)/integrations/data/integrations.json index ae89ed9506..3b578762f4 100644 --- a/apps/sim/app/(landing)/integrations/data/integrations.json +++ b/apps/sim/app/(landing)/integrations/data/integrations.json @@ -9625,11 +9625,11 @@ }, { "name": "Update Object Category", - "description": "Update a object category" + "description": "Update an object category" }, { "name": "Delete Object Category", - "description": "Delete a object category" + "description": "Delete an object category" }, { "name": "Get Report Run", @@ -9652,6 +9652,81 @@ "integrationType": "hr", "tags": ["hiring"] }, + { + "type": "rootly", + "slug": "rootly", + "name": "Rootly", + "description": "Manage incidents, alerts, and on-call with Rootly", + "longDescription": "Integrate Rootly incident management into workflows. Create and manage incidents, alerts, services, severities, and retrospectives.", + "bgColor": "#6C72C8", + "iconName": "RootlyIcon", + "docsUrl": "https://docs.sim.ai/tools/rootly", + "operations": [ + { + "name": "Create Incident", + "description": "Create a new incident in Rootly with optional severity, services, and teams." + }, + { + "name": "Get Incident", + "description": "Retrieve a single incident by ID from Rootly." + }, + { + "name": "Update Incident", + "description": "Update an existing incident in Rootly (status, severity, summary, etc.)." + }, + { + "name": "List Incidents", + "description": "List incidents from Rootly with optional filtering by status, severity, and more." + }, + { + "name": "Create Alert", + "description": "Create a new alert in Rootly for on-call notification and routing." + }, + { + "name": "List Alerts", + "description": "List alerts from Rootly with optional filtering by status, source, and services." + }, + { + "name": "Add Incident Event", + "description": "Add a timeline event to an existing incident in Rootly." + }, + { + "name": "List Services", + "description": "List services from Rootly with optional search filtering." + }, + { + "name": "List Severities", + "description": "List severity levels configured in Rootly." + }, + { + "name": "List Teams", + "description": "List teams (groups) configured in Rootly." + }, + { + "name": "List Environments", + "description": "List environments configured in Rootly." + }, + { + "name": "List Incident Types", + "description": "List incident types configured in Rootly." + }, + { + "name": "List Functionalities", + "description": "List functionalities configured in Rootly." + }, + { + "name": "List Retrospectives", + "description": "List incident retrospectives (post-mortems) from Rootly." + } + ], + "operationCount": 14, + "triggers": [], + "triggerCount": 0, + "authType": "api-key", + "category": "tools", + "integrationType": "developer-tools", + "tags": ["incident-management", "monitoring"] + }, { "type": "s3", "slug": "s3", diff --git a/apps/sim/blocks/blocks/rootly.ts b/apps/sim/blocks/blocks/rootly.ts new file mode 100644 index 0000000000..c00c6c836b --- /dev/null +++ b/apps/sim/blocks/blocks/rootly.ts @@ -0,0 +1,1155 @@ +import { RootlyIcon } from '@/components/icons' +import type { BlockConfig } from '@/blocks/types' +import { AuthMode, IntegrationType } from '@/blocks/types' +import type { RootlyResponse } from '@/tools/rootly/types' + +export const RootlyBlock: BlockConfig = { + type: 'rootly', + name: 'Rootly', + description: 'Manage incidents, alerts, and on-call with Rootly', + authMode: AuthMode.ApiKey, + longDescription: + 'Integrate Rootly incident management into workflows. Create and manage incidents, alerts, services, severities, and retrospectives.', + docsLink: 'https://docs.sim.ai/tools/rootly', + category: 'tools', + integrationType: IntegrationType.DeveloperTools, + tags: ['incident-management', 'monitoring'], + bgColor: '#6C72C8', + icon: RootlyIcon, + subBlocks: [ + { + id: 'operation', + title: 'Operation', + type: 'dropdown', + options: [ + { label: 'Create Incident', id: 'rootly_create_incident' }, + { label: 'Get Incident', id: 'rootly_get_incident' }, + { label: 'Update Incident', id: 'rootly_update_incident' }, + { label: 'List Incidents', id: 'rootly_list_incidents' }, + { label: 'Create Alert', id: 'rootly_create_alert' }, + { label: 'List Alerts', id: 'rootly_list_alerts' }, + { label: 'Add Incident Event', id: 'rootly_add_incident_event' }, + { label: 'List Services', id: 'rootly_list_services' }, + { label: 'List Severities', id: 'rootly_list_severities' }, + { label: 'List Teams', id: 'rootly_list_teams' }, + { label: 'List Environments', id: 'rootly_list_environments' }, + { label: 'List Incident Types', id: 'rootly_list_incident_types' }, + { label: 'List Functionalities', id: 'rootly_list_functionalities' }, + { label: 'List Retrospectives', id: 'rootly_list_retrospectives' }, + ], + value: () => 'rootly_create_incident', + }, + + // Create Incident fields + { + id: 'title', + title: 'Title', + type: 'short-input', + placeholder: 'Incident title', + condition: { field: 'operation', value: 'rootly_create_incident' }, + }, + { + id: 'createSummary', + title: 'Summary', + type: 'long-input', + placeholder: 'Describe the incident', + condition: { field: 'operation', value: 'rootly_create_incident' }, + }, + { + id: 'createSeverityId', + title: 'Severity ID', + type: 'short-input', + placeholder: 'Severity ID (use List Severities to find IDs)', + condition: { field: 'operation', value: 'rootly_create_incident' }, + mode: 'advanced', + }, + { + id: 'createStatus', + title: 'Status', + type: 'dropdown', + options: [ + { label: 'Default', id: '' }, + { label: 'In Triage', id: 'in_triage' }, + { label: 'Started', id: 'started' }, + { label: 'Detected', id: 'detected' }, + { label: 'Acknowledged', id: 'acknowledged' }, + { label: 'Mitigated', id: 'mitigated' }, + { label: 'Resolved', id: 'resolved' }, + { label: 'Closed', id: 'closed' }, + { label: 'Cancelled', id: 'cancelled' }, + { label: 'Scheduled', id: 'scheduled' }, + { label: 'In Progress', id: 'in_progress' }, + { label: 'Completed', id: 'completed' }, + ], + value: () => '', + condition: { field: 'operation', value: 'rootly_create_incident' }, + mode: 'advanced', + }, + { + id: 'createKind', + title: 'Kind', + type: 'dropdown', + options: [ + { label: 'Default', id: '' }, + { label: 'Normal', id: 'normal' }, + { label: 'Test', id: 'test' }, + { label: 'Example', id: 'example' }, + { label: 'Backfilled', id: 'backfilled' }, + { label: 'Scheduled', id: 'scheduled' }, + ], + value: () => '', + condition: { field: 'operation', value: 'rootly_create_incident' }, + mode: 'advanced', + }, + { + id: 'createServiceIds', + title: 'Service IDs', + type: 'short-input', + placeholder: 'Comma-separated service IDs', + condition: { field: 'operation', value: 'rootly_create_incident' }, + mode: 'advanced', + }, + { + id: 'createEnvironmentIds', + title: 'Environment IDs', + type: 'short-input', + placeholder: 'Comma-separated environment IDs', + condition: { field: 'operation', value: 'rootly_create_incident' }, + mode: 'advanced', + }, + { + id: 'createGroupIds', + title: 'Team IDs', + type: 'short-input', + placeholder: 'Comma-separated team/group IDs', + condition: { field: 'operation', value: 'rootly_create_incident' }, + mode: 'advanced', + }, + { + id: 'createIncidentTypeIds', + title: 'Incident Type IDs', + type: 'short-input', + placeholder: 'Comma-separated incident type IDs', + condition: { field: 'operation', value: 'rootly_create_incident' }, + mode: 'advanced', + }, + { + id: 'createFunctionalityIds', + title: 'Functionality IDs', + type: 'short-input', + placeholder: 'Comma-separated functionality IDs', + condition: { field: 'operation', value: 'rootly_create_incident' }, + mode: 'advanced', + }, + { + id: 'createLabels', + title: 'Labels', + type: 'short-input', + placeholder: '{"platform":"osx","version":"1.29"}', + condition: { field: 'operation', value: 'rootly_create_incident' }, + mode: 'advanced', + wandConfig: { + enabled: true, + prompt: + 'Generate a JSON object of key-value label pairs for a Rootly incident. Example: {"platform":"osx","version":"1.29","region":"us-east-1"}. Return ONLY the JSON object - no explanations, no extra text.', + placeholder: 'Describe the labels (e.g., "platform osx, version 1.29")...', + generationType: 'json-object', + }, + }, + { + id: 'createPrivate', + title: 'Private', + type: 'dropdown', + options: [ + { label: 'Default', id: '' }, + { label: 'Yes', id: 'true' }, + { label: 'No', id: 'false' }, + ], + value: () => '', + condition: { field: 'operation', value: 'rootly_create_incident' }, + mode: 'advanced', + }, + + // Get Incident fields + { + id: 'getIncidentId', + title: 'Incident ID', + type: 'short-input', + placeholder: 'The ID of the incident to retrieve', + condition: { field: 'operation', value: 'rootly_get_incident' }, + required: { field: 'operation', value: 'rootly_get_incident' }, + }, + + // Update Incident fields + { + id: 'updateIncidentId', + title: 'Incident ID', + type: 'short-input', + placeholder: 'The ID of the incident to update', + condition: { field: 'operation', value: 'rootly_update_incident' }, + required: { field: 'operation', value: 'rootly_update_incident' }, + }, + { + id: 'updateTitle', + title: 'Title', + type: 'short-input', + placeholder: 'Updated incident title', + condition: { field: 'operation', value: 'rootly_update_incident' }, + }, + { + id: 'updateSummary', + title: 'Summary', + type: 'long-input', + placeholder: 'Updated incident summary', + condition: { field: 'operation', value: 'rootly_update_incident' }, + }, + { + id: 'updateStatus', + title: 'Status', + type: 'dropdown', + options: [ + { label: 'Unchanged', id: '' }, + { label: 'In Triage', id: 'in_triage' }, + { label: 'Started', id: 'started' }, + { label: 'Detected', id: 'detected' }, + { label: 'Acknowledged', id: 'acknowledged' }, + { label: 'Mitigated', id: 'mitigated' }, + { label: 'Resolved', id: 'resolved' }, + { label: 'Closed', id: 'closed' }, + { label: 'Cancelled', id: 'cancelled' }, + { label: 'Scheduled', id: 'scheduled' }, + { label: 'In Progress', id: 'in_progress' }, + { label: 'Completed', id: 'completed' }, + ], + value: () => '', + condition: { field: 'operation', value: 'rootly_update_incident' }, + }, + { + id: 'updateSeverityId', + title: 'Severity ID', + type: 'short-input', + placeholder: 'Updated severity ID', + condition: { field: 'operation', value: 'rootly_update_incident' }, + mode: 'advanced', + }, + { + id: 'mitigationMessage', + title: 'Mitigation Message', + type: 'long-input', + placeholder: 'How was the incident mitigated?', + condition: { field: 'operation', value: 'rootly_update_incident' }, + mode: 'advanced', + }, + { + id: 'resolutionMessage', + title: 'Resolution Message', + type: 'long-input', + placeholder: 'How was the incident resolved?', + condition: { field: 'operation', value: 'rootly_update_incident' }, + mode: 'advanced', + }, + { + id: 'updateServiceIds', + title: 'Service IDs', + type: 'short-input', + placeholder: 'Comma-separated service IDs', + condition: { field: 'operation', value: 'rootly_update_incident' }, + mode: 'advanced', + }, + { + id: 'updateEnvironmentIds', + title: 'Environment IDs', + type: 'short-input', + placeholder: 'Comma-separated environment IDs', + condition: { field: 'operation', value: 'rootly_update_incident' }, + mode: 'advanced', + }, + { + id: 'updateGroupIds', + title: 'Team IDs', + type: 'short-input', + placeholder: 'Comma-separated team/group IDs', + condition: { field: 'operation', value: 'rootly_update_incident' }, + mode: 'advanced', + }, + { + id: 'updateKind', + title: 'Kind', + type: 'dropdown', + options: [ + { label: 'Unchanged', id: '' }, + { label: 'Normal', id: 'normal' }, + { label: 'Test', id: 'test' }, + { label: 'Example', id: 'example' }, + { label: 'Backfilled', id: 'backfilled' }, + { label: 'Scheduled', id: 'scheduled' }, + ], + value: () => '', + condition: { field: 'operation', value: 'rootly_update_incident' }, + mode: 'advanced', + }, + { + id: 'updatePrivate', + title: 'Private', + type: 'dropdown', + options: [ + { label: 'Unchanged', id: '' }, + { label: 'Yes', id: 'true' }, + { label: 'No', id: 'false' }, + ], + value: () => '', + condition: { field: 'operation', value: 'rootly_update_incident' }, + mode: 'advanced', + }, + { + id: 'updateIncidentTypeIds', + title: 'Incident Type IDs', + type: 'short-input', + placeholder: 'Comma-separated incident type IDs', + condition: { field: 'operation', value: 'rootly_update_incident' }, + mode: 'advanced', + }, + { + id: 'updateFunctionalityIds', + title: 'Functionality IDs', + type: 'short-input', + placeholder: 'Comma-separated functionality IDs', + condition: { field: 'operation', value: 'rootly_update_incident' }, + mode: 'advanced', + }, + { + id: 'updateLabels', + title: 'Labels', + type: 'short-input', + placeholder: '{"platform":"osx","version":"1.29"}', + condition: { field: 'operation', value: 'rootly_update_incident' }, + mode: 'advanced', + wandConfig: { + enabled: true, + prompt: + 'Generate a JSON object of key-value label pairs for a Rootly incident. Example: {"platform":"osx","version":"1.29","region":"us-east-1"}. Return ONLY the JSON object - no explanations, no extra text.', + placeholder: 'Describe the labels (e.g., "platform osx, version 1.29")...', + generationType: 'json-object', + }, + }, + { + id: 'cancellationMessage', + title: 'Cancellation Message', + type: 'long-input', + placeholder: 'Why was the incident cancelled?', + condition: { field: 'operation', value: 'rootly_update_incident' }, + mode: 'advanced', + }, + + // List Incidents fields + { + id: 'listIncidentsStatus', + title: 'Status Filter', + type: 'dropdown', + options: [ + { label: 'All', id: '' }, + { label: 'In Triage', id: 'in_triage' }, + { label: 'Started', id: 'started' }, + { label: 'Detected', id: 'detected' }, + { label: 'Acknowledged', id: 'acknowledged' }, + { label: 'Mitigated', id: 'mitigated' }, + { label: 'Resolved', id: 'resolved' }, + { label: 'Closed', id: 'closed' }, + { label: 'Cancelled', id: 'cancelled' }, + { label: 'Scheduled', id: 'scheduled' }, + { label: 'In Progress', id: 'in_progress' }, + { label: 'Completed', id: 'completed' }, + ], + value: () => '', + condition: { field: 'operation', value: 'rootly_list_incidents' }, + }, + { + id: 'listIncidentsSearch', + title: 'Search', + type: 'short-input', + placeholder: 'Search incidents...', + condition: { field: 'operation', value: 'rootly_list_incidents' }, + }, + { + id: 'listIncidentsSeverity', + title: 'Severity Filter', + type: 'short-input', + placeholder: 'Severity slug (e.g., sev0)', + condition: { field: 'operation', value: 'rootly_list_incidents' }, + mode: 'advanced', + }, + { + id: 'listIncidentsServices', + title: 'Services Filter', + type: 'short-input', + placeholder: 'Comma-separated service slugs', + condition: { field: 'operation', value: 'rootly_list_incidents' }, + mode: 'advanced', + }, + { + id: 'listIncidentsSort', + title: 'Sort', + type: 'dropdown', + options: [ + { label: 'Default', id: '' }, + { label: 'Newest First', id: '-created_at' }, + { label: 'Oldest First', id: 'created_at' }, + { label: 'Recently Started', id: '-started_at' }, + { label: 'Recently Updated', id: '-updated_at' }, + ], + value: () => '', + condition: { field: 'operation', value: 'rootly_list_incidents' }, + mode: 'advanced', + }, + { + id: 'listIncidentsTeams', + title: 'Teams Filter', + type: 'short-input', + placeholder: 'Comma-separated team slugs', + condition: { field: 'operation', value: 'rootly_list_incidents' }, + mode: 'advanced', + }, + { + id: 'listIncidentsEnvironments', + title: 'Environments Filter', + type: 'short-input', + placeholder: 'Comma-separated environment slugs', + condition: { field: 'operation', value: 'rootly_list_incidents' }, + mode: 'advanced', + }, + { + id: 'listIncidentsPageSize', + title: 'Page Size', + type: 'short-input', + placeholder: '20', + condition: { field: 'operation', value: 'rootly_list_incidents' }, + mode: 'advanced', + }, + { + id: 'listIncidentsPageNumber', + title: 'Page Number', + type: 'short-input', + placeholder: '1', + condition: { field: 'operation', value: 'rootly_list_incidents' }, + mode: 'advanced', + }, + + // Create Alert fields + { + id: 'alertSummary', + title: 'Summary', + type: 'short-input', + placeholder: 'Alert summary', + condition: { field: 'operation', value: 'rootly_create_alert' }, + required: { field: 'operation', value: 'rootly_create_alert' }, + }, + { + id: 'alertDescription', + title: 'Description', + type: 'long-input', + placeholder: 'Detailed alert description', + condition: { field: 'operation', value: 'rootly_create_alert' }, + }, + { + id: 'alertSource', + title: 'Source', + type: 'short-input', + placeholder: 'Alert source (e.g., api, datadog)', + condition: { field: 'operation', value: 'rootly_create_alert' }, + required: { field: 'operation', value: 'rootly_create_alert' }, + }, + { + id: 'alertServiceIds', + title: 'Service IDs', + type: 'short-input', + placeholder: 'Comma-separated service IDs', + condition: { field: 'operation', value: 'rootly_create_alert' }, + mode: 'advanced', + }, + { + id: 'alertGroupIds', + title: 'Team IDs', + type: 'short-input', + placeholder: 'Comma-separated team/group IDs', + condition: { field: 'operation', value: 'rootly_create_alert' }, + mode: 'advanced', + }, + { + id: 'alertDeduplicationKey', + title: 'Deduplication Key', + type: 'short-input', + placeholder: 'Key to deduplicate alerts', + condition: { field: 'operation', value: 'rootly_create_alert' }, + mode: 'advanced', + }, + { + id: 'alertEnvironmentIds', + title: 'Environment IDs', + type: 'short-input', + placeholder: 'Comma-separated environment IDs', + condition: { field: 'operation', value: 'rootly_create_alert' }, + mode: 'advanced', + }, + { + id: 'alertStatus', + title: 'Status', + type: 'dropdown', + options: [ + { label: 'Default', id: '' }, + { label: 'Open', id: 'open' }, + { label: 'Triggered', id: 'triggered' }, + ], + value: () => '', + condition: { field: 'operation', value: 'rootly_create_alert' }, + mode: 'advanced', + }, + { + id: 'alertExternalId', + title: 'External ID', + type: 'short-input', + placeholder: 'External alert ID', + condition: { field: 'operation', value: 'rootly_create_alert' }, + mode: 'advanced', + }, + { + id: 'alertExternalUrl', + title: 'External URL', + type: 'short-input', + placeholder: 'Link to external source', + condition: { field: 'operation', value: 'rootly_create_alert' }, + mode: 'advanced', + }, + + // List Alerts fields + { + id: 'listAlertsStatus', + title: 'Status Filter', + type: 'dropdown', + options: [ + { label: 'All', id: '' }, + { label: 'Open', id: 'open' }, + { label: 'Triggered', id: 'triggered' }, + { label: 'Acknowledged', id: 'acknowledged' }, + { label: 'Resolved', id: 'resolved' }, + { label: 'Deferred', id: 'deferred' }, + ], + value: () => '', + condition: { field: 'operation', value: 'rootly_list_alerts' }, + }, + { + id: 'listAlertsSource', + title: 'Source Filter', + type: 'short-input', + placeholder: 'Filter by source (e.g., datadog)', + condition: { field: 'operation', value: 'rootly_list_alerts' }, + mode: 'advanced', + }, + { + id: 'listAlertsServices', + title: 'Services Filter', + type: 'short-input', + placeholder: 'Comma-separated service slugs', + condition: { field: 'operation', value: 'rootly_list_alerts' }, + mode: 'advanced', + }, + { + id: 'listAlertsEnvironments', + title: 'Environments Filter', + type: 'short-input', + placeholder: 'Comma-separated environment slugs', + condition: { field: 'operation', value: 'rootly_list_alerts' }, + mode: 'advanced', + }, + { + id: 'listAlertsGroups', + title: 'Teams Filter', + type: 'short-input', + placeholder: 'Comma-separated team slugs', + condition: { field: 'operation', value: 'rootly_list_alerts' }, + mode: 'advanced', + }, + { + id: 'listAlertsPageSize', + title: 'Page Size', + type: 'short-input', + placeholder: '20', + condition: { field: 'operation', value: 'rootly_list_alerts' }, + mode: 'advanced', + }, + { + id: 'listAlertsPageNumber', + title: 'Page Number', + type: 'short-input', + placeholder: '1', + condition: { field: 'operation', value: 'rootly_list_alerts' }, + mode: 'advanced', + }, + + // Add Incident Event fields + { + id: 'eventIncidentId', + title: 'Incident ID', + type: 'short-input', + placeholder: 'The ID of the incident', + condition: { field: 'operation', value: 'rootly_add_incident_event' }, + required: { field: 'operation', value: 'rootly_add_incident_event' }, + }, + { + id: 'eventText', + title: 'Event', + type: 'long-input', + placeholder: 'Describe the timeline event', + condition: { field: 'operation', value: 'rootly_add_incident_event' }, + required: { field: 'operation', value: 'rootly_add_incident_event' }, + }, + { + id: 'eventVisibility', + title: 'Visibility', + type: 'dropdown', + options: [ + { label: 'Default', id: '' }, + { label: 'Internal', id: 'internal' }, + { label: 'External', id: 'external' }, + ], + value: () => '', + condition: { field: 'operation', value: 'rootly_add_incident_event' }, + mode: 'advanced', + }, + + // List Services fields + { + id: 'servicesSearch', + title: 'Search', + type: 'short-input', + placeholder: 'Search services...', + condition: { field: 'operation', value: 'rootly_list_services' }, + }, + { + id: 'servicesPageSize', + title: 'Page Size', + type: 'short-input', + placeholder: '20', + condition: { field: 'operation', value: 'rootly_list_services' }, + mode: 'advanced', + }, + { + id: 'servicesPageNumber', + title: 'Page Number', + type: 'short-input', + placeholder: '1', + condition: { field: 'operation', value: 'rootly_list_services' }, + mode: 'advanced', + }, + + // List Severities fields + { + id: 'severitiesSearch', + title: 'Search', + type: 'short-input', + placeholder: 'Search severities...', + condition: { field: 'operation', value: 'rootly_list_severities' }, + }, + { + id: 'severitiesPageSize', + title: 'Page Size', + type: 'short-input', + placeholder: '20', + condition: { field: 'operation', value: 'rootly_list_severities' }, + mode: 'advanced', + }, + { + id: 'severitiesPageNumber', + title: 'Page Number', + type: 'short-input', + placeholder: '1', + condition: { field: 'operation', value: 'rootly_list_severities' }, + mode: 'advanced', + }, + + // List Teams fields + { + id: 'teamsSearch', + title: 'Search', + type: 'short-input', + placeholder: 'Search teams...', + condition: { field: 'operation', value: 'rootly_list_teams' }, + }, + { + id: 'teamsPageSize', + title: 'Page Size', + type: 'short-input', + placeholder: '20', + condition: { field: 'operation', value: 'rootly_list_teams' }, + mode: 'advanced', + }, + { + id: 'teamsPageNumber', + title: 'Page Number', + type: 'short-input', + placeholder: '1', + condition: { field: 'operation', value: 'rootly_list_teams' }, + mode: 'advanced', + }, + + // List Environments fields + { + id: 'environmentsSearch', + title: 'Search', + type: 'short-input', + placeholder: 'Search environments...', + condition: { field: 'operation', value: 'rootly_list_environments' }, + }, + { + id: 'environmentsPageSize', + title: 'Page Size', + type: 'short-input', + placeholder: '20', + condition: { field: 'operation', value: 'rootly_list_environments' }, + mode: 'advanced', + }, + { + id: 'environmentsPageNumber', + title: 'Page Number', + type: 'short-input', + placeholder: '1', + condition: { field: 'operation', value: 'rootly_list_environments' }, + mode: 'advanced', + }, + + // List Incident Types fields + { + id: 'incidentTypesSearch', + title: 'Name Filter', + type: 'short-input', + placeholder: 'Filter by name...', + condition: { field: 'operation', value: 'rootly_list_incident_types' }, + }, + { + id: 'incidentTypesPageSize', + title: 'Page Size', + type: 'short-input', + placeholder: '20', + condition: { field: 'operation', value: 'rootly_list_incident_types' }, + mode: 'advanced', + }, + { + id: 'incidentTypesPageNumber', + title: 'Page Number', + type: 'short-input', + placeholder: '1', + condition: { field: 'operation', value: 'rootly_list_incident_types' }, + mode: 'advanced', + }, + + // List Functionalities fields + { + id: 'functionalitiesSearch', + title: 'Search', + type: 'short-input', + placeholder: 'Search functionalities...', + condition: { field: 'operation', value: 'rootly_list_functionalities' }, + }, + { + id: 'functionalitiesPageSize', + title: 'Page Size', + type: 'short-input', + placeholder: '20', + condition: { field: 'operation', value: 'rootly_list_functionalities' }, + mode: 'advanced', + }, + { + id: 'functionalitiesPageNumber', + title: 'Page Number', + type: 'short-input', + placeholder: '1', + condition: { field: 'operation', value: 'rootly_list_functionalities' }, + mode: 'advanced', + }, + + // List Retrospectives fields + { + id: 'retrospectivesStatus', + title: 'Status Filter', + type: 'dropdown', + options: [ + { label: 'All', id: '' }, + { label: 'Draft', id: 'draft' }, + { label: 'Published', id: 'published' }, + ], + value: () => '', + condition: { field: 'operation', value: 'rootly_list_retrospectives' }, + }, + { + id: 'retrospectivesSearch', + title: 'Search', + type: 'short-input', + placeholder: 'Search retrospectives...', + condition: { field: 'operation', value: 'rootly_list_retrospectives' }, + }, + { + id: 'retrospectivesPageSize', + title: 'Page Size', + type: 'short-input', + placeholder: '20', + condition: { field: 'operation', value: 'rootly_list_retrospectives' }, + mode: 'advanced', + }, + { + id: 'retrospectivesPageNumber', + title: 'Page Number', + type: 'short-input', + placeholder: '1', + condition: { field: 'operation', value: 'rootly_list_retrospectives' }, + mode: 'advanced', + }, + + // API Key (common) + { + id: 'apiKey', + title: 'API Key', + type: 'short-input', + placeholder: 'Enter your Rootly API key', + password: true, + required: true, + }, + ], + tools: { + access: [ + 'rootly_create_incident', + 'rootly_get_incident', + 'rootly_update_incident', + 'rootly_list_incidents', + 'rootly_create_alert', + 'rootly_list_alerts', + 'rootly_add_incident_event', + 'rootly_list_services', + 'rootly_list_severities', + 'rootly_list_teams', + 'rootly_list_environments', + 'rootly_list_incident_types', + 'rootly_list_functionalities', + 'rootly_list_retrospectives', + ], + config: { + tool: (params) => params.operation, + params: (params) => { + const baseParams: Record = { + apiKey: params.apiKey, + } + + switch (params.operation) { + case 'rootly_create_incident': + return { + ...baseParams, + title: params.title, + summary: params.createSummary, + severityId: params.createSeverityId, + status: params.createStatus, + kind: params.createKind, + serviceIds: params.createServiceIds, + environmentIds: params.createEnvironmentIds, + groupIds: params.createGroupIds, + incidentTypeIds: params.createIncidentTypeIds, + functionalityIds: params.createFunctionalityIds, + labels: params.createLabels, + private: params.createPrivate ? params.createPrivate === 'true' : undefined, + } + + case 'rootly_get_incident': + return { + ...baseParams, + incidentId: params.getIncidentId, + } + + case 'rootly_update_incident': + return { + ...baseParams, + incidentId: params.updateIncidentId, + title: params.updateTitle, + summary: params.updateSummary, + status: params.updateStatus, + severityId: params.updateSeverityId, + kind: params.updateKind, + private: params.updatePrivate ? params.updatePrivate === 'true' : undefined, + mitigationMessage: params.mitigationMessage, + resolutionMessage: params.resolutionMessage, + cancellationMessage: params.cancellationMessage, + serviceIds: params.updateServiceIds, + environmentIds: params.updateEnvironmentIds, + groupIds: params.updateGroupIds, + incidentTypeIds: params.updateIncidentTypeIds, + functionalityIds: params.updateFunctionalityIds, + labels: params.updateLabels, + } + + case 'rootly_list_incidents': + return { + ...baseParams, + status: params.listIncidentsStatus, + search: params.listIncidentsSearch, + severity: params.listIncidentsSeverity, + services: params.listIncidentsServices, + teams: params.listIncidentsTeams, + environments: params.listIncidentsEnvironments, + sort: params.listIncidentsSort, + pageSize: params.listIncidentsPageSize + ? Number(params.listIncidentsPageSize) + : undefined, + pageNumber: params.listIncidentsPageNumber + ? Number(params.listIncidentsPageNumber) + : undefined, + } + + case 'rootly_create_alert': + return { + ...baseParams, + summary: params.alertSummary, + description: params.alertDescription, + source: params.alertSource, + status: params.alertStatus, + serviceIds: params.alertServiceIds, + groupIds: params.alertGroupIds, + environmentIds: params.alertEnvironmentIds, + externalId: params.alertExternalId, + deduplicationKey: params.alertDeduplicationKey, + externalUrl: params.alertExternalUrl, + } + + case 'rootly_list_alerts': + return { + ...baseParams, + status: params.listAlertsStatus, + source: params.listAlertsSource, + services: params.listAlertsServices, + environments: params.listAlertsEnvironments, + groups: params.listAlertsGroups, + pageSize: params.listAlertsPageSize ? Number(params.listAlertsPageSize) : undefined, + pageNumber: params.listAlertsPageNumber + ? Number(params.listAlertsPageNumber) + : undefined, + } + + case 'rootly_add_incident_event': + return { + ...baseParams, + incidentId: params.eventIncidentId, + event: params.eventText, + visibility: params.eventVisibility, + } + + case 'rootly_list_services': + return { + ...baseParams, + search: params.servicesSearch, + pageSize: params.servicesPageSize ? Number(params.servicesPageSize) : undefined, + pageNumber: params.servicesPageNumber ? Number(params.servicesPageNumber) : undefined, + } + + case 'rootly_list_severities': + return { + ...baseParams, + search: params.severitiesSearch, + pageSize: params.severitiesPageSize ? Number(params.severitiesPageSize) : undefined, + pageNumber: params.severitiesPageNumber + ? Number(params.severitiesPageNumber) + : undefined, + } + + case 'rootly_list_teams': + return { + ...baseParams, + search: params.teamsSearch, + pageSize: params.teamsPageSize ? Number(params.teamsPageSize) : undefined, + pageNumber: params.teamsPageNumber ? Number(params.teamsPageNumber) : undefined, + } + + case 'rootly_list_environments': + return { + ...baseParams, + search: params.environmentsSearch, + pageSize: params.environmentsPageSize + ? Number(params.environmentsPageSize) + : undefined, + pageNumber: params.environmentsPageNumber + ? Number(params.environmentsPageNumber) + : undefined, + } + + case 'rootly_list_incident_types': + return { + ...baseParams, + search: params.incidentTypesSearch, + pageSize: params.incidentTypesPageSize + ? Number(params.incidentTypesPageSize) + : undefined, + pageNumber: params.incidentTypesPageNumber + ? Number(params.incidentTypesPageNumber) + : undefined, + } + + case 'rootly_list_functionalities': + return { + ...baseParams, + search: params.functionalitiesSearch, + pageSize: params.functionalitiesPageSize + ? Number(params.functionalitiesPageSize) + : undefined, + pageNumber: params.functionalitiesPageNumber + ? Number(params.functionalitiesPageNumber) + : undefined, + } + + case 'rootly_list_retrospectives': + return { + ...baseParams, + status: params.retrospectivesStatus, + search: params.retrospectivesSearch, + pageSize: params.retrospectivesPageSize + ? Number(params.retrospectivesPageSize) + : undefined, + pageNumber: params.retrospectivesPageNumber + ? Number(params.retrospectivesPageNumber) + : undefined, + } + + default: + return baseParams + } + }, + }, + }, + inputs: { + operation: { type: 'string', description: 'Operation to perform' }, + apiKey: { type: 'string', description: 'Rootly API key' }, + title: { type: 'string', description: 'Incident title' }, + createSummary: { type: 'string', description: 'Incident summary' }, + createSeverityId: { type: 'string', description: 'Severity ID' }, + createStatus: { type: 'string', description: 'Incident status' }, + createKind: { type: 'string', description: 'Incident kind' }, + createServiceIds: { type: 'string', description: 'Service IDs' }, + createEnvironmentIds: { type: 'string', description: 'Environment IDs' }, + createGroupIds: { type: 'string', description: 'Team IDs' }, + createIncidentTypeIds: { type: 'string', description: 'Incident type IDs' }, + createFunctionalityIds: { type: 'string', description: 'Functionality IDs' }, + createLabels: { type: 'string', description: 'Labels as JSON' }, + createPrivate: { type: 'string', description: 'Whether incident is private' }, + getIncidentId: { type: 'string', description: 'Incident ID to retrieve' }, + updateIncidentId: { type: 'string', description: 'Incident ID to update' }, + updateTitle: { type: 'string', description: 'Updated title' }, + updateSummary: { type: 'string', description: 'Updated summary' }, + updateStatus: { type: 'string', description: 'Updated status' }, + updateSeverityId: { type: 'string', description: 'Updated severity ID' }, + mitigationMessage: { type: 'string', description: 'Mitigation message' }, + resolutionMessage: { type: 'string', description: 'Resolution message' }, + updateServiceIds: { type: 'string', description: 'Updated service IDs' }, + updateEnvironmentIds: { type: 'string', description: 'Updated environment IDs' }, + updateGroupIds: { type: 'string', description: 'Updated team IDs' }, + updateKind: { type: 'string', description: 'Updated kind' }, + updatePrivate: { type: 'string', description: 'Whether incident is private' }, + updateIncidentTypeIds: { type: 'string', description: 'Updated incident type IDs' }, + updateFunctionalityIds: { type: 'string', description: 'Updated functionality IDs' }, + updateLabels: { type: 'string', description: 'Updated labels as JSON' }, + cancellationMessage: { type: 'string', description: 'Cancellation message' }, + listIncidentsStatus: { type: 'string', description: 'Filter by status' }, + listIncidentsSearch: { type: 'string', description: 'Search incidents' }, + listIncidentsSeverity: { type: 'string', description: 'Filter by severity' }, + listIncidentsServices: { type: 'string', description: 'Filter by services' }, + listIncidentsTeams: { type: 'string', description: 'Filter by teams' }, + listIncidentsEnvironments: { type: 'string', description: 'Filter by environments' }, + listIncidentsSort: { type: 'string', description: 'Sort order' }, + listIncidentsPageSize: { type: 'string', description: 'Page size' }, + listIncidentsPageNumber: { type: 'string', description: 'Page number' }, + alertSummary: { type: 'string', description: 'Alert summary' }, + alertDescription: { type: 'string', description: 'Alert description' }, + alertSource: { type: 'string', description: 'Alert source' }, + alertServiceIds: { type: 'string', description: 'Alert service IDs' }, + alertGroupIds: { type: 'string', description: 'Alert team IDs' }, + alertEnvironmentIds: { type: 'string', description: 'Alert environment IDs' }, + alertStatus: { type: 'string', description: 'Alert status' }, + alertExternalId: { type: 'string', description: 'External alert ID' }, + alertDeduplicationKey: { type: 'string', description: 'Deduplication key' }, + alertExternalUrl: { type: 'string', description: 'External URL' }, + listAlertsStatus: { type: 'string', description: 'Filter alerts by status' }, + listAlertsSource: { type: 'string', description: 'Filter alerts by source' }, + listAlertsServices: { type: 'string', description: 'Filter alerts by services' }, + listAlertsEnvironments: { type: 'string', description: 'Filter alerts by environments' }, + listAlertsGroups: { type: 'string', description: 'Filter alerts by teams' }, + listAlertsPageSize: { type: 'string', description: 'Alerts page size' }, + listAlertsPageNumber: { type: 'string', description: 'Alerts page number' }, + eventIncidentId: { type: 'string', description: 'Incident ID for event' }, + eventText: { type: 'string', description: 'Event description' }, + eventVisibility: { type: 'string', description: 'Event visibility' }, + servicesSearch: { type: 'string', description: 'Search services' }, + servicesPageSize: { type: 'string', description: 'Services page size' }, + servicesPageNumber: { type: 'string', description: 'Services page number' }, + severitiesSearch: { type: 'string', description: 'Search severities' }, + severitiesPageSize: { type: 'string', description: 'Severities page size' }, + severitiesPageNumber: { type: 'string', description: 'Severities page number' }, + teamsSearch: { type: 'string', description: 'Search teams' }, + teamsPageSize: { type: 'string', description: 'Teams page size' }, + teamsPageNumber: { type: 'string', description: 'Teams page number' }, + environmentsSearch: { type: 'string', description: 'Search environments' }, + environmentsPageSize: { type: 'string', description: 'Environments page size' }, + environmentsPageNumber: { type: 'string', description: 'Environments page number' }, + incidentTypesSearch: { type: 'string', description: 'Search incident types' }, + incidentTypesPageSize: { type: 'string', description: 'Incident types page size' }, + incidentTypesPageNumber: { type: 'string', description: 'Incident types page number' }, + functionalitiesSearch: { type: 'string', description: 'Search functionalities' }, + functionalitiesPageSize: { type: 'string', description: 'Functionalities page size' }, + functionalitiesPageNumber: { type: 'string', description: 'Functionalities page number' }, + retrospectivesStatus: { type: 'string', description: 'Filter retrospectives by status' }, + retrospectivesSearch: { type: 'string', description: 'Search retrospectives' }, + retrospectivesPageSize: { type: 'string', description: 'Retrospectives page size' }, + retrospectivesPageNumber: { type: 'string', description: 'Retrospectives page number' }, + }, + outputs: { + incident: { + type: 'json', + description: 'Incident data (id, title, status, summary, severity, url, timestamps)', + }, + incidents: { + type: 'json', + description: 'List of incidents (id, title, status, summary, severity, url, timestamps)', + }, + alert: { + type: 'json', + description: 'Alert data (id, summary, description, status, source, externalUrl)', + }, + alerts: { + type: 'json', + description: 'List of alerts (id, summary, description, status, source, externalUrl)', + }, + eventId: { type: 'string', description: 'Created event ID' }, + event: { type: 'string', description: 'Event description' }, + visibility: { type: 'string', description: 'Event visibility' }, + occurredAt: { type: 'string', description: 'When the event occurred' }, + createdAt: { type: 'string', description: 'Creation date' }, + updatedAt: { type: 'string', description: 'Last update date' }, + services: { + type: 'json', + description: 'List of services (id, name, slug, description, color)', + }, + severities: { + type: 'json', + description: 'List of severities (id, name, slug, severity, color, position)', + }, + teams: { type: 'json', description: 'List of teams (id, name, slug, description, color)' }, + environments: { + type: 'json', + description: 'List of environments (id, name, slug, description, color)', + }, + incidentTypes: { + type: 'json', + description: 'List of incident types (id, name, slug, description, color)', + }, + functionalities: { + type: 'json', + description: 'List of functionalities (id, name, slug, description, color)', + }, + retrospectives: { + type: 'json', + description: 'List of retrospectives (id, title, status, url, timestamps)', + }, + totalCount: { type: 'number', description: 'Total count of items returned' }, + }, +} diff --git a/apps/sim/blocks/registry.ts b/apps/sim/blocks/registry.ts index 574fdd000e..6f24fe7ba6 100644 --- a/apps/sim/blocks/registry.ts +++ b/apps/sim/blocks/registry.ts @@ -151,6 +151,7 @@ import { ResendBlock } from '@/blocks/blocks/resend' import { ResponseBlock } from '@/blocks/blocks/response' import { RevenueCatBlock } from '@/blocks/blocks/revenuecat' import { RipplingBlock } from '@/blocks/blocks/rippling' +import { RootlyBlock } from '@/blocks/blocks/rootly' import { RouterBlock, RouterV2Block } from '@/blocks/blocks/router' import { RssBlock } from '@/blocks/blocks/rss' import { S3Block } from '@/blocks/blocks/s3' @@ -382,6 +383,7 @@ export const registry: Record = { response: ResponseBlock, revenuecat: RevenueCatBlock, rippling: RipplingBlock, + rootly: RootlyBlock, router: RouterBlock, router_v2: RouterV2Block, rss: RssBlock, diff --git a/apps/sim/components/icons.tsx b/apps/sim/components/icons.tsx index 6614c9f603..4662b1c0d3 100644 --- a/apps/sim/components/icons.tsx +++ b/apps/sim/components/icons.tsx @@ -6387,6 +6387,41 @@ export function RipplingIcon(props: SVGProps) { ) } +export function RootlyIcon(props: SVGProps) { + return ( + + + + + + + + + + ) +} + export function HexIcon(props: SVGProps) { return ( diff --git a/apps/sim/tools/registry.ts b/apps/sim/tools/registry.ts index 09da0ce4bf..eba06d416e 100644 --- a/apps/sim/tools/registry.ts +++ b/apps/sim/tools/registry.ts @@ -1985,6 +1985,22 @@ import { ripplingUpdateTitleTool, ripplingUpdateWorkLocationTool, } from '@/tools/rippling' +import { + rootlyAddIncidentEventTool, + rootlyCreateAlertTool, + rootlyCreateIncidentTool, + rootlyGetIncidentTool, + rootlyListAlertsTool, + rootlyListEnvironmentsTool, + rootlyListFunctionalitiesTool, + rootlyListIncidentsTool, + rootlyListIncidentTypesTool, + rootlyListRetrospectivesTool, + rootlyListServicesTool, + rootlyListSeveritiesTool, + rootlyListTeamsTool, + rootlyUpdateIncidentTool, +} from '@/tools/rootly' import { s3CopyObjectTool, s3DeleteObjectTool, @@ -3774,6 +3790,20 @@ export const tools: Record = { rippling_update_supergroup_inclusion_members: ripplingUpdateSupergroupInclusionMembersTool, rippling_update_title: ripplingUpdateTitleTool, rippling_update_work_location: ripplingUpdateWorkLocationTool, + rootly_add_incident_event: rootlyAddIncidentEventTool, + rootly_create_alert: rootlyCreateAlertTool, + rootly_create_incident: rootlyCreateIncidentTool, + rootly_get_incident: rootlyGetIncidentTool, + rootly_list_alerts: rootlyListAlertsTool, + rootly_list_environments: rootlyListEnvironmentsTool, + rootly_list_functionalities: rootlyListFunctionalitiesTool, + rootly_list_incident_types: rootlyListIncidentTypesTool, + rootly_list_incidents: rootlyListIncidentsTool, + rootly_list_retrospectives: rootlyListRetrospectivesTool, + rootly_list_services: rootlyListServicesTool, + rootly_list_severities: rootlyListSeveritiesTool, + rootly_list_teams: rootlyListTeamsTool, + rootly_update_incident: rootlyUpdateIncidentTool, google_drive_copy: googleDriveCopyTool, google_drive_create_folder: googleDriveCreateFolderTool, google_drive_delete: googleDriveDeleteTool, diff --git a/apps/sim/tools/rootly/add_incident_event.ts b/apps/sim/tools/rootly/add_incident_event.ts new file mode 100644 index 0000000000..cc9f1fe0ed --- /dev/null +++ b/apps/sim/tools/rootly/add_incident_event.ts @@ -0,0 +1,117 @@ +import type { + RootlyAddIncidentEventParams, + RootlyAddIncidentEventResponse, +} from '@/tools/rootly/types' +import type { ToolConfig } from '@/tools/types' + +export const rootlyAddIncidentEventTool: ToolConfig< + RootlyAddIncidentEventParams, + RootlyAddIncidentEventResponse +> = { + id: 'rootly_add_incident_event', + name: 'Rootly Add Incident Event', + description: 'Add a timeline event to an existing incident in Rootly.', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rootly API key', + }, + incidentId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The ID of the incident to add the event to', + }, + event: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The summary/description of the event', + }, + visibility: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Event visibility (internal or external)', + }, + }, + + request: { + url: (params) => `https://api.rootly.com/v1/incidents/${params.incidentId.trim()}/events`, + method: 'POST', + headers: (params) => ({ + 'Content-Type': 'application/vnd.api+json', + Authorization: `Bearer ${params.apiKey}`, + }), + body: (params) => { + const attributes: Record = { + event: params.event, + } + if (params.visibility) attributes.visibility = params.visibility + return { data: { type: 'incident_events', attributes } } + }, + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + const errorData = await response.json().catch(() => ({})) + return { + success: false, + output: { + eventId: '', + event: '', + visibility: null, + occurredAt: null, + createdAt: '', + updatedAt: '', + }, + error: errorData.errors?.[0]?.detail || `HTTP ${response.status}: ${response.statusText}`, + } + } + + const data = await response.json() + const attrs = data.data?.attributes || {} + return { + success: true, + output: { + eventId: data.data?.id ?? '', + event: attrs.event ?? '', + visibility: attrs.visibility ?? null, + occurredAt: attrs.occurred_at ?? null, + createdAt: attrs.created_at ?? '', + updatedAt: attrs.updated_at ?? '', + }, + } + }, + + outputs: { + eventId: { + type: 'string', + description: 'The ID of the created event', + }, + event: { + type: 'string', + description: 'The event summary', + }, + visibility: { + type: 'string', + description: 'Event visibility (internal or external)', + }, + occurredAt: { + type: 'string', + description: 'When the event occurred', + }, + createdAt: { + type: 'string', + description: 'Creation date', + }, + updatedAt: { + type: 'string', + description: 'Last update date', + }, + }, +} diff --git a/apps/sim/tools/rootly/create_alert.ts b/apps/sim/tools/rootly/create_alert.ts new file mode 100644 index 0000000000..0687276f95 --- /dev/null +++ b/apps/sim/tools/rootly/create_alert.ts @@ -0,0 +1,159 @@ +import type { RootlyCreateAlertParams, RootlyCreateAlertResponse } from '@/tools/rootly/types' +import type { ToolConfig } from '@/tools/types' + +export const rootlyCreateAlertTool: ToolConfig = + { + id: 'rootly_create_alert', + name: 'Rootly Create Alert', + description: 'Create a new alert in Rootly for on-call notification and routing.', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rootly API key', + }, + summary: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The summary of the alert', + }, + description: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'A detailed description of the alert', + }, + source: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The source of the alert (e.g., api, manual, datadog, pagerduty)', + }, + status: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Alert status on creation (open, triggered)', + }, + serviceIds: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated service IDs to attach', + }, + groupIds: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated team/group IDs to attach', + }, + environmentIds: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated environment IDs to attach', + }, + externalId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'External ID for the alert', + }, + externalUrl: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'External URL for the alert', + }, + deduplicationKey: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Alerts sharing the same deduplication key are treated as a single alert', + }, + }, + + request: { + url: 'https://api.rootly.com/v1/alerts', + method: 'POST', + headers: (params) => ({ + 'Content-Type': 'application/vnd.api+json', + Authorization: `Bearer ${params.apiKey}`, + }), + body: (params) => { + const attributes: Record = { + summary: params.summary, + source: params.source, + } + if (params.description) attributes.description = params.description + if (params.status) attributes.status = params.status + if (params.externalId) attributes.external_id = params.externalId + if (params.externalUrl) attributes.external_url = params.externalUrl + if (params.deduplicationKey) attributes.deduplication_key = params.deduplicationKey + if (params.serviceIds) { + attributes.service_ids = params.serviceIds.split(',').map((s: string) => s.trim()) + } + if (params.groupIds) { + attributes.group_ids = params.groupIds.split(',').map((s: string) => s.trim()) + } + if (params.environmentIds) { + attributes.environment_ids = params.environmentIds.split(',').map((s: string) => s.trim()) + } + return { data: { type: 'alerts', attributes } } + }, + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + const errorData = await response.json().catch(() => ({})) + return { + success: false, + output: { alert: {} as RootlyCreateAlertResponse['output']['alert'] }, + error: errorData.errors?.[0]?.detail || `HTTP ${response.status}: ${response.statusText}`, + } + } + + const data = await response.json() + const attrs = data.data?.attributes || {} + return { + success: true, + output: { + alert: { + id: data.data?.id ?? null, + summary: attrs.summary ?? '', + description: attrs.description ?? null, + source: attrs.source ?? null, + status: attrs.status ?? null, + externalId: attrs.external_id ?? null, + externalUrl: attrs.external_url ?? null, + deduplicationKey: attrs.deduplication_key ?? null, + createdAt: attrs.created_at ?? '', + updatedAt: attrs.updated_at ?? '', + }, + }, + } + }, + + outputs: { + alert: { + type: 'object', + description: 'The created alert', + properties: { + id: { type: 'string', description: 'Unique alert ID' }, + summary: { type: 'string', description: 'Alert summary' }, + description: { type: 'string', description: 'Alert description' }, + source: { type: 'string', description: 'Alert source' }, + status: { type: 'string', description: 'Alert status' }, + externalId: { type: 'string', description: 'External ID' }, + externalUrl: { type: 'string', description: 'External URL' }, + deduplicationKey: { type: 'string', description: 'Deduplication key' }, + createdAt: { type: 'string', description: 'Creation date' }, + updatedAt: { type: 'string', description: 'Last update date' }, + }, + }, + }, + } diff --git a/apps/sim/tools/rootly/create_incident.ts b/apps/sim/tools/rootly/create_incident.ts new file mode 100644 index 0000000000..fa17b888e6 --- /dev/null +++ b/apps/sim/tools/rootly/create_incident.ts @@ -0,0 +1,208 @@ +import type { RootlyCreateIncidentParams, RootlyCreateIncidentResponse } from '@/tools/rootly/types' +import type { ToolConfig } from '@/tools/types' + +export const rootlyCreateIncidentTool: ToolConfig< + RootlyCreateIncidentParams, + RootlyCreateIncidentResponse +> = { + id: 'rootly_create_incident', + name: 'Rootly Create Incident', + description: 'Create a new incident in Rootly with optional severity, services, and teams.', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rootly API key', + }, + title: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'The title of the incident (auto-generated if not provided)', + }, + summary: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'A summary of the incident', + }, + severityId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Severity ID to attach to the incident', + }, + status: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Incident status (in_triage, started, detected, acknowledged, mitigated, resolved, closed, cancelled, scheduled, in_progress, completed)', + }, + kind: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Incident kind (normal, normal_sub, test, test_sub, example, example_sub, backfilled, scheduled, scheduled_sub)', + }, + serviceIds: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated service IDs to attach', + }, + environmentIds: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated environment IDs to attach', + }, + groupIds: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated team/group IDs to attach', + }, + incidentTypeIds: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated incident type IDs to attach', + }, + functionalityIds: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated functionality IDs to attach', + }, + labels: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Labels as JSON object, e.g. {"platform":"osx","version":"1.29"}', + }, + private: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Create as a private incident (cannot be undone)', + }, + }, + + request: { + url: 'https://api.rootly.com/v1/incidents', + method: 'POST', + headers: (params) => ({ + 'Content-Type': 'application/vnd.api+json', + Authorization: `Bearer ${params.apiKey}`, + }), + body: (params) => { + const attributes: Record = {} + if (params.title) attributes.title = params.title + if (params.summary) attributes.summary = params.summary + if (params.severityId) attributes.severity_id = params.severityId + if (params.status) attributes.status = params.status + if (params.kind) attributes.kind = params.kind + if (params.private !== undefined) attributes.private = params.private + if (params.serviceIds) { + attributes.service_ids = params.serviceIds.split(',').map((s: string) => s.trim()) + } + if (params.environmentIds) { + attributes.environment_ids = params.environmentIds.split(',').map((s: string) => s.trim()) + } + if (params.groupIds) { + attributes.group_ids = params.groupIds.split(',').map((s: string) => s.trim()) + } + if (params.incidentTypeIds) { + attributes.incident_type_ids = params.incidentTypeIds + .split(',') + .map((s: string) => s.trim()) + } + if (params.functionalityIds) { + attributes.functionality_ids = params.functionalityIds + .split(',') + .map((s: string) => s.trim()) + } + if (params.labels) { + try { + attributes.labels = JSON.parse(params.labels) + } catch { + attributes.labels = params.labels + } + } + return { data: { type: 'incidents', attributes } } + }, + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + const errorData = await response.json().catch(() => ({})) + const errorMsg = + errorData.errors?.[0]?.detail || `HTTP ${response.status}: ${response.statusText}` + return { + success: false, + output: { incident: {} as RootlyCreateIncidentResponse['output']['incident'] }, + error: errorMsg, + } + } + + const data = await response.json() + const attrs = data.data?.attributes || {} + return { + success: true, + output: { + incident: { + id: data.data?.id ?? null, + sequentialId: attrs.sequential_id ?? null, + title: attrs.title ?? '', + slug: attrs.slug ?? null, + kind: attrs.kind ?? null, + summary: attrs.summary ?? null, + status: attrs.status ?? null, + private: attrs.private ?? false, + url: attrs.url ?? null, + shortUrl: attrs.short_url ?? null, + severityName: attrs.severity?.data?.attributes?.name ?? null, + severityId: attrs.severity?.data?.id ?? null, + createdAt: attrs.created_at ?? '', + updatedAt: attrs.updated_at ?? '', + startedAt: attrs.started_at ?? null, + mitigatedAt: attrs.mitigated_at ?? null, + resolvedAt: attrs.resolved_at ?? null, + closedAt: attrs.closed_at ?? null, + }, + }, + } + }, + + outputs: { + incident: { + type: 'object', + description: 'The created incident', + properties: { + id: { type: 'string', description: 'Unique incident ID' }, + sequentialId: { type: 'number', description: 'Sequential incident number' }, + title: { type: 'string', description: 'Incident title' }, + slug: { type: 'string', description: 'Incident slug' }, + kind: { type: 'string', description: 'Incident kind' }, + summary: { type: 'string', description: 'Incident summary' }, + status: { type: 'string', description: 'Incident status' }, + private: { type: 'boolean', description: 'Whether the incident is private' }, + url: { type: 'string', description: 'URL to the incident' }, + shortUrl: { type: 'string', description: 'Short URL to the incident' }, + severityName: { type: 'string', description: 'Severity name' }, + severityId: { type: 'string', description: 'Severity ID' }, + createdAt: { type: 'string', description: 'Creation date' }, + updatedAt: { type: 'string', description: 'Last update date' }, + startedAt: { type: 'string', description: 'Start date' }, + mitigatedAt: { type: 'string', description: 'Mitigation date' }, + resolvedAt: { type: 'string', description: 'Resolution date' }, + closedAt: { type: 'string', description: 'Closed date' }, + }, + }, + }, +} diff --git a/apps/sim/tools/rootly/get_incident.ts b/apps/sim/tools/rootly/get_incident.ts new file mode 100644 index 0000000000..70b4f6c0f6 --- /dev/null +++ b/apps/sim/tools/rootly/get_incident.ts @@ -0,0 +1,100 @@ +import type { RootlyGetIncidentParams, RootlyGetIncidentResponse } from '@/tools/rootly/types' +import type { ToolConfig } from '@/tools/types' + +export const rootlyGetIncidentTool: ToolConfig = + { + id: 'rootly_get_incident', + name: 'Rootly Get Incident', + description: 'Retrieve a single incident by ID from Rootly.', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rootly API key', + }, + incidentId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The ID of the incident to retrieve', + }, + }, + + request: { + url: (params) => `https://api.rootly.com/v1/incidents/${params.incidentId.trim()}`, + method: 'GET', + headers: (params) => ({ + 'Content-Type': 'application/vnd.api+json', + Authorization: `Bearer ${params.apiKey}`, + }), + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + const errorData = await response.json().catch(() => ({})) + return { + success: false, + output: { incident: {} as RootlyGetIncidentResponse['output']['incident'] }, + error: errorData.errors?.[0]?.detail || `HTTP ${response.status}: ${response.statusText}`, + } + } + + const data = await response.json() + const attrs = data.data?.attributes || {} + return { + success: true, + output: { + incident: { + id: data.data?.id ?? null, + sequentialId: attrs.sequential_id ?? null, + title: attrs.title ?? '', + slug: attrs.slug ?? null, + kind: attrs.kind ?? null, + summary: attrs.summary ?? null, + status: attrs.status ?? null, + private: attrs.private ?? false, + url: attrs.url ?? null, + shortUrl: attrs.short_url ?? null, + severityName: attrs.severity?.data?.attributes?.name ?? null, + severityId: attrs.severity?.data?.id ?? null, + createdAt: attrs.created_at ?? '', + updatedAt: attrs.updated_at ?? '', + startedAt: attrs.started_at ?? null, + mitigatedAt: attrs.mitigated_at ?? null, + resolvedAt: attrs.resolved_at ?? null, + closedAt: attrs.closed_at ?? null, + }, + }, + } + }, + + outputs: { + incident: { + type: 'object', + description: 'The incident details', + properties: { + id: { type: 'string', description: 'Unique incident ID' }, + sequentialId: { type: 'number', description: 'Sequential incident number' }, + title: { type: 'string', description: 'Incident title' }, + slug: { type: 'string', description: 'Incident slug' }, + kind: { type: 'string', description: 'Incident kind' }, + summary: { type: 'string', description: 'Incident summary' }, + status: { type: 'string', description: 'Incident status' }, + private: { type: 'boolean', description: 'Whether the incident is private' }, + url: { type: 'string', description: 'URL to the incident' }, + shortUrl: { type: 'string', description: 'Short URL to the incident' }, + severityName: { type: 'string', description: 'Severity name' }, + severityId: { type: 'string', description: 'Severity ID' }, + createdAt: { type: 'string', description: 'Creation date' }, + updatedAt: { type: 'string', description: 'Last update date' }, + startedAt: { type: 'string', description: 'Start date' }, + mitigatedAt: { type: 'string', description: 'Mitigation date' }, + resolvedAt: { type: 'string', description: 'Resolution date' }, + closedAt: { type: 'string', description: 'Closed date' }, + }, + }, + }, + } diff --git a/apps/sim/tools/rootly/index.ts b/apps/sim/tools/rootly/index.ts new file mode 100644 index 0000000000..53b2c4beb6 --- /dev/null +++ b/apps/sim/tools/rootly/index.ts @@ -0,0 +1,15 @@ +export { rootlyAddIncidentEventTool } from '@/tools/rootly/add_incident_event' +export { rootlyCreateAlertTool } from '@/tools/rootly/create_alert' +export { rootlyCreateIncidentTool } from '@/tools/rootly/create_incident' +export { rootlyGetIncidentTool } from '@/tools/rootly/get_incident' +export { rootlyListAlertsTool } from '@/tools/rootly/list_alerts' +export { rootlyListEnvironmentsTool } from '@/tools/rootly/list_environments' +export { rootlyListFunctionalitiesTool } from '@/tools/rootly/list_functionalities' +export { rootlyListIncidentTypesTool } from '@/tools/rootly/list_incident_types' +export { rootlyListIncidentsTool } from '@/tools/rootly/list_incidents' +export { rootlyListRetrospectivesTool } from '@/tools/rootly/list_retrospectives' +export { rootlyListServicesTool } from '@/tools/rootly/list_services' +export { rootlyListSeveritiesTool } from '@/tools/rootly/list_severities' +export { rootlyListTeamsTool } from '@/tools/rootly/list_teams' +export * from '@/tools/rootly/types' +export { rootlyUpdateIncidentTool } from '@/tools/rootly/update_incident' diff --git a/apps/sim/tools/rootly/list_alerts.ts b/apps/sim/tools/rootly/list_alerts.ts new file mode 100644 index 0000000000..6752799496 --- /dev/null +++ b/apps/sim/tools/rootly/list_alerts.ts @@ -0,0 +1,142 @@ +import type { RootlyListAlertsParams, RootlyListAlertsResponse } from '@/tools/rootly/types' +import type { ToolConfig } from '@/tools/types' + +export const rootlyListAlertsTool: ToolConfig = { + id: 'rootly_list_alerts', + name: 'Rootly List Alerts', + description: 'List alerts from Rootly with optional filtering by status, source, and services.', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rootly API key', + }, + status: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter by status (open, triggered, acknowledged, resolved)', + }, + source: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter by source (e.g., api, datadog, pagerduty)', + }, + services: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter by service slugs (comma-separated)', + }, + environments: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter by environment slugs (comma-separated)', + }, + groups: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter by team/group slugs (comma-separated)', + }, + pageSize: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Number of items per page (default: 20)', + }, + pageNumber: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Page number for pagination', + }, + }, + + request: { + url: (params) => { + const queryParams = new URLSearchParams() + if (params.status) queryParams.set('filter[status]', params.status) + if (params.source) queryParams.set('filter[source]', params.source) + if (params.services) queryParams.set('filter[services]', params.services) + if (params.environments) queryParams.set('filter[environments]', params.environments) + if (params.groups) queryParams.set('filter[groups]', params.groups) + if (params.pageSize) queryParams.set('page[size]', String(params.pageSize)) + if (params.pageNumber) queryParams.set('page[number]', String(params.pageNumber)) + const qs = queryParams.toString() + return `https://api.rootly.com/v1/alerts${qs ? `?${qs}` : ''}` + }, + method: 'GET', + headers: (params) => ({ + 'Content-Type': 'application/vnd.api+json', + Authorization: `Bearer ${params.apiKey}`, + }), + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + const errorData = await response.json().catch(() => ({})) + return { + success: false, + output: { alerts: [], totalCount: 0 }, + error: errorData.errors?.[0]?.detail || `HTTP ${response.status}: ${response.statusText}`, + } + } + + const data = await response.json() + const alerts = (data.data || []).map((item: Record) => { + const attrs = (item.attributes || {}) as Record + return { + id: item.id ?? null, + summary: (attrs.summary as string) ?? '', + description: (attrs.description as string) ?? null, + source: (attrs.source as string) ?? null, + status: (attrs.status as string) ?? null, + externalId: (attrs.external_id as string) ?? null, + externalUrl: (attrs.external_url as string) ?? null, + deduplicationKey: (attrs.deduplication_key as string) ?? null, + createdAt: (attrs.created_at as string) ?? '', + updatedAt: (attrs.updated_at as string) ?? '', + } + }) + + return { + success: true, + output: { + alerts, + totalCount: data.meta?.total_count ?? alerts.length, + }, + } + }, + + outputs: { + alerts: { + type: 'array', + description: 'List of alerts', + items: { + type: 'object', + properties: { + id: { type: 'string', description: 'Unique alert ID' }, + summary: { type: 'string', description: 'Alert summary' }, + description: { type: 'string', description: 'Alert description' }, + source: { type: 'string', description: 'Alert source' }, + status: { type: 'string', description: 'Alert status' }, + externalId: { type: 'string', description: 'External ID' }, + externalUrl: { type: 'string', description: 'External URL' }, + deduplicationKey: { type: 'string', description: 'Deduplication key' }, + createdAt: { type: 'string', description: 'Creation date' }, + updatedAt: { type: 'string', description: 'Last update date' }, + }, + }, + }, + totalCount: { + type: 'number', + description: 'Total number of alerts returned', + }, + }, +} diff --git a/apps/sim/tools/rootly/list_environments.ts b/apps/sim/tools/rootly/list_environments.ts new file mode 100644 index 0000000000..51b99873c8 --- /dev/null +++ b/apps/sim/tools/rootly/list_environments.ts @@ -0,0 +1,114 @@ +import type { + RootlyListEnvironmentsParams, + RootlyListEnvironmentsResponse, +} from '@/tools/rootly/types' +import type { ToolConfig } from '@/tools/types' + +export const rootlyListEnvironmentsTool: ToolConfig< + RootlyListEnvironmentsParams, + RootlyListEnvironmentsResponse +> = { + id: 'rootly_list_environments', + name: 'Rootly List Environments', + description: 'List environments configured in Rootly.', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rootly API key', + }, + search: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Search term to filter environments', + }, + pageSize: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Number of items per page (default: 20)', + }, + pageNumber: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Page number for pagination', + }, + }, + + request: { + url: (params) => { + const queryParams = new URLSearchParams() + if (params.search) queryParams.set('filter[search]', params.search) + if (params.pageSize) queryParams.set('page[size]', String(params.pageSize)) + if (params.pageNumber) queryParams.set('page[number]', String(params.pageNumber)) + const qs = queryParams.toString() + return `https://api.rootly.com/v1/environments${qs ? `?${qs}` : ''}` + }, + method: 'GET', + headers: (params) => ({ + 'Content-Type': 'application/vnd.api+json', + Authorization: `Bearer ${params.apiKey}`, + }), + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + const errorData = await response.json().catch(() => ({})) + return { + success: false, + output: { environments: [], totalCount: 0 }, + error: errorData.errors?.[0]?.detail || `HTTP ${response.status}: ${response.statusText}`, + } + } + + const data = await response.json() + const environments = (data.data || []).map((item: Record) => { + const attrs = (item.attributes || {}) as Record + return { + id: item.id ?? null, + name: (attrs.name as string) ?? '', + slug: (attrs.slug as string) ?? null, + description: (attrs.description as string) ?? null, + color: (attrs.color as string) ?? null, + createdAt: (attrs.created_at as string) ?? '', + updatedAt: (attrs.updated_at as string) ?? '', + } + }) + + return { + success: true, + output: { + environments, + totalCount: data.meta?.total_count ?? environments.length, + }, + } + }, + + outputs: { + environments: { + type: 'array', + description: 'List of environments', + items: { + type: 'object', + properties: { + id: { type: 'string', description: 'Unique environment ID' }, + name: { type: 'string', description: 'Environment name' }, + slug: { type: 'string', description: 'Environment slug' }, + description: { type: 'string', description: 'Environment description' }, + color: { type: 'string', description: 'Environment color' }, + createdAt: { type: 'string', description: 'Creation date' }, + updatedAt: { type: 'string', description: 'Last update date' }, + }, + }, + }, + totalCount: { + type: 'number', + description: 'Total number of environments returned', + }, + }, +} diff --git a/apps/sim/tools/rootly/list_functionalities.ts b/apps/sim/tools/rootly/list_functionalities.ts new file mode 100644 index 0000000000..a50ceba54a --- /dev/null +++ b/apps/sim/tools/rootly/list_functionalities.ts @@ -0,0 +1,114 @@ +import type { + RootlyListFunctionalitiesParams, + RootlyListFunctionalitiesResponse, +} from '@/tools/rootly/types' +import type { ToolConfig } from '@/tools/types' + +export const rootlyListFunctionalitiesTool: ToolConfig< + RootlyListFunctionalitiesParams, + RootlyListFunctionalitiesResponse +> = { + id: 'rootly_list_functionalities', + name: 'Rootly List Functionalities', + description: 'List functionalities configured in Rootly.', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rootly API key', + }, + search: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Search term to filter functionalities', + }, + pageSize: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Number of items per page (default: 20)', + }, + pageNumber: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Page number for pagination', + }, + }, + + request: { + url: (params) => { + const queryParams = new URLSearchParams() + if (params.search) queryParams.set('filter[search]', params.search) + if (params.pageSize) queryParams.set('page[size]', String(params.pageSize)) + if (params.pageNumber) queryParams.set('page[number]', String(params.pageNumber)) + const qs = queryParams.toString() + return `https://api.rootly.com/v1/functionalities${qs ? `?${qs}` : ''}` + }, + method: 'GET', + headers: (params) => ({ + 'Content-Type': 'application/vnd.api+json', + Authorization: `Bearer ${params.apiKey}`, + }), + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + const errorData = await response.json().catch(() => ({})) + return { + success: false, + output: { functionalities: [], totalCount: 0 }, + error: errorData.errors?.[0]?.detail || `HTTP ${response.status}: ${response.statusText}`, + } + } + + const data = await response.json() + const functionalities = (data.data || []).map((item: Record) => { + const attrs = (item.attributes || {}) as Record + return { + id: item.id ?? null, + name: (attrs.name as string) ?? '', + slug: (attrs.slug as string) ?? null, + description: (attrs.description as string) ?? null, + color: (attrs.color as string) ?? null, + createdAt: (attrs.created_at as string) ?? '', + updatedAt: (attrs.updated_at as string) ?? '', + } + }) + + return { + success: true, + output: { + functionalities, + totalCount: data.meta?.total_count ?? functionalities.length, + }, + } + }, + + outputs: { + functionalities: { + type: 'array', + description: 'List of functionalities', + items: { + type: 'object', + properties: { + id: { type: 'string', description: 'Unique functionality ID' }, + name: { type: 'string', description: 'Functionality name' }, + slug: { type: 'string', description: 'Functionality slug' }, + description: { type: 'string', description: 'Functionality description' }, + color: { type: 'string', description: 'Functionality color' }, + createdAt: { type: 'string', description: 'Creation date' }, + updatedAt: { type: 'string', description: 'Last update date' }, + }, + }, + }, + totalCount: { + type: 'number', + description: 'Total number of functionalities returned', + }, + }, +} diff --git a/apps/sim/tools/rootly/list_incident_types.ts b/apps/sim/tools/rootly/list_incident_types.ts new file mode 100644 index 0000000000..71a906fd30 --- /dev/null +++ b/apps/sim/tools/rootly/list_incident_types.ts @@ -0,0 +1,114 @@ +import type { + RootlyListIncidentTypesParams, + RootlyListIncidentTypesResponse, +} from '@/tools/rootly/types' +import type { ToolConfig } from '@/tools/types' + +export const rootlyListIncidentTypesTool: ToolConfig< + RootlyListIncidentTypesParams, + RootlyListIncidentTypesResponse +> = { + id: 'rootly_list_incident_types', + name: 'Rootly List Incident Types', + description: 'List incident types configured in Rootly.', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rootly API key', + }, + search: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter incident types by name', + }, + pageSize: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Number of items per page (default: 20)', + }, + pageNumber: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Page number for pagination', + }, + }, + + request: { + url: (params) => { + const queryParams = new URLSearchParams() + if (params.search) queryParams.set('filter[name]', params.search) + if (params.pageSize) queryParams.set('page[size]', String(params.pageSize)) + if (params.pageNumber) queryParams.set('page[number]', String(params.pageNumber)) + const qs = queryParams.toString() + return `https://api.rootly.com/v1/incident_types${qs ? `?${qs}` : ''}` + }, + method: 'GET', + headers: (params) => ({ + 'Content-Type': 'application/vnd.api+json', + Authorization: `Bearer ${params.apiKey}`, + }), + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + const errorData = await response.json().catch(() => ({})) + return { + success: false, + output: { incidentTypes: [], totalCount: 0 }, + error: errorData.errors?.[0]?.detail || `HTTP ${response.status}: ${response.statusText}`, + } + } + + const data = await response.json() + const incidentTypes = (data.data || []).map((item: Record) => { + const attrs = (item.attributes || {}) as Record + return { + id: item.id ?? null, + name: (attrs.name as string) ?? '', + slug: (attrs.slug as string) ?? null, + description: (attrs.description as string) ?? null, + color: (attrs.color as string) ?? null, + createdAt: (attrs.created_at as string) ?? '', + updatedAt: (attrs.updated_at as string) ?? '', + } + }) + + return { + success: true, + output: { + incidentTypes, + totalCount: data.meta?.total_count ?? incidentTypes.length, + }, + } + }, + + outputs: { + incidentTypes: { + type: 'array', + description: 'List of incident types', + items: { + type: 'object', + properties: { + id: { type: 'string', description: 'Unique incident type ID' }, + name: { type: 'string', description: 'Incident type name' }, + slug: { type: 'string', description: 'Incident type slug' }, + description: { type: 'string', description: 'Incident type description' }, + color: { type: 'string', description: 'Incident type color' }, + createdAt: { type: 'string', description: 'Creation date' }, + updatedAt: { type: 'string', description: 'Last update date' }, + }, + }, + }, + totalCount: { + type: 'number', + description: 'Total number of incident types returned', + }, + }, +} diff --git a/apps/sim/tools/rootly/list_incidents.ts b/apps/sim/tools/rootly/list_incidents.ts new file mode 100644 index 0000000000..1fecfd38e1 --- /dev/null +++ b/apps/sim/tools/rootly/list_incidents.ts @@ -0,0 +1,179 @@ +import type { RootlyListIncidentsParams, RootlyListIncidentsResponse } from '@/tools/rootly/types' +import type { ToolConfig } from '@/tools/types' + +export const rootlyListIncidentsTool: ToolConfig< + RootlyListIncidentsParams, + RootlyListIncidentsResponse +> = { + id: 'rootly_list_incidents', + name: 'Rootly List Incidents', + description: 'List incidents from Rootly with optional filtering by status, severity, and more.', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rootly API key', + }, + status: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Filter by status (in_triage, started, detected, acknowledged, mitigated, resolved, closed, cancelled, scheduled, in_progress, completed)', + }, + severity: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter by severity slug', + }, + search: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Search term to filter incidents', + }, + services: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter by service slugs (comma-separated)', + }, + teams: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter by team slugs (comma-separated)', + }, + environments: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter by environment slugs (comma-separated)', + }, + sort: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Sort order (e.g., -created_at, created_at, -started_at)', + }, + pageSize: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Number of items per page (default: 20)', + }, + pageNumber: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Page number for pagination', + }, + }, + + request: { + url: (params) => { + const queryParams = new URLSearchParams() + if (params.status) queryParams.set('filter[status]', params.status) + if (params.severity) queryParams.set('filter[severity]', params.severity) + if (params.search) queryParams.set('filter[search]', params.search) + if (params.services) queryParams.set('filter[services]', params.services) + if (params.teams) queryParams.set('filter[teams]', params.teams) + if (params.environments) queryParams.set('filter[environments]', params.environments) + if (params.sort) queryParams.set('sort', params.sort) + if (params.pageSize) queryParams.set('page[size]', String(params.pageSize)) + if (params.pageNumber) queryParams.set('page[number]', String(params.pageNumber)) + const qs = queryParams.toString() + return `https://api.rootly.com/v1/incidents${qs ? `?${qs}` : ''}` + }, + method: 'GET', + headers: (params) => ({ + 'Content-Type': 'application/vnd.api+json', + Authorization: `Bearer ${params.apiKey}`, + }), + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + const errorData = await response.json().catch(() => ({})) + return { + success: false, + output: { incidents: [], totalCount: 0 }, + error: errorData.errors?.[0]?.detail || `HTTP ${response.status}: ${response.statusText}`, + } + } + + const data = await response.json() + const incidents = (data.data || []).map((item: Record) => { + const attrs = (item.attributes || {}) as Record + const severity = attrs.severity as Record | undefined + const severityData = severity?.data as Record | undefined + const severityAttrs = severityData?.attributes as Record | undefined + return { + id: item.id ?? null, + sequentialId: (attrs.sequential_id as number) ?? null, + title: (attrs.title as string) ?? '', + slug: (attrs.slug as string) ?? null, + kind: (attrs.kind as string) ?? null, + summary: (attrs.summary as string) ?? null, + status: (attrs.status as string) ?? null, + private: (attrs.private as boolean) ?? false, + url: (attrs.url as string) ?? null, + shortUrl: (attrs.short_url as string) ?? null, + severityName: (severityAttrs?.name as string) ?? null, + severityId: (severityData?.id as string) ?? null, + createdAt: (attrs.created_at as string) ?? '', + updatedAt: (attrs.updated_at as string) ?? '', + startedAt: (attrs.started_at as string) ?? null, + mitigatedAt: (attrs.mitigated_at as string) ?? null, + resolvedAt: (attrs.resolved_at as string) ?? null, + closedAt: (attrs.closed_at as string) ?? null, + } + }) + + return { + success: true, + output: { + incidents, + totalCount: data.meta?.total_count ?? incidents.length, + }, + } + }, + + outputs: { + incidents: { + type: 'array', + description: 'List of incidents', + items: { + type: 'object', + properties: { + id: { type: 'string', description: 'Unique incident ID' }, + sequentialId: { type: 'number', description: 'Sequential incident number' }, + title: { type: 'string', description: 'Incident title' }, + slug: { type: 'string', description: 'Incident slug' }, + kind: { type: 'string', description: 'Incident kind' }, + summary: { type: 'string', description: 'Incident summary' }, + status: { type: 'string', description: 'Incident status' }, + private: { type: 'boolean', description: 'Whether the incident is private' }, + url: { type: 'string', description: 'URL to the incident' }, + shortUrl: { type: 'string', description: 'Short URL to the incident' }, + severityName: { type: 'string', description: 'Severity name' }, + severityId: { type: 'string', description: 'Severity ID' }, + createdAt: { type: 'string', description: 'Creation date' }, + updatedAt: { type: 'string', description: 'Last update date' }, + startedAt: { type: 'string', description: 'Start date' }, + mitigatedAt: { type: 'string', description: 'Mitigation date' }, + resolvedAt: { type: 'string', description: 'Resolution date' }, + closedAt: { type: 'string', description: 'Closed date' }, + }, + }, + }, + totalCount: { + type: 'number', + description: 'Total number of incidents returned', + }, + }, +} diff --git a/apps/sim/tools/rootly/list_retrospectives.ts b/apps/sim/tools/rootly/list_retrospectives.ts new file mode 100644 index 0000000000..f02a65e186 --- /dev/null +++ b/apps/sim/tools/rootly/list_retrospectives.ts @@ -0,0 +1,125 @@ +import type { + RootlyListRetrospectivesParams, + RootlyListRetrospectivesResponse, +} from '@/tools/rootly/types' +import type { ToolConfig } from '@/tools/types' + +export const rootlyListRetrospectivesTool: ToolConfig< + RootlyListRetrospectivesParams, + RootlyListRetrospectivesResponse +> = { + id: 'rootly_list_retrospectives', + name: 'Rootly List Retrospectives', + description: 'List incident retrospectives (post-mortems) from Rootly.', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rootly API key', + }, + status: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter by status (draft, published)', + }, + search: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Search term to filter retrospectives', + }, + pageSize: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Number of items per page (default: 20)', + }, + pageNumber: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Page number for pagination', + }, + }, + + request: { + url: (params) => { + const queryParams = new URLSearchParams() + if (params.status) queryParams.set('filter[status]', params.status) + if (params.search) queryParams.set('filter[search]', params.search) + if (params.pageSize) queryParams.set('page[size]', String(params.pageSize)) + if (params.pageNumber) queryParams.set('page[number]', String(params.pageNumber)) + const qs = queryParams.toString() + return `https://api.rootly.com/v1/post_mortems${qs ? `?${qs}` : ''}` + }, + method: 'GET', + headers: (params) => ({ + 'Content-Type': 'application/vnd.api+json', + Authorization: `Bearer ${params.apiKey}`, + }), + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + const errorData = await response.json().catch(() => ({})) + return { + success: false, + output: { retrospectives: [], totalCount: 0 }, + error: errorData.errors?.[0]?.detail || `HTTP ${response.status}: ${response.statusText}`, + } + } + + const data = await response.json() + const retrospectives = (data.data || []).map((item: Record) => { + const attrs = (item.attributes || {}) as Record + return { + id: item.id ?? null, + title: (attrs.title as string) ?? '', + status: (attrs.status as string) ?? null, + url: (attrs.url as string) ?? null, + startedAt: (attrs.started_at as string) ?? null, + mitigatedAt: (attrs.mitigated_at as string) ?? null, + resolvedAt: (attrs.resolved_at as string) ?? null, + createdAt: (attrs.created_at as string) ?? '', + updatedAt: (attrs.updated_at as string) ?? '', + } + }) + + return { + success: true, + output: { + retrospectives, + totalCount: data.meta?.total_count ?? retrospectives.length, + }, + } + }, + + outputs: { + retrospectives: { + type: 'array', + description: 'List of retrospectives', + items: { + type: 'object', + properties: { + id: { type: 'string', description: 'Unique retrospective ID' }, + title: { type: 'string', description: 'Retrospective title' }, + status: { type: 'string', description: 'Status (draft or published)' }, + url: { type: 'string', description: 'URL to the retrospective' }, + startedAt: { type: 'string', description: 'Incident start date' }, + mitigatedAt: { type: 'string', description: 'Mitigation date' }, + resolvedAt: { type: 'string', description: 'Resolution date' }, + createdAt: { type: 'string', description: 'Creation date' }, + updatedAt: { type: 'string', description: 'Last update date' }, + }, + }, + }, + totalCount: { + type: 'number', + description: 'Total number of retrospectives returned', + }, + }, +} diff --git a/apps/sim/tools/rootly/list_services.ts b/apps/sim/tools/rootly/list_services.ts new file mode 100644 index 0000000000..436ca34a61 --- /dev/null +++ b/apps/sim/tools/rootly/list_services.ts @@ -0,0 +1,111 @@ +import type { RootlyListServicesParams, RootlyListServicesResponse } from '@/tools/rootly/types' +import type { ToolConfig } from '@/tools/types' + +export const rootlyListServicesTool: ToolConfig< + RootlyListServicesParams, + RootlyListServicesResponse +> = { + id: 'rootly_list_services', + name: 'Rootly List Services', + description: 'List services from Rootly with optional search filtering.', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rootly API key', + }, + search: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Search term to filter services', + }, + pageSize: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Number of items per page (default: 20)', + }, + pageNumber: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Page number for pagination', + }, + }, + + request: { + url: (params) => { + const queryParams = new URLSearchParams() + if (params.search) queryParams.set('filter[search]', params.search) + if (params.pageSize) queryParams.set('page[size]', String(params.pageSize)) + if (params.pageNumber) queryParams.set('page[number]', String(params.pageNumber)) + const qs = queryParams.toString() + return `https://api.rootly.com/v1/services${qs ? `?${qs}` : ''}` + }, + method: 'GET', + headers: (params) => ({ + 'Content-Type': 'application/vnd.api+json', + Authorization: `Bearer ${params.apiKey}`, + }), + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + const errorData = await response.json().catch(() => ({})) + return { + success: false, + output: { services: [], totalCount: 0 }, + error: errorData.errors?.[0]?.detail || `HTTP ${response.status}: ${response.statusText}`, + } + } + + const data = await response.json() + const services = (data.data || []).map((item: Record) => { + const attrs = (item.attributes || {}) as Record + return { + id: item.id ?? null, + name: (attrs.name as string) ?? '', + slug: (attrs.slug as string) ?? null, + description: (attrs.description as string) ?? null, + color: (attrs.color as string) ?? null, + createdAt: (attrs.created_at as string) ?? '', + updatedAt: (attrs.updated_at as string) ?? '', + } + }) + + return { + success: true, + output: { + services, + totalCount: data.meta?.total_count ?? services.length, + }, + } + }, + + outputs: { + services: { + type: 'array', + description: 'List of services', + items: { + type: 'object', + properties: { + id: { type: 'string', description: 'Unique service ID' }, + name: { type: 'string', description: 'Service name' }, + slug: { type: 'string', description: 'Service slug' }, + description: { type: 'string', description: 'Service description' }, + color: { type: 'string', description: 'Service color' }, + createdAt: { type: 'string', description: 'Creation date' }, + updatedAt: { type: 'string', description: 'Last update date' }, + }, + }, + }, + totalCount: { + type: 'number', + description: 'Total number of services returned', + }, + }, +} diff --git a/apps/sim/tools/rootly/list_severities.ts b/apps/sim/tools/rootly/list_severities.ts new file mode 100644 index 0000000000..db5be4546e --- /dev/null +++ b/apps/sim/tools/rootly/list_severities.ts @@ -0,0 +1,115 @@ +import type { RootlyListSeveritiesParams, RootlyListSeveritiesResponse } from '@/tools/rootly/types' +import type { ToolConfig } from '@/tools/types' + +export const rootlyListSeveritiesTool: ToolConfig< + RootlyListSeveritiesParams, + RootlyListSeveritiesResponse +> = { + id: 'rootly_list_severities', + name: 'Rootly List Severities', + description: 'List severity levels configured in Rootly.', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rootly API key', + }, + search: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Search term to filter severities', + }, + pageSize: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Number of items per page (default: 20)', + }, + pageNumber: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Page number for pagination', + }, + }, + + request: { + url: (params) => { + const queryParams = new URLSearchParams() + if (params.search) queryParams.set('filter[search]', params.search) + if (params.pageSize) queryParams.set('page[size]', String(params.pageSize)) + if (params.pageNumber) queryParams.set('page[number]', String(params.pageNumber)) + const qs = queryParams.toString() + return `https://api.rootly.com/v1/severities${qs ? `?${qs}` : ''}` + }, + method: 'GET', + headers: (params) => ({ + 'Content-Type': 'application/vnd.api+json', + Authorization: `Bearer ${params.apiKey}`, + }), + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + const errorData = await response.json().catch(() => ({})) + return { + success: false, + output: { severities: [], totalCount: 0 }, + error: errorData.errors?.[0]?.detail || `HTTP ${response.status}: ${response.statusText}`, + } + } + + const data = await response.json() + const severities = (data.data || []).map((item: Record) => { + const attrs = (item.attributes || {}) as Record + return { + id: item.id ?? null, + name: (attrs.name as string) ?? '', + slug: (attrs.slug as string) ?? null, + description: (attrs.description as string) ?? null, + severity: (attrs.severity as string) ?? null, + color: (attrs.color as string) ?? null, + position: (attrs.position as number) ?? null, + createdAt: (attrs.created_at as string) ?? '', + updatedAt: (attrs.updated_at as string) ?? '', + } + }) + + return { + success: true, + output: { + severities, + totalCount: data.meta?.total_count ?? severities.length, + }, + } + }, + + outputs: { + severities: { + type: 'array', + description: 'List of severity levels', + items: { + type: 'object', + properties: { + id: { type: 'string', description: 'Unique severity ID' }, + name: { type: 'string', description: 'Severity name' }, + slug: { type: 'string', description: 'Severity slug' }, + description: { type: 'string', description: 'Severity description' }, + severity: { type: 'string', description: 'Severity level (critical, high, medium, low)' }, + color: { type: 'string', description: 'Severity color' }, + position: { type: 'number', description: 'Display position' }, + createdAt: { type: 'string', description: 'Creation date' }, + updatedAt: { type: 'string', description: 'Last update date' }, + }, + }, + }, + totalCount: { + type: 'number', + description: 'Total number of severities returned', + }, + }, +} diff --git a/apps/sim/tools/rootly/list_teams.ts b/apps/sim/tools/rootly/list_teams.ts new file mode 100644 index 0000000000..cf5979e11c --- /dev/null +++ b/apps/sim/tools/rootly/list_teams.ts @@ -0,0 +1,108 @@ +import type { RootlyListTeamsParams, RootlyListTeamsResponse } from '@/tools/rootly/types' +import type { ToolConfig } from '@/tools/types' + +export const rootlyListTeamsTool: ToolConfig = { + id: 'rootly_list_teams', + name: 'Rootly List Teams', + description: 'List teams (groups) configured in Rootly.', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rootly API key', + }, + search: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Search term to filter teams', + }, + pageSize: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Number of items per page (default: 20)', + }, + pageNumber: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Page number for pagination', + }, + }, + + request: { + url: (params) => { + const queryParams = new URLSearchParams() + if (params.search) queryParams.set('filter[search]', params.search) + if (params.pageSize) queryParams.set('page[size]', String(params.pageSize)) + if (params.pageNumber) queryParams.set('page[number]', String(params.pageNumber)) + const qs = queryParams.toString() + return `https://api.rootly.com/v1/teams${qs ? `?${qs}` : ''}` + }, + method: 'GET', + headers: (params) => ({ + 'Content-Type': 'application/vnd.api+json', + Authorization: `Bearer ${params.apiKey}`, + }), + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + const errorData = await response.json().catch(() => ({})) + return { + success: false, + output: { teams: [], totalCount: 0 }, + error: errorData.errors?.[0]?.detail || `HTTP ${response.status}: ${response.statusText}`, + } + } + + const data = await response.json() + const teams = (data.data || []).map((item: Record) => { + const attrs = (item.attributes || {}) as Record + return { + id: item.id ?? null, + name: (attrs.name as string) ?? '', + slug: (attrs.slug as string) ?? null, + description: (attrs.description as string) ?? null, + color: (attrs.color as string) ?? null, + createdAt: (attrs.created_at as string) ?? '', + updatedAt: (attrs.updated_at as string) ?? '', + } + }) + + return { + success: true, + output: { + teams, + totalCount: data.meta?.total_count ?? teams.length, + }, + } + }, + + outputs: { + teams: { + type: 'array', + description: 'List of teams', + items: { + type: 'object', + properties: { + id: { type: 'string', description: 'Unique team ID' }, + name: { type: 'string', description: 'Team name' }, + slug: { type: 'string', description: 'Team slug' }, + description: { type: 'string', description: 'Team description' }, + color: { type: 'string', description: 'Team color' }, + createdAt: { type: 'string', description: 'Creation date' }, + updatedAt: { type: 'string', description: 'Last update date' }, + }, + }, + }, + totalCount: { + type: 'number', + description: 'Total number of teams returned', + }, + }, +} diff --git a/apps/sim/tools/rootly/types.ts b/apps/sim/tools/rootly/types.ts new file mode 100644 index 0000000000..82dfd14149 --- /dev/null +++ b/apps/sim/tools/rootly/types.ts @@ -0,0 +1,365 @@ +import type { ToolResponse } from '@/tools/types' + +/** Base parameters for all Rootly API operations */ +export interface RootlyBaseParams { + apiKey: string +} + +/** Create Incident */ +export interface RootlyCreateIncidentParams extends RootlyBaseParams { + title?: string + summary?: string + severityId?: string + status?: string + kind?: string + serviceIds?: string + environmentIds?: string + groupIds?: string + incidentTypeIds?: string + functionalityIds?: string + labels?: string + private?: boolean +} + +export interface RootlyIncidentData { + id: string | null + sequentialId: number | null + title: string + slug: string | null + kind: string | null + summary: string | null + status: string | null + private: boolean + url: string | null + shortUrl: string | null + severityName: string | null + severityId: string | null + createdAt: string + updatedAt: string + startedAt: string | null + mitigatedAt: string | null + resolvedAt: string | null + closedAt: string | null +} + +export interface RootlyCreateIncidentResponse extends ToolResponse { + output: { + incident: RootlyIncidentData + } +} + +/** Get Incident */ +export interface RootlyGetIncidentParams extends RootlyBaseParams { + incidentId: string +} + +export interface RootlyGetIncidentResponse extends ToolResponse { + output: { + incident: RootlyIncidentData + } +} + +/** Update Incident */ +export interface RootlyUpdateIncidentParams extends RootlyBaseParams { + incidentId: string + title?: string + summary?: string + severityId?: string + status?: string + kind?: string + private?: boolean + serviceIds?: string + environmentIds?: string + groupIds?: string + incidentTypeIds?: string + functionalityIds?: string + labels?: string + mitigationMessage?: string + resolutionMessage?: string + cancellationMessage?: string +} + +export interface RootlyUpdateIncidentResponse extends ToolResponse { + output: { + incident: RootlyIncidentData + } +} + +/** List Incidents */ +export interface RootlyListIncidentsParams extends RootlyBaseParams { + status?: string + severity?: string + search?: string + services?: string + teams?: string + environments?: string + sort?: string + pageSize?: number + pageNumber?: number +} + +export interface RootlyListIncidentsResponse extends ToolResponse { + output: { + incidents: RootlyIncidentData[] + totalCount: number + } +} + +/** Create Alert */ +export interface RootlyCreateAlertParams extends RootlyBaseParams { + summary: string + source: string + description?: string + status?: string + serviceIds?: string + groupIds?: string + environmentIds?: string + externalId?: string + externalUrl?: string + deduplicationKey?: string +} + +export interface RootlyAlertData { + id: string | null + summary: string + description: string | null + source: string | null + status: string | null + externalId: string | null + externalUrl: string | null + deduplicationKey: string | null + createdAt: string + updatedAt: string +} + +export interface RootlyCreateAlertResponse extends ToolResponse { + output: { + alert: RootlyAlertData + } +} + +/** List Alerts */ +export interface RootlyListAlertsParams extends RootlyBaseParams { + status?: string + source?: string + services?: string + environments?: string + groups?: string + pageSize?: number + pageNumber?: number +} + +export interface RootlyListAlertsResponse extends ToolResponse { + output: { + alerts: RootlyAlertData[] + totalCount: number + } +} + +/** Add Incident Event */ +export interface RootlyAddIncidentEventParams extends RootlyBaseParams { + incidentId: string + event: string + visibility?: string +} + +export interface RootlyAddIncidentEventResponse extends ToolResponse { + output: { + eventId: string + event: string + visibility: string | null + occurredAt: string | null + createdAt: string + updatedAt: string + } +} + +/** List Services */ +export interface RootlyListServicesParams extends RootlyBaseParams { + search?: string + pageSize?: number + pageNumber?: number +} + +export interface RootlyServiceData { + id: string | null + name: string + slug: string | null + description: string | null + color: string | null + createdAt: string + updatedAt: string +} + +export interface RootlyListServicesResponse extends ToolResponse { + output: { + services: RootlyServiceData[] + totalCount: number + } +} + +/** List Severities */ +export interface RootlyListSeveritiesParams extends RootlyBaseParams { + search?: string + pageSize?: number + pageNumber?: number +} + +export interface RootlySeverityData { + id: string | null + name: string + slug: string | null + description: string | null + severity: string | null + color: string | null + position: number | null + createdAt: string + updatedAt: string +} + +export interface RootlyListSeveritiesResponse extends ToolResponse { + output: { + severities: RootlySeverityData[] + totalCount: number + } +} + +/** List Retrospectives */ +export interface RootlyListRetrospectivesParams extends RootlyBaseParams { + status?: string + search?: string + pageSize?: number + pageNumber?: number +} + +export interface RootlyRetrospectiveData { + id: string | null + title: string + status: string | null + url: string | null + startedAt: string | null + mitigatedAt: string | null + resolvedAt: string | null + createdAt: string + updatedAt: string +} + +export interface RootlyListRetrospectivesResponse extends ToolResponse { + output: { + retrospectives: RootlyRetrospectiveData[] + totalCount: number + } +} + +/** List Teams */ +export interface RootlyListTeamsParams extends RootlyBaseParams { + search?: string + pageSize?: number + pageNumber?: number +} + +export interface RootlyTeamData { + id: string | null + name: string + slug: string | null + description: string | null + color: string | null + createdAt: string + updatedAt: string +} + +export interface RootlyListTeamsResponse extends ToolResponse { + output: { + teams: RootlyTeamData[] + totalCount: number + } +} + +/** List Environments */ +export interface RootlyListEnvironmentsParams extends RootlyBaseParams { + search?: string + pageSize?: number + pageNumber?: number +} + +export interface RootlyEnvironmentData { + id: string | null + name: string + slug: string | null + description: string | null + color: string | null + createdAt: string + updatedAt: string +} + +export interface RootlyListEnvironmentsResponse extends ToolResponse { + output: { + environments: RootlyEnvironmentData[] + totalCount: number + } +} + +/** List Incident Types */ +export interface RootlyListIncidentTypesParams extends RootlyBaseParams { + search?: string + pageSize?: number + pageNumber?: number +} + +export interface RootlyIncidentTypeData { + id: string | null + name: string + slug: string | null + description: string | null + color: string | null + createdAt: string + updatedAt: string +} + +export interface RootlyListIncidentTypesResponse extends ToolResponse { + output: { + incidentTypes: RootlyIncidentTypeData[] + totalCount: number + } +} + +/** List Functionalities */ +export interface RootlyListFunctionalitiesParams extends RootlyBaseParams { + search?: string + pageSize?: number + pageNumber?: number +} + +export interface RootlyFunctionalityData { + id: string | null + name: string + slug: string | null + description: string | null + color: string | null + createdAt: string + updatedAt: string +} + +export interface RootlyListFunctionalitiesResponse extends ToolResponse { + output: { + functionalities: RootlyFunctionalityData[] + totalCount: number + } +} + +/** Union of all responses */ +export type RootlyResponse = + | RootlyCreateIncidentResponse + | RootlyGetIncidentResponse + | RootlyUpdateIncidentResponse + | RootlyListIncidentsResponse + | RootlyCreateAlertResponse + | RootlyListAlertsResponse + | RootlyAddIncidentEventResponse + | RootlyListServicesResponse + | RootlyListSeveritiesResponse + | RootlyListRetrospectivesResponse + | RootlyListTeamsResponse + | RootlyListEnvironmentsResponse + | RootlyListIncidentTypesResponse + | RootlyListFunctionalitiesResponse diff --git a/apps/sim/tools/rootly/update_incident.ts b/apps/sim/tools/rootly/update_incident.ts new file mode 100644 index 0000000000..bebaf6f84e --- /dev/null +++ b/apps/sim/tools/rootly/update_incident.ts @@ -0,0 +1,233 @@ +import type { RootlyUpdateIncidentParams, RootlyUpdateIncidentResponse } from '@/tools/rootly/types' +import type { ToolConfig } from '@/tools/types' + +export const rootlyUpdateIncidentTool: ToolConfig< + RootlyUpdateIncidentParams, + RootlyUpdateIncidentResponse +> = { + id: 'rootly_update_incident', + name: 'Rootly Update Incident', + description: 'Update an existing incident in Rootly (status, severity, summary, etc.).', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rootly API key', + }, + incidentId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The ID of the incident to update', + }, + title: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Updated incident title', + }, + summary: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Updated incident summary', + }, + severityId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Updated severity ID', + }, + status: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Updated status (in_triage, started, detected, acknowledged, mitigated, resolved, closed, cancelled, scheduled, in_progress, completed)', + }, + kind: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Incident kind (normal, normal_sub, test, test_sub, example, example_sub, backfilled, scheduled, scheduled_sub)', + }, + private: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Set incident as private (cannot be undone)', + }, + serviceIds: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated service IDs', + }, + environmentIds: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated environment IDs', + }, + groupIds: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated team/group IDs', + }, + incidentTypeIds: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated incident type IDs to attach', + }, + functionalityIds: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated functionality IDs to attach', + }, + labels: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Labels as JSON object, e.g. {"platform":"osx","version":"1.29"}', + }, + mitigationMessage: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'How was the incident mitigated?', + }, + resolutionMessage: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'How was the incident resolved?', + }, + cancellationMessage: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Why was the incident cancelled?', + }, + }, + + request: { + url: (params) => `https://api.rootly.com/v1/incidents/${params.incidentId.trim()}`, + method: 'PUT', + headers: (params) => ({ + 'Content-Type': 'application/vnd.api+json', + Authorization: `Bearer ${params.apiKey}`, + }), + body: (params) => { + const attributes: Record = {} + if (params.title) attributes.title = params.title + if (params.summary) attributes.summary = params.summary + if (params.severityId) attributes.severity_id = params.severityId + if (params.status) attributes.status = params.status + if (params.kind) attributes.kind = params.kind + if (params.private !== undefined) attributes.private = params.private + if (params.mitigationMessage) attributes.mitigation_message = params.mitigationMessage + if (params.resolutionMessage) attributes.resolution_message = params.resolutionMessage + if (params.cancellationMessage) attributes.cancellation_message = params.cancellationMessage + if (params.incidentTypeIds) { + attributes.incident_type_ids = params.incidentTypeIds + .split(',') + .map((s: string) => s.trim()) + } + if (params.functionalityIds) { + attributes.functionality_ids = params.functionalityIds + .split(',') + .map((s: string) => s.trim()) + } + if (params.labels) { + try { + attributes.labels = JSON.parse(params.labels) + } catch { + attributes.labels = params.labels + } + } + if (params.serviceIds) { + attributes.service_ids = params.serviceIds.split(',').map((s: string) => s.trim()) + } + if (params.environmentIds) { + attributes.environment_ids = params.environmentIds.split(',').map((s: string) => s.trim()) + } + if (params.groupIds) { + attributes.group_ids = params.groupIds.split(',').map((s: string) => s.trim()) + } + return { data: { type: 'incidents', id: params.incidentId.trim(), attributes } } + }, + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + const errorData = await response.json().catch(() => ({})) + return { + success: false, + output: { incident: {} as RootlyUpdateIncidentResponse['output']['incident'] }, + error: errorData.errors?.[0]?.detail || `HTTP ${response.status}: ${response.statusText}`, + } + } + + const data = await response.json() + const attrs = data.data?.attributes || {} + return { + success: true, + output: { + incident: { + id: data.data?.id ?? null, + sequentialId: attrs.sequential_id ?? null, + title: attrs.title ?? '', + slug: attrs.slug ?? null, + kind: attrs.kind ?? null, + summary: attrs.summary ?? null, + status: attrs.status ?? null, + private: attrs.private ?? false, + url: attrs.url ?? null, + shortUrl: attrs.short_url ?? null, + severityName: attrs.severity?.data?.attributes?.name ?? null, + severityId: attrs.severity?.data?.id ?? null, + createdAt: attrs.created_at ?? '', + updatedAt: attrs.updated_at ?? '', + startedAt: attrs.started_at ?? null, + mitigatedAt: attrs.mitigated_at ?? null, + resolvedAt: attrs.resolved_at ?? null, + closedAt: attrs.closed_at ?? null, + }, + }, + } + }, + + outputs: { + incident: { + type: 'object', + description: 'The updated incident', + properties: { + id: { type: 'string', description: 'Unique incident ID' }, + sequentialId: { type: 'number', description: 'Sequential incident number' }, + title: { type: 'string', description: 'Incident title' }, + slug: { type: 'string', description: 'Incident slug' }, + kind: { type: 'string', description: 'Incident kind' }, + summary: { type: 'string', description: 'Incident summary' }, + status: { type: 'string', description: 'Incident status' }, + private: { type: 'boolean', description: 'Whether the incident is private' }, + url: { type: 'string', description: 'URL to the incident' }, + shortUrl: { type: 'string', description: 'Short URL to the incident' }, + severityName: { type: 'string', description: 'Severity name' }, + severityId: { type: 'string', description: 'Severity ID' }, + createdAt: { type: 'string', description: 'Creation date' }, + updatedAt: { type: 'string', description: 'Last update date' }, + startedAt: { type: 'string', description: 'Start date' }, + mitigatedAt: { type: 'string', description: 'Mitigation date' }, + resolvedAt: { type: 'string', description: 'Resolution date' }, + closedAt: { type: 'string', description: 'Closed date' }, + }, + }, + }, +}