Add missing resources to CLI, MCP, and n8n surfaces

Audit all three API surfaces against the console GraphQL schema and add
missing resources: asset, audit, datum, dpia, evidence upload, measure,
obligation, processing activity, rights request, snapshot, task, tia,
trust center (with references/files), and vendor management CLI
commands; MCP tools for deletes, rights requests, trust center, vendor
contacts/services, and compliance external URLs; n8n nodes for
obligation, finding, task, evidence, processing activity, dpia, tia,
rights request, snapshot, audit log, access review, organization
context, trust center, and additional control/measure/vendor operations.

Include MCP e2e test infrastructure (testutil MCP client with API key
auth and JSON-RPC session management) and tests covering all new MCP
tools.

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
Sacha Al Himdani
2026-04-21 16:08:37 +02:00
parent f505e23cb0
commit 7be92defcc
220 changed files with 26395 additions and 7 deletions

View File

@@ -0,0 +1,222 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
package testutil
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"time"
"github.com/stretchr/testify/require"
)
// MCPClient wraps an authenticated MCP session for e2e testing.
type MCPClient struct {
t require.TestingT
baseURL string
apiToken string
sessionID string
httpClient *http.Client
}
// CreateAPIKey creates a personal API key via the connect GraphQL API.
// It returns the raw bearer token string.
func (c *Client) CreateAPIKey(name string) string {
const query = `
mutation($input: CreatePersonalAPIKeyInput!) {
createPersonalAPIKey(input: $input) {
token
}
}
`
var result struct {
CreatePersonalAPIKey struct {
Token string `json:"token"`
} `json:"createPersonalAPIKey"`
}
err := c.ExecuteConnect(query, map[string]any{
"input": map[string]any{
"name": name,
"expiresAt": time.Now().Add(1 * time.Hour).Format(time.RFC3339),
},
}, &result)
require.NoError(c.T, err, "createPersonalAPIKey failed")
require.NotEmpty(c.T, result.CreatePersonalAPIKey.Token, "API key token is empty")
return result.CreatePersonalAPIKey.Token
}
// NewMCPClient creates an MCP client authenticated with an API key.
// It initializes an MCP session and stores the session ID.
func NewMCPClient(t require.TestingT, owner *Client) *MCPClient {
token := owner.CreateAPIKey("e2e-mcp-test")
mc := &MCPClient{
t: t,
baseURL: owner.BaseURL() + "/mcp/v1",
apiToken: token,
httpClient: &http.Client{
Timeout: 30 * time.Second,
},
}
mc.initialize()
return mc
}
// jsonrpcRequest is a JSON-RPC 2.0 request.
type jsonrpcRequest struct {
JSONRPC string `json:"jsonrpc"`
ID int `json:"id"`
Method string `json:"method"`
Params any `json:"params,omitempty"`
}
// jsonrpcResponse is a JSON-RPC 2.0 response.
type jsonrpcResponse struct {
JSONRPC string `json:"jsonrpc"`
ID int `json:"id"`
Result json.RawMessage `json:"result,omitempty"`
Error *jsonrpcError `json:"error,omitempty"`
}
type jsonrpcError struct {
Code int `json:"code"`
Message string `json:"message"`
}
func (e *jsonrpcError) Error() string {
return fmt.Sprintf("JSON-RPC error %d: %s", e.Code, e.Message)
}
func (mc *MCPClient) doRequest(method string, params any) (json.RawMessage, error) {
reqBody := jsonrpcRequest{
JSONRPC: "2.0",
ID: 1,
Method: method,
Params: params,
}
body, err := json.Marshal(reqBody)
if err != nil {
return nil, fmt.Errorf("cannot marshal request: %w", err)
}
req, err := http.NewRequest("POST", mc.baseURL, bytes.NewReader(body))
if err != nil {
return nil, fmt.Errorf("cannot create request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json, text/event-stream")
req.Header.Set("Authorization", "Bearer "+mc.apiToken)
if mc.sessionID != "" {
req.Header.Set("Mcp-Session-Id", mc.sessionID)
}
resp, err := mc.httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("request failed: %w", err)
}
defer func() { _ = resp.Body.Close() }()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("cannot read response: %w", err)
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("unexpected status %d: %s", resp.StatusCode, string(respBody))
}
// Store session ID from response
if sid := resp.Header.Get("Mcp-Session-Id"); sid != "" {
mc.sessionID = sid
}
var rpcResp jsonrpcResponse
if err := json.Unmarshal(respBody, &rpcResp); err != nil {
return nil, fmt.Errorf("cannot decode response: %w (body: %s)", err, string(respBody))
}
if rpcResp.Error != nil {
return nil, rpcResp.Error
}
return rpcResp.Result, nil
}
func (mc *MCPClient) initialize() {
result, err := mc.doRequest("initialize", map[string]any{
"protocolVersion": "2025-03-26",
"capabilities": map[string]any{},
"clientInfo": map[string]any{
"name": "probo-e2e-test",
"version": "1.0.0",
},
})
require.NoError(mc.t, err, "MCP initialize failed")
require.NotNil(mc.t, result, "MCP initialize returned nil result")
}
// MCPToolResult represents the result of a tools/call response.
type MCPToolResult struct {
Content []MCPContent `json:"content"`
IsError bool `json:"isError"`
}
// MCPContent represents content within a tool result.
type MCPContent struct {
Type string `json:"type"`
Text json.RawMessage `json:"text"`
}
// CallTool invokes an MCP tool and returns the parsed result.
func (mc *MCPClient) CallTool(toolName string, args map[string]any) *MCPToolResult {
result, err := mc.doRequest("tools/call", map[string]any{
"name": toolName,
"arguments": args,
})
require.NoError(mc.t, err, "MCP tools/call %s failed", toolName)
var toolResult MCPToolResult
err = json.Unmarshal(result, &toolResult)
require.NoError(mc.t, err, "cannot unmarshal tool result for %s", toolName)
return &toolResult
}
// CallToolInto invokes an MCP tool and unmarshals the first text content into dest.
func (mc *MCPClient) CallToolInto(toolName string, args map[string]any, dest any) {
tr := mc.CallTool(toolName, args)
require.False(mc.t, tr.IsError, "tool %s returned error: %v", toolName, tr.Content)
require.NotEmpty(mc.t, tr.Content, "tool %s returned no content", toolName)
// The text field in MCP content is a JSON-encoded string of the output.
// First unmarshal the raw JSON to get the string.
var textStr string
err := json.Unmarshal(tr.Content[0].Text, &textStr)
require.NoError(mc.t, err, "cannot unmarshal text content for %s", toolName)
// Then unmarshal that string as JSON into the destination.
err = json.Unmarshal([]byte(textStr), dest)
require.NoError(mc.t, err, "cannot unmarshal tool output for %s", toolName)
}

95
e2e/mcp/asset_test.go Normal file
View File

@@ -0,0 +1,95 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
package mcp_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.probo.inc/probo/e2e/internal/factory"
"go.probo.inc/probo/e2e/internal/testutil"
)
func TestMCP_Asset_CRUD(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
mc := testutil.NewMCPClient(t, owner)
orgID := owner.GetOrganizationID().String()
profileID := factory.CreateUser(owner)
// Create
var addResult struct {
Asset struct {
ID string `json:"id"`
Name string `json:"name"`
AssetType string `json:"assetType"`
} `json:"asset"`
}
mc.CallToolInto("addAsset", map[string]any{
"organizationId": orgID,
"name": factory.SafeName("Asset"),
"amount": 5,
"ownerId": profileID,
"assetType": "VIRTUAL",
"dataTypesStored": "PII",
}, &addResult)
require.NotEmpty(t, addResult.Asset.ID)
assert.Equal(t, "VIRTUAL", addResult.Asset.AssetType)
// Get
var getResult struct {
Asset struct {
ID string `json:"id"`
} `json:"asset"`
}
mc.CallToolInto("getAsset", map[string]any{
"id": addResult.Asset.ID,
}, &getResult)
assert.Equal(t, addResult.Asset.ID, getResult.Asset.ID)
// Update
var updateResult struct {
Asset struct {
ID string `json:"id"`
Name string `json:"name"`
} `json:"asset"`
}
mc.CallToolInto("updateAsset", map[string]any{
"id": addResult.Asset.ID,
"name": "Updated Asset",
}, &updateResult)
assert.Equal(t, "Updated Asset", updateResult.Asset.Name)
// List
var listResult struct {
Assets []struct {
ID string `json:"id"`
} `json:"assets"`
}
mc.CallToolInto("listAssets", map[string]any{
"organizationId": orgID,
}, &listResult)
assert.NotEmpty(t, listResult.Assets)
// Delete
var deleteResult struct {
DeletedAssetID string `json:"deletedAssetId"`
}
mc.CallToolInto("deleteAsset", map[string]any{
"id": addResult.Asset.ID,
}, &deleteResult)
assert.Equal(t, addResult.Asset.ID, deleteResult.DeletedAssetID)
}

109
e2e/mcp/audit_test.go Normal file
View File

@@ -0,0 +1,109 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
package mcp_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.probo.inc/probo/e2e/internal/factory"
"go.probo.inc/probo/e2e/internal/testutil"
)
func TestMCP_Audit_CRUD(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
mc := testutil.NewMCPClient(t, owner)
orgID := owner.GetOrganizationID().String()
frameworkID := factory.CreateFramework(owner)
// Create
var addResult struct {
Audit struct {
ID string `json:"id"`
Name string `json:"name"`
} `json:"audit"`
}
mc.CallToolInto("addAudit", map[string]any{
"frameworkId": frameworkID,
"name": factory.SafeName("Audit"),
}, &addResult)
require.NotEmpty(t, addResult.Audit.ID)
// Get
var getResult struct {
Audit struct {
ID string `json:"id"`
} `json:"audit"`
}
mc.CallToolInto("getAudit", map[string]any{
"id": addResult.Audit.ID,
}, &getResult)
assert.Equal(t, addResult.Audit.ID, getResult.Audit.ID)
// Update
var updateResult struct {
Audit struct {
ID string `json:"id"`
Name string `json:"name"`
} `json:"audit"`
}
mc.CallToolInto("updateAudit", map[string]any{
"id": addResult.Audit.ID,
"name": "Updated Audit",
}, &updateResult)
assert.Equal(t, "Updated Audit", updateResult.Audit.Name)
// List
var listResult struct {
Audits []struct {
ID string `json:"id"`
} `json:"audits"`
}
mc.CallToolInto("listAudits", map[string]any{
"organizationId": orgID,
}, &listResult)
assert.NotEmpty(t, listResult.Audits)
// Delete
var deleteResult struct {
DeletedAuditID string `json:"deletedAuditId"`
}
mc.CallToolInto("deleteAudit", map[string]any{
"id": addResult.Audit.ID,
}, &deleteResult)
assert.Equal(t, addResult.Audit.ID, deleteResult.DeletedAuditID)
}
func TestMCP_AuditLog(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
mc := testutil.NewMCPClient(t, owner)
orgID := owner.GetOrganizationID().String()
// Creating something generates audit log entries
factory.CreateVendor(owner)
var listResult struct {
AuditLogEntries []struct {
ID string `json:"id"`
} `json:"auditLogEntries"`
}
mc.CallToolInto("listAuditLogEntries", map[string]any{
"organizationId": orgID,
}, &listResult)
assert.NotEmpty(t, listResult.AuditLogEntries)
}

91
e2e/mcp/datum_test.go Normal file
View File

@@ -0,0 +1,91 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
package mcp_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.probo.inc/probo/e2e/internal/factory"
"go.probo.inc/probo/e2e/internal/testutil"
)
func TestMCP_Datum_CRUD(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
mc := testutil.NewMCPClient(t, owner)
orgID := owner.GetOrganizationID().String()
profileID := factory.CreateUser(owner)
// Create
var addResult struct {
Datum struct {
ID string `json:"id"`
Name string `json:"name"`
} `json:"datum"`
}
mc.CallToolInto("addDatum", map[string]any{
"organizationId": orgID,
"name": factory.SafeName("Datum"),
"ownerId": profileID,
"dataClassification": "PUBLIC",
}, &addResult)
require.NotEmpty(t, addResult.Datum.ID)
// Get
var getResult struct {
Datum struct {
ID string `json:"id"`
} `json:"datum"`
}
mc.CallToolInto("getDatum", map[string]any{
"id": addResult.Datum.ID,
}, &getResult)
assert.Equal(t, addResult.Datum.ID, getResult.Datum.ID)
// Update
var updateResult struct {
Datum struct {
ID string `json:"id"`
Name string `json:"name"`
} `json:"datum"`
}
mc.CallToolInto("updateDatum", map[string]any{
"id": addResult.Datum.ID,
"name": "Updated Datum",
}, &updateResult)
assert.Equal(t, "Updated Datum", updateResult.Datum.Name)
// List
var listResult struct {
Data []struct {
ID string `json:"id"`
} `json:"data"`
}
mc.CallToolInto("listData", map[string]any{
"organizationId": orgID,
}, &listResult)
assert.NotEmpty(t, listResult.Data)
// Delete
var deleteResult struct {
DeletedDatumID string `json:"deletedDatumId"`
}
mc.CallToolInto("deleteDatum", map[string]any{
"id": addResult.Datum.ID,
}, &deleteResult)
assert.Equal(t, addResult.Datum.ID, deleteResult.DeletedDatumID)
}

89
e2e/mcp/document_test.go Normal file
View File

@@ -0,0 +1,89 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
package mcp_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.probo.inc/probo/e2e/internal/factory"
"go.probo.inc/probo/e2e/internal/testutil"
)
func TestMCP_Document_CRUD(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
mc := testutil.NewMCPClient(t, owner)
orgID := owner.GetOrganizationID().String()
// Create
var addResult struct {
Document struct {
ID string `json:"id"`
Title string `json:"title"`
} `json:"document"`
}
mc.CallToolInto("addDocument", map[string]any{
"organizationId": orgID,
"title": factory.SafeName("Document"),
"documentType": "POLICY",
}, &addResult)
require.NotEmpty(t, addResult.Document.ID)
// Get
var getResult struct {
Document struct {
ID string `json:"id"`
} `json:"document"`
}
mc.CallToolInto("getDocument", map[string]any{
"id": addResult.Document.ID,
}, &getResult)
assert.Equal(t, addResult.Document.ID, getResult.Document.ID)
// Update
var updateResult struct {
Document struct {
ID string `json:"id"`
Title string `json:"title"`
} `json:"document"`
}
mc.CallToolInto("updateDocument", map[string]any{
"id": addResult.Document.ID,
"title": "Updated Document",
}, &updateResult)
assert.Equal(t, "Updated Document", updateResult.Document.Title)
// List
var listResult struct {
Documents []struct {
ID string `json:"id"`
} `json:"documents"`
}
mc.CallToolInto("listDocuments", map[string]any{
"organizationId": orgID,
}, &listResult)
assert.NotEmpty(t, listResult.Documents)
// Delete
var deleteResult struct {
DeletedDocumentID string `json:"deletedDocumentId"`
}
mc.CallToolInto("deleteDocument", map[string]any{
"id": addResult.Document.ID,
}, &deleteResult)
assert.Equal(t, addResult.Document.ID, deleteResult.DeletedDocumentID)
}

152
e2e/mcp/dpia_tia_test.go Normal file
View File

@@ -0,0 +1,152 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
package mcp_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.probo.inc/probo/e2e/internal/factory"
"go.probo.inc/probo/e2e/internal/testutil"
)
func TestMCP_DPIA_CRUD(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
mc := testutil.NewMCPClient(t, owner)
orgID := owner.GetOrganizationID().String()
paID := factory.CreateProcessingActivity(owner)
// Create
var addResult struct {
DataProtectionImpactAssessment struct {
ID string `json:"id"`
} `json:"dataProtectionImpactAssessment"`
}
mc.CallToolInto("addDataProtectionImpactAssessment", map[string]any{
"processingActivityId": paID,
}, &addResult)
require.NotEmpty(t, addResult.DataProtectionImpactAssessment.ID)
// Get
var getResult struct {
DataProtectionImpactAssessment struct {
ID string `json:"id"`
} `json:"dataProtectionImpactAssessment"`
}
mc.CallToolInto("getDataProtectionImpactAssessment", map[string]any{
"id": addResult.DataProtectionImpactAssessment.ID,
}, &getResult)
assert.Equal(t, addResult.DataProtectionImpactAssessment.ID, getResult.DataProtectionImpactAssessment.ID)
// Update
var updateResult struct {
DataProtectionImpactAssessment struct {
ID string `json:"id"`
Description string `json:"description"`
} `json:"dataProtectionImpactAssessment"`
}
mc.CallToolInto("updateDataProtectionImpactAssessment", map[string]any{
"id": addResult.DataProtectionImpactAssessment.ID,
"description": "Updated DPIA",
}, &updateResult)
assert.Equal(t, "Updated DPIA", updateResult.DataProtectionImpactAssessment.Description)
// List
var listResult struct {
DataProtectionImpactAssessments []struct {
ID string `json:"id"`
} `json:"dataProtectionImpactAssessments"`
}
mc.CallToolInto("listDataProtectionImpactAssessments", map[string]any{
"organizationId": orgID,
}, &listResult)
assert.NotEmpty(t, listResult.DataProtectionImpactAssessments)
// Delete
var deleteResult struct {
DeletedDataProtectionImpactAssessmentID string `json:"deletedDataProtectionImpactAssessmentId"`
}
mc.CallToolInto("deleteDataProtectionImpactAssessment", map[string]any{
"id": addResult.DataProtectionImpactAssessment.ID,
}, &deleteResult)
assert.Equal(t, addResult.DataProtectionImpactAssessment.ID, deleteResult.DeletedDataProtectionImpactAssessmentID)
}
func TestMCP_TIA_CRUD(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
mc := testutil.NewMCPClient(t, owner)
orgID := owner.GetOrganizationID().String()
paID := factory.CreateProcessingActivity(owner)
// Create
var addResult struct {
TransferImpactAssessment struct {
ID string `json:"id"`
} `json:"transferImpactAssessment"`
}
mc.CallToolInto("addTransferImpactAssessment", map[string]any{
"processingActivityId": paID,
}, &addResult)
require.NotEmpty(t, addResult.TransferImpactAssessment.ID)
// Get
var getResult struct {
TransferImpactAssessment struct {
ID string `json:"id"`
} `json:"transferImpactAssessment"`
}
mc.CallToolInto("getTransferImpactAssessment", map[string]any{
"id": addResult.TransferImpactAssessment.ID,
}, &getResult)
assert.Equal(t, addResult.TransferImpactAssessment.ID, getResult.TransferImpactAssessment.ID)
// Update
var updateResult struct {
TransferImpactAssessment struct {
ID string `json:"id"`
DataSubjects string `json:"dataSubjects"`
} `json:"transferImpactAssessment"`
}
mc.CallToolInto("updateTransferImpactAssessment", map[string]any{
"id": addResult.TransferImpactAssessment.ID,
"dataSubjects": "EU Residents",
}, &updateResult)
assert.Equal(t, "EU Residents", updateResult.TransferImpactAssessment.DataSubjects)
// List
var listResult struct {
TransferImpactAssessments []struct {
ID string `json:"id"`
} `json:"transferImpactAssessments"`
}
mc.CallToolInto("listTransferImpactAssessments", map[string]any{
"organizationId": orgID,
}, &listResult)
assert.NotEmpty(t, listResult.TransferImpactAssessments)
// Delete
var deleteResult struct {
DeletedTransferImpactAssessmentID string `json:"deletedTransferImpactAssessmentId"`
}
mc.CallToolInto("deleteTransferImpactAssessment", map[string]any{
"id": addResult.TransferImpactAssessment.ID,
}, &deleteResult)
assert.Equal(t, addResult.TransferImpactAssessment.ID, deleteResult.DeletedTransferImpactAssessmentID)
}

88
e2e/mcp/finding_test.go Normal file
View File

@@ -0,0 +1,88 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
package mcp_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.probo.inc/probo/e2e/internal/factory"
"go.probo.inc/probo/e2e/internal/testutil"
)
func TestMCP_Finding_CRUD(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
mc := testutil.NewMCPClient(t, owner)
orgID := owner.GetOrganizationID().String()
// Create
var addResult struct {
Finding struct {
ID string `json:"id"`
Title string `json:"title"`
} `json:"finding"`
}
mc.CallToolInto("addFinding", map[string]any{
"organizationId": orgID,
"title": factory.SafeName("Finding"),
}, &addResult)
require.NotEmpty(t, addResult.Finding.ID)
// Get
var getResult struct {
Finding struct {
ID string `json:"id"`
} `json:"finding"`
}
mc.CallToolInto("getFinding", map[string]any{
"id": addResult.Finding.ID,
}, &getResult)
assert.Equal(t, addResult.Finding.ID, getResult.Finding.ID)
// Update
var updateResult struct {
Finding struct {
ID string `json:"id"`
Title string `json:"title"`
} `json:"finding"`
}
mc.CallToolInto("updateFinding", map[string]any{
"id": addResult.Finding.ID,
"title": "Updated Finding",
}, &updateResult)
assert.Equal(t, "Updated Finding", updateResult.Finding.Title)
// List
var listResult struct {
Findings []struct {
ID string `json:"id"`
} `json:"findings"`
}
mc.CallToolInto("listFindings", map[string]any{
"organizationId": orgID,
}, &listResult)
assert.NotEmpty(t, listResult.Findings)
// Delete
var deleteResult struct {
DeletedFindingID string `json:"deletedFindingId"`
}
mc.CallToolInto("deleteFinding", map[string]any{
"id": addResult.Finding.ID,
}, &deleteResult)
assert.Equal(t, addResult.Finding.ID, deleteResult.DeletedFindingID)
}

135
e2e/mcp/framework_test.go Normal file
View File

@@ -0,0 +1,135 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
package mcp_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.probo.inc/probo/e2e/internal/factory"
"go.probo.inc/probo/e2e/internal/testutil"
)
func TestMCP_Framework_CRUD(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
mc := testutil.NewMCPClient(t, owner)
orgID := owner.GetOrganizationID().String()
// Create
var addResult struct {
Framework struct {
ID string `json:"id"`
Name string `json:"name"`
} `json:"framework"`
}
mc.CallToolInto("addFramework", map[string]any{
"organizationId": orgID,
"name": factory.SafeName("Framework"),
}, &addResult)
require.NotEmpty(t, addResult.Framework.ID)
// Get
var getResult struct {
Framework struct {
ID string `json:"id"`
} `json:"framework"`
}
mc.CallToolInto("getFramework", map[string]any{
"id": addResult.Framework.ID,
}, &getResult)
assert.Equal(t, addResult.Framework.ID, getResult.Framework.ID)
// Update
var updateResult struct {
Framework struct {
ID string `json:"id"`
Description string `json:"description"`
} `json:"framework"`
}
mc.CallToolInto("updateFramework", map[string]any{
"id": addResult.Framework.ID,
"description": "Updated description",
}, &updateResult)
assert.Equal(t, "Updated description", updateResult.Framework.Description)
// List
var listResult struct {
Frameworks []struct {
ID string `json:"id"`
} `json:"frameworks"`
}
mc.CallToolInto("listFrameworks", map[string]any{
"organizationId": orgID,
}, &listResult)
assert.NotEmpty(t, listResult.Frameworks)
}
func TestMCP_Control_CRUD(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
mc := testutil.NewMCPClient(t, owner)
frameworkID := factory.CreateFramework(owner)
// Create
var addResult struct {
Control struct {
ID string `json:"id"`
Name string `json:"name"`
} `json:"control"`
}
mc.CallToolInto("addControl", map[string]any{
"frameworkId": frameworkID,
"name": factory.SafeName("Control"),
}, &addResult)
require.NotEmpty(t, addResult.Control.ID)
// Get
var getResult struct {
Control struct {
ID string `json:"id"`
} `json:"control"`
}
mc.CallToolInto("getControl", map[string]any{
"id": addResult.Control.ID,
}, &getResult)
assert.Equal(t, addResult.Control.ID, getResult.Control.ID)
// Update
var updateResult struct {
Control struct {
ID string `json:"id"`
Description string `json:"description"`
} `json:"control"`
}
mc.CallToolInto("updateControl", map[string]any{
"id": addResult.Control.ID,
"description": "Updated control",
}, &updateResult)
assert.Equal(t, "Updated control", updateResult.Control.Description)
// List
var listResult struct {
Controls []struct {
ID string `json:"id"`
} `json:"controls"`
}
mc.CallToolInto("listControls", map[string]any{
"frameworkId": frameworkID,
}, &listResult)
assert.NotEmpty(t, listResult.Controls)
}

29
e2e/mcp/main_test.go Normal file
View File

@@ -0,0 +1,29 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
package mcp_test
import (
"os"
"testing"
"go.probo.inc/probo/e2e/internal/testutil"
)
func TestMain(m *testing.M) {
testutil.Setup()
code := m.Run()
testutil.Teardown()
os.Exit(code)
}

