Scope sub-third-parties per parent

Replace the many-to-many junction table with a direct
parent_third_party_id foreign key on third_parties. Each
sub-third-party now belongs to exactly one parent, making
duplicates across parents independent entities.

Replace the firstLevel boolean with an integer level field
(1 = direct, 2+ = parent level + 1) to support arbitrary
nesting depth.

Remove the createThirdPartyThirdPartyMapping and
deleteThirdPartyThirdPartyMapping mutations, the CLI
link/unlink commands, and the corresponding MCP tools.
Creating a child third party now just requires passing
parentThirdPartyId on the existing createThirdParty mutation.

The frontend walks the parentThirdParty chain to build
display names like "Name (Ancestor1/Ancestor2)" and shows
clickable ancestor links on the detail page.

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
Sacha Al Himdani
2026-05-29 16:22:54 +02:00
parent ec858e58df
commit b6781d3de0
36 changed files with 1276 additions and 1192 deletions

View File

@@ -177,19 +177,19 @@ export const description: INodeProperties[] = [
type: 'string',
default: '',
},
{
displayName: 'Parent Third Party ID',
name: 'parentThirdPartyId',
type: 'string',
default: '',
description: 'The ID of the parent third party (creates a child relationship; level is derived from the parent)',
},
{
displayName: 'Privacy Policy URL',
name: 'privacyPolicyUrl',
type: 'string',
default: '',
},
{
displayName: 'Root',
name: 'firstLevel',
type: 'boolean',
default: false,
description: 'Whether this is a first-level third party',
},
{
displayName: 'Security Page URL',
name: 'securityPageUrl',
@@ -256,7 +256,7 @@ export async function execute(
trustPageUrl?: string;
certifications?: string;
countries?: string;
firstLevel?: boolean;
parentThirdPartyId?: string;
};
const query = `
@@ -283,7 +283,7 @@ export async function execute(
certifications
countries
showOnTrustCenter
firstLevel
level
createdAt
updatedAt
}
@@ -312,7 +312,10 @@ export async function execute(
if (additionalFields.subprocessorsListUrl) input.subprocessorsListUrl = additionalFields.subprocessorsListUrl;
if (additionalFields.securityPageUrl) input.securityPageUrl = additionalFields.securityPageUrl;
if (additionalFields.trustPageUrl) input.trustPageUrl = additionalFields.trustPageUrl;
if (additionalFields.firstLevel !== undefined) input.firstLevel = additionalFields.firstLevel;
if (additionalFields.parentThirdPartyId) {
// The server derives the level from the parent (parent.level + 1).
input.parentThirdPartyId = additionalFields.parentThirdPartyId;
}
if (additionalFields.certifications) {
input.certifications = additionalFields.certifications.split(',').map((c) => c.trim()).filter(Boolean);
}

View File

@@ -74,11 +74,11 @@ export const description: INodeProperties[] = [
},
options: [
{
displayName: 'Filter by Root',
name: 'filterFirstLevel',
type: 'boolean',
default: false,
description: 'Whether to filter by first-level third parties only',
displayName: 'Filter by Level',
name: 'filterLevel',
type: 'number',
default: 0,
description: 'Filter by third party level (1 = direct, 2+ = indirect, 0 = no filter)',
},
{
displayName: 'Include Organization',
@@ -113,7 +113,7 @@ export async function execute(
const returnAll = this.getNodeParameter('returnAll', itemIndex) as boolean;
const limit = this.getNodeParameter('limit', itemIndex, 50) as number;
const options = this.getNodeParameter('options', itemIndex, {}) as {
filterFirstLevel?: boolean;
filterLevel?: number;
includeOrganization?: boolean;
includeBusinessOwner?: boolean;
includeSecurityOwner?: boolean;
@@ -142,8 +142,8 @@ export async function execute(
}`
: '';
const filterVariable = options.filterFirstLevel !== undefined ? ', $filter: ThirdPartyFilter' : '';
const filterArgument = options.filterFirstLevel !== undefined ? ', filter: $filter' : '';
const filterVariable = options.filterLevel ? ', $filter: ThirdPartyFilter' : '';
const filterArgument = options.filterLevel ? ', filter: $filter' : '';
const query = `
query GetThirdParties($organizationId: ID!, $first: Int, $after: CursorKey${filterVariable}) {
@@ -171,7 +171,11 @@ export async function execute(
certifications
countries
showOnTrustCenter
firstLevel
level
ancestors {
id
name
}
${organizationFragment}
${businessOwnerFragment}
${securityOwnerFragment}
@@ -190,8 +194,8 @@ export async function execute(
`;
const variables: IDataObject = { organizationId };
if (options.filterFirstLevel) {
variables.filter = { firstLevel: true };
if (options.filterLevel) {
variables.filter = { level: options.filterLevel };
}
const thirdParties = await proboApiRequestAllItems.call(