Skip to content

Commit ae4bc05

Browse files
authored
feat(gitlab): add repository, code-review, and CI job tools + validation fixes (#5205)
* feat(gitlab): add repository, code-review, and CI job tools + validation fixes Expand the GitLab integration with 12 new tools (all host-aware via getGitLabApiBase, wired through types/index/registry/block): - Repository: list_repository_tree, get_file, create_file, update_file, create_branch, list_branches, list_commits - Code review: get_merge_request_changes, approve_merge_request - CI jobs: list_pipeline_jobs, get_job_log, play_job Validation fixes from /validate-integration: - Correct the block inputs key (credential -> accessToken) so it matches the subBlock id and the params the block reads - Trim projectId before encoding in all tool request URLs (input hygiene) /validate-connector and /validate-trigger passed clean against the GitLab REST API v4 docs — no changes required. * fix(gitlab): address review feedback + regen docs - get_merge_request_changes: use the /diffs endpoint (/changes was removed in GitLab 18.0); return the diff array + count (drops the MR envelope that /diffs no longer provides), fetch max page size in a single call - create_file/update_file: send explicit `encoding: 'text'` for clarity - Remove `// =====` separator comments from types.ts (repo convention) - Regenerate GitLab integration docs + catalog for the 12 new tools
1 parent c3a0969 commit ae4bc05

36 files changed

Lines changed: 2521 additions & 69 deletions

apps/docs/content/docs/en/integrations/gitlab.mdx

Lines changed: 464 additions & 0 deletions
Large diffs are not rendered by default.

apps/sim/blocks/blocks/gitlab.ts

Lines changed: 344 additions & 3 deletions
Large diffs are not rendered by default.

apps/sim/lib/integrations/integrations.json

Lines changed: 144 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"updatedAt": "2026-06-18",
2+
"updatedAt": "2026-06-25",
33
"integrations": [
44
{
55
"type": "onepassword",
@@ -5628,11 +5628,90 @@
56285628
{
56295629
"name": "Cancel Pipeline",
56305630
"description": "Cancel a running GitLab pipeline"
5631+
},
5632+
{
5633+
"name": "List Repository Tree",
5634+
"description": "List files and directories in a GitLab project repository"
5635+
},
5636+
{
5637+
"name": "Get File",
5638+
"description": "Get the contents of a file from a GitLab project repository"
5639+
},
5640+
{
5641+
"name": "Create File",
5642+
"description": "Create a new file in a GitLab project repository"
5643+
},
5644+
{
5645+
"name": "Update File",
5646+
"description": "Update an existing file in a GitLab project repository"
5647+
},
5648+
{
5649+
"name": "List Commits",
5650+
"description": "List commits in a GitLab project repository"
5651+
},
5652+
{
5653+
"name": "List Branches",
5654+
"description": "List branches in a GitLab project repository"
5655+
},
5656+
{
5657+
"name": "Create Branch",
5658+
"description": "Create a new branch in a GitLab project repository"
5659+
},
5660+
{
5661+
"name": "Get MR Changes",
5662+
"description": "Get the file changes (diffs) of a GitLab merge request"
5663+
},
5664+
{
5665+
"name": "Approve Merge Request",
5666+
"description": "Approve a GitLab merge request"
5667+
},
5668+
{
5669+
"name": "List Pipeline Jobs",
5670+
"description": "List jobs for a GitLab pipeline"
5671+
},
5672+
{
5673+
"name": "Get Job Log",
5674+
"description": "Get the log (trace) of a GitLab job"
5675+
},
5676+
{
5677+
"name": "Play Job",
5678+
"description": "Trigger (play) a manual GitLab job"
56315679
}
56325680
],
5633-
"operationCount": 19,
5634-
"triggers": [],
5635-
"triggerCount": 0,
5681+
"operationCount": 31,
5682+
"triggers": [
5683+
{
5684+
"id": "gitlab_push",
5685+
"name": "GitLab Push",
5686+
"description": "Trigger workflow when commits are pushed to a GitLab project"
5687+
},
5688+
{
5689+
"id": "gitlab_merge_request",
5690+
"name": "GitLab Merge Request",
5691+
"description": "Trigger workflow when a merge request is opened, updated, or merged in GitLab"
5692+
},
5693+
{
5694+
"id": "gitlab_issue",
5695+
"name": "GitLab Issue",
5696+
"description": "Trigger workflow when an issue is opened, updated, or closed in GitLab"
5697+
},
5698+
{
5699+
"id": "gitlab_pipeline",
5700+
"name": "GitLab Pipeline",
5701+
"description": "Trigger workflow when a pipeline status changes in GitLab"
5702+
},
5703+
{
5704+
"id": "gitlab_comment",
5705+
"name": "GitLab Comment",
5706+
"description": "Trigger workflow when a comment is added on a commit, merge request, or issue"
5707+
},
5708+
{
5709+
"id": "gitlab_webhook",
5710+
"name": "GitLab Event",
5711+
"description": "Trigger workflow from any GitLab webhook event"
5712+
}
5713+
],
5714+
"triggerCount": 6,
56365715
"authType": "api-key",
56375716
"category": "tools",
56385717
"integrationType": "devops",
@@ -11233,8 +11312,39 @@
1123311312
}
1123411313
],
1123511314
"operationCount": 6,
11236-
"triggers": [],
11237-
"triggerCount": 0,
11315+
"triggers": [
11316+
{
11317+
"id": "pagerduty_incident_triggered",
11318+
"name": "PagerDuty Incident Triggered",
11319+
"description": "Trigger workflow when a new incident is triggered in PagerDuty"
11320+
},
11321+
{
11322+
"id": "pagerduty_incident_acknowledged",
11323+
"name": "PagerDuty Incident Acknowledged",
11324+
"description": "Trigger workflow when an incident is acknowledged in PagerDuty"
11325+
},
11326+
{
11327+
"id": "pagerduty_incident_resolved",
11328+
"name": "PagerDuty Incident Resolved",
11329+
"description": "Trigger workflow when an incident is resolved in PagerDuty"
11330+
},
11331+
{
11332+
"id": "pagerduty_incident_escalated",
11333+
"name": "PagerDuty Incident Escalated",
11334+
"description": "Trigger workflow when an incident is escalated in PagerDuty"
11335+
},
11336+
{
11337+
"id": "pagerduty_incident_reassigned",
11338+
"name": "PagerDuty Incident Reassigned",
11339+
"description": "Trigger workflow when an incident is reassigned in PagerDuty"
11340+
},
11341+
{
11342+
"id": "pagerduty_webhook",
11343+
"name": "PagerDuty Incident Event",
11344+
"description": "Trigger workflow from any PagerDuty incident event"
11345+
}
11346+
],
11347+
"triggerCount": 6,
1123811348
"authType": "api-key",
1123911349
"category": "tools",
1124011350
"integrationType": "observability",
@@ -18289,8 +18399,34 @@
1828918399
}
1829018400
],
1829118401
"operationCount": 26,
18292-
"triggers": [],
18293-
"triggerCount": 0,
18402+
"triggers": [
18403+
{
18404+
"id": "zendesk_ticket_created",
18405+
"name": "Zendesk Ticket Created",
18406+
"description": "Trigger workflow when a new ticket is created in Zendesk"
18407+
},
18408+
{
18409+
"id": "zendesk_ticket_status_changed",
18410+
"name": "Zendesk Ticket Status Changed",
18411+
"description": "Trigger workflow when a ticket status changes in Zendesk"
18412+
},
18413+
{
18414+
"id": "zendesk_ticket_comment_added",
18415+
"name": "Zendesk Ticket Comment Added",
18416+
"description": "Trigger workflow when a comment is added to a Zendesk ticket"
18417+
},
18418+
{
18419+
"id": "zendesk_ticket_priority_changed",
18420+
"name": "Zendesk Ticket Priority Changed",
18421+
"description": "Trigger workflow when a ticket priority changes in Zendesk"
18422+
},
18423+
{
18424+
"id": "zendesk_webhook",
18425+
"name": "Zendesk Ticket Event",
18426+
"description": "Trigger workflow from any Zendesk ticket event"
18427+
}
18428+
],
18429+
"triggerCount": 5,
1829418430
"authType": "api-key",
1829518431
"category": "tools",
1829618432
"integrationType": "support",
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import type {
2+
GitLabApproveMergeRequestParams,
3+
GitLabApproveMergeRequestResponse,
4+
} from '@/tools/gitlab/types'
5+
import { getGitLabApiBase } from '@/tools/gitlab/utils'
6+
import type { ToolConfig } from '@/tools/types'
7+
8+
export const gitlabApproveMergeRequestTool: ToolConfig<
9+
GitLabApproveMergeRequestParams,
10+
GitLabApproveMergeRequestResponse
11+
> = {
12+
id: 'gitlab_approve_merge_request',
13+
name: 'GitLab Approve Merge Request',
14+
description: 'Approve a GitLab merge request',
15+
version: '1.0.0',
16+
17+
params: {
18+
accessToken: {
19+
type: 'string',
20+
required: true,
21+
visibility: 'user-only',
22+
description: 'GitLab Personal Access Token',
23+
},
24+
host: {
25+
type: 'string',
26+
required: false,
27+
visibility: 'user-only',
28+
description: 'Self-managed GitLab host (e.g. gitlab.example.com). Defaults to gitlab.com.',
29+
},
30+
projectId: {
31+
type: 'string',
32+
required: true,
33+
visibility: 'user-or-llm',
34+
description: 'Project ID or URL-encoded path',
35+
},
36+
mergeRequestIid: {
37+
type: 'number',
38+
required: true,
39+
visibility: 'user-or-llm',
40+
description: 'Merge request internal ID (IID)',
41+
},
42+
sha: {
43+
type: 'string',
44+
required: false,
45+
visibility: 'user-or-llm',
46+
description: 'HEAD SHA of the merge request to approve',
47+
},
48+
},
49+
50+
request: {
51+
url: (params) => {
52+
const encodedId = encodeURIComponent(String(params.projectId).trim())
53+
return `${getGitLabApiBase(params.host)}/projects/${encodedId}/merge_requests/${params.mergeRequestIid}/approve`
54+
},
55+
method: 'POST',
56+
headers: (params) => ({
57+
'Content-Type': 'application/json',
58+
'PRIVATE-TOKEN': params.accessToken,
59+
}),
60+
body: (params) => {
61+
const body: Record<string, unknown> = {}
62+
63+
if (params.sha) body.sha = params.sha
64+
65+
return body
66+
},
67+
},
68+
69+
transformResponse: async (response) => {
70+
if (!response.ok) {
71+
const errorText = await response.text()
72+
return {
73+
success: false,
74+
error: `GitLab API error: ${response.status} ${errorText}`,
75+
output: {},
76+
}
77+
}
78+
79+
const data = await response.json()
80+
81+
return {
82+
success: true,
83+
output: {
84+
approvalsRequired: data.approvals_required ?? null,
85+
approvalsLeft: data.approvals_left ?? null,
86+
approvedBy: data.approved_by ?? [],
87+
},
88+
}
89+
},
90+
91+
outputs: {
92+
approvalsRequired: {
93+
type: 'number',
94+
description: 'Number of approvals required',
95+
},
96+
approvalsLeft: {
97+
type: 'number',
98+
description: 'Number of approvals still needed',
99+
},
100+
approvedBy: {
101+
type: 'array',
102+
description: 'List of approvers',
103+
},
104+
},
105+
}