110
e2e/mcp/measure_test.go Normal file
View File

@@ -0,0 +1,110 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
package mcp_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.probo.inc/probo/e2e/internal/factory"
"go.probo.inc/probo/e2e/internal/testutil"
)
func TestMCP_Measure_CRUD(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
mc := testutil.NewMCPClient(t, owner)
orgID := owner.GetOrganizationID().String()
// Create
var addResult struct {
Measure struct {
ID string `json:"id"`
Name string `json:"name"`
} `json:"measure"`
}
mc.CallToolInto("addMeasure", map[string]any{
"organizationId": orgID,
"name": factory.SafeName("Measure"),
}, &addResult)
require.NotEmpty(t, addResult.Measure.ID)
// Get
var getResult struct {
Measure struct {
ID string `json:"id"`
} `json:"measure"`
}
mc.CallToolInto("getMeasure", map[string]any{
"id": addResult.Measure.ID,
}, &getResult)
assert.Equal(t, addResult.Measure.ID, getResult.Measure.ID)
// Update
var updateResult struct {
Measure struct {
ID string `json:"id"`
Name string `json:"name"`
} `json:"measure"`
}
mc.CallToolInto("updateMeasure", map[string]any{
"id": addResult.Measure.ID,
"name": "Updated Measure",
}, &updateResult)
assert.Equal(t, "Updated Measure", updateResult.Measure.Name)
// List
var listResult struct {
Measures []struct {
ID string `json:"id"`
} `json:"measures"`
}
mc.CallToolInto("listMeasures", map[string]any{
"organizationId": orgID,
}, &listResult)
assert.NotEmpty(t, listResult.Measures)
// Sub-resources (empty lists are fine, just verify the tools work)
var risksResult struct {
Risks []struct{ ID string } `json:"risks"`
}
mc.CallToolInto("listMeasureRisks", map[string]any{
"measureId": addResult.Measure.ID,
}, &risksResult)
var controlsResult struct {
Controls []struct{ ID string } `json:"controls"`
}
mc.CallToolInto("listMeasureControls", map[string]any{
"measureId": addResult.Measure.ID,
}, &controlsResult)
var tasksResult struct {
Tasks []struct{ ID string } `json:"tasks"`
}
mc.CallToolInto("listMeasureTasks", map[string]any{
"measureId": addResult.Measure.ID,
}, &tasksResult)
// Delete
var deleteResult struct {
DeletedMeasureID string `json:"deletedMeasureId"`
}
mc.CallToolInto("deleteMeasure", map[string]any{
"id": addResult.Measure.ID,
}, &deleteResult)
assert.Equal(t, addResult.Measure.ID, deleteResult.DeletedMeasureID)
}

View File

@@ -0,0 +1,89 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
package mcp_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.probo.inc/probo/e2e/internal/factory"
"go.probo.inc/probo/e2e/internal/testutil"
)
func TestMCP_Obligation_CRUD(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
mc := testutil.NewMCPClient(t, owner)
orgID := owner.GetOrganizationID().String()
// Create
var addResult struct {
Obligation struct {
ID string `json:"id"`
Name string `json:"name"`
} `json:"obligation"`
}
mc.CallToolInto("addObligation", map[string]any{
"organizationId": orgID,
"name": factory.SafeName("Obligation"),
"description": "Test obligation",
}, &addResult)
require.NotEmpty(t, addResult.Obligation.ID)
// Get
var getResult struct {
Obligation struct {
ID string `json:"id"`
} `json:"obligation"`
}
mc.CallToolInto("getObligation", map[string]any{
"id": addResult.Obligation.ID,
}, &getResult)
assert.Equal(t, addResult.Obligation.ID, getResult.Obligation.ID)
// Update
var updateResult struct {
Obligation struct {
ID string `json:"id"`
Name string `json:"name"`
} `json:"obligation"`
}
mc.CallToolInto("updateObligation", map[string]any{
"id": addResult.Obligation.ID,
"name": "Updated Obligation",
}, &updateResult)
assert.Equal(t, "Updated Obligation", updateResult.Obligation.Name)
// List
var listResult struct {
Obligations []struct {
ID string `json:"id"`
} `json:"obligations"`
}
mc.CallToolInto("listObligations", map[string]any{
"organizationId": orgID,
}, &listResult)
assert.NotEmpty(t, listResult.Obligations)
// Delete
var deleteResult struct {
DeletedObligationID string `json:"deletedObligationId"`
}
mc.CallToolInto("deleteObligation", map[string]any{
"id": addResult.Obligation.ID,
}, &deleteResult)
assert.Equal(t, addResult.Obligation.ID, deleteResult.DeletedObligationID)
}

View File

@@ -0,0 +1,52 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
package mcp_test
import (
"testing"
"github.com/stretchr/testify/assert"
"go.probo.inc/probo/e2e/internal/testutil"
)
func TestMCP_OrganizationContext(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
mc := testutil.NewMCPClient(t, owner)
orgID := owner.GetOrganizationID().String()
// Get
var getResult struct {
OrganizationContext struct {
ID string `json:"id"`
} `json:"organizationContext"`
}
mc.CallToolInto("getOrganizationContext", map[string]any{
"organizationId": orgID,
}, &getResult)
assert.NotEmpty(t, getResult.OrganizationContext.ID)
// Update
var updateResult struct {
OrganizationContext struct {
ID string `json:"id"`
} `json:"organizationContext"`
}
mc.CallToolInto("updateOrganizationContext", map[string]any{
"id": getResult.OrganizationContext.ID,
"companyLegalName": "Test Company LLC",
}, &updateResult)
assert.Equal(t, getResult.OrganizationContext.ID, updateResult.OrganizationContext.ID)
}

View File

@@ -0,0 +1,38 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
package mcp_test
import (
"testing"
"github.com/stretchr/testify/assert"
"go.probo.inc/probo/e2e/internal/testutil"
)
func TestMCP_ListOrganizations(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
mc := testutil.NewMCPClient(t, owner)
var result struct {
Organizations []struct {
ID string `json:"id"`
Name string `json:"name"`
} `json:"organizations"`
}
mc.CallToolInto("listOrganizations", map[string]any{}, &result)
assert.NotEmpty(t, result.Organizations)
}

View File

@@ -0,0 +1,89 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
package mcp_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.probo.inc/probo/e2e/internal/factory"
"go.probo.inc/probo/e2e/internal/testutil"
)
func TestMCP_ProcessingActivity_CRUD(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
mc := testutil.NewMCPClient(t, owner)
orgID := owner.GetOrganizationID().String()
// Create
var addResult struct {
ProcessingActivity struct {
ID string `json:"id"`
Name string `json:"name"`
} `json:"processingActivity"`
}
mc.CallToolInto("addProcessingActivity", map[string]any{
"organizationId": orgID,
"name": factory.SafeName("PA"),
"lawfulBasis": "CONSENT",
}, &addResult)
require.NotEmpty(t, addResult.ProcessingActivity.ID)
// Get
var getResult struct {
ProcessingActivity struct {
ID string `json:"id"`
} `json:"processingActivity"`
}
mc.CallToolInto("getProcessingActivity", map[string]any{
"id": addResult.ProcessingActivity.ID,
}, &getResult)
assert.Equal(t, addResult.ProcessingActivity.ID, getResult.ProcessingActivity.ID)
// Update
var updateResult struct {
ProcessingActivity struct {
ID string `json:"id"`
Name string `json:"name"`
} `json:"processingActivity"`
}
mc.CallToolInto("updateProcessingActivity", map[string]any{
"id": addResult.ProcessingActivity.ID,
"name": "Updated PA",
}, &updateResult)
assert.Equal(t, "Updated PA", updateResult.ProcessingActivity.Name)
// List
var listResult struct {
ProcessingActivities []struct {
ID string `json:"id"`
} `json:"processingActivities"`
}
mc.CallToolInto("listProcessingActivities", map[string]any{
"organizationId": orgID,
}, &listResult)
assert.NotEmpty(t, listResult.ProcessingActivities)
// Delete
var deleteResult struct {
DeletedProcessingActivityID string `json:"deletedProcessingActivityId"`
}
mc.CallToolInto("deleteProcessingActivity", map[string]any{
"id": addResult.ProcessingActivity.ID,
}, &deleteResult)
assert.Equal(t, addResult.ProcessingActivity.ID, deleteResult.DeletedProcessingActivityID)
}

View File

@@ -0,0 +1,204 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
package mcp_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.probo.inc/probo/e2e/internal/factory"
"go.probo.inc/probo/e2e/internal/testutil"
)
type rightsRequest struct {
ID string `json:"id"`
RequestType string `json:"requestType"`
RequestState string `json:"requestState"`
DataSubject string `json:"dataSubject"`
Contact *string `json:"contact"`
Details *string `json:"details"`
}
func TestMCP_AddRightsRequest(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
mc := testutil.NewMCPClient(t, owner)
orgID := owner.GetOrganizationID().String()
var result struct {
RightsRequest rightsRequest `json:"rightsRequest"`
}
mc.CallToolInto("addRightsRequest", map[string]any{
"organizationId": orgID,
"requestType": "ACCESS",
"requestState": "TODO",
"dataSubject": "John Doe",
"contact": "john@example.com",
"details": "Request for data access",
}, &result)
assert.NotEmpty(t, result.RightsRequest.ID)
assert.Equal(t, "ACCESS", result.RightsRequest.RequestType)
assert.Equal(t, "TODO", result.RightsRequest.RequestState)
assert.Equal(t, "John Doe", result.RightsRequest.DataSubject)
}
func TestMCP_GetRightsRequest(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
mc := testutil.NewMCPClient(t, owner)
orgID := owner.GetOrganizationID().String()
// Create
var addResult struct {
RightsRequest rightsRequest `json:"rightsRequest"`
}
mc.CallToolInto("addRightsRequest", map[string]any{
"organizationId": orgID,
"requestType": "DELETION",
"requestState": "TODO",
"dataSubject": "Jane Doe",
}, &addResult)
require.NotEmpty(t, addResult.RightsRequest.ID)
// Get
var getResult struct {
RightsRequest rightsRequest `json:"rightsRequest"`
}
mc.CallToolInto("getRightsRequest", map[string]any{
"id": addResult.RightsRequest.ID,
}, &getResult)
assert.Equal(t, addResult.RightsRequest.ID, getResult.RightsRequest.ID)
assert.Equal(t, "DELETION", getResult.RightsRequest.RequestType)
assert.Equal(t, "Jane Doe", getResult.RightsRequest.DataSubject)
}
func TestMCP_UpdateRightsRequest(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
mc := testutil.NewMCPClient(t, owner)
orgID := owner.GetOrganizationID().String()
// Create
var addResult struct {
RightsRequest rightsRequest `json:"rightsRequest"`
}
mc.CallToolInto("addRightsRequest", map[string]any{
"organizationId": orgID,
"requestType": "ACCESS",
"requestState": "TODO",
"dataSubject": "Test Subject",
}, &addResult)
require.NotEmpty(t, addResult.RightsRequest.ID)
// Update
var updateResult struct {
RightsRequest rightsRequest `json:"rightsRequest"`
}
mc.CallToolInto("updateRightsRequest", map[string]any{
"id": addResult.RightsRequest.ID,
"requestState": "IN_PROGRESS",
"dataSubject": "Updated Subject",
}, &updateResult)
assert.Equal(t, addResult.RightsRequest.ID, updateResult.RightsRequest.ID)
assert.Equal(t, "IN_PROGRESS", updateResult.RightsRequest.RequestState)
assert.Equal(t, "Updated Subject", updateResult.RightsRequest.DataSubject)
}
func TestMCP_DeleteRightsRequest(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
mc := testutil.NewMCPClient(t, owner)
orgID := owner.GetOrganizationID().String()
// Create
var addResult struct {
RightsRequest rightsRequest `json:"rightsRequest"`
}
mc.CallToolInto("addRightsRequest", map[string]any{
"organizationId": orgID,
"requestType": "PORTABILITY",
"requestState": "TODO",
"dataSubject": "Delete Subject",
}, &addResult)
require.NotEmpty(t, addResult.RightsRequest.ID)
// Delete
var deleteResult struct {
DeletedRightsRequestID string `json:"deletedRightsRequestId"`
}
mc.CallToolInto("deleteRightsRequest", map[string]any{
"id": addResult.RightsRequest.ID,
}, &deleteResult)
assert.Equal(t, addResult.RightsRequest.ID, deleteResult.DeletedRightsRequestID)
}
func TestMCP_ListRightsRequests(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
mc := testutil.NewMCPClient(t, owner)
orgID := owner.GetOrganizationID().String()
// Create multiple rights requests
for _, reqType := range []string{"ACCESS", "DELETION", "PORTABILITY"} {
var result struct {
RightsRequest rightsRequest `json:"rightsRequest"`
}
mc.CallToolInto("addRightsRequest", map[string]any{
"organizationId": orgID,
"requestType": reqType,
"requestState": "TODO",
"dataSubject": factory.SafeName("Subject"),
}, &result)
require.NotEmpty(t, result.RightsRequest.ID)
}
// List
var listResult struct {
RightsRequests []rightsRequest `json:"rightsRequests"`
}
mc.CallToolInto("listRightsRequests", map[string]any{
"organizationId": orgID,
}, &listResult)
assert.GreaterOrEqual(t, len(listResult.RightsRequests), 3)
}
func TestMCP_RightsRequest_Types(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
mc := testutil.NewMCPClient(t, owner)
orgID := owner.GetOrganizationID().String()
for _, reqType := range []string{"ACCESS", "DELETION", "PORTABILITY"} {
t.Run(reqType, func(t *testing.T) {
var result struct {
RightsRequest rightsRequest `json:"rightsRequest"`
}
mc.CallToolInto("addRightsRequest", map[string]any{
"organizationId": orgID,
"requestType": reqType,
"requestState": "TODO",
"dataSubject": factory.SafeName("Subject"),
}, &result)
assert.Equal(t, reqType, result.RightsRequest.RequestType)
})
}
}

89
e2e/mcp/risk_test.go Normal file
View File

@@ -0,0 +1,89 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
package mcp_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.probo.inc/probo/e2e/internal/factory"
"go.probo.inc/probo/e2e/internal/testutil"
)
func TestMCP_Risk_CRUD(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
mc := testutil.NewMCPClient(t, owner)
orgID := owner.GetOrganizationID().String()
// Create
var addResult struct {
Risk struct {
ID string `json:"id"`
Name string `json:"name"`
} `json:"risk"`
}
mc.CallToolInto("addRisk", map[string]any{
"organizationId": orgID,
"name": factory.SafeName("Risk"),
}, &addResult)
require.NotEmpty(t, addResult.Risk.ID)
// Get
var getResult struct {
Risk struct {
ID string `json:"id"`
Name string `json:"name"`
} `json:"risk"`
}
mc.CallToolInto("getRisk", map[string]any{
"id": addResult.Risk.ID,
}, &getResult)
assert.Equal(t, addResult.Risk.ID, getResult.Risk.ID)
// Update
var updateResult struct {
Risk struct {
ID string `json:"id"`
Name string `json:"name"`
} `json:"risk"`
}
mc.CallToolInto("updateRisk", map[string]any{
"id": addResult.Risk.ID,
"name": "Updated Risk",
}, &updateResult)
assert.Equal(t, "Updated Risk", updateResult.Risk.Name)
// List
var listResult struct {
Risks []struct {
ID string `json:"id"`
} `json:"risks"`
}
mc.CallToolInto("listRisks", map[string]any{
"organizationId": orgID,
}, &listResult)
assert.NotEmpty(t, listResult.Risks)
// Delete
var deleteResult struct {
DeletedRiskID string `json:"deletedRiskId"`
}
mc.CallToolInto("deleteRisk", map[string]any{
"id": addResult.Risk.ID,
}, &deleteResult)
assert.Equal(t, addResult.Risk.ID, deleteResult.DeletedRiskID)
}

69
e2e/mcp/snapshot_test.go Normal file
View File

@@ -0,0 +1,69 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
package mcp_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.probo.inc/probo/e2e/internal/factory"
"go.probo.inc/probo/e2e/internal/testutil"
)
func TestMCP_Snapshot(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
mc := testutil.NewMCPClient(t, owner)
orgID := owner.GetOrganizationID().String()
// Create a vendor so the snapshot has data
factory.CreateVendor(owner)
// Take snapshot
var takeResult struct {
Snapshot struct {
ID string `json:"id"`
} `json:"snapshot"`
}
mc.CallToolInto("takeSnapshot", map[string]any{
"organizationId": orgID,
"name": factory.SafeName("Snapshot"),
"snapshotsType": "VENDORS",
}, &takeResult)
require.NotEmpty(t, takeResult.Snapshot.ID)
// Get
var getResult struct {
Snapshot struct {
ID string `json:"id"`
} `json:"snapshot"`
}
mc.CallToolInto("getSnapshot", map[string]any{
"id": takeResult.Snapshot.ID,
}, &getResult)
assert.Equal(t, takeResult.Snapshot.ID, getResult.Snapshot.ID)
// List
var listResult struct {
Snapshots []struct {
ID string `json:"id"`
} `json:"snapshots"`
}
mc.CallToolInto("listSnapshots", map[string]any{
"organizationId": orgID,
}, &listResult)
assert.NotEmpty(t, listResult.Snapshots)
}

88
e2e/mcp/task_test.go Normal file
View File

@@ -0,0 +1,88 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
package mcp_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.probo.inc/probo/e2e/internal/factory"
"go.probo.inc/probo/e2e/internal/testutil"
)
func TestMCP_Task_CRUD(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
mc := testutil.NewMCPClient(t, owner)
measureID := factory.CreateMeasure(owner)
// Create
var addResult struct {
Task struct {
ID string `json:"id"`
Name string `json:"name"`
} `json:"task"`
}
mc.CallToolInto("addTask", map[string]any{
"measureId": measureID,
"name": factory.SafeName("Task"),
}, &addResult)
require.NotEmpty(t, addResult.Task.ID)
// Get
var getResult struct {
Task struct {
ID string `json:"id"`
} `json:"task"`
}
mc.CallToolInto("getTask", map[string]any{
"id": addResult.Task.ID,
}, &getResult)
assert.Equal(t, addResult.Task.ID, getResult.Task.ID)
// Update
var updateResult struct {
Task struct {
ID string `json:"id"`
Name string `json:"name"`
} `json:"task"`
}
mc.CallToolInto("updateTask", map[string]any{
"id": addResult.Task.ID,
"name": "Updated Task",
}, &updateResult)
assert.Equal(t, "Updated Task", updateResult.Task.Name)
// List
var listResult struct {
Tasks []struct {
ID string `json:"id"`
} `json:"tasks"`
}
mc.CallToolInto("listTasks", map[string]any{
"measureId": measureID,
}, &listResult)
assert.NotEmpty(t, listResult.Tasks)
// Delete
var deleteResult struct {
DeletedTaskID string `json:"deletedTaskId"`
}
mc.CallToolInto("deleteTask", map[string]any{
"id": addResult.Task.ID,
}, &deleteResult)
assert.Equal(t, addResult.Task.ID, deleteResult.DeletedTaskID)
}

View File

