diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index ccbeffe1..88adf46f 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -9,6 +9,7 @@ # Request review from original author /plugins/Algolia/ @andrewmumblebee /plugins/AutoTask/ @TimWheeler-SQUP +/plugins/CodeRabbit/ @TimWheeler-SQUP /plugins/DattoRMM/ @TimWheeler-SQUP /plugins/DigiCert/ @shaswot77 /plugins/FantasyPremierLeague/ @TimWheeler-SQUP diff --git a/plugins/CodeRabbit/v1/configValidation.json b/plugins/CodeRabbit/v1/configValidation.json new file mode 100644 index 00000000..65ffebeb --- /dev/null +++ b/plugins/CodeRabbit/v1/configValidation.json @@ -0,0 +1,18 @@ +{ + "steps": [ + { + "displayName": "Authenticate", + "dataStream": { "name": "organizations" }, + "required": true, + "success": "Connected to CodeRabbit.", + "error": "Could not connect. Check the API key is valid and the organization is on the CodeRabbit Enterprise plan." + }, + { + "displayName": "Check seat access", + "dataStream": { "name": "licenses" }, + "required": false, + "success": "Seat and license data is accessible.", + "error": "Seat and license data is unavailable. Create the API key as an organization admin to see seat usage." + } + ] +} diff --git a/plugins/CodeRabbit/v1/custom_types.json b/plugins/CodeRabbit/v1/custom_types.json new file mode 100644 index 00000000..a47ce8e2 --- /dev/null +++ b/plugins/CodeRabbit/v1/custom_types.json @@ -0,0 +1,23 @@ +[ + { + "name": "Organization", + "sourceType": "Organization", + "icon": "building", + "singular": "Organization", + "plural": "Organizations" + }, + { + "name": "Repository", + "sourceType": "Repository", + "icon": "code-branch", + "singular": "Repository", + "plural": "Repositories" + }, + { + "name": "User", + "sourceType": "User", + "icon": "user", + "singular": "User", + "plural": "Users" + } +] diff --git a/plugins/CodeRabbit/v1/dataStreams/auditLogs.json b/plugins/CodeRabbit/v1/dataStreams/auditLogs.json new file mode 100644 index 00000000..00d3baae --- /dev/null +++ b/plugins/CodeRabbit/v1/dataStreams/auditLogs.json @@ -0,0 +1,52 @@ +{ + "name": "auditLogs", + "displayName": "Audit logs", + "description": "Organization audit log entries for administrative actions", + "tags": ["Activity", "Security"], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "get", + "endpointPath": "/v1/audit-logs", + "pathToData": "data", + "expandInnerObjects": true, + "getArgs": [ + { "key": "date_from", "value": "{{timeframe.start}}" }, + { "key": "date_to", "value": "{{timeframe.end}}" }, + { "key": "search", "value": "{{search || null}}" } + ], + "paging": { + "mode": "offset", + "pageSize": { "realm": "queryArg", "path": "page_size", "value": "100" }, + "offset": { + "mode": "page", + "rowCountIn": { "realm": "payloadArraySize", "path": "data" }, + "base": 1 + }, + "out": { "realm": "queryArg", "path": "page" } + }, + "errorHandling": { "type": "path", "realm": "payload", "path": "error.message" } + }, + "matches": "none", + "ui": [ + { + "type": "text", + "name": "search", + "label": "Search user name", + "placeholder": "Enter a user name", + "help": "Supports partial or full name matching" + } + ], + "metadata": [ + { "name": "createdAt", "displayName": "Time", "shape": "date", "role": "timestamp" }, + { "name": "actionLabel", "displayName": "Action", "shape": "string", "role": "label" }, + { "name": "action", "displayName": "Action key", "shape": "string", "visible": false }, + { "name": "actor.name", "displayName": "Actor", "shape": "string" }, + { "name": "actor.subtitle", "displayName": "Actor role", "shape": "string" }, + { "name": "actor.isBot", "displayName": "Bot", "shape": "boolean", "visible": false }, + { "name": "resourceTypeLabel", "displayName": "Resource type", "shape": "string" }, + { "name": "resourceSummary", "displayName": "Resource", "shape": "string" }, + { "name": "ipAddress", "displayName": "IP address", "shape": "string" }, + { "name": "id", "displayName": "ID", "shape": "string", "role": "id", "visible": false } + ], + "timeframes": true +} diff --git a/plugins/CodeRabbit/v1/dataStreams/licenses.json b/plugins/CodeRabbit/v1/dataStreams/licenses.json new file mode 100644 index 00000000..b7ff5f1d --- /dev/null +++ b/plugins/CodeRabbit/v1/dataStreams/licenses.json @@ -0,0 +1,37 @@ +{ + "name": "licenses", + "displayName": "Licenses", + "description": "Seats purchased, seats assigned and the seat assignment mode", + "tags": ["License"], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "get", + "endpointPath": "/v1/users", + "getArgs": [{ "key": "limit", "value": "1" }], + "paging": { "mode": "none" }, + "errorHandling": { "type": "path", "realm": "payload", "path": "error.message" } + }, + "matches": "none", + "metadata": [ + { "name": "seats_purchased", "displayName": "Seats purchased", "shape": "number" }, + { "name": "seats_assigned", "displayName": "Seats assigned", "shape": "number", "role": "value" }, + { + "name": "seatsAvailable", + "displayName": "Seats available", + "computed": true, + "valueExpression": "{{ Math.max($['seats_purchased'] - $['seats_assigned'], 0) }}", + "shape": "number" + }, + { + "name": "seatUtilization", + "displayName": "Seat utilization", + "computed": true, + "valueExpression": "{{ $['seats_purchased'] > 0 ? $['seats_assigned'] / $['seats_purchased'] * 100 : null }}", + "shape": ["percent", { "decimalPlaces": 1 }] + }, + { "name": "seat_assignment_mode", "displayName": "Seat assignment mode", "shape": "string" } + ], + "timeframes": ["none"], + "supportsNoneTimeframe": true, + "defaultTimeframe": "none" +} diff --git a/plugins/CodeRabbit/v1/dataStreams/mcpUsage.json b/plugins/CodeRabbit/v1/dataStreams/mcpUsage.json new file mode 100644 index 00000000..2331adb8 --- /dev/null +++ b/plugins/CodeRabbit/v1/dataStreams/mcpUsage.json @@ -0,0 +1,66 @@ +{ + "name": "mcpUsage", + "displayName": "MCP usage", + "description": "Pull requests that used MCP servers during review, with insight and tool call counts per server", + "tags": ["Code Review", "Usage"], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "get", + "endpointPath": "/v1/metrics/mcp", + "pathToData": "data", + "getArgs": [ + { "key": "start_date", "value": "{{timeframe.start.slice(0, 10)}}" }, + { "key": "end_date", "value": "{{timeframe.end.slice(0, 10)}}" }, + { + "key": "repository_ids", + "value": "{{repository && repository.length ? repository.map(o => [].concat(o.rawId)[0]).join(',') : null}}" + }, + { + "key": "user_ids", + "value": "{{user && user.length ? user.map(o => [].concat(o.rawId)[0]).join(',') : null}}" + } + ], + "paging": { + "mode": "token", + "pageSize": { "realm": "queryArg", "path": "limit", "value": "1000" }, + "in": { "realm": "payload", "path": "next_cursor" }, + "out": { "realm": "queryArg", "path": "cursor" } + }, + "errorHandling": { "type": "script", "script": "errorHandling/mcpUsage.js" }, + "postRequestScript": "mcpUsage.js" + }, + "matches": "none", + "ui": [ + { + "type": "objects", + "name": "repository", + "label": "Repository", + "matches": { "sourceType": { "type": "oneOf", "values": ["Repository"] } } + }, + { + "type": "objects", + "name": "user", + "label": "User", + "matches": { "sourceType": { "type": "oneOf", "values": ["User"] } } + } + ], + "metadata": [ + { "name": "prUrl", "displayName": "Pull request", "shape": ["url", { "label": "#{{column.prNumber}}" }], "role": "link" }, + { + "name": "prNumber", + "displayName": "PR number", + "shape": ["number", { "thousandsSeparator": false }], + "visible": false + }, + { "name": "merged_at", "displayName": "Merged", "shape": "date", "role": "timestamp" }, + { "name": "serverName", "displayName": "MCP server", "shape": "string", "role": "label" }, + { "name": "insightCount", "displayName": "Insights", "shape": "number" }, + { "name": "toolCallCount", "displayName": "Tool calls", "shape": "number" }, + { "name": "tools", "displayName": "Tools", "shape": "string" }, + { "name": "repository_id", "displayName": "Repository ID", "shape": "string", "visible": false }, + { "name": "author_id", "displayName": "Author ID", "shape": "string", "visible": false }, + { "name": "repositoryName", "displayName": "Repository", "sourceId": "repository_id", "sourceType": "Repository", "objectPropertyPath": "name" }, + { "name": "authorName", "displayName": "Author", "sourceId": "author_id", "sourceType": "User", "objectPropertyPath": "name" } + ], + "timeframes": ["last7days", "last30days", "thisMonth", "lastMonth", "thisQuarter", "lastQuarter"] +} diff --git a/plugins/CodeRabbit/v1/dataStreams/organizations.json b/plugins/CodeRabbit/v1/dataStreams/organizations.json new file mode 100644 index 00000000..7ab7b201 --- /dev/null +++ b/plugins/CodeRabbit/v1/dataStreams/organizations.json @@ -0,0 +1,29 @@ +{ + "name": "organizations", + "displayName": "Organizations", + "description": "CodeRabbit organizations the API key can access", + "tags": ["Organization"], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "get", + "endpointPath": "/v1/organizations", + "pathToData": "organizations", + "paging": { + "mode": "token", + "pageSize": { "realm": "queryArg", "path": "limit", "value": "100" }, + "in": { "realm": "payload", "path": "next_cursor" }, + "out": { "realm": "queryArg", "path": "cursor" } + }, + "errorHandling": { "type": "path", "realm": "payload", "path": "error.message" } + }, + "matches": "none", + "metadata": [ + { "name": "name", "displayName": "Name", "shape": "string", "role": "label" }, + { "name": "provider", "displayName": "Git provider", "shape": "string" }, + { "name": "provider_organization_id", "displayName": "Provider organization ID", "shape": "string", "role": "id" }, + { "name": "id", "displayName": "CodeRabbit organization ID", "shape": "string", "visible": false } + ], + "timeframes": ["none"], + "supportsNoneTimeframe": true, + "defaultTimeframe": "none" +} diff --git a/plugins/CodeRabbit/v1/dataStreams/pullRequests.json b/plugins/CodeRabbit/v1/dataStreams/pullRequests.json new file mode 100644 index 00000000..bb2f7744 --- /dev/null +++ b/plugins/CodeRabbit/v1/dataStreams/pullRequests.json @@ -0,0 +1,292 @@ +{ + "name": "pullRequests", + "displayName": "Pull requests", + "description": "Merged or closed pull requests reviewed by CodeRabbit, with review timings and comment outcomes", + "tags": [ + "Pull Request", + "Code Review" + ], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "get", + "endpointPath": "/v1/metrics/reviews", + "getArgs": [ + { + "key": "start_date", + "value": "{{timeframe.start.slice(0,10)}}" + }, + { + "key": "end_date", + "value": "{{timeframe.end.slice(0,10)}}" + }, + { + "key": "status", + "value": "{{status || null}}" + }, + { + "key": "repository_ids", + "value": "{{repository && repository.length ? repository.map(o => Array.isArray(o.rawId) ? o.rawId[0] : o.rawId).join(',') : null}}" + }, + { + "key": "user_ids", + "value": "{{user && user.length ? user.map(o => Array.isArray(o.rawId) ? o.rawId[0] : o.rawId).join(',') : null}}" + } + ], + "paging": { + "mode": "token", + "pageSize": { + "realm": "queryArg", + "path": "limit", + "value": "1000" + }, + "in": { + "realm": "payload", + "path": "next_cursor" + }, + "out": { + "realm": "queryArg", + "path": "cursor" + } + }, + "errorHandling": { + "type": "path", + "realm": "payload", + "path": "error.message" + }, + "postRequestScript": "pullRequests.js" + }, + "matches": "none", + "ui": [ + { + "type": "radio", + "name": "status", + "label": "Status", + "defaultValue": "merged", + "options": [ + { + "value": "merged", + "label": "Merged" + }, + { + "value": "closed", + "label": "Closed" + }, + { + "value": "all", + "label": "All" + } + ] + }, + { + "type": "objects", + "name": "repository", + "label": "Repository (optional)", + "matches": { + "sourceType": { + "type": "oneOf", + "values": [ + "Repository" + ] + } + } + }, + { + "type": "objects", + "name": "user", + "label": "User (optional)", + "matches": { + "sourceType": { + "type": "oneOf", + "values": [ + "User" + ] + } + } + } + ], + "metadata": [ + { + "name": "prUrl", + "displayName": "Pull request", + "shape": [ + "url", + { + "label": "#{{column.prNumber}}" + } + ], + "role": "link" + }, + { + "name": "prNumber", + "displayName": "Number", + "shape": ["number", { "thousandsSeparator": false }], + "visible": false + }, + { + "name": "repository_name", + "displayName": "Repository", + "shape": "string", + "role": "label" + }, + { + "name": "organization_name", + "displayName": "Organization", + "shape": "string" + }, + { + "name": "author_username", + "displayName": "Author", + "shape": "string" + }, + { + "name": "status", + "displayName": "Status", + "shape": "string" + }, + { + "name": "created_at", + "displayName": "Created", + "shape": "date" + }, + { + "name": "ready_for_review_at", + "displayName": "Ready for review", + "shape": "date" + }, + { + "name": "first_human_review_at", + "displayName": "First human review", + "shape": "date" + }, + { + "name": "last_commit_at", + "displayName": "Last commit", + "shape": "date" + }, + { + "name": "merged_at", + "displayName": "Merged", + "shape": "date", + "role": "timestamp" + }, + { + "name": "hoursToFirstHumanReview", + "displayName": "Hours to first human review", + "computed": true, + "valueExpression": "{{ $['first_human_review_at'] ? Math.max((new Date($['first_human_review_at']) - new Date($['ready_for_review_at'] || $['created_at'])) / 3600000, 0) : null }}", + "shape": [ + "number", + { + "decimalPlaces": 1 + } + ] + }, + { + "name": "hoursToMerge", + "displayName": "Hours to merge", + "computed": true, + "valueExpression": "{{ $['merged_at'] ? Math.max((new Date($['merged_at']) - new Date($['ready_for_review_at'] || $['created_at'])) / 3600000, 0) : null }}", + "shape": [ + "number", + { + "decimalPlaces": 1 + } + ] + }, + { + "name": "hoursToLastCommit", + "displayName": "Hours to last commit", + "computed": true, + "valueExpression": "{{ $['last_commit_at'] ? Math.max((new Date($['last_commit_at']) - new Date($['ready_for_review_at'] || $['created_at'])) / 3600000, 0) : null }}", + "shape": [ + "number", + { + "decimalPlaces": 1 + } + ] + }, + { + "name": "review_iterations", + "displayName": "Review iterations", + "shape": "number" + }, + { + "name": "estimated_complexity", + "displayName": "Estimated complexity", + "shape": "number" + }, + { + "name": "estimated_review_minutes", + "displayName": "Estimated review time", + "shape": "minutes" + }, + { + "name": "commentsPosted", + "displayName": "Comments posted", + "shape": "number" + }, + { + "name": "commentsAccepted", + "displayName": "Comments accepted", + "shape": "number" + }, + { + "name": "acceptanceRate", + "displayName": "Acceptance rate", + "computed": true, + "valueExpression": "{{ $['commentsPosted'] > 0 ? $['commentsAccepted'] / $['commentsPosted'] * 100 : null }}", + "shape": [ + "percent", + { + "decimalPlaces": 1 + } + ] + }, + { + "name": "criticalPosted", + "displayName": "Critical comments", + "shape": "number" + }, + { + "name": "majorPosted", + "displayName": "Major comments", + "shape": "number" + }, + { + "name": "securityPosted", + "displayName": "Security comments", + "shape": "number" + }, + { + "name": "repository_id", + "displayName": "Repository ID", + "shape": "string", + "visible": false + }, + { + "name": "author_id", + "displayName": "Author ID", + "shape": "string", + "visible": false + }, + { + "sourceId": "repository_id", + "sourceType": "Repository", + "name": "repository_name" + }, + { + "sourceId": "author_id", + "sourceType": "User", + "name": "author_username" + } + ], + "timeframes": [ + "last24hours", + "last7days", + "last30days", + "thisMonth", + "lastMonth", + "thisQuarter", + "lastQuarter" + ] +} diff --git a/plugins/CodeRabbit/v1/dataStreams/repositories.json b/plugins/CodeRabbit/v1/dataStreams/repositories.json new file mode 100644 index 00000000..97891bc9 --- /dev/null +++ b/plugins/CodeRabbit/v1/dataStreams/repositories.json @@ -0,0 +1,32 @@ +{ + "name": "repositories", + "displayName": "Repositories", + "description": "Repositories CodeRabbit records for an organization, with installation status", + "tags": ["Repository"], + "baseDataSourceName": "httpRequestScopedSingle", + "config": { + "httpMethod": "get", + "endpointPath": "/v1/organizations/{{object.codeRabbitId}}/repositories", + "paging": { + "mode": "token", + "pageSize": { "realm": "queryArg", "path": "limit", "value": "100" }, + "in": { "realm": "payload", "path": "next_cursor" }, + "out": { "realm": "queryArg", "path": "cursor" } + }, + "errorHandling": { "type": "path", "realm": "payload", "path": "error.message" }, + "postRequestScript": "repositories.js" + }, + "matches": { "sourceType": { "type": "oneOf", "values": ["Organization"] } }, + "metadata": [ + { "name": "repository_name", "displayName": "Repository", "shape": "string", "role": "label" }, + { "name": "repository_id", "displayName": "Repository ID", "shape": "string", "role": "id", "visible": false }, + { "name": "private", "displayName": "Private", "shape": "boolean" }, + { "name": "is_installed", "displayName": "Installed", "shape": "boolean" }, + { "name": "group_path", "displayName": "Group path", "shape": "string" }, + { "name": "organizationName", "displayName": "Organization", "shape": "string" }, + { "name": "organizationId", "displayName": "Organization ID", "shape": "string", "visible": false } + ], + "timeframes": ["none"], + "supportsNoneTimeframe": true, + "defaultTimeframe": "none" +} diff --git a/plugins/CodeRabbit/v1/dataStreams/reviewComments.json b/plugins/CodeRabbit/v1/dataStreams/reviewComments.json new file mode 100644 index 00000000..92adeebf --- /dev/null +++ b/plugins/CodeRabbit/v1/dataStreams/reviewComments.json @@ -0,0 +1,80 @@ +{ + "name": "reviewComments", + "displayName": "Review comments", + "description": "Findings raised by CodeRabbit on merged pull requests, one row per finding", + "tags": ["Code Review", "Pull Request"], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "get", + "endpointPath": "/v1/metrics/review-comments", + "getArgs": [ + { "key": "start_date", "value": "{{timeframe.start.slice(0,10)}}" }, + { "key": "end_date", "value": "{{timeframe.end.slice(0,10)}}" }, + { "key": "repository_ids", "value": "{{repository && repository.length ? repository.map(o => Array.isArray(o.rawId) ? o.rawId[0] : o.rawId).join(',') : null}}" }, + { "key": "user_ids", "value": "{{user && user.length ? user.map(o => Array.isArray(o.rawId) ? o.rawId[0] : o.rawId).join(',') : null}}" } + ], + "paging": { + "mode": "token", + "pageSize": { "realm": "queryArg", "path": "limit", "value": "250" }, + "in": { "realm": "payload", "path": "next_cursor" }, + "out": { "realm": "queryArg", "path": "cursor" } + }, + "errorHandling": { "type": "path", "realm": "payload", "path": "error.message" }, + "postRequestScript": "reviewComments.js" + }, + "matches": "none", + "ui": [ + { "type": "objects", "name": "repository", "label": "Repository (optional)", "matches": { "sourceType": { "type": "oneOf", "values": ["Repository"] } } }, + { "type": "objects", "name": "user", "label": "User (optional)", "matches": { "sourceType": { "type": "oneOf", "values": ["User"] } } } + ], + "metadata": [ + { "name": "commentUrl", "displayName": "Comment", "shape": ["url", { "label": "View" }], "role": "link" }, + { "name": "prUrl", "displayName": "Pull request", "shape": ["url", { "label": "#{{column.prNumber}}" }], "role": "link" }, + { + "name": "prNumber", + "displayName": "PR number", + "shape": ["number", { "thousandsSeparator": false }], + "visible": false + }, + { "name": "severityKey", "displayName": "Severity key", "shape": "string", "visible": false }, + { "name": "categoryKey", "displayName": "Category key", "shape": "string", "visible": false }, + { + "name": "severity", + "displayName": "Severity", + "computed": true, + "valueExpression": "{{ $['severityKey'] ? $['severityKey'].charAt(0).toUpperCase() + $['severityKey'].slice(1).toLowerCase() : 'Unclassified' }}", + "shape": "string" + }, + { + "name": "severityRank", + "displayName": "Severity rank", + "computed": true, + "valueExpression": "{{ ({ critical: 1, major: 2, minor: 3, trivial: 4, info: 5 })[String($['severityKey']).toLowerCase()] || 6 }}", + "shape": "number", + "visible": false + }, + { + "name": "category", + "displayName": "Category", + "computed": true, + "valueExpression": "{{ $['categoryKey'] ? $['categoryKey'].charAt(0).toUpperCase() + $['categoryKey'].slice(1).toLowerCase().replace(/_/g, ' ') : 'Unclassified' }}", + "shape": "string" + }, + { "name": "accepted", "displayName": "Accepted", "shape": "boolean" }, + { + "name": "acceptedCount", + "displayName": "Accepted count", + "computed": true, + "valueExpression": "{{ $['accepted'] === true ? 1 : 0 }}", + "shape": "number" + }, + { "name": "merged_at", "displayName": "Merged", "shape": "date", "role": "timestamp" }, + { "name": "repository_id", "displayName": "Repository ID", "shape": "string", "visible": false }, + { "name": "author_id", "displayName": "Author ID", "shape": "string", "visible": false }, + { "name": "repositoryName", "displayName": "Repository", "sourceId": "repository_id", "sourceType": "Repository", "objectPropertyPath": "name" }, + { "name": "authorName", "displayName": "Author", "sourceId": "author_id", "sourceType": "User", "objectPropertyPath": "name" }, + { "sourceId": "repository_id", "sourceType": "Repository", "name": "repositoryName" }, + { "sourceId": "author_id", "sourceType": "User", "name": "authorName" } + ], + "timeframes": ["last24hours", "last7days", "last30days", "thisMonth", "lastMonth", "thisQuarter", "lastQuarter"] +} diff --git a/plugins/CodeRabbit/v1/dataStreams/scripts/errorHandling/mcpUsage.js b/plugins/CodeRabbit/v1/dataStreams/scripts/errorHandling/mcpUsage.js new file mode 100644 index 00000000..bcaab0e0 --- /dev/null +++ b/plugins/CodeRabbit/v1/dataStreams/scripts/errorHandling/mcpUsage.js @@ -0,0 +1,7 @@ +const apiMsg = data && (data.message || (data.error && data.error.message) || data.error); + +if (response.status === 403) { + result = "MCP usage metrics are not enabled for this CodeRabbit organization"; +} else { + result = apiMsg || "Request failed with HTTP " + response.status; +} diff --git a/plugins/CodeRabbit/v1/dataStreams/scripts/errorHandling/securityFindings.js b/plugins/CodeRabbit/v1/dataStreams/scripts/errorHandling/securityFindings.js new file mode 100644 index 00000000..3a97bfdf --- /dev/null +++ b/plugins/CodeRabbit/v1/dataStreams/scripts/errorHandling/securityFindings.js @@ -0,0 +1,7 @@ +const apiMsg = data && (data.message || (data.error && (data.error.message || data.error))); + +if (response.status === 403) { + result = 'AI Deep Scan findings need an API key with the security read permission' + (apiMsg ? ' (' + apiMsg + ')' : ''); +} else { + result = apiMsg || ('Request failed with HTTP ' + response.status); +} diff --git a/plugins/CodeRabbit/v1/dataStreams/scripts/mcpUsage.js b/plugins/CodeRabbit/v1/dataStreams/scripts/mcpUsage.js new file mode 100644 index 00000000..cf8e987e --- /dev/null +++ b/plugins/CodeRabbit/v1/dataStreams/scripts/mcpUsage.js @@ -0,0 +1,18 @@ +// Flatten each pull request into one row per MCP server used. +result = (data.data || []).flatMap((pr) => + (pr.servers || []).map((server) => { + const tools = server.tools || []; + return { + prUrl: pr.pr_url, + // The url shape's label can't read computed columns, so the number is derived here + prNumber: Number(String(pr.pr_url || "").split("/").pop()) || null, + merged_at: pr.merged_at, + serverName: server.server_name, + insightCount: server.insight_count, + toolCallCount: tools.reduce((sum, t) => sum + (t.call_count || 0), 0), + tools: tools.map((t) => t.tool_name).join(", "), + repository_id: pr.repository_id, + author_id: pr.author_id, + }; + }), +); diff --git a/plugins/CodeRabbit/v1/dataStreams/scripts/pullRequests.js b/plugins/CodeRabbit/v1/dataStreams/scripts/pullRequests.js new file mode 100644 index 00000000..e9d3c13c --- /dev/null +++ b/plugins/CodeRabbit/v1/dataStreams/scripts/pullRequests.js @@ -0,0 +1,29 @@ +// Flatten nested comment counts and trim the day-granular API window to a 24 hour timeframe. +const start = new Date(context.timeframe.start); +const end = new Date(context.timeframe.end); +const trim = context.timeframe.enum === 'last24hours'; + +result = (data.data || []) + .filter((pr) => { + // Only merged PRs expose a finalized time; closed PRs stay at day granularity + if (!trim || !pr.merged_at) return true; + const t = new Date(pr.merged_at); + return t >= start && t <= end; + }) + .map(({ coderabbit_comments: comments = {}, ...pr }) => { + const total = comments.total || {}; + const severity = comments.severity || {}; + const category = comments.category || {}; + return { + ...pr, + prUrl: pr.pr_url, + // The url shape's label can't read computed columns, so the number is derived here + prNumber: Number(String(pr.pr_url || '').split('/').pop()) || null, + commentsPosted: total.posted ?? 0, + commentsAccepted: total.accepted ?? 0, + criticalPosted: severity.critical?.posted ?? 0, + majorPosted: severity.major?.posted ?? 0, + // The reviews endpoint returns upper-case category keys, unlike its docs + securityPosted: (category.SECURITY_AND_PRIVACY ?? category.security_and_privacy)?.posted ?? 0, + }; + }); diff --git a/plugins/CodeRabbit/v1/dataStreams/scripts/repositories.js b/plugins/CodeRabbit/v1/dataStreams/scripts/repositories.js new file mode 100644 index 00000000..0d52bb0f --- /dev/null +++ b/plugins/CodeRabbit/v1/dataStreams/scripts/repositories.js @@ -0,0 +1,7 @@ +// Tag each repository with its parent organization, which the API response omits. +const org = context.objects[0] || {}; +result = (data.repositories || []).map((repo) => ({ + ...repo, + organizationId: org.rawId, + organizationName: org.name, +})); diff --git a/plugins/CodeRabbit/v1/dataStreams/scripts/reviewComments.js b/plugins/CodeRabbit/v1/dataStreams/scripts/reviewComments.js new file mode 100644 index 00000000..c4300226 --- /dev/null +++ b/plugins/CodeRabbit/v1/dataStreams/scripts/reviewComments.js @@ -0,0 +1,25 @@ +// Flatten the nested findings array so each finding is a row. +// last24hours is narrower than the date-only API filter, so trim by merge time +const start = Date.parse(context.timeframe.start); +const end = Date.parse(context.timeframe.end); +const inWindow = (mergedAt) => { + const t = Date.parse(mergedAt); + return Number.isNaN(t) || (t >= start && t <= end); +}; + +result = (data.data || []) + .filter((pr) => inWindow(pr.merged_at)) + .flatMap((pr) => + (pr.coderabbit_comments || []).map((c) => ({ + commentUrl: c.url, + prUrl: pr.pr_url, + // The url shape's label can't read computed columns, so the number is derived here + prNumber: Number(String(pr.pr_url || "").split("/").pop()) || null, + severityKey: c.severity, + categoryKey: c.category, + accepted: c.accepted === true, + merged_at: pr.merged_at, + repository_id: pr.repository_id, + author_id: pr.author_id, + })), + ); diff --git a/plugins/CodeRabbit/v1/dataStreams/scripts/securityFindings.js b/plugins/CodeRabbit/v1/dataStreams/scripts/securityFindings.js new file mode 100644 index 00000000..ebc31d01 --- /dev/null +++ b/plugins/CodeRabbit/v1/dataStreams/scripts/securityFindings.js @@ -0,0 +1,8 @@ +// The API filters by a single repo_id only, so narrow to the selected repositories here when more +// than one is chosen. The bulky evidence fields are dropped to keep the response small. +const selected = (context.config && context.config.repository) || []; +const ids = new Set(selected.map((o) => [].concat(o.rawId)[0])); + +result = (data.data || []) + .filter((f) => ids.size === 0 || ids.has(f.repository_id)) + .map(({ reachability_call_stack, scan_provenance, ...f }) => f); diff --git a/plugins/CodeRabbit/v1/dataStreams/securityFindings.json b/plugins/CodeRabbit/v1/dataStreams/securityFindings.json new file mode 100644 index 00000000..6f5be7b6 --- /dev/null +++ b/plugins/CodeRabbit/v1/dataStreams/securityFindings.json @@ -0,0 +1,83 @@ +{ + "name": "securityFindings", + "displayName": "Security findings", + "description": "AI Deep Scan code findings with severity, CWE and location", + "tags": ["Security", "Repository"], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "get", + "endpointPath": "/v1/security/scans/code", + "pathToData": "data", + "getArgs": [ + { "key": "format", "value": "json" }, + { "key": "state", "value": "{{state || 'open'}}" }, + { + "key": "repo_id", + "value": "{{(typeof repository !== 'undefined' && repository && repository.length === 1) ? [].concat(repository[0].rawId)[0] : null}}" + } + ], + "paging": { + "mode": "token", + "pageSize": { "realm": "queryArg", "path": "limit", "value": "1000" }, + "in": { "realm": "payload", "path": "next_cursor" }, + "out": { "realm": "queryArg", "path": "cursor" } + }, + "errorHandling": { "type": "script", "script": "errorHandling/securityFindings.js" }, + "postRequestScript": "securityFindings.js" + }, + "matches": "none", + "ui": [ + { + "type": "radio", + "name": "state", + "label": "State", + "defaultValue": "open", + "options": [ + { "value": "open", "label": "Open" }, + { "value": "fixed", "label": "Fixed" }, + { "value": "dismissed", "label": "Dismissed" }, + { "value": "all", "label": "All" } + ] + }, + { + "type": "objects", + "name": "repository", + "label": "Repository (optional)", + "matches": { "sourceType": { "type": "oneOf", "values": ["Repository"] } } + } + ], + "metadata": [ + { + "name": "summary", + "displayName": "Finding", + "computed": true, + "valueExpression": "{{ (() => { const s = String($['description'] || '').replace(/\\\\s+/g, ' ').trim().split(/(?<=[.!?])\\\\s/)[0]; return s.length > 140 ? s.slice(0, 137) + '...' : s; })() }}", + "shape": "string", + "role": "label" + }, + { "name": "id", "displayName": "Finding ID", "shape": "string", "role": "id", "visible": false }, + { "name": "severity", "displayName": "Severity", "shape": "string", "formatExpression": "{{ $['severity'] ? $['severity'].charAt(0).toUpperCase() + $['severity'].slice(1).toLowerCase() : '' }}" }, + { + "name": "severityRank", + "displayName": "Severity rank", + "computed": true, + "valueExpression": "{{ ({ critical: 5, high: 4, medium: 3, low: 2, info: 1 })[String($['severity']).toLowerCase()] || 0 }}", + "shape": "number", + "visible": false + }, + { "name": "state", "displayName": "State", "shape": "string", "formatExpression": "{{ $['state'] ? $['state'].charAt(0).toUpperCase() + $['state'].slice(1) : '' }}" }, + { "name": "cwe", "displayName": "CWE", "shape": "string" }, + { "name": "repository_name", "displayName": "Repository", "shape": "string" }, + { "name": "repository_id", "displayName": "Repository ID", "shape": "string", "visible": false }, + { "sourceId": "repository_id", "sourceType": "Repository", "name": "repository_name" }, + { "name": "file_path", "displayName": "File", "shape": "string" }, + { "name": "start_line", "displayName": "Start line", "shape": "number" }, + { "name": "end_line", "displayName": "End line", "shape": "number" }, + { "name": "description", "displayName": "Description", "shape": "string", "visible": false }, + { "name": "organization_name", "displayName": "Organization", "shape": "string" }, + { "name": "organization_id", "displayName": "Organization ID", "shape": "string", "visible": false } + ], + "timeframes": ["none"], + "supportsNoneTimeframe": true, + "defaultTimeframe": "none" +} diff --git a/plugins/CodeRabbit/v1/dataStreams/users.json b/plugins/CodeRabbit/v1/dataStreams/users.json new file mode 100644 index 00000000..7c2c0fba --- /dev/null +++ b/plugins/CodeRabbit/v1/dataStreams/users.json @@ -0,0 +1,30 @@ +{ + "name": "users", + "displayName": "Users", + "description": "CodeRabbit users with their seat assignment and role", + "tags": ["Users", "License"], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "get", + "endpointPath": "/v1/users", + "pathToData": "users", + "paging": { + "mode": "token", + "pageSize": { "realm": "queryArg", "path": "limit", "value": "100" }, + "in": { "realm": "payload", "path": "next_cursor" }, + "out": { "realm": "queryArg", "path": "cursor" } + }, + "errorHandling": { "type": "path", "realm": "payload", "path": "error.message" } + }, + "matches": "none", + "metadata": [ + { "name": "username", "displayName": "Username", "shape": "string", "role": "label" }, + { "name": "user_id", "displayName": "User ID", "shape": "string", "role": "id", "visible": false }, + { "name": "seat_assigned", "displayName": "Seat assigned", "shape": "boolean" }, + { "name": "role_name", "displayName": "Role", "shape": "string" }, + { "sourceId": "user_id", "sourceType": "User", "name": "username" } + ], + "timeframes": ["none"], + "supportsNoneTimeframe": true, + "defaultTimeframe": "none" +} diff --git a/plugins/CodeRabbit/v1/defaultContent/licenses.dash.json b/plugins/CodeRabbit/v1/defaultContent/licenses.dash.json new file mode 100644 index 00000000..0b70e02a --- /dev/null +++ b/plugins/CodeRabbit/v1/defaultContent/licenses.dash.json @@ -0,0 +1,559 @@ +{ + "name": "Licenses", + "schemaVersion": "1.5", + "timeframe": "last30days", + "variables": [], + "dashboard": { + "_type": "layout/grid", + "columns": 4, + "version": 1, + "contents": [ + { + "i": "8367698f-156a-4fdf-bafd-e08a0832bfa1", + "x": 0, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Seats Purchased", + "description": "", + "activePluginConfigIds": [ + "{{configId}}" + ], + "timeframe": "none", + "dataStream": { + "id": "{{dataStreams.[licenses]}}", + "name": "licenses", + "pluginConfigId": "{{configId}}" + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "scalar", + "encoding": { + "values": [ + { + "field": "seats_purchased", + "type": "quantitative", + "title": "Seats Purchased" + } + ] + }, + "style": { + "scalarLabelPosition": "none" + } + } + } + } + }, + { + "i": "daa3150a-3c3f-442b-88e3-1e23103a84ea", + "x": 1, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Seats Assigned", + "description": "", + "activePluginConfigIds": [ + "{{configId}}" + ], + "timeframe": "none", + "dataStream": { + "id": "{{dataStreams.[licenses]}}", + "name": "licenses", + "pluginConfigId": "{{configId}}" + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "scalar", + "encoding": { + "values": [ + { + "field": "seats_assigned", + "type": "quantitative", + "title": "Seats Assigned" + } + ] + }, + "style": { + "scalarLabelPosition": "none" + } + } + } + } + }, + { + "i": "bf1dd624-8b9d-4fd6-bcc7-62700a0b5bd5", + "x": 2, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Seats Available", + "description": "", + "activePluginConfigIds": [ + "{{configId}}" + ], + "timeframe": "none", + "dataStream": { + "id": "{{dataStreams.[licenses]}}", + "name": "licenses", + "pluginConfigId": "{{configId}}" + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "scalar", + "encoding": { + "values": [ + { + "field": "seatsAvailable", + "type": "quantitative", + "title": "Seats Available" + } + ] + }, + "style": { + "scalarLabelPosition": "none" + } + } + } + } + }, + { + "i": "8bed8a63-fba9-46e5-ac91-07861bbcb1bf", + "x": 3, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Seat Utilization", + "description": "", + "activePluginConfigIds": [ + "{{configId}}" + ], + "timeframe": "none", + "dataStream": { + "id": "{{dataStreams.[licenses]}}", + "name": "licenses", + "pluginConfigId": "{{configId}}" + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "gauge", + "encoding": { + "value": { + "field": "seatUtilization", + "type": "quantitative", + "title": "Seat Utilization" + } + }, + "style": { + "gaugeMin": 0, + "gaugeMax": 100, + "gaugeBands": [ + { + "to": 0.5, + "color": "#DE0038" + }, + { + "to": 0.99, + "color": "#E18700" + }, + { + "to": 1, + "color": "#259A51" + } + ] + } + } + } + } + }, + { + "i": "a580c996-9227-4766-bfbc-0ded51000f62", + "x": 0, + "y": 2, + "w": 3, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Active Users Per Day", + "description": "", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[pullRequests]}}", + "name": "pullRequests", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "status": "merged" + }, + "group": { + "by": [ + [ + "merged_at", + "byDay" + ] + ], + "aggregate": [ + { + "type": "distinctCount", + "names": [ + "author_id" + ] + } + ] + }, + "sort": { + "by": [ + [ + "merged_at_byDay", + "asc" + ] + ] + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "line", + "encoding": { + "x": { + "field": "merged_at_byDay", + "type": "temporal", + "title": "Merged" + }, + "y": { + "field": "author_id_distinctCount", + "type": "quantitative", + "title": "Active Users" + } + }, + "style": { + "legendPosition": "none", + "cartesianAreaGradient": true + } + } + } + } + }, + { + "i": "b10c6103-9ea1-4625-a6a8-2a4381212022", + "x": 3, + "y": 2, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Seat Assignment Mode", + "description": "", + "activePluginConfigIds": [ + "{{configId}}" + ], + "timeframe": "none", + "dataStream": { + "id": "{{dataStreams.[licenses]}}", + "name": "licenses", + "pluginConfigId": "{{configId}}" + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "scalar", + "encoding": { + "values": [ + { + "field": "seat_assignment_mode", + "type": "nominal", + "title": "Seat Assignment Mode" + } + ] + }, + "style": { + "scalarLabelPosition": "none" + } + } + } + } + }, + { + "i": "99648d6f-5ae5-4264-a382-54d715f3047f", + "x": 0, + "y": 6, + "w": 2, + "h": 5, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Users", + "description": "", + "activePluginConfigIds": [ + "{{configId}}" + ], + "timeframe": "none", + "dataStream": { + "id": "{{dataStreams.[users]}}", + "name": "users", + "pluginConfigId": "{{configId}}", + "sort": { + "by": [ + [ + "username", + "asc" + ] + ] + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "richTable", + "encoding": { + "columns": [ + { + "field": "username", + "title": "Username", + "renderer": { + "type": "text" + } + }, + { + "field": "seat_assigned", + "title": "Seat Assigned", + "renderer": { + "type": "boolean", + "trueLabel": "Yes", + "falseLabel": "No" + }, + "width": 140 + }, + { + "field": "role_name", + "title": "Role", + "renderer": { + "type": "text" + }, + "width": 160 + } + ] + }, + "style": { + "richTableSort": { + "field": "username", + "direction": "asc" + } + } + } + } + } + }, + { + "i": "6747238e-48ec-4cb6-9593-ecb397b9f06d", + "x": 2, + "y": 6, + "w": 2, + "h": 5, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Recent Admin Activity", + "description": "", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[auditLogs]}}", + "name": "auditLogs", + "pluginConfigId": "{{configId}}", + "sort": { + "by": [ + [ + "createdAt", + "desc" + ] + ] + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "richTable", + "encoding": { + "columns": [ + { + "field": "createdAt", + "title": "Time", + "renderer": { + "type": "date", + "relative": true + }, + "width": 120 + }, + { + "field": "actionLabel", + "title": "Action", + "renderer": { + "type": "text" + } + }, + { + "field": "actor.name", + "title": "Actor", + "renderer": { + "type": "text" + } + }, + { + "field": "resourceTypeLabel", + "title": "Resource Type", + "renderer": { + "type": "text" + }, + "width": 130 + }, + { + "field": "resourceSummary", + "title": "Resource", + "renderer": { + "type": "text" + } + } + ] + }, + "style": { + "richTableSort": { + "field": "createdAt", + "direction": "desc" + } + } + } + } + } + }, + { + "i": "3f141345-2937-4a6c-add5-ccff36dae1ec", + "x": 0, + "y": 11, + "w": 2, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Idle Seats", + "description": "Users holding a seat with no merged pull request in the timeframe.", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "datastream-sql", + "dataSourceConfig": { + "version": "2.0", + "sql": "SELECT u.\"username\" AS \"username\", u.\"role_name\" AS \"role\"\nFROM \"dataset1\" u\nWHERE u.\"seat_assigned\" = TRUE\n AND CAST(u.\"user_id\" AS VARCHAR) NOT IN (\n SELECT DISTINCT CAST(\"author_id\" AS VARCHAR) FROM \"dataset2\" WHERE \"author_id\" IS NOT NULL\n )\nORDER BY u.\"username\"", + "tables": [ + { + "tableName": "dataset1", + "config": { + "timeframe": "none", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[users]}}", + "name": "users", + "pluginConfigId": "{{configId}}" + } + } + }, + { + "tableName": "dataset2", + "config": { + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[pullRequests]}}", + "name": "pullRequests", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "status": "merged" + } + } + } + } + ] + }, + "metadata": [ + { + "pattern": ".*" + } + ] + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "richTable", + "encoding": { + "columns": [ + { + "field": "username", + "title": "Username", + "renderer": { + "type": "text" + } + }, + { + "field": "role", + "title": "Role", + "renderer": { + "type": "text" + }, + "width": 160 + } + ] + }, + "style": { + "richTableSort": { + "field": "username", + "direction": "asc" + } + } + } + } + } + } + ] + } +} diff --git a/plugins/CodeRabbit/v1/defaultContent/manifest.json b/plugins/CodeRabbit/v1/defaultContent/manifest.json new file mode 100644 index 00000000..0a8da548 --- /dev/null +++ b/plugins/CodeRabbit/v1/defaultContent/manifest.json @@ -0,0 +1,24 @@ +{ + "items": [ + { + "name": "overview", + "type": "dashboard" + }, + { + "name": "licenses", + "type": "dashboard" + }, + { + "name": "prsAndReviews", + "type": "dashboard" + }, + { + "name": "security", + "type": "dashboard" + }, + { + "name": "user", + "type": "dashboard" + } + ] +} diff --git a/plugins/CodeRabbit/v1/defaultContent/overview.dash.json b/plugins/CodeRabbit/v1/defaultContent/overview.dash.json new file mode 100644 index 00000000..0bb9c2c7 --- /dev/null +++ b/plugins/CodeRabbit/v1/defaultContent/overview.dash.json @@ -0,0 +1,530 @@ +{ + "name": "Overview", + "schemaVersion": "1.5", + "timeframe": "last30days", + "variables": [], + "dashboard": { + "_type": "layout/grid", + "columns": 4, + "version": 1, + "contents": [ + { + "i": "8f34d2cc-0962-4d85-8f8c-4ee90b7eb853", + "x": 0, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Seats Used", + "description": "", + "activePluginConfigIds": [ + "{{configId}}" + ], + "timeframe": "none", + "dataStream": { + "id": "{{dataStreams.[licenses]}}", + "name": "licenses", + "pluginConfigId": "{{configId}}" + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "scalar", + "encoding": { + "values": [ + { + "field": "seats_assigned", + "type": "quantitative", + "title": "Seats Used" + } + ] + }, + "style": { + "scalarLabelPosition": "none" + } + } + } + } + }, + { + "i": "a417d00b-16d1-419a-b202-5da9daa40db8", + "x": 1, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Active Users", + "description": "Authors with at least one merged pull request.", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[pullRequests]}}", + "name": "pullRequests", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "status": "merged" + }, + "group": { + "by": [], + "aggregate": [ + { + "type": "distinctCount", + "names": [ + "author_id" + ] + } + ] + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "scalar", + "encoding": { + "values": [ + { + "field": "author_id_distinctCount", + "type": "quantitative", + "title": "Active Users" + } + ] + }, + "style": { + "scalarLabelPosition": "none" + } + } + } + } + }, + { + "i": "e844ce7e-97ea-4df3-9631-d9b9adb0fb1c", + "x": 2, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "PRs Merged", + "description": "", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[pullRequests]}}", + "name": "pullRequests", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "status": "merged" + }, + "group": { + "by": [], + "aggregate": [ + { + "type": "count" + } + ] + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "scalar", + "encoding": { + "values": [ + { + "field": "count", + "type": "quantitative", + "title": "PRs Merged" + } + ] + }, + "style": { + "scalarLabelPosition": "none" + } + } + } + } + }, + { + "i": "c8ffc787-8014-403d-b14f-ce822aad9c40", + "x": 3, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Acceptance Rate", + "description": "Share of CodeRabbit findings that were accepted.", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "datastream-sql", + "dataSourceConfig": { + "version": "2.0", + "sql": "SELECT AVG(\"acceptedCount\") * 100 AS \"acceptanceRate\" FROM \"dataset1\"", + "tables": [ + { + "tableName": "dataset1", + "config": { + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[reviewComments]}}", + "name": "reviewComments", + "pluginConfigId": "{{configId}}" + } + } + } + ] + }, + "metadata": [ + { + "name": "acceptanceRate", + "shape": [ + "percent", + { + "decimalPlaces": 1 + } + ] + }, + { + "pattern": ".*" + } + ] + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "gauge", + "encoding": { + "value": { + "field": "acceptanceRate", + "type": "quantitative", + "title": "Acceptance Rate" + } + }, + "style": { + "gaugeMin": 0, + "gaugeMax": 100, + "gaugeBands": [ + { + "to": 0.6, + "color": "#DE0038" + }, + { + "to": 0.8, + "color": "#E18700" + }, + { + "to": 1, + "color": "#259A51" + } + ] + } + } + } + } + }, + { + "i": "3788ab33-9823-42b0-afa2-5debd33cc683", + "x": 0, + "y": 2, + "w": 2, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Estimated Review Hours", + "description": "Review effort CodeRabbit estimated for merged pull requests.", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "datastream-sql", + "dataSourceConfig": { + "version": "2.0", + "sql": "SELECT SUM(\"estimated_review_minutes\") / 60.0 AS \"estimatedReviewHours\" FROM \"dataset1\"", + "tables": [ + { + "tableName": "dataset1", + "config": { + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[pullRequests]}}", + "name": "pullRequests", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "status": "merged" + } + } + } + } + ] + }, + "metadata": [ + { + "name": "estimatedReviewHours", + "shape": [ + "number", + { + "decimalPlaces": 1 + } + ] + }, + { + "pattern": ".*" + } + ] + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "scalar", + "encoding": { + "values": [ + { + "field": "estimatedReviewHours", + "type": "quantitative", + "title": "Hours" + } + ] + }, + "style": { + "scalarLabelPosition": "none" + } + } + } + } + }, + { + "i": "aa0ec097-07f9-4e7c-9ba9-43cc4672eedf", + "x": 2, + "y": 2, + "w": 2, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Findings Posted", + "description": "CodeRabbit review findings on merged pull requests.", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[reviewComments]}}", + "name": "reviewComments", + "pluginConfigId": "{{configId}}", + "group": { + "by": [], + "aggregate": [ + { + "type": "count" + } + ] + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "scalar", + "encoding": { + "values": [ + { + "field": "count", + "type": "quantitative", + "title": "Findings Posted" + } + ] + }, + "style": { + "scalarLabelPosition": "none" + } + } + } + } + }, + { + "i": "b2ac0510-47ad-4b90-9e1d-7e5ce560d666", + "x": 0, + "y": 4, + "w": 2, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "PRs Merged Per Day", + "description": "", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[pullRequests]}}", + "name": "pullRequests", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "status": "merged" + }, + "group": { + "by": [ + [ + "merged_at", + "byDay" + ] + ], + "aggregate": [ + { + "type": "count" + } + ] + }, + "sort": { + "by": [ + [ + "merged_at_byDay", + "asc" + ] + ] + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "bar", + "encoding": { + "x": { + "field": "merged_at_byDay", + "type": "temporal", + "title": "Merged" + }, + "y": { + "field": "count", + "type": "quantitative", + "title": "PRs Merged" + } + }, + "style": { + "legendPosition": "none", + "colors": [ + "#FF570A" + ], + "cartesianColorRules": [ + { + "gte": 0, + "color": "#FF570A" + } + ] + } + } + } + } + }, + { + "i": "4dba54bd-de9b-4d44-ad70-2de31d70cdbd", + "x": 2, + "y": 4, + "w": 2, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Acceptance Rate Over Time", + "description": "", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "datastream-sql", + "dataSourceConfig": { + "version": "2.0", + "sql": "SELECT\n STRFTIME(DATE_TRUNC('day', \"merged_at\"), '%Y-%m-%dT%H:%M:%SZ') AS \"day\",\n AVG(\"acceptedCount\") * 100 AS \"acceptanceRate\"\nFROM \"dataset1\"\nWHERE \"merged_at\" IS NOT NULL\nGROUP BY 1\nORDER BY 1", + "tables": [ + { + "tableName": "dataset1", + "config": { + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[reviewComments]}}", + "name": "reviewComments", + "pluginConfigId": "{{configId}}" + } + } + } + ] + }, + "metadata": [ + { + "name": "day", + "shape": "date" + }, + { + "name": "acceptanceRate", + "shape": [ + "percent", + { + "decimalPlaces": 1 + } + ] + }, + { + "pattern": ".*" + } + ] + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "line", + "encoding": { + "x": { + "field": "day", + "type": "temporal", + "title": "Merged" + }, + "y": { + "field": "acceptanceRate", + "type": "quantitative", + "title": "Acceptance Rate" + } + }, + "style": { + "legendPosition": "none", + "cartesianYAxisMin": 0, + "cartesianYAxisMax": 100, + "cartesianAreaGradient": true + } + } + } + } + } + ] + } +} diff --git a/plugins/CodeRabbit/v1/defaultContent/prsAndReviews.dash.json b/plugins/CodeRabbit/v1/defaultContent/prsAndReviews.dash.json new file mode 100644 index 00000000..6443f63d --- /dev/null +++ b/plugins/CodeRabbit/v1/defaultContent/prsAndReviews.dash.json @@ -0,0 +1,833 @@ +{ + "name": "PRs & Reviews", + "schemaVersion": "1.5", + "timeframe": "last30days", + "variables": [ + "{{variables.[Repository]}}" + ], + "dashboard": { + "_type": "layout/grid", + "columns": 4, + "version": 1, + "contents": [ + { + "i": "77b7900a-a918-428a-9ac6-e33b0b8470b6", + "x": 0, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Median Time to First Human Review", + "description": "Hours from ready for review to the first human review.", + "activePluginConfigIds": [ + "{{configId}}" + ], + "variables": [ + "{{variables.[Repository]}}" + ], + "dataStream": { + "id": "{{dataStreams.[pullRequests]}}", + "name": "pullRequests", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "status": "merged", + "repository": { + "variable": "{{variables.[Repository]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Repositories]}}" + } + }, + "group": { + "by": [], + "aggregate": [ + { + "type": "median", + "names": [ + "hoursToFirstHumanReview" + ] + } + ] + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "scalar", + "encoding": { + "values": [ + { + "field": "hoursToFirstHumanReview_median", + "type": "quantitative", + "title": "Hours" + } + ] + }, + "style": { + "scalarLabelPosition": "none" + } + } + } + } + }, + { + "i": "0b7a2ae7-1333-41c7-8523-b7c472bf76b0", + "x": 1, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Median Time to Merge", + "description": "Hours from ready for review to merge.", + "activePluginConfigIds": [ + "{{configId}}" + ], + "variables": [ + "{{variables.[Repository]}}" + ], + "dataStream": { + "id": "{{dataStreams.[pullRequests]}}", + "name": "pullRequests", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "status": "merged", + "repository": { + "variable": "{{variables.[Repository]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Repositories]}}" + } + }, + "group": { + "by": [], + "aggregate": [ + { + "type": "median", + "names": [ + "hoursToMerge" + ] + } + ] + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "scalar", + "encoding": { + "values": [ + { + "field": "hoursToMerge_median", + "type": "quantitative", + "title": "Hours" + } + ] + }, + "style": { + "scalarLabelPosition": "none" + } + } + } + } + }, + { + "i": "267b4bdd-4d22-49e4-b71f-8f496ebf7bbb", + "x": 2, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Acceptance Rate", + "description": "Share of CodeRabbit findings that were accepted.", + "activePluginConfigIds": [ + "{{configId}}" + ], + "variables": [ + "{{variables.[Repository]}}" + ], + "dataStream": { + "id": "datastream-sql", + "dataSourceConfig": { + "version": "2.0", + "sql": "SELECT AVG(\"acceptedCount\") * 100 AS \"acceptanceRate\" FROM \"dataset1\"", + "tables": [ + { + "tableName": "dataset1", + "config": { + "variables": [ + "{{variables.[Repository]}}" + ], + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[reviewComments]}}", + "name": "reviewComments", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "repository": { + "variable": "{{variables.[Repository]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Repositories]}}" + } + } + } + } + } + ] + }, + "metadata": [ + { + "name": "acceptanceRate", + "shape": [ + "percent", + { + "decimalPlaces": 1 + } + ] + }, + { + "pattern": ".*" + } + ] + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "gauge", + "encoding": { + "value": { + "field": "acceptanceRate", + "type": "quantitative", + "title": "Acceptance Rate" + } + }, + "style": { + "gaugeMin": 0, + "gaugeMax": 100, + "gaugeBands": [ + { + "to": 0.6, + "color": "#DE0038" + }, + { + "to": 0.8, + "color": "#E18700" + }, + { + "to": 1, + "color": "#259A51" + } + ] + } + } + } + } + }, + { + "i": "f4f4c8ea-1460-4347-a046-35e6220b7984", + "x": 3, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "PRs Merged", + "description": "", + "activePluginConfigIds": [ + "{{configId}}" + ], + "variables": [ + "{{variables.[Repository]}}" + ], + "dataStream": { + "id": "{{dataStreams.[pullRequests]}}", + "name": "pullRequests", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "status": "merged", + "repository": { + "variable": "{{variables.[Repository]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Repositories]}}" + } + }, + "group": { + "by": [], + "aggregate": [ + { + "type": "count" + } + ] + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "scalar", + "encoding": { + "values": [ + { + "field": "count", + "type": "quantitative", + "title": "PRs Merged" + } + ] + }, + "style": { + "scalarLabelPosition": "none" + } + } + } + } + }, + { + "i": "33933337-ab10-49cc-8e53-b3f0e7f07061", + "x": 0, + "y": 2, + "w": 2, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Time to Merge Trend", + "description": "Median hours from ready for review to merge, by merge day.", + "activePluginConfigIds": [ + "{{configId}}" + ], + "variables": [ + "{{variables.[Repository]}}" + ], + "dataStream": { + "id": "{{dataStreams.[pullRequests]}}", + "name": "pullRequests", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "status": "merged", + "repository": { + "variable": "{{variables.[Repository]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Repositories]}}" + } + }, + "group": { + "by": [ + [ + "merged_at", + "byDay" + ] + ], + "aggregate": [ + { + "type": "median", + "names": [ + "hoursToMerge" + ] + } + ] + }, + "sort": { + "by": [ + [ + "merged_at_byDay", + "asc" + ] + ] + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "line", + "encoding": { + "x": { + "field": "merged_at_byDay", + "type": "temporal", + "title": "Merged" + }, + "y": { + "field": "hoursToMerge_median", + "type": "quantitative", + "title": "Hours to Merge" + } + }, + "style": { + "legendPosition": "none", + "cartesianAreaGradient": true + } + } + } + } + }, + { + "i": "a907092f-30e7-499b-8399-ea2ae5b8ef82", + "x": 2, + "y": 2, + "w": 2, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Acceptance Rate by Severity", + "description": "", + "activePluginConfigIds": [ + "{{configId}}" + ], + "variables": [ + "{{variables.[Repository]}}" + ], + "dataStream": { + "id": "datastream-sql", + "dataSourceConfig": { + "version": "2.0", + "sql": "SELECT\n \"severity\",\n MIN(\"severityRank\") AS \"severityRank\",\n COUNT(*) AS \"findings\",\n AVG(\"acceptedCount\") * 100 AS \"acceptanceRate\"\nFROM \"dataset1\"\nGROUP BY \"severity\"\nORDER BY \"severityRank\"", + "tables": [ + { + "tableName": "dataset1", + "config": { + "variables": [ + "{{variables.[Repository]}}" + ], + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[reviewComments]}}", + "name": "reviewComments", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "repository": { + "variable": "{{variables.[Repository]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Repositories]}}" + } + } + } + } + } + ] + }, + "metadata": [ + { + "name": "acceptanceRate", + "shape": [ + "percent", + { + "decimalPlaces": 1 + } + ] + }, + { + "pattern": ".*" + } + ] + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "ringGrid", + "encoding": { + "value": { + "field": "acceptanceRate", + "type": "quantitative", + "title": "Acceptance Rate" + }, + "label": { + "field": "severity", + "type": "nominal", + "title": "Severity" + } + }, + "style": { + "ringGridMax": 100, + "ringGridThresholds": [ + { + "to": 0.6, + "color": "#DE0038" + }, + { + "to": 0.8, + "color": "#E18700" + }, + { + "to": 1, + "color": "#259A51" + } + ] + } + } + } + } + }, + { + "i": "87da594e-999f-422d-9fe0-8043c7728527", + "x": 0, + "y": 6, + "w": 2, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Acceptance Rate by Category", + "description": "", + "activePluginConfigIds": [ + "{{configId}}" + ], + "variables": [ + "{{variables.[Repository]}}" + ], + "dataStream": { + "id": "datastream-sql", + "dataSourceConfig": { + "version": "2.0", + "sql": "SELECT\n \"category\",\n MIN(\"severityRank\") AS \"severityRank\",\n COUNT(*) AS \"findings\",\n AVG(\"acceptedCount\") * 100 AS \"acceptanceRate\"\nFROM \"dataset1\"\nGROUP BY \"category\"\nORDER BY \"acceptanceRate\" DESC", + "tables": [ + { + "tableName": "dataset1", + "config": { + "variables": [ + "{{variables.[Repository]}}" + ], + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[reviewComments]}}", + "name": "reviewComments", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "repository": { + "variable": "{{variables.[Repository]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Repositories]}}" + } + } + } + } + } + ] + }, + "metadata": [ + { + "name": "acceptanceRate", + "shape": [ + "percent", + { + "decimalPlaces": 1 + } + ] + }, + { + "pattern": ".*" + } + ] + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "ringGrid", + "encoding": { + "value": { + "field": "acceptanceRate", + "type": "quantitative", + "title": "Acceptance Rate" + }, + "label": { + "field": "category", + "type": "nominal", + "title": "Category" + } + }, + "style": { + "ringGridMax": 100, + "ringGridThresholds": [ + { + "to": 0.6, + "color": "#DE0038" + }, + { + "to": 0.8, + "color": "#E18700" + }, + { + "to": 1, + "color": "#259A51" + } + ] + } + } + } + } + }, + { + "i": "83b0df73-2b8f-471f-9fd2-3176efbffdac", + "x": 2, + "y": 6, + "w": 2, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Critical and Major Findings", + "description": "", + "activePluginConfigIds": [ + "{{configId}}" + ], + "variables": [ + "{{variables.[Repository]}}" + ], + "dataStream": { + "id": "{{dataStreams.[reviewComments]}}", + "name": "reviewComments", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "repository": { + "variable": "{{variables.[Repository]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Repositories]}}" + } + }, + "filter": { + "multiOperation": "or", + "filters": [ + { + "column": "severity", + "operation": "equals", + "value": "Critical" + }, + { + "column": "severity", + "operation": "equals", + "value": "Major" + } + ] + }, + "sort": { + "by": [ + [ + "severityRank", + "asc" + ], + [ + "merged_at", + "desc" + ] + ] + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "richTable", + "encoding": { + "columns": [ + { + "field": "severity", + "title": "Severity", + "renderer": { + "type": "badge", + "colors": { + "Critical": "#c01616", + "Major": "#f97316", + "Minor": "#f59e0b", + "Trivial": "#3b82f6", + "Info": "#94a3b8", + "Unclassified": "#cbd5e1" + } + }, + "width": 100 + }, + { + "field": "commentUrl", + "title": "Finding", + "renderer": { + "type": "link", + "labelField": "category" + } + }, + { + "field": "prUrl", + "title": "Pull Request", + "renderer": { + "type": "link", + "labelField": "prNumber" + }, + "width": 110 + }, + { + "field": "repositoryName", + "title": "Repository", + "renderer": { + "type": "text" + } + }, + { + "field": "accepted", + "title": "Accepted", + "renderer": { + "type": "boolean", + "trueLabel": "Yes", + "falseLabel": "No" + }, + "width": 100 + } + ] + }, + "style": {} + } + } + } + }, + { + "i": "65b021ef-6fd3-4d78-88bd-069f5855c1c6", + "x": 0, + "y": 10, + "w": 4, + "h": 5, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Recent Pull Requests", + "description": "", + "activePluginConfigIds": [ + "{{configId}}" + ], + "variables": [ + "{{variables.[Repository]}}" + ], + "dataStream": { + "id": "{{dataStreams.[pullRequests]}}", + "name": "pullRequests", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "status": "merged", + "repository": { + "variable": "{{variables.[Repository]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Repositories]}}" + } + }, + "sort": { + "by": [ + [ + "merged_at", + "desc" + ] + ] + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "richTable", + "encoding": { + "columns": [ + { + "field": "prUrl", + "title": "Pull Request", + "renderer": { + "type": "link", + "labelField": "prNumber" + }, + "width": 110 + }, + { + "field": "repository_name", + "title": "Repository", + "renderer": { + "type": "text" + } + }, + { + "field": "author_username", + "title": "Author", + "renderer": { + "type": "text" + } + }, + { + "field": "merged_at", + "title": "Merged", + "renderer": { + "type": "date" + }, + "width": 170 + }, + { + "field": "hoursToMerge", + "title": "Hours to Merge", + "renderer": { + "type": "number", + "decimals": 1 + }, + "align": "right", + "width": 130 + }, + { + "field": "review_iterations", + "title": "Review Iterations", + "renderer": { + "type": "number", + "decimals": 0 + }, + "align": "right", + "width": 140 + }, + { + "field": "commentsPosted", + "title": "Comments Posted", + "renderer": { + "type": "number", + "decimals": 0 + }, + "align": "right", + "width": 140 + }, + { + "field": "commentsAccepted", + "title": "Comments Accepted", + "renderer": { + "type": "number", + "decimals": 0 + }, + "align": "right", + "width": 150 + } + ] + }, + "style": { + "richTableSort": { + "field": "merged_at", + "direction": "desc" + } + } + } + } + } + } + ] + } +} diff --git a/plugins/CodeRabbit/v1/defaultContent/scopes.json b/plugins/CodeRabbit/v1/defaultContent/scopes.json new file mode 100644 index 00000000..1a3d950f --- /dev/null +++ b/plugins/CodeRabbit/v1/defaultContent/scopes.json @@ -0,0 +1,53 @@ +[ + { + "name": "Organizations", + "matches": { + "sourceType": { + "type": "oneOf", + "values": [ + "Organization" + ] + } + }, + "variable": { + "name": "Organization", + "allowMultipleSelection": false, + "default": "none", + "type": "object" + } + }, + { + "name": "Repositories", + "matches": { + "sourceType": { + "type": "oneOf", + "values": [ + "Repository" + ] + } + }, + "variable": { + "name": "Repository", + "allowMultipleSelection": true, + "default": "none", + "type": "object" + } + }, + { + "name": "Users", + "matches": { + "sourceType": { + "type": "oneOf", + "values": [ + "User" + ] + } + }, + "variable": { + "name": "User", + "allowMultipleSelection": false, + "default": "none", + "type": "object" + } + } +] diff --git a/plugins/CodeRabbit/v1/defaultContent/security.dash.json b/plugins/CodeRabbit/v1/defaultContent/security.dash.json new file mode 100644 index 00000000..3808970c --- /dev/null +++ b/plugins/CodeRabbit/v1/defaultContent/security.dash.json @@ -0,0 +1,562 @@ +{ + "name": "Security", + "schemaVersion": "1.5", + "timeframe": "none", + "variables": [ + "{{variables.[Repository]}}" + ], + "dashboard": { + "_type": "layout/grid", + "columns": 4, + "version": 1, + "contents": [ + { + "i": "82742b42-02ba-495e-9abe-1fd3f30ee9ba", + "x": 0, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Open Findings", + "description": "", + "activePluginConfigIds": [ + "{{configId}}" + ], + "timeframe": "none", + "variables": [ + "{{variables.[Repository]}}" + ], + "dataStream": { + "id": "{{dataStreams.[securityFindings]}}", + "name": "securityFindings", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "state": "open", + "repository": { + "variable": "{{variables.[Repository]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Repositories]}}" + } + }, + "group": { + "by": [], + "aggregate": [ + { + "type": "count" + } + ] + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "scalar", + "encoding": { + "values": [ + { + "field": "count", + "type": "quantitative", + "title": "Open Findings" + } + ] + }, + "style": { + "scalarLabelPosition": "none" + } + } + } + } + }, + { + "i": "39573536-5645-4303-9a37-7d324e9bcce6", + "x": 1, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Critical and High Findings", + "description": "", + "activePluginConfigIds": [ + "{{configId}}" + ], + "timeframe": "none", + "variables": [ + "{{variables.[Repository]}}" + ], + "dataStream": { + "id": "{{dataStreams.[securityFindings]}}", + "name": "securityFindings", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "state": "open", + "repository": { + "variable": "{{variables.[Repository]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Repositories]}}" + } + }, + "filter": { + "multiOperation": "and", + "filters": [ + { + "column": "severityRank", + "operation": "greaterthan", + "value": "3" + } + ] + }, + "group": { + "by": [], + "aggregate": [ + { + "type": "count" + } + ] + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "scalar", + "encoding": { + "values": [ + { + "field": "count", + "type": "quantitative", + "title": "Critical and High Findings" + } + ] + }, + "style": { + "scalarLabelPosition": "none" + } + } + } + } + }, + { + "i": "1826fe15-de25-4643-90aa-bd136f0e5b3d", + "x": 2, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Repositories Affected", + "description": "", + "activePluginConfigIds": [ + "{{configId}}" + ], + "timeframe": "none", + "variables": [ + "{{variables.[Repository]}}" + ], + "dataStream": { + "id": "{{dataStreams.[securityFindings]}}", + "name": "securityFindings", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "state": "open", + "repository": { + "variable": "{{variables.[Repository]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Repositories]}}" + } + }, + "group": { + "by": [], + "aggregate": [ + { + "type": "distinctCount", + "names": [ + "repository_id" + ] + } + ] + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "scalar", + "encoding": { + "values": [ + { + "field": "repository_id_distinctCount", + "type": "quantitative", + "title": "Repositories Affected" + } + ] + }, + "style": { + "scalarLabelPosition": "none" + } + } + } + } + }, + { + "i": "0d69bdc8-2abe-461d-ac07-b4a6fb045852", + "x": 3, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "CWE Types", + "description": "Distinct weakness types among open findings.", + "activePluginConfigIds": [ + "{{configId}}" + ], + "timeframe": "none", + "variables": [ + "{{variables.[Repository]}}" + ], + "dataStream": { + "id": "{{dataStreams.[securityFindings]}}", + "name": "securityFindings", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "state": "open", + "repository": { + "variable": "{{variables.[Repository]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Repositories]}}" + } + }, + "group": { + "by": [], + "aggregate": [ + { + "type": "distinctCount", + "names": [ + "cwe" + ] + } + ] + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "scalar", + "encoding": { + "values": [ + { + "field": "cwe_distinctCount", + "type": "quantitative", + "title": "CWE Types" + } + ] + }, + "style": { + "scalarLabelPosition": "none" + } + } + } + } + }, + { + "i": "7c2e986d-002f-429d-8f66-0c3758225978", + "x": 0, + "y": 2, + "w": 2, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Findings by Severity", + "description": "", + "activePluginConfigIds": [ + "{{configId}}" + ], + "timeframe": "none", + "variables": [ + "{{variables.[Repository]}}" + ], + "dataStream": { + "id": "{{dataStreams.[securityFindings]}}", + "name": "securityFindings", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "state": "open", + "repository": { + "variable": "{{variables.[Repository]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Repositories]}}" + } + }, + "group": { + "by": [ + [ + "severity", + "uniqueValues" + ], + [ + "severityRank", + "uniqueValues" + ] + ], + "aggregate": [ + { + "type": "count" + } + ] + }, + "sort": { + "by": [ + [ + "severityRank_uniqueValues", + "desc" + ] + ] + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "bar", + "encoding": { + "x": { + "field": "severity_uniqueValues", + "type": "nominal", + "title": "Severity" + }, + "y": { + "field": "count", + "type": "quantitative", + "title": "Findings" + } + }, + "style": { + "legendPosition": "none", + "showLabels": true, + "colors": [ + "#FF570A" + ], + "cartesianColorRules": [ + { + "gte": 0, + "color": "#FF570A" + } + ] + } + } + } + } + }, + { + "i": "4666ae0a-663d-4c14-80de-134165b93f5d", + "x": 2, + "y": 2, + "w": 2, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Findings by CWE", + "description": "", + "activePluginConfigIds": [ + "{{configId}}" + ], + "timeframe": "none", + "variables": [ + "{{variables.[Repository]}}" + ], + "dataStream": { + "id": "{{dataStreams.[securityFindings]}}", + "name": "securityFindings", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "state": "open", + "repository": { + "variable": "{{variables.[Repository]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Repositories]}}" + } + }, + "group": { + "by": [ + [ + "cwe", + "uniqueValues" + ] + ], + "aggregate": [ + { + "type": "count" + } + ] + }, + "sort": { + "by": [ + [ + "count", + "desc" + ] + ], + "top": 10 + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "bar", + "encoding": { + "x": { + "field": "cwe_uniqueValues", + "type": "nominal", + "title": "CWE" + }, + "y": { + "field": "count", + "type": "quantitative", + "title": "Findings" + } + }, + "style": { + "cartesianHorizontal": true, + "legendPosition": "none", + "showLabels": true, + "colors": [ + "#FF570A" + ], + "cartesianColorRules": [ + { + "gte": 0, + "color": "#FF570A" + } + ] + } + } + } + } + }, + { + "i": "cd0d4ef1-c8f9-4535-bb18-47faebeea174", + "x": 0, + "y": 6, + "w": 4, + "h": 5, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Findings", + "description": "", + "activePluginConfigIds": [ + "{{configId}}" + ], + "timeframe": "none", + "variables": [ + "{{variables.[Repository]}}" + ], + "dataStream": { + "id": "{{dataStreams.[securityFindings]}}", + "name": "securityFindings", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "state": "open", + "repository": { + "variable": "{{variables.[Repository]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Repositories]}}" + } + }, + "sort": { + "by": [ + [ + "severityRank", + "desc" + ] + ] + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "richTable", + "encoding": { + "columns": [ + { + "field": "severity", + "title": "Severity", + "renderer": { + "type": "text" + }, + "width": 100 + }, + { + "field": "summary", + "title": "Finding", + "renderer": { + "type": "text" + } + }, + { + "field": "cwe", + "title": "CWE", + "renderer": { + "type": "text" + }, + "width": 110 + }, + { + "field": "repository_name", + "title": "Repository", + "renderer": { + "type": "text" + }, + "width": 180 + }, + { + "field": "file_path", + "title": "File", + "renderer": { + "type": "text" + } + }, + { + "field": "start_line", + "title": "Line", + "renderer": { + "type": "number", + "decimals": 0 + }, + "align": "right", + "width": 80 + } + ] + }, + "style": { + "richTableSort": { + "field": "severityRank", + "direction": "desc" + } + } + } + } + } + } + ] + } +} diff --git a/plugins/CodeRabbit/v1/defaultContent/user.dash.json b/plugins/CodeRabbit/v1/defaultContent/user.dash.json new file mode 100644 index 00000000..31515b88 --- /dev/null +++ b/plugins/CodeRabbit/v1/defaultContent/user.dash.json @@ -0,0 +1,609 @@ +{ + "name": "User", + "schemaVersion": "1.5", + "timeframe": "last30days", + "variables": [ + "{{variables.[User]}}" + ], + "dashboard": { + "_type": "layout/grid", + "columns": 4, + "version": 1, + "contents": [ + { + "i": "7b17584f-5583-46ac-ade4-5e3424965c10", + "x": 0, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "User Details", + "description": "", + "activePluginConfigIds": [ + "{{configId}}" + ], + "timeframe": "none", + "variables": [ + "{{variables.[User]}}" + ], + "dataStream": { + "id": "datastream-properties", + "name": "properties", + "pluginConfigId": "{{configId}}" + }, + "scope": { + "scope": "{{scopes.[Users]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[User]}}" + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "richTable", + "encoding": { + "columns": [ + { + "field": "name", + "title": "Name", + "renderer": { + "type": "text" + } + }, + { + "field": "seatAssigned", + "title": "Seat Assigned", + "renderer": { + "type": "boolean", + "trueLabel": "Yes", + "falseLabel": "No" + } + }, + { + "field": "role", + "title": "Role", + "renderer": { + "type": "text" + } + } + ] + }, + "style": { + "richTableTranspose": true, + "richTableFontSize": 14, + "richTableCellPadding": 8 + } + } + } + } + }, + { + "i": "074b642d-c67f-4812-a9e7-a519d6e7f75a", + "x": 1, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "PRs Merged", + "description": "", + "activePluginConfigIds": [ + "{{configId}}" + ], + "variables": [ + "{{variables.[User]}}" + ], + "dataStream": { + "id": "{{dataStreams.[pullRequests]}}", + "name": "pullRequests", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "status": "merged", + "user": { + "variable": "{{variables.[User]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Users]}}" + } + }, + "group": { + "by": [], + "aggregate": [ + { + "type": "count" + } + ] + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "scalar", + "encoding": { + "values": [ + { + "field": "count", + "type": "quantitative", + "title": "PRs Merged" + } + ] + }, + "style": { + "scalarLabelPosition": "none" + } + } + } + } + }, + { + "i": "d0312110-a50a-4382-9674-c50d6b2573ec", + "x": 2, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Acceptance Rate", + "description": "Share of CodeRabbit findings that were accepted.", + "activePluginConfigIds": [ + "{{configId}}" + ], + "variables": [ + "{{variables.[User]}}" + ], + "dataStream": { + "id": "datastream-sql", + "dataSourceConfig": { + "version": "2.0", + "sql": "SELECT AVG(\"acceptedCount\") * 100 AS \"acceptanceRate\" FROM \"dataset1\"", + "tables": [ + { + "tableName": "dataset1", + "config": { + "variables": [ + "{{variables.[User]}}" + ], + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[reviewComments]}}", + "name": "reviewComments", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "user": { + "variable": "{{variables.[User]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Users]}}" + } + } + } + } + } + ] + }, + "metadata": [ + { + "name": "acceptanceRate", + "shape": [ + "percent", + { + "decimalPlaces": 1 + } + ] + }, + { + "pattern": ".*" + } + ] + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "gauge", + "encoding": { + "value": { + "field": "acceptanceRate", + "type": "quantitative", + "title": "Acceptance Rate" + } + }, + "style": { + "gaugeMin": 0, + "gaugeMax": 100, + "gaugeBands": [ + { + "to": 0.6, + "color": "#DE0038" + }, + { + "to": 0.8, + "color": "#E18700" + }, + { + "to": 1, + "color": "#259A51" + } + ] + } + } + } + } + }, + { + "i": "b5ac9a89-ef60-4eda-815a-8ac5b73b550b", + "x": 3, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Median Time to Merge", + "description": "Hours from ready for review to merge.", + "activePluginConfigIds": [ + "{{configId}}" + ], + "variables": [ + "{{variables.[User]}}" + ], + "dataStream": { + "id": "{{dataStreams.[pullRequests]}}", + "name": "pullRequests", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "status": "merged", + "user": { + "variable": "{{variables.[User]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Users]}}" + } + }, + "group": { + "by": [], + "aggregate": [ + { + "type": "median", + "names": [ + "hoursToMerge" + ] + } + ] + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "scalar", + "encoding": { + "values": [ + { + "field": "hoursToMerge_median", + "type": "quantitative", + "title": "Hours" + } + ] + }, + "style": { + "scalarLabelPosition": "none" + } + } + } + } + }, + { + "i": "f0097e03-b7c1-4763-bf7c-b55619e59e61", + "x": 0, + "y": 2, + "w": 2, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "PRs Merged Per Day", + "description": "", + "activePluginConfigIds": [ + "{{configId}}" + ], + "variables": [ + "{{variables.[User]}}" + ], + "dataStream": { + "id": "{{dataStreams.[pullRequests]}}", + "name": "pullRequests", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "status": "merged", + "user": { + "variable": "{{variables.[User]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Users]}}" + } + }, + "group": { + "by": [ + [ + "merged_at", + "byDay" + ] + ], + "aggregate": [ + { + "type": "count" + } + ] + }, + "sort": { + "by": [ + [ + "merged_at_byDay", + "asc" + ] + ] + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "bar", + "encoding": { + "x": { + "field": "merged_at_byDay", + "type": "temporal", + "title": "Merged" + }, + "y": { + "field": "count", + "type": "quantitative", + "title": "PRs Merged" + } + }, + "style": { + "legendPosition": "none", + "colors": [ + "#FF570A" + ], + "cartesianColorRules": [ + { + "gte": 0, + "color": "#FF570A" + } + ] + } + } + } + } + }, + { + "i": "b987ee78-40c3-421c-8f7a-64ae40140247", + "x": 2, + "y": 2, + "w": 2, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Findings by Severity", + "description": "", + "activePluginConfigIds": [ + "{{configId}}" + ], + "variables": [ + "{{variables.[User]}}" + ], + "dataStream": { + "id": "{{dataStreams.[reviewComments]}}", + "name": "reviewComments", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "user": { + "variable": "{{variables.[User]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Users]}}" + } + }, + "group": { + "by": [ + [ + "severity", + "uniqueValues" + ], + [ + "severityRank", + "uniqueValues" + ] + ], + "aggregate": [ + { + "type": "count" + } + ] + }, + "sort": { + "by": [ + [ + "severityRank_uniqueValues", + "asc" + ] + ] + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "bar", + "encoding": { + "x": { + "field": "severity_uniqueValues", + "type": "nominal", + "title": "Severity" + }, + "y": { + "field": "count", + "type": "quantitative", + "title": "Findings" + } + }, + "style": { + "legendPosition": "none", + "showLabels": true, + "colors": [ + "#FF570A" + ], + "cartesianColorRules": [ + { + "gte": 0, + "color": "#FF570A" + } + ] + } + } + } + } + }, + { + "i": "705c2ef5-a1e1-4223-b711-46189a4ef1e3", + "x": 0, + "y": 6, + "w": 4, + "h": 5, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Recent Pull Requests", + "description": "", + "activePluginConfigIds": [ + "{{configId}}" + ], + "variables": [ + "{{variables.[User]}}" + ], + "dataStream": { + "id": "{{dataStreams.[pullRequests]}}", + "name": "pullRequests", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "status": "merged", + "user": { + "variable": "{{variables.[User]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Users]}}" + } + }, + "sort": { + "by": [ + [ + "merged_at", + "desc" + ] + ] + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "richTable", + "encoding": { + "columns": [ + { + "field": "prUrl", + "title": "Pull Request", + "renderer": { + "type": "link", + "labelField": "prNumber" + }, + "width": 110 + }, + { + "field": "repository_name", + "title": "Repository", + "renderer": { + "type": "text" + } + }, + { + "field": "author_username", + "title": "Author", + "renderer": { + "type": "text" + } + }, + { + "field": "merged_at", + "title": "Merged", + "renderer": { + "type": "date" + }, + "width": 170 + }, + { + "field": "hoursToMerge", + "title": "Hours to Merge", + "renderer": { + "type": "number", + "decimals": 1 + }, + "align": "right", + "width": 130 + }, + { + "field": "review_iterations", + "title": "Review Iterations", + "renderer": { + "type": "number", + "decimals": 0 + }, + "align": "right", + "width": 140 + }, + { + "field": "commentsPosted", + "title": "Comments Posted", + "renderer": { + "type": "number", + "decimals": 0 + }, + "align": "right", + "width": 140 + }, + { + "field": "commentsAccepted", + "title": "Comments Accepted", + "renderer": { + "type": "number", + "decimals": 0 + }, + "align": "right", + "width": 150 + } + ] + }, + "style": { + "richTableSort": { + "field": "merged_at", + "direction": "desc" + } + } + } + } + } + } + ] + } +} diff --git a/plugins/CodeRabbit/v1/docs/README.md b/plugins/CodeRabbit/v1/docs/README.md new file mode 100644 index 00000000..1c341b67 --- /dev/null +++ b/plugins/CodeRabbit/v1/docs/README.md @@ -0,0 +1,68 @@ +Track [CodeRabbit](https://www.coderabbit.ai) seat usage, adoption, pull request review times, review quality and security findings in SquaredUp, via the [CodeRabbit API](https://docs.coderabbit.ai/api-reference). + +> ⚠️ The CodeRabbit API is only available on the **Enterprise** plan. + +## Setup + +You will need a CodeRabbit **API key**, created by an organization admin. + +1. Sign in to [CodeRabbit](https://app.coderabbit.ai) as an organization **admin**. Keys created by other roles cannot read seat and license data. +2. Go to **Organization Settings > [API Keys](https://app.coderabbit.ai/settings/api-keys)** and create a new key. +3. To see AI Deep Scan results on the **Security** dashboard, give the key the **security** read permission. +4. Copy the key and paste it into the **API key** field. + +## Configuration fields + +| Field | What it is | Where to find it | Required | +| ----- | ---------- | ---------------- | -------- | +| **API key** | Authenticates every request to the CodeRabbit API. | CodeRabbit > **Organization Settings > API Keys**. | Yes | + +On save, the data source lists your CodeRabbit organizations to check the key, then checks that seat data can be read. An invalid key, or an organization that is not on the Enterprise plan, fails setup. A key created without admin rights still connects, but seat and license data is not available. + +## What this data source contains + +- **Seats and licenses**: seats purchased, seats assigned, seats available and which users hold a seat. +- **Adoption**: active users and repositories, pull requests per user and per repository, and seats held by users with no merged pull requests. +- **Review speed**: time from ready for review to first human review, to last commit and to merge, plus CodeRabbit's estimated review time and complexity. +- **Review quality**: every finding CodeRabbit raised, its severity and category, and whether the author accepted it. +- **Security**: AI Deep Scan findings by severity, CWE, repository and file. +- **MCP usage**: which MCP servers CodeRabbit called during reviews, with tool call and insight counts. +- **Admin activity**: the organization audit log. + +The out-of-the-box dashboards are **Overview**, **Licenses**, **PRs & Reviews**, **Security** and a **User** perspective. + +## Data streams + +**Organization streams: no object selection needed** + +- **Licenses**: seats purchased, seats assigned, seats available, seat utilization and the seat assignment mode. +- **Users**: every CodeRabbit user with their role and whether they hold a seat. +- **Organizations**: the CodeRabbit organizations the API key can access, with their Git provider. +- **Audit logs**: administrative actions in the organization, with the actor, resource and IP address. Supports filtering by user name, with partial or full name matching. + +**Review streams: optionally filtered to one or more repositories or users** + +- **Pull requests**: merged or closed pull requests reviewed by CodeRabbit, one row per pull request, with review timings and comment counts. +- **Review comments**: findings CodeRabbit raised on merged pull requests, one row per finding, with severity, category and whether it was accepted. +- **MCP usage**: one row per MCP server used on a pull request, with insight and tool call counts. +- **Security findings**: AI Deep Scan findings, filtered by state (open, fixed, dismissed or all). + +**Repository streams: for one organization** + +- **Repositories**: repositories CodeRabbit records for an organization, and whether CodeRabbit is installed on each. + +## What gets indexed + +| Object type | API source | Represents | +| ----------- | ---------- | ---------- | +| **Organization** | `GET /v1/organizations` | A CodeRabbit organization, linked to an organization in your Git provider. | +| **Repository** | `GET /v1/organizations/{id}/repositories` | A repository in an organization, with its installation status and parent organization. | +| **User** | `GET /v1/users` | A CodeRabbit user, with their role and seat assignment. | + +## Notes + +- **Review data is reported by day**: CodeRabbit filters pull requests and findings by date, not time. For **Last 24 hours**, merged pull requests are trimmed to the exact window; closed pull requests are reported for the whole day. +- **Review data covers up to the last quarter**: the review streams offer timeframes from **Last 24 hours** (**Last 7 days** for MCP usage) up to **Last quarter**. +- **Security findings need the security permission**: without it, the Security findings stream reports a permissions error. +- **MCP usage must be enabled**: if MCP usage metrics are not enabled for your CodeRabbit organization, the MCP usage stream says so rather than returning rows. +- **Read-only**: the data source never creates, modifies or deletes anything in CodeRabbit. diff --git a/plugins/CodeRabbit/v1/icon.svg b/plugins/CodeRabbit/v1/icon.svg new file mode 100644 index 00000000..2de58abc --- /dev/null +++ b/plugins/CodeRabbit/v1/icon.svg @@ -0,0 +1 @@ + diff --git a/plugins/CodeRabbit/v1/indexDefinitions/default.json b/plugins/CodeRabbit/v1/indexDefinitions/default.json new file mode 100644 index 00000000..d243de90 --- /dev/null +++ b/plugins/CodeRabbit/v1/indexDefinitions/default.json @@ -0,0 +1,50 @@ +{ + "steps": [ + { + "name": "organizations", + "dataStream": { "name": "organizations" }, + "timeframe": "none", + "objectMapping": { + "id": "provider_organization_id", + "name": "name", + "type": { "value": "Organization" }, + "properties": ["provider", { "codeRabbitId": "id" }] + } + }, + { + "name": "repositories", + "dataStream": { "name": "repositories" }, + "scope": { + "query": "g.V().has(\"sourceType\", \"Organization\")" + }, + "timeframe": "none", + "objectMapping": { + "id": "repository_id", + "name": "repository_name", + "type": { "value": "Repository" }, + "properties": [ + "private", + { "groupPath": "group_path" }, + { "installed": "is_installed" }, + "organizationId", + "organizationName" + ] + }, + "dependsOn": ["organizations"] + }, + { + "name": "users", + "dataStream": { "name": "users" }, + "timeframe": "none", + "objectMapping": { + "id": "user_id", + "name": "username", + "type": { "value": "User" }, + "properties": [ + { "seatAssigned": "seat_assigned" }, + { "role": "role_name" } + ] + } + } + ] +} diff --git a/plugins/CodeRabbit/v1/metadata.json b/plugins/CodeRabbit/v1/metadata.json new file mode 100644 index 00000000..3d37c1f9 --- /dev/null +++ b/plugins/CodeRabbit/v1/metadata.json @@ -0,0 +1,50 @@ +{ + "name": "coderabbit", + "displayName": "CodeRabbit", + "version": "1.0.0", + "author": { "name": "@TimWheeler-SQUP", "type": "community" }, + "description": "Track CodeRabbit seat usage, adoption, pull request review times, review quality and security findings.", + "category": "Analytics", + "type": "hybrid", + "schemaVersion": "2.1", + "importNotSupported": false, + "restrictedToPlatforms": [], + "keywords": [ + "coderabbit", + "code review", + "ai", + "pull requests", + "developer productivity", + "seats", + "licenses", + "security" + ], + "objectTypes": ["Organization", "Repository", "User"], + "links": [ + { + "category": "documentation", + "url": "https://github.com/squaredup/plugins/blob/main/plugins/CodeRabbit/v1/docs/README.md", + "label": "Help adding this plugin" + }, + { + "category": "source", + "url": "https://github.com/squaredup/plugins/tree/main/plugins/CodeRabbit/v1", + "label": "Repository" + } + ], + "base": { + "plugin": "WebAPI", + "majorVersion": "1", + "config": { + "baseUrl": "https://api.coderabbit.ai", + "authMode": "none", + "headers": [ + { + "key": "x-coderabbitai-api-key", + "value": "{{apiKey}}" + } + ], + "queryArgs": [] + } + } +} diff --git a/plugins/CodeRabbit/v1/ui.json b/plugins/CodeRabbit/v1/ui.json new file mode 100644 index 00000000..8b31b929 --- /dev/null +++ b/plugins/CodeRabbit/v1/ui.json @@ -0,0 +1,10 @@ +[ + { + "type": "password", + "name": "apiKey", + "label": "API key", + "placeholder": "cr-xxxxxxxxxx", + "help": "Create an API key on the [API Keys](https://app.coderabbit.ai/settings/api-keys) page under **Organization Settings** in CodeRabbit, signed in as an organization admin", + "validation": { "required": true } + } +]