apps/sim/tools/gitlab/cancel_pipeline.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export const gitlabCancelPipelineTool: ToolConfig<
4040

4141
request: {
4242
url: (params) => {
43-
const encodedId = encodeURIComponent(String(params.projectId))
43+
const encodedId = encodeURIComponent(String(params.projectId).trim())
4444
return `${getGitLabApiBase(params.host)}/projects/${encodedId}/pipelines/${params.pipelineId}/cancel`
4545
},
4646
method: 'POST',
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import type { GitLabCreateBranchParams, GitLabCreateBranchResponse } from '@/tools/gitlab/types'
2+
import { getGitLabApiBase } from '@/tools/gitlab/utils'
3+
import type { ToolConfig } from '@/tools/types'
4+
5+
export const gitlabCreateBranchTool: ToolConfig<
6+
GitLabCreateBranchParams,
7+
GitLabCreateBranchResponse
8+
> = {
9+
id: 'gitlab_create_branch',
10+
name: 'GitLab Create Branch',
11+
description: 'Create a new branch in a GitLab project repository',
12+
version: '1.0.0',
13+
14+
params: {
15+
accessToken: {
16+
type: 'string',
17+
required: true,
18+
visibility: 'user-only',
19+
description: 'GitLab Personal Access Token',
20+
},
21+
host: {
22+
type: 'string',
23+
required: false,
24+
visibility: 'user-only',
25+
description: 'Self-managed GitLab host (e.g. gitlab.example.com). Defaults to gitlab.com.',
26+
},
27+
projectId: {
28+
type: 'string',
29+
required: true,
30+
visibility: 'user-or-llm',
31+
description: 'Project ID or URL-encoded path',
32+
},
33+
branch: {
34+
type: 'string',
35+
required: true,
36+
visibility: 'user-or-llm',
37+
description: 'Name of the new branch',
38+
},
39+
ref: {
40+
type: 'string',
41+
required: true,
42+
visibility: 'user-or-llm',
43+
description: 'Source branch/tag/SHA',
44+
},
45+
},
46+
47+
request: {
48+
url: (params) => {
49+
const encodedId = encodeURIComponent(String(params.projectId).trim())
50+
const queryParams = new URLSearchParams()
51+
queryParams.append('branch', String(params.branch))
52+
queryParams.append('ref', String(params.ref))
53+
return `${getGitLabApiBase(params.host)}/projects/${encodedId}/repository/branches?${queryParams.toString()}`
54+
},
55+
method: 'POST',
56+
headers: (params) => ({
57+
'PRIVATE-TOKEN': params.accessToken,
58+
}),
59+
},
60+
61+
transformResponse: async (response) => {
62+
if (!response.ok) {
63+
const errorText = await response.text()
64+
return {
65+
success: false,
66+
error: `GitLab API error: ${response.status} ${errorText}`,
67+
output: {},
68+
}
69+
}
70+
71+
const data = await response.json()
72+
73+
return {
74+
success: true,
75+
output: {
76+
name: data.name ?? null,
77+
webUrl: data.web_url ?? null,
78+
protected: data.protected ?? null,
79+
commit: data.commit ?? null,
80+
},
81+
}
82+
},
83+
84+
outputs: {
85+
name: {
86+
type: 'string',
87+
description: 'The created branch name',
88+
},
89+
webUrl: {
90+
type: 'string',
91+
description: 'The web URL of the branch',
92+
},
93+
protected: {
94+
type: 'boolean',
95+
description: 'Whether the branch is protected',
96+
},
97+
commit: {
98+
type: 'object',
99+
description: 'The commit the branch points to',
100+
},
101+
},
102+
}

0 commit comments

Comments
 (0)