@@ -0,0 +1,410 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
package mcp_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.probo.inc/probo/e2e/internal/factory"
"go.probo.inc/probo/e2e/internal/testutil"
)
type trustCenter struct {
ID string `json:"id"`
CompanyName string `json:"companyName"`
PageTitle string `json:"pageTitle"`
TrustCenterVisible bool `json:"trustCenterVisible"`
}
type trustCenterReference struct {
ID string `json:"id"`
Name string `json:"name"`
URL string `json:"url"`
Order int `json:"order"`
}
type complianceExternalURL struct {
ID string `json:"id"`
Name string `json:"name"`
URL string `json:"url"`
}
func TestMCP_GetTrustCenter(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
mc := testutil.NewMCPClient(t, owner)
orgID := owner.GetOrganizationID().String()
var result struct {
TrustCenter trustCenter `json:"trustCenter"`
}
mc.CallToolInto("getTrustCenter", map[string]any{
"organizationId": orgID,
}, &result)
assert.NotEmpty(t, result.TrustCenter.ID)
}
func TestMCP_UpdateTrustCenter(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
mc := testutil.NewMCPClient(t, owner)
orgID := owner.GetOrganizationID().String()
// Get trust center ID
var getResult struct {
TrustCenter trustCenter `json:"trustCenter"`
}
mc.CallToolInto("getTrustCenter", map[string]any{
"organizationId": orgID,
}, &getResult)
require.NotEmpty(t, getResult.TrustCenter.ID)
// Update
var updateResult struct {
TrustCenter trustCenter `json:"trustCenter"`
}
mc.CallToolInto("updateTrustCenter", map[string]any{
"id": getResult.TrustCenter.ID,
"companyName": "Updated Company",
"pageTitle": "Updated Trust Center",
}, &updateResult)
assert.Equal(t, getResult.TrustCenter.ID, updateResult.TrustCenter.ID)
assert.Equal(t, "Updated Company", updateResult.TrustCenter.CompanyName)
assert.Equal(t, "Updated Trust Center", updateResult.TrustCenter.PageTitle)
}
func TestMCP_AddTrustCenterReference(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
mc := testutil.NewMCPClient(t, owner)
orgID := owner.GetOrganizationID().String()
// Get trust center ID
var getResult struct {
TrustCenter trustCenter `json:"trustCenter"`
}
mc.CallToolInto("getTrustCenter", map[string]any{
"organizationId": orgID,
}, &getResult)
tcID := getResult.TrustCenter.ID
var result struct {
TrustCenterReference trustCenterReference `json:"trustCenterReference"`
}
mc.CallToolInto("addTrustCenterReference", map[string]any{
"trustCenterId": tcID,
"name": "SOC 2 Report",
"url": "https://example.com/soc2",
}, &result)
assert.NotEmpty(t, result.TrustCenterReference.ID)
assert.Equal(t, "SOC 2 Report", result.TrustCenterReference.Name)
}
func TestMCP_UpdateTrustCenterReference(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
mc := testutil.NewMCPClient(t, owner)
orgID := owner.GetOrganizationID().String()
// Get trust center ID
var getResult struct {
TrustCenter trustCenter `json:"trustCenter"`
}
mc.CallToolInto("getTrustCenter", map[string]any{
"organizationId": orgID,
}, &getResult)
tcID := getResult.TrustCenter.ID
// Create reference
var addResult struct {
TrustCenterReference trustCenterReference `json:"trustCenterReference"`
}
mc.CallToolInto("addTrustCenterReference", map[string]any{
"trustCenterId": tcID,
"name": "Original Reference",
"url": "https://example.com/original",
}, &addResult)
require.NotEmpty(t, addResult.TrustCenterReference.ID)
// Update reference
var updateResult struct {
TrustCenterReference trustCenterReference `json:"trustCenterReference"`
}
mc.CallToolInto("updateTrustCenterReference", map[string]any{
"id": addResult.TrustCenterReference.ID,
"name": "Updated Reference",
"url": "https://example.com/updated",
}, &updateResult)
assert.Equal(t, addResult.TrustCenterReference.ID, updateResult.TrustCenterReference.ID)
assert.Equal(t, "Updated Reference", updateResult.TrustCenterReference.Name)
}
func TestMCP_DeleteTrustCenterReference(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
mc := testutil.NewMCPClient(t, owner)
orgID := owner.GetOrganizationID().String()
// Get trust center ID
var getResult struct {
TrustCenter trustCenter `json:"trustCenter"`
}
mc.CallToolInto("getTrustCenter", map[string]any{
"organizationId": orgID,
}, &getResult)
tcID := getResult.TrustCenter.ID
// Create reference
var addResult struct {
TrustCenterReference trustCenterReference `json:"trustCenterReference"`
}
mc.CallToolInto("addTrustCenterReference", map[string]any{
"trustCenterId": tcID,
"name": "Reference to delete",
"url": "https://example.com/delete",
}, &addResult)
require.NotEmpty(t, addResult.TrustCenterReference.ID)
// Delete
var deleteResult struct {
DeletedTrustCenterReferenceID string `json:"deletedTrustCenterReferenceId"`
}
mc.CallToolInto("deleteTrustCenterReference", map[string]any{
"id": addResult.TrustCenterReference.ID,
}, &deleteResult)
assert.Equal(t, addResult.TrustCenterReference.ID, deleteResult.DeletedTrustCenterReferenceID)
}
func TestMCP_ListTrustCenterReferences(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
mc := testutil.NewMCPClient(t, owner)
orgID := owner.GetOrganizationID().String()
// Get trust center ID
var getResult struct {
TrustCenter trustCenter `json:"trustCenter"`
}
mc.CallToolInto("getTrustCenter", map[string]any{
"organizationId": orgID,
}, &getResult)
tcID := getResult.TrustCenter.ID
// Create references
for i := range 2 {
var result struct {
TrustCenterReference trustCenterReference `json:"trustCenterReference"`
}
mc.CallToolInto("addTrustCenterReference", map[string]any{
"trustCenterId": tcID,
"name": factory.SafeName("Ref"),
"url": "https://example.com/" + factory.SafeName("path"),
}, &result)
require.NotEmpty(t, result.TrustCenterReference.ID)
_ = i
}
// List
var listResult struct {
TrustCenterReferences []trustCenterReference `json:"trustCenterReferences"`
}
mc.CallToolInto("listTrustCenterReferences", map[string]any{
"trustCenterId": tcID,
}, &listResult)
assert.GreaterOrEqual(t, len(listResult.TrustCenterReferences), 2)
}
func TestMCP_ListTrustCenterFiles(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
mc := testutil.NewMCPClient(t, owner)
orgID := owner.GetOrganizationID().String()
// Get trust center ID
var getResult struct {
TrustCenter trustCenter `json:"trustCenter"`
}
mc.CallToolInto("getTrustCenter", map[string]any{
"organizationId": orgID,
}, &getResult)
tcID := getResult.TrustCenter.ID
// List files (may be empty, just verify the tool works)
var listResult struct {
TrustCenterFiles []struct {
ID string `json:"id"`
Name string `json:"name"`
} `json:"trustCenterFiles"`
}
mc.CallToolInto("listTrustCenterFiles", map[string]any{
"trustCenterId": tcID,
}, &listResult)
// Just assert the call succeeded — files require multipart upload
assert.NotNil(t, listResult.TrustCenterFiles)
}
func TestMCP_AddComplianceExternalURL(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
mc := testutil.NewMCPClient(t, owner)
orgID := owner.GetOrganizationID().String()
// Get trust center ID
var getResult struct {
TrustCenter trustCenter `json:"trustCenter"`
}
mc.CallToolInto("getTrustCenter", map[string]any{
"organizationId": orgID,
}, &getResult)
tcID := getResult.TrustCenter.ID
var result struct {
ComplianceExternalURL complianceExternalURL `json:"complianceExternalUrl"`
}
mc.CallToolInto("addComplianceExternalURL", map[string]any{
"trustCenterId": tcID,
"name": "ISO 27001 Certificate",
"url": "https://example.com/iso27001",
}, &result)
assert.NotEmpty(t, result.ComplianceExternalURL.ID)
assert.Equal(t, "ISO 27001 Certificate", result.ComplianceExternalURL.Name)
}
func TestMCP_UpdateComplianceExternalURL(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
mc := testutil.NewMCPClient(t, owner)
orgID := owner.GetOrganizationID().String()
// Get trust center ID
var getResult struct {
TrustCenter trustCenter `json:"trustCenter"`
}
mc.CallToolInto("getTrustCenter", map[string]any{
"organizationId": orgID,
}, &getResult)
tcID := getResult.TrustCenter.ID
// Create
var addResult struct {
ComplianceExternalURL complianceExternalURL `json:"complianceExternalUrl"`
}
mc.CallToolInto("addComplianceExternalURL", map[string]any{
"trustCenterId": tcID,
"name": "Original URL",
"url": "https://example.com/original",
}, &addResult)
require.NotEmpty(t, addResult.ComplianceExternalURL.ID)
// Update
var updateResult struct {
ComplianceExternalURL complianceExternalURL `json:"complianceExternalUrl"`
}
mc.CallToolInto("updateComplianceExternalURL", map[string]any{
"id": addResult.ComplianceExternalURL.ID,
"name": "Updated URL",
"url": "https://example.com/updated",
}, &updateResult)
assert.Equal(t, addResult.ComplianceExternalURL.ID, updateResult.ComplianceExternalURL.ID)
assert.Equal(t, "Updated URL", updateResult.ComplianceExternalURL.Name)
}
func TestMCP_DeleteComplianceExternalURL(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
mc := testutil.NewMCPClient(t, owner)
orgID := owner.GetOrganizationID().String()
// Get trust center ID
var getResult struct {
TrustCenter trustCenter `json:"trustCenter"`
}
mc.CallToolInto("getTrustCenter", map[string]any{
"organizationId": orgID,
}, &getResult)
tcID := getResult.TrustCenter.ID
// Create
var addResult struct {
ComplianceExternalURL complianceExternalURL `json:"complianceExternalUrl"`
}
mc.CallToolInto("addComplianceExternalURL", map[string]any{
"trustCenterId": tcID,
"name": "URL to delete",
"url": "https://example.com/delete",
}, &addResult)
require.NotEmpty(t, addResult.ComplianceExternalURL.ID)
// Delete
var deleteResult struct {
DeletedComplianceExternalURLID string `json:"deletedComplianceExternalUrlId"`
}
mc.CallToolInto("deleteComplianceExternalURL", map[string]any{
"id": addResult.ComplianceExternalURL.ID,
}, &deleteResult)
assert.Equal(t, addResult.ComplianceExternalURL.ID, deleteResult.DeletedComplianceExternalURLID)
}
func TestMCP_ListComplianceExternalURLs(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
mc := testutil.NewMCPClient(t, owner)
orgID := owner.GetOrganizationID().String()
// Get trust center ID
var getResult struct {
TrustCenter trustCenter `json:"trustCenter"`
}
mc.CallToolInto("getTrustCenter", map[string]any{
"organizationId": orgID,
}, &getResult)
tcID := getResult.TrustCenter.ID
// Create URLs
for i := range 2 {
var result struct {
ComplianceExternalURL complianceExternalURL `json:"complianceExternalUrl"`
}
mc.CallToolInto("addComplianceExternalURL", map[string]any{
"trustCenterId": tcID,
"name": factory.SafeName("URL"),
"url": "https://example.com/" + factory.SafeName("path"),
}, &result)
require.NotEmpty(t, result.ComplianceExternalURL.ID)
_ = i
}
// List
var listResult struct {
ComplianceExternalURLs []complianceExternalURL `json:"complianceExternalUrls"`
}
mc.CallToolInto("listComplianceExternalURLs", map[string]any{
"trustCenterId": tcID,
}, &listResult)
assert.GreaterOrEqual(t, len(listResult.ComplianceExternalURLs), 2)
}

69
e2e/mcp/user_test.go Normal file
View File

@@ -0,0 +1,69 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
package mcp_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.probo.inc/probo/e2e/internal/factory"
"go.probo.inc/probo/e2e/internal/testutil"
)
func TestMCP_User_CRUD(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
mc := testutil.NewMCPClient(t, owner)
orgID := owner.GetOrganizationID().String()
// Create
var createResult struct {
User struct {
ID string `json:"id"`
FullName string `json:"fullName"`
} `json:"user"`
}
mc.CallToolInto("createUser", map[string]any{
"organizationId": orgID,
"fullName": "Test User",
"emailAddress": factory.SafeEmail(),
"role": "EMPLOYEE",
"kind": "EMPLOYEE",
}, &createResult)
require.NotEmpty(t, createResult.User.ID)
// Get
var getResult struct {
User struct {
ID string `json:"id"`
} `json:"user"`
}
mc.CallToolInto("getUser", map[string]any{
"id": createResult.User.ID,
}, &getResult)
assert.Equal(t, createResult.User.ID, getResult.User.ID)
// List
var listResult struct {
Users []struct {
ID string `json:"id"`
} `json:"users"`
}
mc.CallToolInto("listUsers", map[string]any{
"organizationId": orgID,
}, &listResult)
assert.NotEmpty(t, listResult.Users)
}

View File

@@ -0,0 +1,142 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
package mcp_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.probo.inc/probo/e2e/internal/factory"
"go.probo.inc/probo/e2e/internal/testutil"
)
type vendorContact struct {
ID string `json:"id"`
Name string `json:"name"`
Email *string `json:"email"`
Phone *string `json:"phone"`
Role *string `json:"role"`
}
func TestMCP_AddVendorContact(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
mc := testutil.NewMCPClient(t, owner)
vendorID := factory.CreateVendor(owner)
var result struct {
VendorContact vendorContact `json:"vendorContact"`
}
mc.CallToolInto("addVendorContact", map[string]any{
"vendorId": vendorID,
"name": "Alice Smith",
"email": "alice@example.com",
"phone": "+1-555-0100",
"role": "Account Manager",
}, &result)
assert.NotEmpty(t, result.VendorContact.ID)
assert.Equal(t, "Alice Smith", result.VendorContact.Name)
}
func TestMCP_UpdateVendorContact(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
mc := testutil.NewMCPClient(t, owner)
vendorID := factory.CreateVendor(owner)
// Create
var addResult struct {
VendorContact vendorContact `json:"vendorContact"`
}
mc.CallToolInto("addVendorContact", map[string]any{
"vendorId": vendorID,
"name": "Bob Jones",
"email": "bob@example.com",
}, &addResult)
require.NotEmpty(t, addResult.VendorContact.ID)
// Update
var updateResult struct {
VendorContact vendorContact `json:"vendorContact"`
}
mc.CallToolInto("updateVendorContact", map[string]any{
"id": addResult.VendorContact.ID,
"name": "Robert Jones",
"role": "CTO",
}, &updateResult)
assert.Equal(t, addResult.VendorContact.ID, updateResult.VendorContact.ID)
assert.Equal(t, "Robert Jones", updateResult.VendorContact.Name)
}
func TestMCP_DeleteVendorContact(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
mc := testutil.NewMCPClient(t, owner)
vendorID := factory.CreateVendor(owner)
// Create
var addResult struct {
VendorContact vendorContact `json:"vendorContact"`
}
mc.CallToolInto("addVendorContact", map[string]any{
"vendorId": vendorID,
"name": "Contact to delete",
}, &addResult)
require.NotEmpty(t, addResult.VendorContact.ID)
// Delete
var deleteResult struct {
DeletedVendorContactID string `json:"deletedVendorContactId"`
}
mc.CallToolInto("deleteVendorContact", map[string]any{
"id": addResult.VendorContact.ID,
}, &deleteResult)
assert.Equal(t, addResult.VendorContact.ID, deleteResult.DeletedVendorContactID)
}
func TestMCP_ListVendorContacts(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
mc := testutil.NewMCPClient(t, owner)
vendorID := factory.CreateVendor(owner)
// Create contacts
for i := range 3 {
var result struct {
VendorContact vendorContact `json:"vendorContact"`
}
mc.CallToolInto("addVendorContact", map[string]any{
"vendorId": vendorID,
"name": factory.SafeName("Contact"),
"email": factory.SafeEmail(),
}, &result)
require.NotEmpty(t, result.VendorContact.ID)
_ = i
}
// List
var listResult struct {
VendorContacts []vendorContact `json:"vendorContacts"`
}
mc.CallToolInto("listVendorContacts", map[string]any{
"vendorId": vendorID,
}, &listResult)
assert.GreaterOrEqual(t, len(listResult.VendorContacts), 3)
}

View File

@@ -0,0 +1,135 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
package mcp_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.probo.inc/probo/e2e/internal/factory"
"go.probo.inc/probo/e2e/internal/testutil"
)
type vendorService struct {
ID string `json:"id"`
Name string `json:"name"`
Description *string `json:"description"`
}
func TestMCP_AddVendorService(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
mc := testutil.NewMCPClient(t, owner)
vendorID := factory.CreateVendor(owner)
var result struct {
VendorService vendorService `json:"vendorService"`
}
mc.CallToolInto("addVendorService", map[string]any{
"vendorId": vendorID,
"name": "Cloud Storage",
"description": "Object storage service",
}, &result)
assert.NotEmpty(t, result.VendorService.ID)
assert.Equal(t, "Cloud Storage", result.VendorService.Name)
}
func TestMCP_UpdateVendorService(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
mc := testutil.NewMCPClient(t, owner)
vendorID := factory.CreateVendor(owner)
// Create
var addResult struct {
VendorService vendorService `json:"vendorService"`
}
mc.CallToolInto("addVendorService", map[string]any{
"vendorId": vendorID,
"name": "Original Service",
}, &addResult)
require.NotEmpty(t, addResult.VendorService.ID)
// Update
var updateResult struct {
VendorService vendorService `json:"vendorService"`
}
mc.CallToolInto("updateVendorService", map[string]any{
"id": addResult.VendorService.ID,
"name": "Updated Service",
}, &updateResult)
assert.Equal(t, addResult.VendorService.ID, updateResult.VendorService.ID)
assert.Equal(t, "Updated Service", updateResult.VendorService.Name)
}
func TestMCP_DeleteVendorService(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
mc := testutil.NewMCPClient(t, owner)
vendorID := factory.CreateVendor(owner)
// Create
var addResult struct {
VendorService vendorService `json:"vendorService"`
}
mc.CallToolInto("addVendorService", map[string]any{
"vendorId": vendorID,
"name": "Service to delete",
}, &addResult)
require.NotEmpty(t, addResult.VendorService.ID)
// Delete
var deleteResult struct {
DeletedVendorServiceID string `json:"deletedVendorServiceId"`
}
mc.CallToolInto("deleteVendorService", map[string]any{
"id": addResult.VendorService.ID,
}, &deleteResult)
assert.Equal(t, addResult.VendorService.ID, deleteResult.DeletedVendorServiceID)
}
func TestMCP_ListVendorServices(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
mc := testutil.NewMCPClient(t, owner)
vendorID := factory.CreateVendor(owner)
// Create services
for i := range 3 {
var result struct {
VendorService vendorService `json:"vendorService"`
}
mc.CallToolInto("addVendorService", map[string]any{
"vendorId": vendorID,
"name": factory.SafeName("Service"),
}, &result)
require.NotEmpty(t, result.VendorService.ID)
_ = i
}
// List
var listResult struct {
VendorServices []vendorService `json:"vendorServices"`
}
mc.CallToolInto("listVendorServices", map[string]any{
"vendorId": vendorID,
}, &listResult)
assert.GreaterOrEqual(t, len(listResult.VendorServices), 3)
}

79
e2e/mcp/vendor_test.go Normal file
View File

@@ -0,0 +1,79 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
package mcp_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.probo.inc/probo/e2e/internal/factory"
"go.probo.inc/probo/e2e/internal/testutil"
)
func TestMCP_Vendor_CRUD(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
mc := testutil.NewMCPClient(t, owner)
orgID := owner.GetOrganizationID().String()
// Create
var addResult struct {
Vendor struct {
ID string `json:"id"`
Name string `json:"name"`
} `json:"vendor"`
}
name := factory.SafeName("Vendor")
mc.CallToolInto("addVendor", map[string]any{
"organizationId": orgID,
"name": name,
}, &addResult)
require.NotEmpty(t, addResult.Vendor.ID)
assert.Equal(t, name, addResult.Vendor.Name)
// Update
var updateResult struct {
Vendor struct {
ID string `json:"id"`
Name string `json:"name"`
} `json:"vendor"`
}
mc.CallToolInto("updateVendor", map[string]any{
"id": addResult.Vendor.ID,
"name": "Updated Vendor",
}, &updateResult)
assert.Equal(t, "Updated Vendor", updateResult.Vendor.Name)
// List
var listResult struct {
Vendors []struct {
ID string `json:"id"`
} `json:"vendors"`
}
mc.CallToolInto("listVendors", map[string]any{
"organizationId": orgID,
}, &listResult)
assert.NotEmpty(t, listResult.Vendors)
// Delete
var deleteResult struct {
DeletedVendorID string `json:"deletedVendorId"`
}
mc.CallToolInto("deleteVendor", map[string]any{
"id": addResult.Vendor.ID,
}, &deleteResult)
assert.Equal(t, addResult.Vendor.ID, deleteResult.DeletedVendorID)
}

View File

@@ -76,6 +76,11 @@ export class Probo implements INodeType {
type: 'options',
noDataExpression: true,
options: [
{
name: 'Access Review',
value: 'accessReview',
description: 'Manage access review campaigns',
},
{
name: 'Asset',
value: 'asset',
@@ -86,6 +91,11 @@ export class Probo implements INodeType {
value: 'audit',
description: 'Manage audits',
},
{
name: 'Audit Log',
value: 'auditLog',
description: 'View audit log entries',
},
{
name: 'Control',
value: 'control',
@@ -101,11 +111,26 @@ export class Probo implements INodeType {
value: 'document',
description: 'Manage documents, versions, and signatures',
},
{
name: 'DPIA',
value: 'dpia',
description: 'Manage data protection impact assessments',
},
{
name: 'Evidence',
value: 'evidence',
description: 'Manage evidences',
},
{
name: 'Execute',
value: 'execute',
description: 'Execute a GraphQL query or mutation',
},
{
name: 'Finding',
value: 'finding',
description: 'Manage findings',
},
{
name: 'Framework',
value: 'framework',
@@ -116,21 +141,61 @@ export class Probo implements INodeType {
value: 'measure',
description: 'Manage measures',
},
{
name: 'Obligation',
value: 'obligation',
description: 'Manage obligations',
},
{
name: 'Organization',
value: 'organization',
description: 'Manage organizations',
},
{
name: 'Organization Context',
value: 'organizationContext',
description: 'Manage organization context',
},
{
name: 'Processing Activity',
value: 'processingActivity',
description: 'Manage processing activities',
},
{
name: 'Rights Request',
value: 'rightsRequest',
description: 'Manage rights requests',
},
{
name: 'Risk',
value: 'risk',
description: 'Manage risks',
},
{
name: 'Snapshot',
value: 'snapshot',
description: 'Manage snapshots',
},
{
name: 'Statement of Applicability',
value: 'statementOfApplicability',
description: 'Manage statements of applicability',
},
{
name: 'Task',
value: 'task',
description: 'Manage tasks',
},
{
name: 'TIA',
value: 'tia',
description: 'Manage transfer impact assessments',
},
{
name: 'Trust Center',
value: 'trustCenter',
description: 'Manage trust center',
},
{
name: 'User',
value: 'user',

View File

@@ -0,0 +1,64 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import type { INodeProperties, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
import { proboApiRequest } from '../../GenericFunctions';
export const description: INodeProperties[] = [
{
displayName: 'Access Review Campaign ID',
name: 'accessReviewCampaignId',
type: 'string',
displayOptions: {
show: {
resource: ['accessReview'],
operation: ['cancel'],
},
},
default: '',
description: 'The ID of the access review campaign to cancel',
required: true,
},
];
export async function execute(
this: IExecuteFunctions,
itemIndex: number,
): Promise<INodeExecutionData> {
const accessReviewCampaignId = this.getNodeParameter('accessReviewCampaignId', itemIndex) as string;
const query = `
mutation CancelAccessReviewCampaign($input: CancelAccessReviewCampaignInput!) {
cancelAccessReviewCampaign(input: $input) {
accessReviewCampaign {
id
name
description
status
startedAt
completedAt
createdAt
updatedAt
}
}
}
`;
const responseData = await proboApiRequest.call(this, query, { input: { accessReviewCampaignId } });
return {
json: responseData,
pairedItem: { item: itemIndex },
};
}

View File

@@ -0,0 +1,64 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import type { INodeProperties, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
import { proboApiRequest } from '../../GenericFunctions';
export const description: INodeProperties[] = [
{
displayName: 'Access Review Campaign ID',
name: 'accessReviewCampaignId',
type: 'string',
displayOptions: {
show: {
resource: ['accessReview'],
operation: ['close'],
},
},
default: '',
description: 'The ID of the access review campaign to close',
required: true,
},
];
export async function execute(
this: IExecuteFunctions,
itemIndex: number,
): Promise<INodeExecutionData> {
const accessReviewCampaignId = this.getNodeParameter('accessReviewCampaignId', itemIndex) as string;
const query = `
mutation CloseAccessReviewCampaign($input: CloseAccessReviewCampaignInput!) {
closeAccessReviewCampaign(input: $input) {
accessReviewCampaign {
id
name
description
status
startedAt
completedAt
createdAt
updatedAt
}
}
}
`;
const responseData = await proboApiRequest.call(this, query, { input: { accessReviewCampaignId } });
return {
json: responseData,
pairedItem: { item: itemIndex },
};
}

View File

@@ -0,0 +1,103 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import type { INodeProperties, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
import { proboApiRequest } from '../../GenericFunctions';
export const description: INodeProperties[] = [
{
displayName: 'Organization ID',
name: 'organizationId',
type: 'string',
displayOptions: {
show: {
resource: ['accessReview'],
operation: ['create'],
},
},
default: '',
description: 'The ID of the organization',
required: true,
},
{
displayName: 'Name',
name: 'name',
type: 'string',
displayOptions: {
show: {
resource: ['accessReview'],
operation: ['create'],
},
},
default: '',
description: 'The name of the access review campaign',
required: true,
},
{
displayName: 'Description',
name: 'description',
type: 'string',
displayOptions: {
show: {
resource: ['accessReview'],
operation: ['create'],
},
},
default: '',
description: 'The description of the access review campaign',
},
];
export async function execute(
this: IExecuteFunctions,
itemIndex: number,
): Promise<INodeExecutionData> {
const organizationId = this.getNodeParameter('organizationId', itemIndex) as string;
const name = this.getNodeParameter('name', itemIndex) as string;
const description = this.getNodeParameter('description', itemIndex, '') as string;
const query = `
mutation CreateAccessReviewCampaign($input: CreateAccessReviewCampaignInput!) {
createAccessReviewCampaign(input: $input) {
accessReviewCampaignEdge {
node {
id
name
description
status
startedAt
completedAt
createdAt
updatedAt
}
}
}
}
`;
const variables = {
input: {
organizationId,
name,
...(description && { description }),
},
};
const responseData = await proboApiRequest.call(this, query, variables);
return {
json: responseData,
pairedItem: { item: itemIndex },
};
}

View File

@@ -0,0 +1,55 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import type { INodeProperties, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
import { proboApiRequest } from '../../GenericFunctions';
export const description: INodeProperties[] = [
{
displayName: 'Access Review Campaign ID',
name: 'accessReviewCampaignId',
type: 'string',
displayOptions: {
show: {
resource: ['accessReview'],
operation: ['delete'],
},
},
default: '',
description: 'The ID of the access review campaign to delete',
required: true,
},
];
export async function execute(
this: IExecuteFunctions,
itemIndex: number,
): Promise<INodeExecutionData> {
const accessReviewCampaignId = this.getNodeParameter('accessReviewCampaignId', itemIndex) as string;
const query = `
mutation DeleteAccessReviewCampaign($input: DeleteAccessReviewCampaignInput!) {
deleteAccessReviewCampaign(input: $input) {
deletedAccessReviewCampaignId
}
}
`;
const responseData = await proboApiRequest.call(this, query, { input: { accessReviewCampaignId } });
return {
json: responseData,
pairedItem: { item: itemIndex },
};
}

View File

@@ -0,0 +1,68 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import type { INodeProperties, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
import { proboApiRequest } from '../../GenericFunctions';
export const description: INodeProperties[] = [
{
displayName: 'Access Review Campaign ID',
name: 'accessReviewCampaignId',
type: 'string',
displayOptions: {
show: {
resource: ['accessReview'],
operation: ['get'],
},
},
default: '',
description: 'The ID of the access review campaign',
required: true,
},
];
export async function execute(
this: IExecuteFunctions,
itemIndex: number,
): Promise<INodeExecutionData> {
const accessReviewCampaignId = this.getNodeParameter('accessReviewCampaignId', itemIndex) as string;
const query = `
query GetAccessReviewCampaign($accessReviewCampaignId: ID!) {
node(id: $accessReviewCampaignId) {
... on AccessReviewCampaign {
id
name
description
status
startedAt
completedAt
createdAt
updatedAt
}
}
}
`;
const variables = {
accessReviewCampaignId,
};
const responseData = await proboApiRequest.call(this, query, variables);
return {
json: responseData,
pairedItem: { item: itemIndex },
};
}

View File

@@ -0,0 +1,117 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import type { INodeProperties, IExecuteFunctions, INodeExecutionData, IDataObject } from 'n8n-workflow';
import { proboApiRequestAllItems } from '../../GenericFunctions';
export const description: INodeProperties[] = [
{
displayName: 'Organization ID',
name: 'organizationId',
type: 'string',
displayOptions: {
show: {
resource: ['accessReview'],
operation: ['getAll'],
},
},
default: '',
description: 'The ID of the organization',
required: true,
},
{
displayName: 'Return All',
name: 'returnAll',
type: 'boolean',
displayOptions: {
show: {
resource: ['accessReview'],
operation: ['getAll'],
},
},
default: false,
description: 'Whether to return all results or only up to a given limit',
},
{
displayName: 'Limit',
name: 'limit',
type: 'number',
displayOptions: {
show: {
resource: ['accessReview'],
operation: ['getAll'],
returnAll: [false],
},
},
typeOptions: {
minValue: 1,
},
default: 50,
description: 'Max number of results to return',
},
];
export async function execute(
this: IExecuteFunctions,
itemIndex: number,
): Promise<INodeExecutionData> {
const organizationId = this.getNodeParameter('organizationId', itemIndex) as string;
const returnAll = this.getNodeParameter('returnAll', itemIndex) as boolean;
const limit = this.getNodeParameter('limit', itemIndex, 50) as number;
const query = `
query GetAccessReviewCampaigns($organizationId: ID!, $first: Int, $after: CursorKey) {
node(id: $organizationId) {
... on Organization {
accessReviewCampaigns(first: $first, after: $after) {
edges {
node {
id
name
description
status
startedAt
completedAt
createdAt
updatedAt
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
}
}
`;
const accessReviewCampaigns = await proboApiRequestAllItems.call(
this,
query,
{ organizationId },
(response) => {
const data = response?.data as IDataObject | undefined;
const node = data?.node as IDataObject | undefined;
return node?.accessReviewCampaigns as IDataObject | undefined;
},
returnAll,
limit,
);
return {
json: { accessReviewCampaigns },
pairedItem: { item: itemIndex },
};
}

View File

@@ -0,0 +1,107 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import type { INodeProperties } from 'n8n-workflow';
import * as createOp from './create.operation';
import * as deleteOp from './delete.operation';
import * as getOp from './get.operation';
import * as getAllOp from './getAll.operation';
import * as updateOp from './update.operation';
import * as startOp from './start.operation';
import * as closeOp from './close.operation';
import * as cancelOp from './cancel.operation';
export const description: INodeProperties[] = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
resource: ['accessReview'],
},
},
options: [
{
name: 'Cancel',
value: 'cancel',
description: 'Cancel an access review campaign',
action: 'Cancel an access review campaign',
},
{
name: 'Close',
value: 'close',
description: 'Close an access review campaign',
action: 'Close an access review campaign',
},
{
name: 'Create',
value: 'create',
description: 'Create a new access review campaign',
action: 'Create an access review campaign',
},
{
name: 'Delete',
value: 'delete',
description: 'Delete an access review campaign',
action: 'Delete an access review campaign',
},
{
name: 'Get',
value: 'get',
description: 'Get an access review campaign',
action: 'Get an access review campaign',
},
{
name: 'Get Many',
value: 'getAll',
description: 'Get many access review campaigns',
action: 'Get many access review campaigns',
},
{
name: 'Start',
value: 'start',
description: 'Start an access review campaign',
action: 'Start an access review campaign',
},
{
name: 'Update',
value: 'update',
description: 'Update an existing access review campaign',
action: 'Update an access review campaign',
},
],
default: 'create',
},
...createOp.description,
...deleteOp.description,
...getOp.description,
...getAllOp.description,
...updateOp.description,
...startOp.description,
...closeOp.description,
...cancelOp.description,
];
export {
createOp as create,
deleteOp as delete,
getOp as get,
getAllOp as getAll,
updateOp as update,
startOp as start,
closeOp as close,
cancelOp as cancel,
};

View File

@@ -0,0 +1,64 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import type { INodeProperties, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
import { proboApiRequest } from '../../GenericFunctions';
export const description: INodeProperties[] = [
{
displayName: 'Access Review Campaign ID',
name: 'accessReviewCampaignId',
type: 'string',
displayOptions: {
show: {
resource: ['accessReview'],
operation: ['start'],
},
},
default: '',
description: 'The ID of the access review campaign to start',
required: true,
},
];
export async function execute(
this: IExecuteFunctions,
itemIndex: number,
): Promise<INodeExecutionData> {
const accessReviewCampaignId = this.getNodeParameter('accessReviewCampaignId', itemIndex) as string;
const query = `
mutation StartAccessReviewCampaign($input: StartAccessReviewCampaignInput!) {
startAccessReviewCampaign(input: $input) {
accessReviewCampaign {
id
name
description
status
startedAt
completedAt
createdAt
updatedAt
}
}
}
`;
const responseData = await proboApiRequest.call(this, query, { input: { accessReviewCampaignId } });
return {
json: responseData,
pairedItem: { item: itemIndex },
};
}

View File

@@ -0,0 +1,96 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import type { INodeProperties, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
import { proboApiRequest } from '../../GenericFunctions';
export const description: INodeProperties[] = [
{
displayName: 'Access Review Campaign ID',
name: 'accessReviewCampaignId',
type: 'string',
displayOptions: {
show: {
resource: ['accessReview'],
operation: ['update'],
},
},
default: '',
description: 'The ID of the access review campaign to update',
required: true,
},
{
displayName: 'Name',
name: 'name',
type: 'string',
displayOptions: {
show: {
resource: ['accessReview'],
operation: ['update'],
},
},
default: '',
description: 'The name of the access review campaign',
},
{
displayName: 'Description',
name: 'description',
type: 'string',
displayOptions: {
show: {
resource: ['accessReview'],
operation: ['update'],
},
},
default: '',
description: 'The description of the access review campaign',
},
];
export async function execute(
this: IExecuteFunctions,
itemIndex: number,
): Promise<INodeExecutionData> {
const accessReviewCampaignId = this.getNodeParameter('accessReviewCampaignId', itemIndex) as string;
const name = this.getNodeParameter('name', itemIndex, '') as string;
const description = this.getNodeParameter('description', itemIndex, '') as string;
const query = `
mutation UpdateAccessReviewCampaign($input: UpdateAccessReviewCampaignInput!) {
updateAccessReviewCampaign(input: $input) {
accessReviewCampaign {
id
name
description
status
startedAt
completedAt
createdAt
updatedAt
}
}
}
`;
const input: Record<string, string> = { accessReviewCampaignId };
if (name) input.name = name;
if (description) input.description = description;
const responseData = await proboApiRequest.call(this, query, { input });
return {
json: responseData,
pairedItem: { item: itemIndex },
};
}

View File

@@ -0,0 +1,68 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import type { INodeProperties, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
import { proboApiRequest } from '../../GenericFunctions';
export const description: INodeProperties[] = [
{
displayName: 'Audit Log Entry ID',
name: 'auditLogEntryId',
type: 'string',
displayOptions: {
show: {
resource: ['auditLog'],
operation: ['get'],
},
},
default: '',
description: 'The ID of the audit log entry',
required: true,
},
];
export async function execute(
this: IExecuteFunctions,
itemIndex: number,
): Promise<INodeExecutionData> {
const auditLogEntryId = this.getNodeParameter('auditLogEntryId', itemIndex) as string;
const query = `
query GetAuditLogEntry($auditLogEntryId: ID!) {
node(id: $auditLogEntryId) {
... on AuditLogEntry {
id
actorId
actorType
action
resourceType
resourceId
metadata
createdAt
}
}
}
`;
const variables = {
auditLogEntryId,
};
const responseData = await proboApiRequest.call(this, query, variables);
return {
json: responseData,
pairedItem: { item: itemIndex },
};
}

View File

@@ -0,0 +1,117 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import type { INodeProperties, IExecuteFunctions, INodeExecutionData, IDataObject } from 'n8n-workflow';
import { proboApiRequestAllItems } from '../../GenericFunctions';
export const description: INodeProperties[] = [
{
displayName: 'Organization ID',
name: 'organizationId',
type: 'string',
displayOptions: {
show: {
resource: ['auditLog'],
operation: ['getAll'],
},
},
default: '',
description: 'The ID of the organization',
required: true,
},
{
displayName: 'Return All',
name: 'returnAll',
type: 'boolean',
displayOptions: {
show: {
resource: ['auditLog'],
operation: ['getAll'],
},
},
default: false,
description: 'Whether to return all results or only up to a given limit',
},
{
displayName: 'Limit',
name: 'limit',
type: 'number',
displayOptions: {
show: {
resource: ['auditLog'],
operation: ['getAll'],
returnAll: [false],
},
},
typeOptions: {
minValue: 1,
},
default: 50,
description: 'Max number of results to return',
},
];
export async function execute(
this: IExecuteFunctions,
itemIndex: number,
): Promise<INodeExecutionData> {
const organizationId = this.getNodeParameter('organizationId', itemIndex) as string;
const returnAll = this.getNodeParameter('returnAll', itemIndex) as boolean;
const limit = this.getNodeParameter('limit', itemIndex, 50) as number;
const query = `
query GetAuditLogEntries($organizationId: ID!, $first: Int, $after: CursorKey) {
node(id: $organizationId) {
... on Organization {
auditLogEntries(first: $first, after: $after) {
edges {
node {
id
actorId
actorType
action
resourceType
resourceId
metadata
createdAt
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
}
}
`;
const auditLogEntries = await proboApiRequestAllItems.call(
this,
query,
{ organizationId },
(response) => {
const data = response?.data as IDataObject | undefined;
const node = data?.node as IDataObject | undefined;
return node?.auditLogEntries as IDataObject | undefined;
},
returnAll,
limit,
);
return {
json: { auditLogEntries },
pairedItem: { item: itemIndex },
};
}

View File

@@ -0,0 +1,50 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import type { INodeProperties } from 'n8n-workflow';
import * as getOp from './get.operation';
import * as getAllOp from './getAll.operation';
export const description: INodeProperties[] = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
resource: ['auditLog'],
},
},
options: [
{
name: 'Get',
value: 'get',
description: 'Get an audit log entry',
action: 'Get an audit log entry',
},
{
name: 'Get Many',
value: 'getAll',
description: 'Get many audit log entries',
action: 'Get many audit log entries',
},
],
default: 'get',
},
...getOp.description,
...getAllOp.description,
];
export { getOp as get, getAllOp as getAll };

View File

@@ -18,6 +18,16 @@ import * as updateOp from './update.operation';
import * as deleteOp from './delete.operation';
import * as getOp from './get.operation';
import * as getAllOp from './getAll.operation';
import * as linkMeasureOp from './linkMeasure.operation';
import * as unlinkMeasureOp from './unlinkMeasure.operation';
import * as linkDocumentOp from './linkDocument.operation';
import * as unlinkDocumentOp from './unlinkDocument.operation';
import * as linkAuditOp from './linkAudit.operation';
import * as unlinkAuditOp from './unlinkAudit.operation';
import * as linkObligationOp from './linkObligation.operation';
import * as unlinkObligationOp from './unlinkObligation.operation';
import * as linkSnapshotOp from './linkSnapshot.operation';
import * as unlinkSnapshotOp from './unlinkSnapshot.operation';
export const description: INodeProperties[] = [
{
@@ -55,6 +65,66 @@ export const description: INodeProperties[] = [
description: 'Get many controls',
action: 'Get many controls',
},
{
name: 'Link Audit',
value: 'linkAudit',
description: 'Link an audit to a control',
action: 'Link an audit to a control',
},
{
name: 'Link Document',
value: 'linkDocument',
description: 'Link a document to a control',
action: 'Link a document to a control',
},
{
name: 'Link Measure',
value: 'linkMeasure',
description: 'Link a measure to a control',
action: 'Link a measure to a control',
},
{
name: 'Link Obligation',
value: 'linkObligation',
description: 'Link an obligation to a control',
action: 'Link an obligation to a control',
},
{
name: 'Link Snapshot',
value: 'linkSnapshot',
description: 'Link a snapshot to a control',
action: 'Link a snapshot to a control',
},
{
name: 'Unlink Audit',
value: 'unlinkAudit',
description: 'Unlink an audit from a control',
action: 'Unlink an audit from a control',
},
{
name: 'Unlink Document',
value: 'unlinkDocument',
description: 'Unlink a document from a control',
action: 'Unlink a document from a control',
},
{
name: 'Unlink Measure',
value: 'unlinkMeasure',
description: 'Unlink a measure from a control',
action: 'Unlink a measure from a control',
},
{
name: 'Unlink Obligation',
value: 'unlinkObligation',
description: 'Unlink an obligation from a control',
action: 'Unlink an obligation from a control',
},
{
name: 'Unlink Snapshot',
value: 'unlinkSnapshot',
description: 'Unlink a snapshot from a control',
action: 'Unlink a snapshot from a control',
},
{
name: 'Update',
value: 'update',
@@ -69,6 +139,32 @@ export const description: INodeProperties[] = [
...deleteOp.description,
...getOp.description,
...getAllOp.description,
...linkMeasureOp.description,
...unlinkMeasureOp.description,
...linkDocumentOp.description,
...unlinkDocumentOp.description,
...linkAuditOp.description,
...unlinkAuditOp.description,
...linkObligationOp.description,
...unlinkObligationOp.description,
...linkSnapshotOp.description,
...unlinkSnapshotOp.description,
];
export { createOp as create, updateOp as update, deleteOp as delete, getOp as get, getAllOp as getAll };
export {
createOp as create,
updateOp as update,
deleteOp as delete,
getOp as get,
getAllOp as getAll,
linkMeasureOp as linkMeasure,
unlinkMeasureOp as unlinkMeasure,
linkDocumentOp as linkDocument,
unlinkDocumentOp as unlinkDocument,
linkAuditOp as linkAudit,
unlinkAuditOp as unlinkAudit,
linkObligationOp as linkObligation,
unlinkObligationOp as unlinkObligation,
linkSnapshotOp as linkSnapshot,
unlinkSnapshotOp as unlinkSnapshot,
};

View File

@@ -0,0 +1,81 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import type { INodeProperties, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
import { proboApiRequest } from '../../GenericFunctions';
export const description: INodeProperties[] = [
{
displayName: 'Control ID',
name: 'controlId',
type: 'string',
displayOptions: {
show: {
resource: ['control'],
operation: ['linkAudit'],
},
},
default: '',
description: 'The ID of the control',
required: true,
},
{
displayName: 'Audit ID',
name: 'auditId',
type: 'string',
displayOptions: {
show: {
resource: ['control'],
operation: ['linkAudit'],
},
},
default: '',
description: 'The ID of the audit to link',
required: true,
},
];
export async function execute(
this: IExecuteFunctions,
itemIndex: number,
): Promise<INodeExecutionData> {
const controlId = this.getNodeParameter('controlId', itemIndex) as string;
const auditId = this.getNodeParameter('auditId', itemIndex) as string;
const query = `
mutation CreateControlAuditMapping($input: CreateControlAuditMappingInput!) {
createControlAuditMapping(input: $input) {
controlEdge {
node {
id
name
}
}
auditEdge {
node {
id
name
}
}
}
}
`;
const responseData = await proboApiRequest.call(this, query, { input: { controlId, auditId } });
return {
json: responseData,
pairedItem: { item: itemIndex },
};
}

View File

@@ -0,0 +1,81 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import type { INodeProperties, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
import { proboApiRequest } from '../../GenericFunctions';
export const description: INodeProperties[] = [
{
displayName: 'Control ID',
name: 'controlId',
type: 'string',
displayOptions: {
show: {
resource: ['control'],
operation: ['linkDocument'],
},
},
default: '',
description: 'The ID of the control',
required: true,
},
{
displayName: 'Document ID',
name: 'documentId',
type: 'string',
displayOptions: {
show: {
resource: ['control'],
operation: ['linkDocument'],
},
},
default: '',
description: 'The ID of the document to link',
required: true,
},
];
export async function execute(
this: IExecuteFunctions,
itemIndex: number,
): Promise<INodeExecutionData> {
const controlId = this.getNodeParameter('controlId', itemIndex) as string;
const documentId = this.getNodeParameter('documentId', itemIndex) as string;
const query = `
mutation CreateControlDocumentMapping($input: CreateControlDocumentMappingInput!) {
createControlDocumentMapping(input: $input) {
controlEdge {
node {
id
name
}
}
documentEdge {
node {
id
title
}
}
}
}
`;
const responseData = await proboApiRequest.call(this, query, { input: { controlId, documentId } });
return {
json: responseData,
pairedItem: { item: itemIndex },
};
}

View File

@@ -0,0 +1,81 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import type { INodeProperties, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
import { proboApiRequest } from '../../GenericFunctions';
export const description: INodeProperties[] = [
{
displayName: 'Control ID',
name: 'controlId',
type: 'string',
displayOptions: {
show: {
resource: ['control'],
operation: ['linkMeasure'],
},
},
default: '',
description: 'The ID of the control',
required: true,
},
{
displayName: 'Measure ID',
name: 'measureId',
type: 'string',
displayOptions: {
show: {
resource: ['control'],
operation: ['linkMeasure'],
},
},
default: '',
description: 'The ID of the measure to link',
required: true,
},
];
export async function execute(
this: IExecuteFunctions,
itemIndex: number,
): Promise<INodeExecutionData> {
const controlId = this.getNodeParameter('controlId', itemIndex) as string;
const measureId = this.getNodeParameter('measureId', itemIndex) as string;
const query = `
mutation CreateControlMeasureMapping($input: CreateControlMeasureMappingInput!) {
createControlMeasureMapping(input: $input) {
controlEdge {
node {
id
name
}
}
measureEdge {
node {
id
name
}
}
}
}
`;
const responseData = await proboApiRequest.call(this, query, { input: { controlId, measureId } });
return {
json: responseData,
pairedItem: { item: itemIndex },
};
}

View File

@@ -0,0 +1,81 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import type { INodeProperties, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
import { proboApiRequest } from '../../GenericFunctions';
export const description: INodeProperties[] = [
{
displayName: 'Control ID',
name: 'controlId',
type: 'string',
displayOptions: {
show: {
resource: ['control'],
operation: ['linkObligation'],
},
},
default: '',
description: 'The ID of the control',
required: true,
},
{
displayName: 'Obligation ID',
name: 'obligationId',
type: 'string',
displayOptions: {
show: {
resource: ['control'],
operation: ['linkObligation'],
},
},
default: '',
description: 'The ID of the obligation to link',
required: true,
},
];
export async function execute(
this: IExecuteFunctions,
itemIndex: number,
): Promise<INodeExecutionData> {
const controlId = this.getNodeParameter('controlId', itemIndex) as string;
const obligationId = this.getNodeParameter('obligationId', itemIndex) as string;
const query = `
mutation CreateControlObligationMapping($input: CreateControlObligationMappingInput!) {
createControlObligationMapping(input: $input) {
controlEdge {
node {
id
name
}
}
obligationEdge {
node {
id
name
}
}
}
}
`;
const responseData = await proboApiRequest.call(this, query, { input: { controlId, obligationId } });
return {
json: responseData,
pairedItem: { item: itemIndex },
};
}

View File

@@ -0,0 +1,81 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import type { INodeProperties, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
import { proboApiRequest } from '../../GenericFunctions';
export const description: INodeProperties[] = [
{
displayName: 'Control ID',
name: 'controlId',
type: 'string',
displayOptions: {
show: {
resource: ['control'],
operation: ['linkSnapshot'],
},
},
default: '',
description: 'The ID of the control',
required: true,
},
{
displayName: 'Snapshot ID',
name: 'snapshotId',
type: 'string',
displayOptions: {
show: {
resource: ['control'],
operation: ['linkSnapshot'],
},
},
default: '',
description: 'The ID of the snapshot to link',
required: true,
},
];
export async function execute(
this: IExecuteFunctions,
itemIndex: number,
): Promise<INodeExecutionData> {
const controlId = this.getNodeParameter('controlId', itemIndex) as string;
const snapshotId = this.getNodeParameter('snapshotId', itemIndex) as string;
const query = `
mutation CreateControlSnapshotMapping($input: CreateControlSnapshotMappingInput!) {
createControlSnapshotMapping(input: $input) {
controlEdge {
node {
id
name
}
}
snapshotEdge {
node {
id
name
}
}
}
}
`;
const responseData = await proboApiRequest.call(this, query, { input: { controlId, snapshotId } });
return {
json: responseData,
pairedItem: { item: itemIndex },
};
}

View File

@@ -0,0 +1,71 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import type { INodeProperties, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
import { proboApiRequest } from '../../GenericFunctions';
export const description: INodeProperties[] = [
{
displayName: 'Control ID',
name: 'controlId',
type: 'string',
displayOptions: {
show: {
resource: ['control'],
operation: ['unlinkAudit'],
},
},
default: '',
description: 'The ID of the control',
required: true,
},
{
displayName: 'Audit ID',
name: 'auditId',
type: 'string',
displayOptions: {
show: {
resource: ['control'],
operation: ['unlinkAudit'],
},
},
default: '',
description: 'The ID of the audit to unlink',
required: true,
},
];
export async function execute(
this: IExecuteFunctions,
itemIndex: number,
): Promise<INodeExecutionData> {
const controlId = this.getNodeParameter('controlId', itemIndex) as string;
const auditId = this.getNodeParameter('auditId', itemIndex) as string;
const query = `
mutation DeleteControlAuditMapping($input: DeleteControlAuditMappingInput!) {
deleteControlAuditMapping(input: $input) {
deletedControlId
deletedAuditId
}
}
`;
const responseData = await proboApiRequest.call(this, query, { input: { controlId, auditId } });
return {
json: responseData,
pairedItem: { item: itemIndex },
};
}

View File

@@ -0,0 +1,71 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import type { INodeProperties, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
import { proboApiRequest } from '../../GenericFunctions';
export const description: INodeProperties[] = [
{
displayName: 'Control ID',
name: 'controlId',
type: 'string',
displayOptions: {
show: {
resource: ['control'],
operation: ['unlinkDocument'],
},
},
default: '',
description: 'The ID of the control',
required: true,
},
{
displayName: 'Document ID',
name: 'documentId',
type: 'string',
displayOptions: {
show: {
resource: ['control'],
operation: ['unlinkDocument'],
},
},
default: '',
description: 'The ID of the document to unlink',
required: true,
},
];
export async function execute(
this: IExecuteFunctions,
itemIndex: number,
): Promise<INodeExecutionData> {
const controlId = this.getNodeParameter('controlId', itemIndex) as string;
const documentId = this.getNodeParameter('documentId', itemIndex) as string;
const query = `
mutation DeleteControlDocumentMapping($input: DeleteControlDocumentMappingInput!) {
deleteControlDocumentMapping(input: $input) {
deletedControlId
deletedDocumentId
}
}
`;
const responseData = await proboApiRequest.call(this, query, { input: { controlId, documentId } });
return {
json: responseData,
pairedItem: { item: itemIndex },
};
}

View File

@@ -0,0 +1,71 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import type { INodeProperties, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
import { proboApiRequest } from '../../GenericFunctions';
export const description: INodeProperties[] = [
{
displayName: 'Control ID',
name: 'controlId',
type: 'string',
displayOptions: {
show: {
resource: ['control'],
operation: ['unlinkMeasure'],
},
},
default: '',
description: 'The ID of the control',
required: true,
},
{
displayName: 'Measure ID',
name: 'measureId',
type: 'string',
displayOptions: {
show: {
resource: ['control'],
operation: ['unlinkMeasure'],
},
},
default: '',
description: 'The ID of the measure to unlink',
required: true,
},
];
export async function execute(
this: IExecuteFunctions,
itemIndex: number,
): Promise<INodeExecutionData> {
const controlId = this.getNodeParameter('controlId', itemIndex) as string;
const measureId = this.getNodeParameter('measureId', itemIndex) as string;
const query = `
mutation DeleteControlMeasureMapping($input: DeleteControlMeasureMappingInput!) {
deleteControlMeasureMapping(input: $input) {
deletedControlId
deletedMeasureId
}
}
`;
const responseData = await proboApiRequest.call(this, query, { input: { controlId, measureId } });
return {
json: responseData,
pairedItem: { item: itemIndex },
};
}

View File

@@ -0,0 +1,71 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import type { INodeProperties, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
import { proboApiRequest } from '../../GenericFunctions';
export const description: INodeProperties[] = [
{
displayName: 'Control ID',
name: 'controlId',
type: 'string',
displayOptions: {
show: {
resource: ['control'],
operation: ['unlinkObligation'],
},
},
default: '',
description: 'The ID of the control',
required: true,
},
{
displayName: 'Obligation ID',
name: 'obligationId',
type: 'string',
displayOptions: {
show: {
resource: ['control'],
operation: ['unlinkObligation'],
},
},
default: '',
description: 'The ID of the obligation to unlink',
required: true,
},
];
export async function execute(
this: IExecuteFunctions,
itemIndex: number,
): Promise<INodeExecutionData> {
const controlId = this.getNodeParameter('controlId', itemIndex) as string;
const obligationId = this.getNodeParameter('obligationId', itemIndex) as string;
const query = `
mutation DeleteControlObligationMapping($input: DeleteControlObligationMappingInput!) {
deleteControlObligationMapping(input: $input) {
deletedControlId
deletedObligationId
}
}
`;
const responseData = await proboApiRequest.call(this, query, { input: { controlId, obligationId } });
return {
json: responseData,
pairedItem: { item: itemIndex },
};
}

View File

@@ -0,0 +1,71 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import type { INodeProperties, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
import { proboApiRequest } from '../../GenericFunctions';
export const description: INodeProperties[] = [
{
displayName: 'Control ID',
name: 'controlId',
type: 'string',
displayOptions: {
show: {
resource: ['control'],
operation: ['unlinkSnapshot'],
},
},
default: '',
description: 'The ID of the control',
required: true,
},
{
displayName: 'Snapshot ID',
name: 'snapshotId',
type: 'string',
displayOptions: {
show: {
resource: ['control'],
operation: ['unlinkSnapshot'],
},
},
default: '',
description: 'The ID of the snapshot to unlink',
required: true,
},
];
export async function execute(
this: IExecuteFunctions,
itemIndex: number,
): Promise<INodeExecutionData> {
const controlId = this.getNodeParameter('controlId', itemIndex) as string;
const snapshotId = this.getNodeParameter('snapshotId', itemIndex) as string;
const query = `
mutation DeleteControlSnapshotMapping($input: DeleteControlSnapshotMappingInput!) {
deleteControlSnapshotMapping(input: $input) {
deletedControlId
deletedSnapshotId
}
}
`;
const responseData = await proboApiRequest.call(this, query, { input: { controlId, snapshotId } });
return {
json: responseData,
pairedItem: { item: itemIndex },
};
}

View File

@@ -0,0 +1,166 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import type { INodeProperties, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
import { proboApiRequest } from '../../GenericFunctions';
export const description: INodeProperties[] = [
{
displayName: 'Processing Activity ID',
name: 'processingActivityId',
type: 'string',
displayOptions: {
show: {
resource: ['dpia'],
operation: ['create'],
},
},
default: '',
description: 'The ID of the processing activity',
required: true,
},
{
displayName: 'Description',
name: 'description',
type: 'string',
displayOptions: {
show: {
resource: ['dpia'],
operation: ['create'],
},
},
default: '',
description: 'The description of the DPIA',
required: true,
},
{
displayName: 'Necessity and Proportionality',
name: 'necessityAndProportionality',
type: 'string',
displayOptions: {
show: {
resource: ['dpia'],
operation: ['create'],
},
},
default: '',
description: 'The necessity and proportionality assessment',
required: true,
},
{
displayName: 'Potential Risk',
name: 'potentialRisk',
type: 'string',
displayOptions: {
show: {
resource: ['dpia'],
operation: ['create'],
},
},
default: '',
description: 'The potential risk assessment',
required: true,
},
{
displayName: 'Mitigations',
name: 'mitigations',
type: 'string',
displayOptions: {
show: {
resource: ['dpia'],
operation: ['create'],
},
},
default: '',
description: 'The mitigations for the identified risks',
required: true,
},
{
displayName: 'Residual Risk',
name: 'residualRisk',
type: 'options',
displayOptions: {
show: {
resource: ['dpia'],
operation: ['create'],
},
},
options: [
{
name: 'Low',
value: 'LOW',
},
{
name: 'Medium',
value: 'MEDIUM',
},
{
name: 'High',
value: 'HIGH',
},
],
default: 'LOW',
description: 'The residual risk level',
required: true,
},
];
export async function execute(
this: IExecuteFunctions,
itemIndex: number,
): Promise<INodeExecutionData> {
const processingActivityId = this.getNodeParameter('processingActivityId', itemIndex) as string;
const description = this.getNodeParameter('description', itemIndex) as string;
const necessityAndProportionality = this.getNodeParameter('necessityAndProportionality', itemIndex) as string;
const potentialRisk = this.getNodeParameter('potentialRisk', itemIndex) as string;
const mitigations = this.getNodeParameter('mitigations', itemIndex) as string;
const residualRisk = this.getNodeParameter('residualRisk', itemIndex) as string;
const query = `
mutation CreateDataProtectionImpactAssessment($input: CreateDataProtectionImpactAssessmentInput!) {
createDataProtectionImpactAssessment(input: $input) {
dataProtectionImpactAssessmentEdge {
node {
id
description
necessityAndProportionality
potentialRisk
mitigations
residualRisk
createdAt
updatedAt
}
}
}
}
`;
const variables = {
input: {
processingActivityId,
description,
necessityAndProportionality,
potentialRisk,
mitigations,
residualRisk,
},
};
const responseData = await proboApiRequest.call(this, query, variables);
return {
json: responseData,
pairedItem: { item: itemIndex },
};
}

View File

@@ -0,0 +1,55 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import type { INodeProperties, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
import { proboApiRequest } from '../../GenericFunctions';
export const description: INodeProperties[] = [
{
displayName: 'DPIA ID',
name: 'dataProtectionImpactAssessmentId',
type: 'string',
displayOptions: {
show: {
resource: ['dpia'],
operation: ['delete'],
},
},
default: '',
description: 'The ID of the DPIA to delete',
required: true,
},
];
export async function execute(
this: IExecuteFunctions,
itemIndex: number,
): Promise<INodeExecutionData> {
const dataProtectionImpactAssessmentId = this.getNodeParameter('dataProtectionImpactAssessmentId', itemIndex) as string;
const query = `
mutation DeleteDataProtectionImpactAssessment($input: DeleteDataProtectionImpactAssessmentInput!) {
deleteDataProtectionImpactAssessment(input: $input) {
deletedDataProtectionImpactAssessmentId
}
}
`;
const responseData = await proboApiRequest.call(this, query, { input: { dataProtectionImpactAssessmentId } });
return {
json: responseData,
pairedItem: { item: itemIndex },
};
}

View File

@@ -0,0 +1,68 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import type { INodeProperties, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
import { proboApiRequest } from '../../GenericFunctions';
export const description: INodeProperties[] = [
{
displayName: 'DPIA ID',
name: 'dataProtectionImpactAssessmentId',
type: 'string',
displayOptions: {
show: {
resource: ['dpia'],
operation: ['get'],
},
},
default: '',
description: 'The ID of the DPIA',
required: true,
},
];
export async function execute(
this: IExecuteFunctions,
itemIndex: number,
): Promise<INodeExecutionData> {
const dataProtectionImpactAssessmentId = this.getNodeParameter('dataProtectionImpactAssessmentId', itemIndex) as string;
const query = `
query GetDataProtectionImpactAssessment($dataProtectionImpactAssessmentId: ID!) {
node(id: $dataProtectionImpactAssessmentId) {
... on DataProtectionImpactAssessment {
id
description
necessityAndProportionality
potentialRisk
mitigations
residualRisk
createdAt
updatedAt
}
}
}
`;
const variables = {
dataProtectionImpactAssessmentId,
};
const responseData = await proboApiRequest.call(this, query, variables);
return {
json: responseData,
pairedItem: { item: itemIndex },
};
}

View File

@@ -0,0 +1,117 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import type { INodeProperties, IExecuteFunctions, INodeExecutionData, IDataObject } from 'n8n-workflow';
import { proboApiRequestAllItems } from '../../GenericFunctions';
export const description: INodeProperties[] = [
{
displayName: 'Organization ID',
name: 'organizationId',
type: 'string',
displayOptions: {
show: {
resource: ['dpia'],
operation: ['getAll'],
},
},
default: '',
description: 'The ID of the organization',
required: true,
},
{
displayName: 'Return All',
name: 'returnAll',
type: 'boolean',
displayOptions: {
show: {
resource: ['dpia'],
operation: ['getAll'],
},
},
default: false,
description: 'Whether to return all results or only up to a given limit',
},
{
displayName: 'Limit',
name: 'limit',
type: 'number',
displayOptions: {
show: {
resource: ['dpia'],
operation: ['getAll'],
returnAll: [false],
},
},
typeOptions: {
minValue: 1,
},
default: 50,
description: 'Max number of results to return',
},
];
export async function execute(
this: IExecuteFunctions,
itemIndex: number,
): Promise<INodeExecutionData> {
const organizationId = this.getNodeParameter('organizationId', itemIndex) as string;
const returnAll = this.getNodeParameter('returnAll', itemIndex) as boolean;
const limit = this.getNodeParameter('limit', itemIndex, 50) as number;
const query = `
query GetDataProtectionImpactAssessments($organizationId: ID!, $first: Int, $after: CursorKey) {
node(id: $organizationId) {
... on Organization {
dataProtectionImpactAssessments(first: $first, after: $after) {
edges {
node {
id
description
necessityAndProportionality
potentialRisk
mitigations
residualRisk
createdAt
updatedAt
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
}
}
`;
const dataProtectionImpactAssessments = await proboApiRequestAllItems.call(
this,
query,
{ organizationId },
(response) => {
const data = response?.data as IDataObject | undefined;
const node = data?.node as IDataObject | undefined;
return node?.dataProtectionImpactAssessments as IDataObject | undefined;
},
returnAll,
limit,
);
return {
json: { dataProtectionImpactAssessments },
pairedItem: { item: itemIndex },
};
}

View File

@@ -0,0 +1,74 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import type { INodeProperties } from 'n8n-workflow';
import * as createOp from './create.operation';
import * as updateOp from './update.operation';
import * as deleteOp from './delete.operation';
import * as getOp from './get.operation';
import * as getAllOp from './getAll.operation';
export const description: INodeProperties[] = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
resource: ['dpia'],
},
},
options: [
{
name: 'Create',
value: 'create',
description: 'Create a new DPIA',
action: 'Create a DPIA',
},
{
name: 'Delete',
value: 'delete',
description: 'Delete a DPIA',
action: 'Delete a DPIA',
},
{
name: 'Get',
value: 'get',
description: 'Get a DPIA',
action: 'Get a DPIA',
},
{
name: 'Get Many',
value: 'getAll',
description: 'Get many DPIAs',
action: 'Get many dpias',
},
{
name: 'Update',
value: 'update',
description: 'Update an existing DPIA',
action: 'Update a DPIA',
},
],
default: 'create',
},
...createOp.description,
...updateOp.description,
...deleteOp.description,
...getOp.description,
...getAllOp.description,
];
export { createOp as create, updateOp as update, deleteOp as delete, getOp as get, getAllOp as getAll };

View File

@@ -0,0 +1,159 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import type { INodeProperties, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
import { proboApiRequest } from '../../GenericFunctions';
export const description: INodeProperties[] = [
{
displayName: 'DPIA ID',
name: 'id',
type: 'string',
displayOptions: {
show: {
resource: ['dpia'],
operation: ['update'],
},
},
default: '',
description: 'The ID of the DPIA to update',
required: true,
},
{
displayName: 'Description',
name: 'description',
type: 'string',
displayOptions: {
show: {
resource: ['dpia'],
operation: ['update'],
},
},
default: '',
description: 'The description of the DPIA',
},
{
displayName: 'Necessity and Proportionality',
name: 'necessityAndProportionality',
type: 'string',
displayOptions: {
show: {
resource: ['dpia'],
operation: ['update'],
},
},
default: '',
description: 'The necessity and proportionality assessment',
},
{
displayName: 'Potential Risk',
name: 'potentialRisk',
type: 'string',
displayOptions: {
show: {
resource: ['dpia'],
operation: ['update'],
},
},
default: '',
description: 'The potential risk assessment',
},
{
displayName: 'Mitigations',
name: 'mitigations',
type: 'string',
displayOptions: {
show: {
resource: ['dpia'],
operation: ['update'],
},
},
default: '',
description: 'The mitigations for the identified risks',
},
{
displayName: 'Residual Risk',
name: 'residualRisk',
type: 'options',
displayOptions: {
show: {
resource: ['dpia'],
operation: ['update'],
},
},
options: [
{
name: '(Unchanged)',
value: '',
},
{
name: 'Low',
value: 'LOW',
},
{
name: 'Medium',
value: 'MEDIUM',
},
{
name: 'High',
value: 'HIGH',
},
],
default: '',
description: 'The residual risk level',
},
];
export async function execute(
this: IExecuteFunctions,
itemIndex: number,
): Promise<INodeExecutionData> {
const id = this.getNodeParameter('id', itemIndex) as string;
const description = this.getNodeParameter('description', itemIndex, '') as string;
const necessityAndProportionality = this.getNodeParameter('necessityAndProportionality', itemIndex, '') as string;
const potentialRisk = this.getNodeParameter('potentialRisk', itemIndex, '') as string;
const mitigations = this.getNodeParameter('mitigations', itemIndex, '') as string;
const residualRisk = this.getNodeParameter('residualRisk', itemIndex, '') as string;
const query = `
mutation UpdateDataProtectionImpactAssessment($input: UpdateDataProtectionImpactAssessmentInput!) {
updateDataProtectionImpactAssessment(input: $input) {
dataProtectionImpactAssessment {
id
description
necessityAndProportionality
potentialRisk
mitigations
residualRisk
createdAt
updatedAt
}
}
}
`;
const input: Record<string, string> = { id };
if (description) input.description = description;
if (necessityAndProportionality) input.necessityAndProportionality = necessityAndProportionality;
if (potentialRisk) input.potentialRisk = potentialRisk;
if (mitigations) input.mitigations = mitigations;
if (residualRisk) input.residualRisk = residualRisk;
const responseData = await proboApiRequest.call(this, query, { input });
return {
json: responseData,
pairedItem: { item: itemIndex },
};
}

View File

@@ -0,0 +1,55 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import type { INodeProperties, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
import { proboApiRequest } from '../../GenericFunctions';
export const description: INodeProperties[] = [
{
displayName: 'Evidence ID',
name: 'evidenceId',
type: 'string',
displayOptions: {
show: {
resource: ['evidence'],
operation: ['delete'],
},
},
default: '',
description: 'The ID of the evidence to delete',
required: true,
},
];
export async function execute(
this: IExecuteFunctions,
itemIndex: number,
): Promise<INodeExecutionData> {
const evidenceId = this.getNodeParameter('evidenceId', itemIndex) as string;
const query = `
mutation DeleteEvidence($input: DeleteEvidenceInput!) {
deleteEvidence(input: $input) {
deletedEvidenceId
}
}
`;
const responseData = await proboApiRequest.call(this, query, { input: { evidenceId } });
return {
json: responseData,
pairedItem: { item: itemIndex },
};
}

View File

@@ -0,0 +1,66 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import type { INodeProperties, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
import { proboApiRequest } from '../../GenericFunctions';
export const description: INodeProperties[] = [
{
displayName: 'Evidence ID',
name: 'evidenceId',
type: 'string',
displayOptions: {
show: {
resource: ['evidence'],
operation: ['get'],
},
},
default: '',
description: 'The ID of the evidence',
required: true,
},
];
export async function execute(
this: IExecuteFunctions,
itemIndex: number,
): Promise<INodeExecutionData> {
const evidenceId = this.getNodeParameter('evidenceId', itemIndex) as string;
const query = `
query GetEvidence($evidenceId: ID!) {
node(id: $evidenceId) {
... on Evidence {
id
state
type
description
createdAt
updatedAt
}
}
}
`;
const variables = {
evidenceId,
};
const responseData = await proboApiRequest.call(this, query, variables);
return {
json: responseData,
pairedItem: { item: itemIndex },
};
}

View File

@@ -0,0 +1,115 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import type { INodeProperties, IExecuteFunctions, INodeExecutionData, IDataObject } from 'n8n-workflow';
import { proboApiRequestAllItems } from '../../GenericFunctions';
export const description: INodeProperties[] = [
{
displayName: 'Measure ID',
name: 'measureId',
type: 'string',
displayOptions: {
show: {
resource: ['evidence'],
operation: ['getAll'],
},
},
default: '',
description: 'The ID of the measure to list evidences for',
required: true,
},
{
displayName: 'Return All',
name: 'returnAll',
type: 'boolean',
displayOptions: {
show: {
resource: ['evidence'],
operation: ['getAll'],
},
},
default: false,
description: 'Whether to return all results or only up to a given limit',
},
{
displayName: 'Limit',
name: 'limit',
type: 'number',
displayOptions: {
show: {
resource: ['evidence'],
operation: ['getAll'],
returnAll: [false],
},
},
typeOptions: {
minValue: 1,
},
default: 50,
description: 'Max number of results to return',
},
];
export async function execute(
this: IExecuteFunctions,
itemIndex: number,
): Promise<INodeExecutionData> {
const measureId = this.getNodeParameter('measureId', itemIndex) as string;
const returnAll = this.getNodeParameter('returnAll', itemIndex) as boolean;
const limit = this.getNodeParameter('limit', itemIndex, 50) as number;
const query = `
query GetEvidences($measureId: ID!, $first: Int, $after: CursorKey) {
node(id: $measureId) {
... on Measure {
evidences(first: $first, after: $after) {
edges {
node {
id
state
type
description
createdAt
updatedAt
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
}
}
`;
const evidences = await proboApiRequestAllItems.call(
this,
query,
{ measureId },
(response) => {
const data = response?.data as IDataObject | undefined;
const node = data?.node as IDataObject | undefined;
return node?.evidences as IDataObject | undefined;
},
returnAll,
limit,
);
return {
json: { evidences },
pairedItem: { item: itemIndex },
};
}

View File

@@ -0,0 +1,66 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import type { INodeProperties } from 'n8n-workflow';
import * as deleteOp from './delete.operation';
import * as getOp from './get.operation';
import * as getAllOp from './getAll.operation';
import * as uploadOp from './upload.operation';
export const description: INodeProperties[] = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
resource: ['evidence'],
},
},
options: [
{
name: 'Delete',
value: 'delete',
description: 'Delete an evidence',
action: 'Delete an evidence',
},
{
name: 'Get',
value: 'get',
description: 'Get an evidence',
action: 'Get an evidence',
},
{
name: 'Get Many',
value: 'getAll',
description: 'Get many evidences for a measure',
action: 'Get many evidences',
},
{
name: 'Upload',
value: 'upload',
description: 'Upload evidence for a measure',
action: 'Upload evidence',
},
],
default: 'getAll',
},
...deleteOp.description,
...getOp.description,
...getAllOp.description,
...uploadOp.description,
];
export { deleteOp as delete, getOp as get, getAllOp as getAll, uploadOp as upload };

View File

@@ -0,0 +1,100 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import type { INodeProperties, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
import { proboApiMultipartRequest } from '../../GenericFunctions';
export const description: INodeProperties[] = [
{
displayName: 'Measure ID',
name: 'measureId',
type: 'string',
displayOptions: {
show: {
resource: ['evidence'],
operation: ['upload'],
},
},
default: '',
description: 'The ID of the measure to upload evidence for',
required: true,
},
{
displayName: 'Input Data Field Name',
name: 'binaryPropertyName',
type: 'string',
displayOptions: {
show: {
resource: ['evidence'],
operation: ['upload'],
},
},
default: 'data',
description: 'The name of the input field containing the binary file data to upload',
required: true,
},
];
export async function execute(
this: IExecuteFunctions,
itemIndex: number,
): Promise<INodeExecutionData> {
const measureId = this.getNodeParameter('measureId', itemIndex) as string;
const binaryPropertyName = this.getNodeParameter('binaryPropertyName', itemIndex) as string;
const binaryData = this.helpers.assertBinaryData(itemIndex, binaryPropertyName);
const fileBuffer = await this.helpers.getBinaryDataBuffer(itemIndex, binaryPropertyName);
const fileName = binaryData.fileName || 'evidence';
const mimeType = binaryData.mimeType || 'application/octet-stream';
const query = `
mutation UploadMeasureEvidence($input: UploadMeasureEvidenceInput!) {
uploadMeasureEvidence(input: $input) {
evidenceEdge {
node {
id
state
type
description
createdAt
updatedAt
}
}
}
}
`;
const variables = {
input: {
measureId,
file: null,
},
};
const responseData = await proboApiMultipartRequest.call(
this,
query,
variables,
'variables.input.file',
fileBuffer,
fileName,
mimeType,
);
return {
json: responseData,
pairedItem: { item: itemIndex },
};
}

View File

@@ -0,0 +1,272 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import type { INodeProperties, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
import { proboApiRequest } from '../../GenericFunctions';
export const description: INodeProperties[] = [
{
displayName: 'Organization ID',
name: 'organizationId',
type: 'string',
displayOptions: {
show: {
resource: ['finding'],
operation: ['create'],
},
},
default: '',
description: 'The ID of the organization',
required: true,
},
{
displayName: 'Kind',
name: 'kind',
type: 'options',
displayOptions: {
show: {
resource: ['finding'],
operation: ['create'],
},
},
options: [
{
name: 'Minor Nonconformity',
value: 'MINOR_NONCONFORMITY',
},
{
name: 'Major Nonconformity',
value: 'MAJOR_NONCONFORMITY',
},
{
name: 'Observation',
value: 'OBSERVATION',
},
{
name: 'Exception',
value: 'EXCEPTION',
},
],
default: 'MINOR_NONCONFORMITY',
description: 'The kind of finding',
required: true,
},
{
displayName: 'Description',
name: 'description',
type: 'string',
displayOptions: {
show: {
resource: ['finding'],
operation: ['create'],
},
},
default: '',
description: 'The description of the finding',
required: true,
},
{
displayName: 'Additional Fields',
name: 'additionalFields',
type: 'collection',
placeholder: 'Add Field',
default: {},
displayOptions: {
show: {
resource: ['finding'],
operation: ['create'],
},
},
options: [
{
displayName: 'Corrective Action',
name: 'correctiveAction',
type: 'string',
default: '',
description: 'The corrective action for the finding',
},
{
displayName: 'Due Date',
name: 'dueDate',
type: 'string',
default: '',
description: 'The due date for the finding (ISO 8601 format)',
},
{
displayName: 'Effectiveness Check',
name: 'effectivenessCheck',
type: 'string',
default: '',
description: 'The effectiveness check for the finding',
},
{
displayName: 'Identified On',
name: 'identifiedOn',
type: 'string',
default: '',
description: 'The date the finding was identified (ISO 8601 format)',
},
{
displayName: 'Owner ID',
name: 'ownerId',
type: 'string',
default: '',
description: 'The ID of the person who owns this finding',
},
{
displayName: 'Priority',
name: 'priority',
type: 'options',
options: [
{
name: 'Low',
value: 'LOW',
},
{
name: 'Medium',
value: 'MEDIUM',
},
{
name: 'High',
value: 'HIGH',
},
],
default: 'MEDIUM',
description: 'The priority of the finding',
},
{
displayName: 'Risk ID',
name: 'riskId',
type: 'string',
default: '',
description: 'The ID of the associated risk',
},
{
displayName: 'Root Cause',
name: 'rootCause',
type: 'string',
default: '',
description: 'The root cause of the finding',
},
{
displayName: 'Source',
name: 'source',
type: 'string',
default: '',
description: 'The source of the finding',
},
{
displayName: 'Status',
name: 'status',
type: 'options',
options: [
{
name: 'Closed',
value: 'CLOSED',
},
{
name: 'False Positive',
value: 'FALSE_POSITIVE',
},
{
name: 'In Progress',
value: 'IN_PROGRESS',
},
{
name: 'Mitigated',
value: 'MITIGATED',
},
{
name: 'Open',
value: 'OPEN',
},
{
name: 'Risk Accepted',
value: 'RISK_ACCEPTED',
},
],
default: 'OPEN',
description: 'The status of the finding',
},
],
},
];
export async function execute(
this: IExecuteFunctions,
itemIndex: number,
): Promise<INodeExecutionData> {
const organizationId = this.getNodeParameter('organizationId', itemIndex) as string;
const kind = this.getNodeParameter('kind', itemIndex) as string;
const description = this.getNodeParameter('description', itemIndex) as string;
const additionalFields = this.getNodeParameter('additionalFields', itemIndex, {}) as {
source?: string;
identifiedOn?: string;
rootCause?: string;
correctiveAction?: string;
ownerId?: string;
dueDate?: string;
status?: string;
priority?: string;
riskId?: string;
effectivenessCheck?: string;
};
const query = `
mutation CreateFinding($input: CreateFindingInput!) {
createFinding(input: $input) {
findingEdge {
node {
id
kind
description
source
identifiedOn
rootCause
correctiveAction
dueDate
status
priority
effectivenessCheck
createdAt
updatedAt
}
}
}
}
`;
const input: Record<string, unknown> = {
organizationId,
kind,
description,
};
if (additionalFields.source) input.source = additionalFields.source;
if (additionalFields.identifiedOn) input.identifiedOn = additionalFields.identifiedOn;
if (additionalFields.rootCause) input.rootCause = additionalFields.rootCause;
if (additionalFields.correctiveAction) input.correctiveAction = additionalFields.correctiveAction;
if (additionalFields.ownerId) input.ownerId = additionalFields.ownerId;
if (additionalFields.dueDate) input.dueDate = additionalFields.dueDate;
if (additionalFields.status) input.status = additionalFields.status;
if (additionalFields.priority) input.priority = additionalFields.priority;
if (additionalFields.riskId) input.riskId = additionalFields.riskId;
if (additionalFields.effectivenessCheck) input.effectivenessCheck = additionalFields.effectivenessCheck;
const responseData = await proboApiRequest.call(this, query, { input });
return {
json: responseData,
pairedItem: { item: itemIndex },
};
}

View File

@@ -0,0 +1,55 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import type { INodeProperties, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
import { proboApiRequest } from '../../GenericFunctions';
export const description: INodeProperties[] = [
{
displayName: 'Finding ID',
name: 'findingId',
type: 'string',
displayOptions: {
show: {
resource: ['finding'],
operation: ['delete'],
},
},
default: '',
description: 'The ID of the finding to delete',
required: true,
},
];
export async function execute(
this: IExecuteFunctions,
itemIndex: number,
): Promise<INodeExecutionData> {
const findingId = this.getNodeParameter('findingId', itemIndex) as string;
const query = `
mutation DeleteFinding($input: DeleteFindingInput!) {
deleteFinding(input: $input) {
deletedFindingId
}
}
`;
const responseData = await proboApiRequest.call(this, query, { input: { findingId } });
return {
json: responseData,
pairedItem: { item: itemIndex },
};
}

View File

@@ -0,0 +1,73 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import type { INodeProperties, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
import { proboApiRequest } from '../../GenericFunctions';
export const description: INodeProperties[] = [
{
displayName: 'Finding ID',
name: 'findingId',
type: 'string',
displayOptions: {
show: {
resource: ['finding'],
operation: ['get'],
},
},
default: '',
description: 'The ID of the finding',
required: true,
},
];
export async function execute(
this: IExecuteFunctions,
itemIndex: number,
): Promise<INodeExecutionData> {
const findingId = this.getNodeParameter('findingId', itemIndex) as string;
const query = `
query GetFinding($findingId: ID!) {
node(id: $findingId) {
... on Finding {
id
kind
description
source
identifiedOn
rootCause
correctiveAction
dueDate
status
priority
effectivenessCheck
createdAt
updatedAt
}
}
}
`;
const variables = {
findingId,
};
const responseData = await proboApiRequest.call(this, query, variables);
return {
json: responseData,
pairedItem: { item: itemIndex },
};
}

View File

@@ -0,0 +1,122 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import type { INodeProperties, IExecuteFunctions, INodeExecutionData, IDataObject } from 'n8n-workflow';
import { proboApiRequestAllItems } from '../../GenericFunctions';
export const description: INodeProperties[] = [
{
displayName: 'Organization ID',
name: 'organizationId',
type: 'string',
displayOptions: {
show: {
resource: ['finding'],
operation: ['getAll'],
},
},
default: '',
description: 'The ID of the organization',
required: true,
},
{
displayName: 'Return All',
name: 'returnAll',
type: 'boolean',
displayOptions: {
show: {
resource: ['finding'],
operation: ['getAll'],
},
},
default: false,
description: 'Whether to return all results or only up to a given limit',
},
{
displayName: 'Limit',
name: 'limit',
type: 'number',
displayOptions: {
show: {
resource: ['finding'],
operation: ['getAll'],
returnAll: [false],
},
},
typeOptions: {
minValue: 1,
},
default: 50,
description: 'Max number of results to return',
},
];
export async function execute(
this: IExecuteFunctions,
itemIndex: number,
): Promise<INodeExecutionData> {
const organizationId = this.getNodeParameter('organizationId', itemIndex) as string;
const returnAll = this.getNodeParameter('returnAll', itemIndex) as boolean;
const limit = this.getNodeParameter('limit', itemIndex, 50) as number;
const query = `
query GetFindings($organizationId: ID!, $first: Int, $after: CursorKey) {
node(id: $organizationId) {
... on Organization {
findings(first: $first, after: $after) {
edges {
node {
id
kind
description
source
identifiedOn
rootCause
correctiveAction
dueDate
status
priority
effectivenessCheck
createdAt
updatedAt
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
}
}
`;
const findings = await proboApiRequestAllItems.call(
this,
query,
{ organizationId },
(response) => {
const data = response?.data as IDataObject | undefined;
const node = data?.node as IDataObject | undefined;
return node?.findings as IDataObject | undefined;
},
returnAll,
limit,
);
return {
json: { findings },
pairedItem: { item: itemIndex },
};
}

View File

@@ -0,0 +1,98 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import type { INodeProperties } from 'n8n-workflow';
import * as createOp from './create.operation';
import * as updateOp from './update.operation';
import * as deleteOp from './delete.operation';
import * as getOp from './get.operation';
import * as getAllOp from './getAll.operation';
import * as linkAuditOp from './linkAudit.operation';
import * as unlinkAuditOp from './unlinkAudit.operation';
export const description: INodeProperties[] = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
resource: ['finding'],
},
},
options: [
{
name: 'Create',
value: 'create',
description: 'Create a new finding',
action: 'Create a finding',
},
{
name: 'Delete',
value: 'delete',
description: 'Delete a finding',
action: 'Delete a finding',
},
{
name: 'Get',
value: 'get',
description: 'Get a finding',
action: 'Get a finding',
},
{
name: 'Get Many',
value: 'getAll',
description: 'Get many findings',
action: 'Get many findings',
},
{
name: 'Link Audit',
value: 'linkAudit',
description: 'Link an audit to a finding',
action: 'Link an audit to a finding',
},
{
name: 'Unlink Audit',
value: 'unlinkAudit',
description: 'Unlink an audit from a finding',
action: 'Unlink an audit from a finding',
},
{
name: 'Update',
value: 'update',
description: 'Update an existing finding',
action: 'Update a finding',
},
],
default: 'create',
},
...createOp.description,
...updateOp.description,
...deleteOp.description,
...getOp.description,
...getAllOp.description,
...linkAuditOp.description,
...unlinkAuditOp.description,
];
export {
createOp as create,
updateOp as update,
deleteOp as delete,
getOp as get,
getAllOp as getAll,
linkAuditOp as linkAudit,
unlinkAuditOp as unlinkAudit,
};

View File

@@ -0,0 +1,87 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import type { INodeProperties, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
import { proboApiRequest } from '../../GenericFunctions';
export const description: INodeProperties[] = [
{
displayName: 'Finding ID',
name: 'findingId',
type: 'string',
displayOptions: {
show: {
resource: ['finding'],
operation: ['linkAudit'],
},
},
default: '',
description: 'The ID of the finding',
required: true,
},
{
displayName: 'Audit ID',
name: 'auditId',
type: 'string',
displayOptions: {
show: {
resource: ['finding'],
operation: ['linkAudit'],
},
},
default: '',
description: 'The ID of the audit to link',
required: true,
},
{
displayName: 'Reference ID',
name: 'referenceId',
type: 'string',
displayOptions: {
show: {
resource: ['finding'],
operation: ['linkAudit'],
},
},
default: '',
description: 'The reference ID for the mapping',
required: true,
},
];
export async function execute(
this: IExecuteFunctions,
itemIndex: number,
): Promise<INodeExecutionData> {
const findingId = this.getNodeParameter('findingId', itemIndex) as string;
const auditId = this.getNodeParameter('auditId', itemIndex) as string;
const referenceId = this.getNodeParameter('referenceId', itemIndex) as string;
const query = `
mutation CreateFindingAuditMapping($input: CreateFindingAuditMappingInput!) {
createFindingAuditMapping(input: $input) {
finding {
id
}
}
}
`;
const responseData = await proboApiRequest.call(this, query, { input: { findingId, auditId, referenceId } });
return {
json: responseData,
pairedItem: { item: itemIndex },
};
}

View File

@@ -0,0 +1,72 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import type { INodeProperties, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
import { proboApiRequest } from '../../GenericFunctions';
export const description: INodeProperties[] = [
{
displayName: 'Finding ID',
name: 'findingId',
type: 'string',
displayOptions: {
show: {
resource: ['finding'],
operation: ['unlinkAudit'],
},
},
default: '',
description: 'The ID of the finding',
required: true,
},
{
displayName: 'Audit ID',
name: 'auditId',
type: 'string',
displayOptions: {
show: {
resource: ['finding'],
operation: ['unlinkAudit'],
},
},
default: '',
description: 'The ID of the audit to unlink',
required: true,
},
];
export async function execute(
this: IExecuteFunctions,
itemIndex: number,
): Promise<INodeExecutionData> {
const findingId = this.getNodeParameter('findingId', itemIndex) as string;
const auditId = this.getNodeParameter('auditId', itemIndex) as string;
const query = `
mutation DeleteFindingAuditMapping($input: DeleteFindingAuditMappingInput!) {
deleteFindingAuditMapping(input: $input) {
finding {
id
}
}
}
`;
const responseData = await proboApiRequest.call(this, query, { input: { findingId, auditId } });
return {
json: responseData,
pairedItem: { item: itemIndex },
};
}

View File

@@ -0,0 +1,235 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import type { INodeProperties, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
import { proboApiRequest } from '../../GenericFunctions';
export const description: INodeProperties[] = [
{
displayName: 'Finding ID',
name: 'findingId',
type: 'string',
displayOptions: {
show: {
resource: ['finding'],
operation: ['update'],
},
},
default: '',
description: 'The ID of the finding to update',
required: true,
},
{
displayName: 'Additional Fields',
name: 'additionalFields',
type: 'collection',
placeholder: 'Add Field',
default: {},
displayOptions: {
show: {
resource: ['finding'],
operation: ['update'],
},
},
options: [
{
displayName: 'Corrective Action',
name: 'correctiveAction',
type: 'string',
default: '',
description: 'The corrective action for the finding',
},
{
displayName: 'Description',
name: 'description',
type: 'string',
default: '',
description: 'The description of the finding',
},
{
displayName: 'Due Date',
name: 'dueDate',
type: 'string',
default: '',
description: 'The due date for the finding (ISO 8601 format)',
},
{
displayName: 'Effectiveness Check',
name: 'effectivenessCheck',
type: 'string',
default: '',
description: 'The effectiveness check for the finding',
},
{
displayName: 'Identified On',
name: 'identifiedOn',
type: 'string',
default: '',
description: 'The date the finding was identified (ISO 8601 format)',
},
{
displayName: 'Owner ID',
name: 'ownerId',
type: 'string',
default: '',
description: 'The ID of the person who owns this finding',
},
{
displayName: 'Priority',
name: 'priority',
type: 'options',
options: [
{
name: '(Unchanged)',
value: '',
},
{
name: 'Low',
value: 'LOW',
},
{
name: 'Medium',
value: 'MEDIUM',
},
{
name: 'High',
value: 'HIGH',
},
],
default: '',
description: 'The priority of the finding',
},
{
displayName: 'Risk ID',
name: 'riskId',
type: 'string',
default: '',
description: 'The ID of the associated risk',
},
{
displayName: 'Root Cause',
name: 'rootCause',
type: 'string',
default: '',
description: 'The root cause of the finding',
},
{
displayName: 'Source',
name: 'source',
type: 'string',
default: '',
description: 'The source of the finding',
},
{
displayName: 'Status',
name: 'status',
type: 'options',
options: [
{
name: '(Unchanged)',
value: '',
},
{
name: 'Closed',
value: 'CLOSED',
},
{
name: 'False Positive',
value: 'FALSE_POSITIVE',
},
{
name: 'In Progress',
value: 'IN_PROGRESS',
},
{
name: 'Mitigated',
value: 'MITIGATED',
},
{
name: 'Open',
value: 'OPEN',
},
{
name: 'Risk Accepted',
value: 'RISK_ACCEPTED',
},
],
default: '',
description: 'The status of the finding',
},
],
},
];
export async function execute(
this: IExecuteFunctions,
itemIndex: number,
): Promise<INodeExecutionData> {
const findingId = this.getNodeParameter('findingId', itemIndex) as string;
const additionalFields = this.getNodeParameter('additionalFields', itemIndex, {}) as {
description?: string;
source?: string;
identifiedOn?: string;
rootCause?: string;
correctiveAction?: string;
ownerId?: string;
dueDate?: string;
status?: string;
priority?: string;
riskId?: string;
effectivenessCheck?: string;
};
const query = `
mutation UpdateFinding($input: UpdateFindingInput!) {
updateFinding(input: $input) {
finding {
id
kind
description
source
identifiedOn
rootCause
correctiveAction
dueDate
status
priority
effectivenessCheck
createdAt
updatedAt
}
}
}
`;
const input: Record<string, unknown> = { id: findingId };
if (additionalFields.description !== undefined) input.description = additionalFields.description === '' ? null : additionalFields.description;
if (additionalFields.source !== undefined) input.source = additionalFields.source === '' ? null : additionalFields.source;
if (additionalFields.identifiedOn !== undefined) input.identifiedOn = additionalFields.identifiedOn === '' ? null : additionalFields.identifiedOn;
if (additionalFields.rootCause !== undefined) input.rootCause = additionalFields.rootCause === '' ? null : additionalFields.rootCause;
if (additionalFields.correctiveAction !== undefined) input.correctiveAction = additionalFields.correctiveAction === '' ? null : additionalFields.correctiveAction;
if (additionalFields.ownerId !== undefined) input.ownerId = additionalFields.ownerId === '' ? null : additionalFields.ownerId;
if (additionalFields.dueDate !== undefined) input.dueDate = additionalFields.dueDate === '' ? null : additionalFields.dueDate;
if (additionalFields.status !== undefined) input.status = additionalFields.status;
if (additionalFields.priority !== undefined) input.priority = additionalFields.priority;
if (additionalFields.riskId !== undefined) input.riskId = additionalFields.riskId === '' ? null : additionalFields.riskId;
if (additionalFields.effectivenessCheck !== undefined) input.effectivenessCheck = additionalFields.effectivenessCheck === '' ? null : additionalFields.effectivenessCheck;
const responseData = await proboApiRequest.call(this, query, { input });
return {
json: responseData,
pairedItem: { item: itemIndex },
};
}

View File

@@ -13,18 +13,31 @@
// PERFORMANCE OF THIS SOFTWARE.
import type { IExecuteFunctions, INodeExecutionData, INodeProperties } from 'n8n-workflow';
import * as accessReview from './accessReview';
import * as asset from './asset';
import * as audit from './audit';
import * as auditLog from './auditLog';
import * as control from './control';
import * as datum from './datum';
import * as document from './document';
import * as dpia from './dpia';
import * as evidence from './evidence';
import * as execute from './execute';
import * as finding from './finding';
import * as framework from './framework';
import * as measure from './measure';
import * as obligation from './obligation';
import * as organization from './organization';
import * as organizationContext from './organizationContext';
import * as processingActivity from './processingActivity';
import * as rightsRequest from './rightsRequest';
import * as user from './user';
import * as risk from './risk';
import * as snapshot from './snapshot';
import * as statementOfApplicability from './statementOfApplicability';
import * as task from './task';
import * as tia from './tia';
import * as trustCenter from './trustCenter';
import * as vendor from './vendor';
import * as webhook from './webhook';
@@ -39,18 +52,31 @@ export interface OperationModule {
}
export const resources: Record<string, ResourceModule> = {
accessReview: accessReview as ResourceModule,
asset: asset as ResourceModule,
audit: audit as ResourceModule,
auditLog: auditLog as ResourceModule,
control: control as ResourceModule,
datum: datum as ResourceModule,
document: document as ResourceModule,
dpia: dpia as ResourceModule,
evidence: evidence as ResourceModule,
execute: execute as ResourceModule,
finding: finding as ResourceModule,
framework: framework as ResourceModule,
measure: measure as ResourceModule,
obligation: obligation as ResourceModule,
organization: organization as ResourceModule,
organizationContext: organizationContext as ResourceModule,
processingActivity: processingActivity as ResourceModule,
rightsRequest: rightsRequest as ResourceModule,
user: user as ResourceModule,
risk: risk as ResourceModule,
snapshot: snapshot as ResourceModule,
statementOfApplicability: statementOfApplicability as ResourceModule,
task: task as ResourceModule,
tia: tia as ResourceModule,
trustCenter: trustCenter as ResourceModule,
vendor: vendor as ResourceModule,
webhook: webhook as ResourceModule,
};

View File

@@ -18,6 +18,8 @@ import * as updateOp from './update.operation';
import * as deleteOp from './delete.operation';
import * as getOp from './get.operation';
import * as getAllOp from './getAll.operation';
import * as linkDocumentOp from './linkDocument.operation';
import * as unlinkDocumentOp from './unlinkDocument.operation';
export const description: INodeProperties[] = [
{
@@ -55,6 +57,18 @@ export const description: INodeProperties[] = [
description: 'Get many measures',
action: 'Get many measures',
},
{
name: 'Link Document',
value: 'linkDocument',
description: 'Link a document to a measure',
action: 'Link a document to a measure',
},
{
name: 'Unlink Document',
value: 'unlinkDocument',
description: 'Unlink a document from a measure',
action: 'Unlink a document from a measure',
},
{
name: 'Update',
value: 'update',
@@ -69,6 +83,16 @@ export const description: INodeProperties[] = [
...deleteOp.description,
...getOp.description,
...getAllOp.description,
...linkDocumentOp.description,
...unlinkDocumentOp.description,
];
export { createOp as create, updateOp as update, deleteOp as delete, getOp as get, getAllOp as getAll };
export {
createOp as create,
updateOp as update,
deleteOp as delete,
getOp as get,
getAllOp as getAll,
linkDocumentOp as linkDocument,
unlinkDocumentOp as unlinkDocument,
};

View File

@@ -0,0 +1,81 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import type { INodeProperties, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
import { proboApiRequest } from '../../GenericFunctions';
export const description: INodeProperties[] = [
{
displayName: 'Measure ID',
name: 'measureId',
type: 'string',
displayOptions: {
show: {
resource: ['measure'],
operation: ['linkDocument'],
},
},
default: '',
description: 'The ID of the measure',
required: true,
},
{
displayName: 'Document ID',
name: 'documentId',
type: 'string',
displayOptions: {
show: {
resource: ['measure'],
operation: ['linkDocument'],
},
},
default: '',
description: 'The ID of the document to link',
required: true,
},
];
export async function execute(
this: IExecuteFunctions,
itemIndex: number,
): Promise<INodeExecutionData> {
const measureId = this.getNodeParameter('measureId', itemIndex) as string;
const documentId = this.getNodeParameter('documentId', itemIndex) as string;
const query = `
mutation CreateMeasureDocumentMapping($input: CreateMeasureDocumentMappingInput!) {
createMeasureDocumentMapping(input: $input) {
measureEdge {
node {
id
name
}
}
documentEdge {
node {
id
title
}
}
}
}
`;
const responseData = await proboApiRequest.call(this, query, { input: { measureId, documentId } });
return {
json: responseData,
pairedItem: { item: itemIndex },
};
}

View File

@@ -0,0 +1,71 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import type { INodeProperties, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
import { proboApiRequest } from '../../GenericFunctions';
export const description: INodeProperties[] = [
{
displayName: 'Measure ID',
name: 'measureId',
type: 'string',
displayOptions: {
show: {
resource: ['measure'],
operation: ['unlinkDocument'],
},
},
default: '',
description: 'The ID of the measure',
required: true,
},
{
displayName: 'Document ID',
name: 'documentId',
type: 'string',
displayOptions: {
show: {
resource: ['measure'],
operation: ['unlinkDocument'],
},
},
default: '',
description: 'The ID of the document to unlink',
required: true,
},
];
export async function execute(
this: IExecuteFunctions,
itemIndex: number,
): Promise<INodeExecutionData> {
const measureId = this.getNodeParameter('measureId', itemIndex) as string;
const documentId = this.getNodeParameter('documentId', itemIndex) as string;
const query = `
mutation DeleteMeasureDocumentMapping($input: DeleteMeasureDocumentMappingInput!) {
deleteMeasureDocumentMapping(input: $input) {
deletedMeasureId
deletedDocumentId
}
}
`;
const responseData = await proboApiRequest.call(this, query, { input: { measureId, documentId } });
return {
json: responseData,
pairedItem: { item: itemIndex },
};
}

View File

@@ -0,0 +1,253 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import type { INodeProperties, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
import { proboApiRequest } from '../../GenericFunctions';
export const description: INodeProperties[] = [
{
displayName: 'Organization ID',
name: 'organizationId',
type: 'string',
displayOptions: {
show: {
resource: ['obligation'],
operation: ['create'],
},
},
default: '',
description: 'The ID of the organization',
required: true,
},
{
displayName: 'Area',
name: 'area',
type: 'string',
displayOptions: {
show: {
resource: ['obligation'],
operation: ['create'],
},
},
default: '',
description: 'The area of the obligation',
required: true,
},
{
displayName: 'Source',
name: 'source',
type: 'string',
displayOptions: {
show: {
resource: ['obligation'],
operation: ['create'],
},
},
default: '',
description: 'The source of the obligation',
required: true,
},
{
displayName: 'Requirement',
name: 'requirement',
type: 'string',
displayOptions: {
show: {
resource: ['obligation'],
operation: ['create'],
},
},
default: '',
description: 'The requirement of the obligation',
required: true,
},
{
displayName: 'Actions to Be Implemented',
name: 'actionsToBeImplemented',
type: 'string',
displayOptions: {
show: {
resource: ['obligation'],
operation: ['create'],
},
},
default: '',
description: 'The actions to be implemented for the obligation',
},
{
displayName: 'Regulator',
name: 'regulator',
type: 'string',
displayOptions: {
show: {
resource: ['obligation'],
operation: ['create'],
},
},
default: '',
description: 'The regulator of the obligation',
},
{
displayName: 'Owner ID',
name: 'ownerId',
type: 'string',
displayOptions: {
show: {
resource: ['obligation'],
operation: ['create'],
},
},
default: '',
description: 'The ID of the owner',
},
{
displayName: 'Last Review Date',
name: 'lastReviewDate',
type: 'string',
displayOptions: {
show: {
resource: ['obligation'],
operation: ['create'],
},
},
default: '',
description: 'The last review date of the obligation',
},
{
displayName: 'Due Date',
name: 'dueDate',
type: 'string',
displayOptions: {
show: {
resource: ['obligation'],
operation: ['create'],
},
},
default: '',
description: 'The due date of the obligation',
},
{
displayName: 'Status',
name: 'status',
type: 'options',
displayOptions: {
show: {
resource: ['obligation'],
operation: ['create'],
},
},
options: [
{
name: 'Non Compliant',
value: 'NON_COMPLIANT',
},
{
name: 'Partially Compliant',
value: 'PARTIALLY_COMPLIANT',
},
{
name: 'Compliant',
value: 'COMPLIANT',
},
],
default: 'NON_COMPLIANT',
description: 'The status of the obligation',
},
{
displayName: 'Type',
name: 'type',
type: 'options',
displayOptions: {
show: {
resource: ['obligation'],
operation: ['create'],
},
},
options: [
{
name: 'Legal',
value: 'LEGAL',
},
{
name: 'Contractual',
value: 'CONTRACTUAL',
},
],
default: 'LEGAL',
description: 'The type of the obligation',
},
];
export async function execute(
this: IExecuteFunctions,
itemIndex: number,
): Promise<INodeExecutionData> {
const organizationId = this.getNodeParameter('organizationId', itemIndex) as string;
const area = this.getNodeParameter('area', itemIndex) as string;
const source = this.getNodeParameter('source', itemIndex) as string;
const requirement = this.getNodeParameter('requirement', itemIndex) as string;
const actionsToBeImplemented = this.getNodeParameter('actionsToBeImplemented', itemIndex, '') as string;
const regulator = this.getNodeParameter('regulator', itemIndex, '') as string;
const ownerId = this.getNodeParameter('ownerId', itemIndex, '') as string;
const lastReviewDate = this.getNodeParameter('lastReviewDate', itemIndex, '') as string;
const dueDate = this.getNodeParameter('dueDate', itemIndex, '') as string;
const status = this.getNodeParameter('status', itemIndex, '') as string;
const type = this.getNodeParameter('type', itemIndex, '') as string;
const query = `
mutation CreateObligation($input: CreateObligationInput!) {
createObligation(input: $input) {
obligationEdge {
node {
id
area
source
requirement
actionsToBeImplemented
regulator
lastReviewDate
dueDate
status
type
createdAt
updatedAt
}
}
}
}
`;
const variables = {
input: {
organizationId,
area,
source,
requirement,
...(actionsToBeImplemented && { actionsToBeImplemented }),
...(regulator && { regulator }),
...(ownerId && { ownerId }),
...(lastReviewDate && { lastReviewDate }),
...(dueDate && { dueDate }),
...(status && { status }),
...(type && { type }),
},
};
const responseData = await proboApiRequest.call(this, query, variables);
return {
json: responseData,
pairedItem: { item: itemIndex },
};
}

View File

@@ -0,0 +1,55 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import type { INodeProperties, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
import { proboApiRequest } from '../../GenericFunctions';
export const description: INodeProperties[] = [
{
displayName: 'Obligation ID',
name: 'obligationId',
type: 'string',
displayOptions: {
show: {
resource: ['obligation'],
operation: ['delete'],
},
},
default: '',
description: 'The ID of the obligation to delete',
required: true,
},
];
export async function execute(
this: IExecuteFunctions,
itemIndex: number,
): Promise<INodeExecutionData> {
const obligationId = this.getNodeParameter('obligationId', itemIndex) as string;
const query = `
mutation DeleteObligation($input: DeleteObligationInput!) {
deleteObligation(input: $input) {
deletedObligationId
}
}
`;
const responseData = await proboApiRequest.call(this, query, { input: { obligationId } });
return {
json: responseData,
pairedItem: { item: itemIndex },
};
}

View File

@@ -0,0 +1,72 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import type { INodeProperties, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
import { proboApiRequest } from '../../GenericFunctions';
export const description: INodeProperties[] = [
{
displayName: 'Obligation ID',
name: 'obligationId',
type: 'string',
displayOptions: {
show: {
resource: ['obligation'],
operation: ['get'],
},
},
default: '',
description: 'The ID of the obligation',
required: true,
},
];
export async function execute(
this: IExecuteFunctions,
itemIndex: number,
): Promise<INodeExecutionData> {
const obligationId = this.getNodeParameter('obligationId', itemIndex) as string;
const query = `
query GetObligation($obligationId: ID!) {
node(id: $obligationId) {
... on Obligation {
id
area
source
requirement
actionsToBeImplemented
regulator
lastReviewDate
dueDate
status
type
createdAt
updatedAt
}
}
}
`;
const variables = {
obligationId,
};
const responseData = await proboApiRequest.call(this, query, variables);
return {
json: responseData,
pairedItem: { item: itemIndex },
};
}

View File

@@ -0,0 +1,121 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import type { INodeProperties, IExecuteFunctions, INodeExecutionData, IDataObject } from 'n8n-workflow';
import { proboApiRequestAllItems } from '../../GenericFunctions';
export const description: INodeProperties[] = [
{
displayName: 'Organization ID',
name: 'organizationId',
type: 'string',
displayOptions: {
show: {
resource: ['obligation'],
operation: ['getAll'],
},
},
default: '',
description: 'The ID of the organization',
required: true,
},
{
displayName: 'Return All',
name: 'returnAll',
type: 'boolean',
displayOptions: {
show: {
resource: ['obligation'],
operation: ['getAll'],
},
},
default: false,
description: 'Whether to return all results or only up to a given limit',
},
{
displayName: 'Limit',
name: 'limit',
type: 'number',
displayOptions: {
show: {
resource: ['obligation'],
operation: ['getAll'],
returnAll: [false],
},
},
typeOptions: {
minValue: 1,
},
default: 50,
description: 'Max number of results to return',
},
];
export async function execute(
this: IExecuteFunctions,
itemIndex: number,
): Promise<INodeExecutionData> {
const organizationId = this.getNodeParameter('organizationId', itemIndex) as string;
const returnAll = this.getNodeParameter('returnAll', itemIndex) as boolean;
const limit = this.getNodeParameter('limit', itemIndex, 50) as number;
const query = `
query GetObligations($organizationId: ID!, $first: Int, $after: CursorKey) {
node(id: $organizationId) {
... on Organization {
obligations(first: $first, after: $after) {
edges {
node {
id
area
source
requirement
actionsToBeImplemented
regulator
lastReviewDate
dueDate
status
type
createdAt
updatedAt
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
}
}
`;
const obligations = await proboApiRequestAllItems.call(
this,
query,
{ organizationId },
(response) => {
const data = response?.data as IDataObject | undefined;
const node = data?.node as IDataObject | undefined;
return node?.obligations as IDataObject | undefined;
},
returnAll,
limit,
);
return {
json: { obligations },
pairedItem: { item: itemIndex },
};
}

View File

@@ -0,0 +1,74 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import type { INodeProperties } from 'n8n-workflow';
import * as createOp from './create.operation';
import * as updateOp from './update.operation';
import * as deleteOp from './delete.operation';
import * as getOp from './get.operation';
import * as getAllOp from './getAll.operation';
export const description: INodeProperties[] = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
resource: ['obligation'],
},
},
options: [
{
name: 'Create',
value: 'create',
description: 'Create a new obligation',
action: 'Create an obligation',
},
{
name: 'Delete',
value: 'delete',
description: 'Delete an obligation',
action: 'Delete an obligation',
},
{
name: 'Get',
value: 'get',
description: 'Get an obligation',
action: 'Get an obligation',
},
{
name: 'Get Many',
value: 'getAll',
description: 'Get many obligations',
action: 'Get many obligations',
},
{
name: 'Update',
value: 'update',
description: 'Update an existing obligation',
action: 'Update an obligation',
},
],
default: 'create',
},
...createOp.description,
...updateOp.description,
...deleteOp.description,
...getOp.description,
...getAllOp.description,
];
export { createOp as create, updateOp as update, deleteOp as delete, getOp as get, getAllOp as getAll };

View File

@@ -0,0 +1,252 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import type { INodeProperties, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
import { proboApiRequest } from '../../GenericFunctions';
export const description: INodeProperties[] = [
{
displayName: 'Obligation ID',
name: 'id',
type: 'string',
displayOptions: {
show: {
resource: ['obligation'],
operation: ['update'],
},
},
default: '',
description: 'The ID of the obligation to update',
required: true,
},
{
displayName: 'Area',
name: 'area',
type: 'string',
displayOptions: {
show: {
resource: ['obligation'],
operation: ['update'],
},
},
default: '',
description: 'The area of the obligation',
},
{
displayName: 'Source',
name: 'source',
type: 'string',
displayOptions: {
show: {
resource: ['obligation'],
operation: ['update'],
},
},
default: '',
description: 'The source of the obligation',
},
{
displayName: 'Requirement',
name: 'requirement',
type: 'string',
displayOptions: {
show: {
resource: ['obligation'],
operation: ['update'],
},
},
default: '',
description: 'The requirement of the obligation',
},
{
displayName: 'Actions to Be Implemented',
name: 'actionsToBeImplemented',
type: 'string',
displayOptions: {
show: {
resource: ['obligation'],
operation: ['update'],
},
},
default: '',
description: 'The actions to be implemented for the obligation',
},
{
displayName: 'Regulator',
name: 'regulator',
type: 'string',
displayOptions: {
show: {
resource: ['obligation'],
operation: ['update'],
},
},
default: '',
description: 'The regulator of the obligation',
},
{
displayName: 'Owner ID',
name: 'ownerId',
type: 'string',
displayOptions: {
show: {
resource: ['obligation'],
operation: ['update'],
},
},
default: '',
description: 'The ID of the owner',
},
{
displayName: 'Last Review Date',
name: 'lastReviewDate',
type: 'string',
displayOptions: {
show: {
resource: ['obligation'],
operation: ['update'],
},
},
default: '',
description: 'The last review date of the obligation',
},
{
displayName: 'Due Date',
name: 'dueDate',
type: 'string',
displayOptions: {
show: {
resource: ['obligation'],
operation: ['update'],
},
},
default: '',
description: 'The due date of the obligation',
},
{
displayName: 'Status',
name: 'status',
type: 'options',
displayOptions: {
show: {
resource: ['obligation'],
operation: ['update'],
},
},
options: [
{
name: '(Unchanged)',
value: '',
},
{
name: 'Non Compliant',
value: 'NON_COMPLIANT',
},
{
name: 'Partially Compliant',
value: 'PARTIALLY_COMPLIANT',
},
{
name: 'Compliant',
value: 'COMPLIANT',
},
],
default: '',
description: 'The status of the obligation',
},
{
displayName: 'Type',
name: 'type',
type: 'options',
displayOptions: {
show: {
resource: ['obligation'],
operation: ['update'],
},
},
options: [
{
name: '(Unchanged)',
value: '',
},
{
name: 'Legal',
value: 'LEGAL',
},
{
name: 'Contractual',
value: 'CONTRACTUAL',
},
],
default: '',
description: 'The type of the obligation',
},
];
export async function execute(
this: IExecuteFunctions,
itemIndex: number,
): Promise<INodeExecutionData> {
const id = this.getNodeParameter('id', itemIndex) as string;
const area = this.getNodeParameter('area', itemIndex, '') as string;
const source = this.getNodeParameter('source', itemIndex, '') as string;
const requirement = this.getNodeParameter('requirement', itemIndex, '') as string;
const actionsToBeImplemented = this.getNodeParameter('actionsToBeImplemented', itemIndex, '') as string;
const regulator = this.getNodeParameter('regulator', itemIndex, '') as string;
const ownerId = this.getNodeParameter('ownerId', itemIndex, '') as string;
const lastReviewDate = this.getNodeParameter('lastReviewDate', itemIndex, '') as string;
const dueDate = this.getNodeParameter('dueDate', itemIndex, '') as string;
const status = this.getNodeParameter('status', itemIndex, '') as string;
const type = this.getNodeParameter('type', itemIndex, '') as string;
const query = `
mutation UpdateObligation($input: UpdateObligationInput!) {
updateObligation(input: $input) {
obligation {
id
area
source
requirement
actionsToBeImplemented
regulator
lastReviewDate
dueDate
status
type
createdAt
updatedAt
}
}
}
`;
const input: Record<string, string> = { id };
if (area) input.area = area;
if (source) input.source = source;
if (requirement) input.requirement = requirement;
if (actionsToBeImplemented) input.actionsToBeImplemented = actionsToBeImplemented;
if (regulator) input.regulator = regulator;
if (ownerId) input.ownerId = ownerId;
if (lastReviewDate) input.lastReviewDate = lastReviewDate;
if (dueDate) input.dueDate = dueDate;
if (status) input.status = status;
if (type) input.type = type;
const responseData = await proboApiRequest.call(this, query, { input });
return {
json: responseData,
pairedItem: { item: itemIndex },
};
}

View File

@@ -0,0 +1,68 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import type { INodeProperties, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
import { proboApiRequest } from '../../GenericFunctions';
export const description: INodeProperties[] = [
{
displayName: 'Organization ID',
name: 'organizationId',
type: 'string',
displayOptions: {
show: {
resource: ['organizationContext'],
operation: ['get'],
},
},
default: '',
description: 'The ID of the organization',
required: true,
},
];
export async function execute(
this: IExecuteFunctions,
itemIndex: number,
): Promise<INodeExecutionData> {
const organizationId = this.getNodeParameter('organizationId', itemIndex) as string;
const query = `
query GetOrganizationContext($organizationId: ID!) {
node(id: $organizationId) {
... on Organization {
context {
organizationId
product
architecture
team
processes
customers
}
}
}
}
`;
const variables = {
organizationId,
};
const responseData = await proboApiRequest.call(this, query, variables);
return {
json: responseData,
pairedItem: { item: itemIndex },
};
}

View File

@@ -0,0 +1,50 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import type { INodeProperties } from 'n8n-workflow';
import * as getOp from './get.operation';
import * as updateOp from './update.operation';
export const description: INodeProperties[] = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
resource: ['organizationContext'],
},
},
options: [
{
name: 'Get',
value: 'get',
description: 'Get the organization context',
action: 'Get the organization context',
},
{
name: 'Update',
value: 'update',
description: 'Update the organization context',
action: 'Update the organization context',
},
],
default: 'get',
},
...getOp.description,
...updateOp.description,
];
export { getOp as get, updateOp as update };

View File

@@ -0,0 +1,139 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import type { INodeProperties, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
import { proboApiRequest } from '../../GenericFunctions';
export const description: INodeProperties[] = [
{
displayName: 'Organization ID',
name: 'organizationId',
type: 'string',
displayOptions: {
show: {
resource: ['organizationContext'],
operation: ['update'],
},
},
default: '',
description: 'The ID of the organization',
required: true,
},
{
displayName: 'Product',
name: 'product',
type: 'string',
displayOptions: {
show: {
resource: ['organizationContext'],
operation: ['update'],
},
},
default: '',
description: 'The product description of the organization',
},
{
displayName: 'Architecture',
name: 'architecture',
type: 'string',
displayOptions: {
show: {
resource: ['organizationContext'],
operation: ['update'],
},
},
default: '',
description: 'The architecture description of the organization',
},
{
displayName: 'Team',
name: 'team',
type: 'string',
displayOptions: {
show: {
resource: ['organizationContext'],
operation: ['update'],
},
},
default: '',
description: 'The team description of the organization',
},
{
displayName: 'Processes',
name: 'processes',
type: 'string',
displayOptions: {
show: {
resource: ['organizationContext'],
operation: ['update'],
},
},
default: '',
description: 'The processes description of the organization',
},
{
displayName: 'Customers',
name: 'customers',
type: 'string',
displayOptions: {
show: {
resource: ['organizationContext'],
operation: ['update'],
},
},
default: '',
description: 'The customers description of the organization',
},
];
export async function execute(
this: IExecuteFunctions,
itemIndex: number,
): Promise<INodeExecutionData> {
const organizationId = this.getNodeParameter('organizationId', itemIndex) as string;
const product = this.getNodeParameter('product', itemIndex, '') as string;
const architecture = this.getNodeParameter('architecture', itemIndex, '') as string;
const team = this.getNodeParameter('team', itemIndex, '') as string;
const processes = this.getNodeParameter('processes', itemIndex, '') as string;
const customers = this.getNodeParameter('customers', itemIndex, '') as string;
const query = `
mutation UpdateOrganizationContext($input: UpdateOrganizationContextInput!) {
updateOrganizationContext(input: $input) {
context {
organizationId
product
architecture
team
processes
customers
}
}
}
`;
const input: Record<string, string> = { organizationId };
if (product) input.product = product;
if (architecture) input.architecture = architecture;
if (team) input.team = team;
if (processes) input.processes = processes;
if (customers) input.customers = customers;
const responseData = await proboApiRequest.call(this, query, { input });
return {
json: responseData,
pairedItem: { item: itemIndex },
};
}

View File

@@ -0,0 +1,171 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import type { INodeProperties, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
import { proboApiRequest } from '../../GenericFunctions';
export const description: INodeProperties[] = [
{
displayName: 'Organization ID',
name: 'organizationId',
type: 'string',
displayOptions: {
show: {
resource: ['processingActivity'],
operation: ['create'],
},
},
default: '',
description: 'The ID of the organization',
required: true,
},
{
displayName: 'Name',
name: 'name',
type: 'string',
displayOptions: {
show: {
resource: ['processingActivity'],
operation: ['create'],
},
},
default: '',
description: 'The name of the processing activity',
required: true,
},
{
displayName: 'Purpose',
name: 'purpose',
type: 'string',
displayOptions: {
show: {
resource: ['processingActivity'],
operation: ['create'],
},
},
default: '',
description: 'The purpose of the processing activity',
required: true,
},
{
displayName: 'Role',
name: 'role',
type: 'options',
displayOptions: {
show: {
resource: ['processingActivity'],
operation: ['create'],
},
},
options: [
{
name: 'Controller',
value: 'CONTROLLER',
},
{
name: 'Processor',
value: 'PROCESSOR',
},
],
default: 'CONTROLLER',
description: 'The role for the processing activity',
required: true,
},
{
displayName: 'Lawful Basis',
name: 'lawfulBasis',
type: 'options',
displayOptions: {
show: {
resource: ['processingActivity'],
operation: ['create'],
},
},
options: [
{
name: 'Consent',
value: 'CONSENT',
},
{
name: 'Contractual Necessity',
value: 'CONTRACTUAL_NECESSITY',
},
{
name: 'Legal Obligation',
value: 'LEGAL_OBLIGATION',
},
{
name: 'Legitimate Interest',
value: 'LEGITIMATE_INTEREST',
},
{
name: 'Public Task',
value: 'PUBLIC_TASK',
},
{
name: 'Vital Interests',
value: 'VITAL_INTERESTS',
},
],
default: 'LEGITIMATE_INTEREST',
description: 'The lawful basis for the processing activity',
required: true,
},
];
export async function execute(
this: IExecuteFunctions,
itemIndex: number,
): Promise<INodeExecutionData> {
const organizationId = this.getNodeParameter('organizationId', itemIndex) as string;
const name = this.getNodeParameter('name', itemIndex) as string;
const purpose = this.getNodeParameter('purpose', itemIndex) as string;
const role = this.getNodeParameter('role', itemIndex) as string;
const lawfulBasis = this.getNodeParameter('lawfulBasis', itemIndex) as string;
const query = `
mutation CreateProcessingActivity($input: CreateProcessingActivityInput!) {
createProcessingActivity(input: $input) {
processingActivityEdge {
node {
id
name
purpose
role
lawfulBasis
createdAt
updatedAt
}
}
}
}
`;
const variables = {
input: {
organizationId,
name,
purpose,
role,
lawfulBasis,
},
};
const responseData = await proboApiRequest.call(this, query, variables);
return {
json: responseData,
pairedItem: { item: itemIndex },
};
}

View File

@@ -0,0 +1,55 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import type { INodeProperties, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
import { proboApiRequest } from '../../GenericFunctions';
export const description: INodeProperties[] = [
{
displayName: 'Processing Activity ID',
name: 'processingActivityId',
type: 'string',
displayOptions: {
show: {
resource: ['processingActivity'],
operation: ['delete'],
},
},
default: '',
description: 'The ID of the processing activity to delete',
required: true,
},
];
export async function execute(
this: IExecuteFunctions,
itemIndex: number,
): Promise<INodeExecutionData> {
const processingActivityId = this.getNodeParameter('processingActivityId', itemIndex) as string;
const query = `
mutation DeleteProcessingActivity($input: DeleteProcessingActivityInput!) {
deleteProcessingActivity(input: $input) {
deletedProcessingActivityId
}
}
`;
const responseData = await proboApiRequest.call(this, query, { input: { processingActivityId } });
return {
json: responseData,
pairedItem: { item: itemIndex },
};
}

View File

@@ -0,0 +1,67 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import type { INodeProperties, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
import { proboApiRequest } from '../../GenericFunctions';
export const description: INodeProperties[] = [
{
displayName: 'Processing Activity ID',
name: 'processingActivityId',
type: 'string',
displayOptions: {
show: {
resource: ['processingActivity'],
operation: ['get'],
},
},
default: '',
description: 'The ID of the processing activity',
required: true,
},
];
export async function execute(
this: IExecuteFunctions,
itemIndex: number,
): Promise<INodeExecutionData> {
const processingActivityId = this.getNodeParameter('processingActivityId', itemIndex) as string;
const query = `
query GetProcessingActivity($processingActivityId: ID!) {
node(id: $processingActivityId) {
... on ProcessingActivity {
id
name
purpose
role
lawfulBasis
createdAt
updatedAt
}
}
}
`;
const variables = {
processingActivityId,
};
const responseData = await proboApiRequest.call(this, query, variables);
return {
json: responseData,
pairedItem: { item: itemIndex },
};
}

View File

@@ -0,0 +1,116 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import type { INodeProperties, IExecuteFunctions, INodeExecutionData, IDataObject } from 'n8n-workflow';
import { proboApiRequestAllItems } from '../../GenericFunctions';
export const description: INodeProperties[] = [
{
displayName: 'Organization ID',
name: 'organizationId',
type: 'string',
displayOptions: {
show: {
resource: ['processingActivity'],
operation: ['getAll'],
},
},
default: '',
description: 'The ID of the organization',
required: true,
},
{
displayName: 'Return All',
name: 'returnAll',
type: 'boolean',
displayOptions: {
show: {
resource: ['processingActivity'],
operation: ['getAll'],
},
},
default: false,
description: 'Whether to return all results or only up to a given limit',
},
{
displayName: 'Limit',
name: 'limit',
type: 'number',
displayOptions: {
show: {
resource: ['processingActivity'],
operation: ['getAll'],
returnAll: [false],
},
},
typeOptions: {
minValue: 1,
},
default: 50,
description: 'Max number of results to return',
},
];
export async function execute(
this: IExecuteFunctions,
itemIndex: number,
): Promise<INodeExecutionData> {
const organizationId = this.getNodeParameter('organizationId', itemIndex) as string;
const returnAll = this.getNodeParameter('returnAll', itemIndex) as boolean;
const limit = this.getNodeParameter('limit', itemIndex, 50) as number;
const query = `
query GetProcessingActivities($organizationId: ID!, $first: Int, $after: CursorKey) {
node(id: $organizationId) {
... on Organization {
processingActivities(first: $first, after: $after) {
edges {
node {
id
name
purpose
role
lawfulBasis
createdAt
updatedAt
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
}
}
`;
const processingActivities = await proboApiRequestAllItems.call(
this,
query,
{ organizationId },
(response) => {
const data = response?.data as IDataObject | undefined;
const node = data?.node as IDataObject | undefined;
return node?.processingActivities as IDataObject | undefined;
},
returnAll,
limit,
);
return {
json: { processingActivities },
pairedItem: { item: itemIndex },
};
}

View File

@@ -0,0 +1,74 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import type { INodeProperties } from 'n8n-workflow';
import * as createOp from './create.operation';
import * as updateOp from './update.operation';
import * as deleteOp from './delete.operation';
import * as getOp from './get.operation';
import * as getAllOp from './getAll.operation';
export const description: INodeProperties[] = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
resource: ['processingActivity'],
},
},
options: [
{
name: 'Create',
value: 'create',
description: 'Create a new processing activity',
action: 'Create a processing activity',
},
{
name: 'Delete',
value: 'delete',
description: 'Delete a processing activity',
action: 'Delete a processing activity',
},
{
name: 'Get',
value: 'get',
description: 'Get a processing activity',
action: 'Get a processing activity',
},
{
name: 'Get Many',
value: 'getAll',
description: 'Get many processing activities',
action: 'Get many processing activities',
},
{
name: 'Update',
value: 'update',
description: 'Update an existing processing activity',
action: 'Update a processing activity',
},
],
default: 'create',
},
...createOp.description,
...updateOp.description,
...deleteOp.description,
...getOp.description,
...getAllOp.description,
];
export { createOp as create, updateOp as update, deleteOp as delete, getOp as get, getAllOp as getAll };

View File

@@ -0,0 +1,169 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import type { INodeProperties, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
import { proboApiRequest } from '../../GenericFunctions';
export const description: INodeProperties[] = [
{
displayName: 'Processing Activity ID',
name: 'id',
type: 'string',
displayOptions: {
show: {
resource: ['processingActivity'],
operation: ['update'],
},
},
default: '',
description: 'The ID of the processing activity to update',
required: true,
},
{
displayName: 'Name',
name: 'name',
type: 'string',
displayOptions: {
show: {
resource: ['processingActivity'],
operation: ['update'],
},
},
default: '',
description: 'The name of the processing activity',
},
{
displayName: 'Purpose',
name: 'purpose',
type: 'string',
displayOptions: {
show: {
resource: ['processingActivity'],
operation: ['update'],
},
},
default: '',
description: 'The purpose of the processing activity',
},
{
displayName: 'Role',
name: 'role',
type: 'options',
displayOptions: {
show: {
resource: ['processingActivity'],
operation: ['update'],
},
},
options: [
{
name: '(Unchanged)',
value: '',
},
{
name: 'Controller',
value: 'CONTROLLER',
},
{
name: 'Processor',
value: 'PROCESSOR',
},
],
default: '',
description: 'The role for the processing activity',
},
{
displayName: 'Lawful Basis',
name: 'lawfulBasis',
type: 'options',
displayOptions: {
show: {
resource: ['processingActivity'],
operation: ['update'],
},
},
options: [
{
name: '(Unchanged)',
value: '',
},
{
name: 'Consent',
value: 'CONSENT',
},
{
name: 'Contractual Necessity',
value: 'CONTRACTUAL_NECESSITY',
},
{
name: 'Legal Obligation',
value: 'LEGAL_OBLIGATION',
},
{
name: 'Legitimate Interest',
value: 'LEGITIMATE_INTEREST',
},
{
name: 'Public Task',
value: 'PUBLIC_TASK',
},
{
name: 'Vital Interests',
value: 'VITAL_INTERESTS',
},
],
default: '',
description: 'The lawful basis for the processing activity',
},
];
export async function execute(
this: IExecuteFunctions,
itemIndex: number,
): Promise<INodeExecutionData> {
const id = this.getNodeParameter('id', itemIndex) as string;
const name = this.getNodeParameter('name', itemIndex, '') as string;
const purpose = this.getNodeParameter('purpose', itemIndex, '') as string;
const role = this.getNodeParameter('role', itemIndex, '') as string;
const lawfulBasis = this.getNodeParameter('lawfulBasis', itemIndex, '') as string;
const query = `
mutation UpdateProcessingActivity($input: UpdateProcessingActivityInput!) {
updateProcessingActivity(input: $input) {
processingActivity {
id
name
purpose
role
lawfulBasis
createdAt
updatedAt
}
}
}
`;
const input: Record<string, string> = { id };
if (name) input.name = name;
if (purpose) input.purpose = purpose;
if (role) input.role = role;
if (lawfulBasis) input.lawfulBasis = lawfulBasis;
const responseData = await proboApiRequest.call(this, query, { input });
return {
json: responseData,
pairedItem: { item: itemIndex },
};
}

View File

@@ -0,0 +1,210 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import type { INodeProperties, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
import { proboApiRequest } from '../../GenericFunctions';
export const description: INodeProperties[] = [
{
displayName: 'Organization ID',
name: 'organizationId',
type: 'string',
displayOptions: {
show: {
resource: ['rightsRequest'],
operation: ['create'],
},
},
default: '',
description: 'The ID of the organization',
required: true,
},
{
displayName: 'Request Type',
name: 'requestType',
type: 'options',
displayOptions: {
show: {
resource: ['rightsRequest'],
operation: ['create'],
},
},
options: [
{
name: 'Access',
value: 'ACCESS',
},
{
name: 'Deletion',
value: 'DELETION',
},
{
name: 'Portability',
value: 'PORTABILITY',
},
],
default: 'ACCESS',
description: 'The type of rights request',
required: true,
},
{
displayName: 'Request State',
name: 'requestState',
type: 'options',
displayOptions: {
show: {
resource: ['rightsRequest'],
operation: ['create'],
},
},
options: [
{
name: 'To Do',
value: 'TODO',
},
{
name: 'In Progress',
value: 'IN_PROGRESS',
},
{
name: 'Done',
value: 'DONE',
},
],
default: 'TODO',
description: 'The state of the rights request',
required: true,
},
{
displayName: 'Data Subject',
name: 'dataSubject',
type: 'string',
displayOptions: {
show: {
resource: ['rightsRequest'],
operation: ['create'],
},
},
default: '',
description: 'The data subject of the rights request',
required: true,
},
{
displayName: 'Contact',
name: 'contact',
type: 'string',
displayOptions: {
show: {
resource: ['rightsRequest'],
operation: ['create'],
},
},
default: '',
description: 'The contact for the rights request',
},
{
displayName: 'Details',
name: 'details',
type: 'string',
displayOptions: {
show: {
resource: ['rightsRequest'],
operation: ['create'],
},
},
default: '',
description: 'The details of the rights request',
},
{
displayName: 'Deadline',
name: 'deadline',
type: 'string',
displayOptions: {
show: {
resource: ['rightsRequest'],
operation: ['create'],
},
},
default: '',
description: 'The deadline for the rights request',
},
{
displayName: 'Action Taken',
name: 'actionTaken',
type: 'string',
displayOptions: {
show: {
resource: ['rightsRequest'],
operation: ['create'],
},
},
default: '',
description: 'The action taken for the rights request',
},
];
export async function execute(
this: IExecuteFunctions,
itemIndex: number,
): Promise<INodeExecutionData> {
const organizationId = this.getNodeParameter('organizationId', itemIndex) as string;
const requestType = this.getNodeParameter('requestType', itemIndex) as string;
const requestState = this.getNodeParameter('requestState', itemIndex) as string;
const dataSubject = this.getNodeParameter('dataSubject', itemIndex) as string;
const contact = this.getNodeParameter('contact', itemIndex, '') as string;
const details = this.getNodeParameter('details', itemIndex, '') as string;
const deadline = this.getNodeParameter('deadline', itemIndex, '') as string;
const actionTaken = this.getNodeParameter('actionTaken', itemIndex, '') as string;
const query = `
mutation CreateRightsRequest($input: CreateRightsRequestInput!) {
createRightsRequest(input: $input) {
rightsRequestEdge {
node {
id
requestType
requestState
dataSubject
contact
details
deadline
actionTaken
createdAt
updatedAt
}
}
}
}
`;
const variables = {
input: {
organizationId,
requestType,
requestState,
dataSubject,
...(contact && { contact }),
...(details && { details }),
...(deadline && { deadline }),
...(actionTaken && { actionTaken }),
},
};
const responseData = await proboApiRequest.call(this, query, variables);
return {
json: responseData,
pairedItem: { item: itemIndex },
};
}

View File

@@ -0,0 +1,55 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import type { INodeProperties, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
import { proboApiRequest } from '../../GenericFunctions';
export const description: INodeProperties[] = [
{
displayName: 'Rights Request ID',
name: 'rightsRequestId',
type: 'string',
displayOptions: {
show: {
resource: ['rightsRequest'],
operation: ['delete'],
},
},
default: '',
description: 'The ID of the rights request to delete',
required: true,
},
];
export async function execute(
this: IExecuteFunctions,
itemIndex: number,
): Promise<INodeExecutionData> {
const rightsRequestId = this.getNodeParameter('rightsRequestId', itemIndex) as string;
const query = `
mutation DeleteRightsRequest($input: DeleteRightsRequestInput!) {
deleteRightsRequest(input: $input) {
deletedRightsRequestId
}
}
`;
const responseData = await proboApiRequest.call(this, query, { input: { rightsRequestId } });
return {
json: responseData,
pairedItem: { item: itemIndex },
};
}

View File

@@ -0,0 +1,70 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import type { INodeProperties, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
import { proboApiRequest } from '../../GenericFunctions';
export const description: INodeProperties[] = [
{
displayName: 'Rights Request ID',
name: 'rightsRequestId',
type: 'string',
displayOptions: {
show: {
resource: ['rightsRequest'],
operation: ['get'],
},
},
default: '',
description: 'The ID of the rights request',
required: true,
},
];
export async function execute(
this: IExecuteFunctions,
itemIndex: number,
): Promise<INodeExecutionData> {
const rightsRequestId = this.getNodeParameter('rightsRequestId', itemIndex) as string;
const query = `
query GetRightsRequest($rightsRequestId: ID!) {
node(id: $rightsRequestId) {
... on RightsRequest {
id
requestType
requestState
dataSubject
contact
details
deadline
actionTaken
createdAt
updatedAt
}
}
}
`;
const variables = {
rightsRequestId,
};
const responseData = await proboApiRequest.call(this, query, variables);
return {
json: responseData,
pairedItem: { item: itemIndex },
};
}

View File

@@ -0,0 +1,119 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import type { INodeProperties, IExecuteFunctions, INodeExecutionData, IDataObject } from 'n8n-workflow';
import { proboApiRequestAllItems } from '../../GenericFunctions';
export const description: INodeProperties[] = [
{
displayName: 'Organization ID',
name: 'organizationId',
type: 'string',
displayOptions: {
show: {
resource: ['rightsRequest'],
operation: ['getAll'],
},
},
default: '',
description: 'The ID of the organization',
required: true,
},
{
displayName: 'Return All',
name: 'returnAll',
type: 'boolean',
displayOptions: {
show: {
resource: ['rightsRequest'],
operation: ['getAll'],
},
},
default: false,
description: 'Whether to return all results or only up to a given limit',
},
{
displayName: 'Limit',
name: 'limit',
type: 'number',
displayOptions: {
show: {
resource: ['rightsRequest'],
operation: ['getAll'],
returnAll: [false],
},
},
typeOptions: {
minValue: 1,
},
default: 50,
description: 'Max number of results to return',
},
];
export async function execute(
this: IExecuteFunctions,
itemIndex: number,
): Promise<INodeExecutionData> {
const organizationId = this.getNodeParameter('organizationId', itemIndex) as string;
const returnAll = this.getNodeParameter('returnAll', itemIndex) as boolean;
const limit = this.getNodeParameter('limit', itemIndex, 50) as number;
const query = `
query GetRightsRequests($organizationId: ID!, $first: Int, $after: CursorKey) {
node(id: $organizationId) {
... on Organization {
rightsRequests(first: $first, after: $after) {
edges {
node {
id
requestType
requestState
dataSubject
contact
details
deadline
actionTaken
createdAt
updatedAt
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
}
}
`;
const rightsRequests = await proboApiRequestAllItems.call(
this,
query,
{ organizationId },
(response) => {
const data = response?.data as IDataObject | undefined;
const node = data?.node as IDataObject | undefined;
return node?.rightsRequests as IDataObject | undefined;
},
returnAll,
limit,
);
return {
json: { rightsRequests },
pairedItem: { item: itemIndex },
};
}

View File

@@ -0,0 +1,74 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import type { INodeProperties } from 'n8n-workflow';
import * as createOp from './create.operation';
import * as updateOp from './update.operation';
import * as deleteOp from './delete.operation';
import * as getOp from './get.operation';
import * as getAllOp from './getAll.operation';
export const description: INodeProperties[] = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
resource: ['rightsRequest'],
},
},
options: [
{
name: 'Create',
value: 'create',
description: 'Create a new rights request',
action: 'Create a rights request',
},
{
name: 'Delete',
value: 'delete',
description: 'Delete a rights request',
action: 'Delete a rights request',
},
{
name: 'Get',
value: 'get',
description: 'Get a rights request',
action: 'Get a rights request',
},
{
name: 'Get Many',
value: 'getAll',
description: 'Get many rights requests',
action: 'Get many rights requests',
},
{
name: 'Update',
value: 'update',
description: 'Update an existing rights request',
action: 'Update a rights request',
},
],
default: 'create',
},
...createOp.description,
...updateOp.description,
...deleteOp.description,
...getOp.description,
...getAllOp.description,
];
export { createOp as create, updateOp as update, deleteOp as delete, getOp as get, getAllOp as getAll };

View File

@@ -0,0 +1,209 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import type { INodeProperties, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
import { proboApiRequest } from '../../GenericFunctions';
export const description: INodeProperties[] = [
{
displayName: 'Rights Request ID',
name: 'id',
type: 'string',
displayOptions: {
show: {
resource: ['rightsRequest'],
operation: ['update'],
},
},
default: '',
description: 'The ID of the rights request to update',
required: true,
},
{
displayName: 'Request Type',
name: 'requestType',
type: 'options',
displayOptions: {
show: {
resource: ['rightsRequest'],
operation: ['update'],
},
},
options: [
{
name: '(Unchanged)',
value: '',
},
{
name: 'Access',
value: 'ACCESS',
},
{
name: 'Deletion',
value: 'DELETION',
},
{
name: 'Portability',
value: 'PORTABILITY',
},
],
default: '',
description: 'The type of rights request',
},
{
displayName: 'Request State',
name: 'requestState',
type: 'options',
displayOptions: {
show: {
resource: ['rightsRequest'],
operation: ['update'],
},
},
options: [
{
name: '(Unchanged)',
value: '',
},
{
name: 'To Do',
value: 'TODO',
},
{
name: 'In Progress',
value: 'IN_PROGRESS',
},
{
name: 'Done',
value: 'DONE',
},
],
default: '',
description: 'The state of the rights request',
},
{
displayName: 'Data Subject',
name: 'dataSubject',
type: 'string',
displayOptions: {
show: {
resource: ['rightsRequest'],
operation: ['update'],
},
},
default: '',
description: 'The data subject of the rights request',
},
{
displayName: 'Contact',
name: 'contact',
type: 'string',
displayOptions: {
show: {
resource: ['rightsRequest'],
operation: ['update'],
},
},
default: '',
description: 'The contact for the rights request',
},
{
displayName: 'Details',
name: 'details',
type: 'string',
displayOptions: {
show: {
resource: ['rightsRequest'],
operation: ['update'],
},
},
default: '',
description: 'The details of the rights request',
},
{
displayName: 'Deadline',
name: 'deadline',
type: 'string',
displayOptions: {
show: {
resource: ['rightsRequest'],
operation: ['update'],
},
},
default: '',
description: 'The deadline for the rights request',
},
{
displayName: 'Action Taken',
name: 'actionTaken',
type: 'string',
displayOptions: {
show: {
resource: ['rightsRequest'],
operation: ['update'],
},
},
default: '',
description: 'The action taken for the rights request',
},
];
export async function execute(
this: IExecuteFunctions,
itemIndex: number,
): Promise<INodeExecutionData> {
const id = this.getNodeParameter('id', itemIndex) as string;
const requestType = this.getNodeParameter('requestType', itemIndex, '') as string;
const requestState = this.getNodeParameter('requestState', itemIndex, '') as string;
const dataSubject = this.getNodeParameter('dataSubject', itemIndex, '') as string;
const contact = this.getNodeParameter('contact', itemIndex, '') as string;
const details = this.getNodeParameter('details', itemIndex, '') as string;
const deadline = this.getNodeParameter('deadline', itemIndex, '') as string;
const actionTaken = this.getNodeParameter('actionTaken', itemIndex, '') as string;
const query = `
mutation UpdateRightsRequest($input: UpdateRightsRequestInput!) {
updateRightsRequest(input: $input) {
rightsRequest {
id
requestType
requestState
dataSubject
contact
details
deadline
actionTaken
createdAt
updatedAt
}
}
}
`;
const input: Record<string, string> = { id };
if (requestType) input.requestType = requestType;
if (requestState) input.requestState = requestState;
if (dataSubject) input.dataSubject = dataSubject;
if (contact) input.contact = contact;
if (details) input.details = details;
if (deadline) input.deadline = deadline;
if (actionTaken) input.actionTaken = actionTaken;
const responseData = await proboApiRequest.call(this, query, { input });
return {
json: responseData,
pairedItem: { item: itemIndex },
};
}

View File

@@ -67,10 +67,7 @@ export const description: INodeProperties[] = [
},
},
options: [
{
name: 'Mitigated',
value: 'MITIGATED',
},
{ name: '(Unchanged)', value: '' },
{
name: 'Accepted',
value: 'ACCEPTED',
@@ -79,12 +76,16 @@ export const description: INodeProperties[] = [
name: 'Avoided',
value: 'AVOIDED',
},
{
name: 'Mitigated',
value: 'MITIGATED',
},
{
name: 'Transferred',
value: 'TRANSFERRED',
},
],
default: 'MITIGATED',
default: '',
description: 'The treatment strategy for the risk',
},
{

View File

@@ -0,0 +1,146 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import type { INodeProperties, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
import { proboApiRequest } from '../../GenericFunctions';
export const description: INodeProperties[] = [
{
displayName: 'Organization ID',
name: 'organizationId',
type: 'string',
displayOptions: {
show: {
resource: ['snapshot'],
operation: ['create'],
},
},
default: '',
description: 'The ID of the organization',
required: true,
},
{
displayName: 'Name',
name: 'name',
type: 'string',
displayOptions: {
show: {
resource: ['snapshot'],
operation: ['create'],
},
},
default: '',
description: 'The name of the snapshot',
required: true,
},
{
displayName: 'Description',
name: 'description',
type: 'string',
displayOptions: {
show: {
resource: ['snapshot'],
operation: ['create'],
},
},
default: '',
description: 'The description of the snapshot',
},
{
displayName: 'Type',
name: 'type',
type: 'options',
displayOptions: {
show: {
resource: ['snapshot'],
operation: ['create'],
},
},
options: [
{
name: 'Assets',
value: 'ASSETS',
},
{
name: 'Findings',
value: 'FINDINGS',
},
{
name: 'Obligations',
value: 'OBLIGATIONS',
},
{
name: 'Processing Activities',
value: 'PROCESSING_ACTIVITIES',
},
{
name: 'Risks',
value: 'RISKS',
},
{
name: 'Statements of Applicability',
value: 'STATEMENTS_OF_APPLICABILITY',
},
{
name: 'Vendors',
value: 'VENDORS',
},
],
default: 'RISKS',
description: 'The type of snapshot',
required: true,
},
];
export async function execute(
this: IExecuteFunctions,
itemIndex: number,
): Promise<INodeExecutionData> {
const organizationId = this.getNodeParameter('organizationId', itemIndex) as string;
const name = this.getNodeParameter('name', itemIndex) as string;
const description = this.getNodeParameter('description', itemIndex, '') as string;
const type = this.getNodeParameter('type', itemIndex) as string;
const query = `
mutation CreateSnapshot($input: CreateSnapshotInput!) {
createSnapshot(input: $input) {
snapshotEdge {
node {
id
name
description
type
createdAt
}
}
}
}
`;
const variables = {
input: {
organizationId,
name,
...(description && { description }),
type,
},
};
const responseData = await proboApiRequest.call(this, query, variables);
return {
json: responseData,
pairedItem: { item: itemIndex },
};
}

View File

@@ -0,0 +1,55 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import type { INodeProperties, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
import { proboApiRequest } from '../../GenericFunctions';
export const description: INodeProperties[] = [
{
displayName: 'Snapshot ID',
name: 'snapshotId',
type: 'string',
displayOptions: {
show: {
resource: ['snapshot'],
operation: ['delete'],
},
},
default: '',
description: 'The ID of the snapshot to delete',
required: true,
},
];
export async function execute(
this: IExecuteFunctions,
itemIndex: number,
): Promise<INodeExecutionData> {
const snapshotId = this.getNodeParameter('snapshotId', itemIndex) as string;
const query = `
mutation DeleteSnapshot($input: DeleteSnapshotInput!) {
deleteSnapshot(input: $input) {
deletedSnapshotId
}
}
`;
const responseData = await proboApiRequest.call(this, query, { input: { snapshotId } });
return {
json: responseData,
pairedItem: { item: itemIndex },
};
}

View File

@@ -0,0 +1,65 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import type { INodeProperties, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
import { proboApiRequest } from '../../GenericFunctions';
export const description: INodeProperties[] = [
{
displayName: 'Snapshot ID',
name: 'snapshotId',
type: 'string',
displayOptions: {
show: {
resource: ['snapshot'],
operation: ['get'],
},
},
default: '',
description: 'The ID of the snapshot',
required: true,
},
];
export async function execute(
this: IExecuteFunctions,
itemIndex: number,
): Promise<INodeExecutionData> {
const snapshotId = this.getNodeParameter('snapshotId', itemIndex) as string;
const query = `
query GetSnapshot($snapshotId: ID!) {
node(id: $snapshotId) {
... on Snapshot {
id
name
description
type
createdAt
}
}
}
`;
const variables = {
snapshotId,
};
const responseData = await proboApiRequest.call(this, query, variables);
return {
json: responseData,
pairedItem: { item: itemIndex },
};
}

View File

@@ -0,0 +1,114 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import type { INodeProperties, IExecuteFunctions, INodeExecutionData, IDataObject } from 'n8n-workflow';
import { proboApiRequestAllItems } from '../../GenericFunctions';
export const description: INodeProperties[] = [
{
displayName: 'Organization ID',
name: 'organizationId',
type: 'string',
displayOptions: {
show: {
resource: ['snapshot'],
operation: ['getAll'],
},
},
default: '',
description: 'The ID of the organization',
required: true,
},
{
displayName: 'Return All',
name: 'returnAll',
type: 'boolean',
displayOptions: {
show: {
resource: ['snapshot'],
operation: ['getAll'],
},
},
default: false,
description: 'Whether to return all results or only up to a given limit',
},
{
displayName: 'Limit',
name: 'limit',
type: 'number',
displayOptions: {
show: {
resource: ['snapshot'],
operation: ['getAll'],
returnAll: [false],
},
},
typeOptions: {
minValue: 1,
},
default: 50,
description: 'Max number of results to return',
},
];
export async function execute(
this: IExecuteFunctions,
itemIndex: number,
): Promise<INodeExecutionData> {
const organizationId = this.getNodeParameter('organizationId', itemIndex) as string;
const returnAll = this.getNodeParameter('returnAll', itemIndex) as boolean;
const limit = this.getNodeParameter('limit', itemIndex, 50) as number;
const query = `
query GetSnapshots($organizationId: ID!, $first: Int, $after: CursorKey) {
node(id: $organizationId) {
... on Organization {
snapshots(first: $first, after: $after) {
edges {
node {
id
name
description
type
createdAt
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
}
}
`;
const snapshots = await proboApiRequestAllItems.call(
this,
query,
{ organizationId },
(response) => {
const data = response?.data as IDataObject | undefined;
const node = data?.node as IDataObject | undefined;
return node?.snapshots as IDataObject | undefined;
},
returnAll,
limit,
);
return {
json: { snapshots },
pairedItem: { item: itemIndex },
};
}

View File

@@ -0,0 +1,66 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import type { INodeProperties } from 'n8n-workflow';
import * as createOp from './create.operation';
import * as deleteOp from './delete.operation';
import * as getOp from './get.operation';
import * as getAllOp from './getAll.operation';
export const description: INodeProperties[] = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
resource: ['snapshot'],
},
},
options: [
{
name: 'Create',
value: 'create',
description: 'Create a new snapshot',
action: 'Create a snapshot',
},
{
name: 'Delete',
value: 'delete',
description: 'Delete a snapshot',
action: 'Delete a snapshot',
},
{
name: 'Get',
value: 'get',
description: 'Get a snapshot',
action: 'Get a snapshot',
},
{
name: 'Get Many',
value: 'getAll',
description: 'Get many snapshots',
action: 'Get many snapshots',
},
],
default: 'create',
},
...createOp.description,
...deleteOp.description,
...getOp.description,
...getAllOp.description,
];
export { createOp as create, deleteOp as delete, getOp as get, getAllOp as getAll };

View File

@@ -0,0 +1,198 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import type { INodeProperties, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
import { proboApiRequest } from '../../GenericFunctions';
export const description: INodeProperties[] = [
{
displayName: 'Organization ID',
name: 'organizationId',
type: 'string',
displayOptions: {
show: {
resource: ['task'],
operation: ['create'],
},
},
default: '',
description: 'The ID of the organization',
required: true,
},
{
displayName: 'Measure ID',
name: 'measureId',
type: 'string',
displayOptions: {
show: {
resource: ['task'],
operation: ['create'],
},
},
default: '',
description: 'The ID of the measure this task belongs to',
required: true,
},
{
displayName: 'Name',
name: 'name',
type: 'string',
displayOptions: {
show: {
resource: ['task'],
operation: ['create'],
},
},
default: '',
description: 'The name of the task',
required: true,
},
{
displayName: 'Description',
name: 'description',
type: 'string',
displayOptions: {
show: {
resource: ['task'],
operation: ['create'],
},
},
default: '',
description: 'The description of the task',
},
{
displayName: 'Priority',
name: 'priority',
type: 'options',
displayOptions: {
show: {
resource: ['task'],
operation: ['create'],
},
},
options: [
{
name: 'Urgent',
value: 'URGENT',
},
{
name: 'High',
value: 'HIGH',
},
{
name: 'Medium',
value: 'MEDIUM',
},
{
name: 'Low',
value: 'LOW',
},
],
default: 'MEDIUM',
description: 'The priority of the task',
},
{
displayName: 'Time Estimate',
name: 'timeEstimate',
type: 'string',
displayOptions: {
show: {
resource: ['task'],
operation: ['create'],
},
},
default: '',
description: 'The time estimate for the task',
},
{
displayName: 'Assigned To ID',
name: 'assignedToId',
type: 'string',
displayOptions: {
show: {
resource: ['task'],
operation: ['create'],
},
},
default: '',
description: 'The ID of the user assigned to this task',
},
{
displayName: 'Deadline',
name: 'deadline',
type: 'dateTime',
displayOptions: {
show: {
resource: ['task'],
operation: ['create'],
},
},
default: '',
description: 'The deadline for the task',
},
];
export async function execute(
this: IExecuteFunctions,
itemIndex: number,
): Promise<INodeExecutionData> {
const organizationId = this.getNodeParameter('organizationId', itemIndex) as string;
const measureId = this.getNodeParameter('measureId', itemIndex) as string;
const name = this.getNodeParameter('name', itemIndex) as string;
const description = this.getNodeParameter('description', itemIndex, '') as string;
const priority = this.getNodeParameter('priority', itemIndex, '') as string;
const timeEstimate = this.getNodeParameter('timeEstimate', itemIndex, '') as string;
const assignedToId = this.getNodeParameter('assignedToId', itemIndex, '') as string;
const deadline = this.getNodeParameter('deadline', itemIndex, '') as string;
const query = `
mutation CreateTask($input: CreateTaskInput!) {
createTask(input: $input) {
taskEdge {
node {
id
name
description
state
priority
timeEstimate
deadline
createdAt
updatedAt
}
}
}
}
`;
const variables = {
input: {
organizationId,
measureId,
name,
...(description && { description }),
...(priority && { priority }),
...(timeEstimate && { timeEstimate }),
...(assignedToId && { assignedToId }),
...(deadline && { deadline }),
},
};
const responseData = await proboApiRequest.call(this, query, variables);
return {
json: responseData,
pairedItem: { item: itemIndex },
};
}

View File

@@ -0,0 +1,55 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import type { INodeProperties, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
import { proboApiRequest } from '../../GenericFunctions';
export const description: INodeProperties[] = [
{
displayName: 'Task ID',
name: 'taskId',
type: 'string',
displayOptions: {
show: {
resource: ['task'],
operation: ['delete'],
},
},
default: '',
description: 'The ID of the task to delete',
required: true,
},
];
export async function execute(
this: IExecuteFunctions,
itemIndex: number,
): Promise<INodeExecutionData> {
const taskId = this.getNodeParameter('taskId', itemIndex) as string;
const query = `
mutation DeleteTask($input: DeleteTaskInput!) {
deleteTask(input: $input) {
deletedTaskId
}
}
`;
const responseData = await proboApiRequest.call(this, query, { input: { taskId } });
return {
json: responseData,
pairedItem: { item: itemIndex },
};
}

View File

@@ -0,0 +1,69 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import type { INodeProperties, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
import { proboApiRequest } from '../../GenericFunctions';
export const description: INodeProperties[] = [
{
displayName: 'Task ID',
name: 'taskId',
type: 'string',
displayOptions: {
show: {
resource: ['task'],
operation: ['get'],
},
},
default: '',
description: 'The ID of the task',
required: true,
},
];
export async function execute(
this: IExecuteFunctions,
itemIndex: number,
): Promise<INodeExecutionData> {
const taskId = this.getNodeParameter('taskId', itemIndex) as string;
const query = `
query GetTask($taskId: ID!) {
node(id: $taskId) {
... on Task {
id
name
description
state
priority
timeEstimate
deadline
createdAt
updatedAt
}
}
}
`;
const variables = {
taskId,
};
const responseData = await proboApiRequest.call(this, query, variables);
return {
json: responseData,
pairedItem: { item: itemIndex },
};
}

Some files were not shown because too many files have changed in this diff Show More