Link measures to third parties

Add a many-to-many relationship between measures and third parties,
surfaced as a measures tab on the third party detail page and a third
parties tab on the measure detail page. Each side gets a paginated
list with a link/unlink dialog.

Also remove the right-hand drawer on the measure detail page and
expose the state as a badge in the page header, mirroring how the
compliance page surfaces its active flag.

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
Sacha Al Himdani
2026-05-22 16:07:48 +02:00
parent b6b1e801b1
commit 6dfdd7ca49
35 changed files with 2109 additions and 54 deletions

View File

@@ -70,7 +70,7 @@ func (r *Resolver) ListThirdPartiesTool(ctx context.Context, req *mcp.CallToolRe
cursor := types.NewCursor(input.Size, input.Cursor, pageOrderBy)
thirdPartyFilter := coredata.NewThirdPartyFilter(nil, input.FirstLevel)
thirdPartyFilter := coredata.NewThirdPartyFilter(nil, input.FirstLevel, nil)
page, err := prb.ThirdParties.ListForOrganizationID(ctx, scope, input.OrganizationID, cursor, thirdPartyFilter)
if err != nil {
@@ -2664,6 +2664,14 @@ func (r *Resolver) LinkMeasureTool(ctx context.Context, req *mcp.CallToolRequest
if _, _, err := svc.Measures.CreateDocumentMapping(ctx, scope, input.MeasureID, input.ResourceID); err != nil {
return nil, types.LinkMeasureOutput{}, fmt.Errorf("failed to link measure to document: %w", err)
}
case coredata.ThirdPartyEntityType:
if _, err := r.Authorize(ctx, input.MeasureID, probo.ActionMeasureThirdPartyMappingCreate); err != nil {
return nil, types.LinkMeasureOutput{}, err
}
if _, _, err := svc.Measures.CreateThirdPartyMapping(ctx, scope, input.MeasureID, input.ResourceID); err != nil {
return nil, types.LinkMeasureOutput{}, fmt.Errorf("failed to link measure to third party: %w", err)
}
default:
return nil, types.LinkMeasureOutput{}, fmt.Errorf("unsupported resource type for measure linking: entity type %d", input.ResourceID.EntityType())
}
@@ -2700,6 +2708,14 @@ func (r *Resolver) UnlinkMeasureTool(ctx context.Context, req *mcp.CallToolReque
if _, _, err := svc.Measures.DeleteDocumentMapping(ctx, scope, input.MeasureID, input.ResourceID); err != nil {
return nil, types.UnlinkMeasureOutput{}, fmt.Errorf("failed to unlink measure from document: %w", err)
}
case coredata.ThirdPartyEntityType:
if _, err := r.Authorize(ctx, input.MeasureID, probo.ActionMeasureThirdPartyMappingDelete); err != nil {
return nil, types.UnlinkMeasureOutput{}, err
}
if _, _, err := svc.Measures.DeleteThirdPartyMapping(ctx, scope, input.MeasureID, input.ResourceID); err != nil {
return nil, types.UnlinkMeasureOutput{}, fmt.Errorf("failed to unlink measure from third party: %w", err)
}
default:
return nil, types.UnlinkMeasureOutput{}, fmt.Errorf("unsupported resource type for measure unlinking: entity type %d", input.ResourceID.EntityType())
}

View File

@@ -2396,7 +2396,7 @@ components:
description: Measure ID
resource_id:
$ref: "#/components/schemas/GID"
description: ID of the resource to link (control, risk, or document)
description: ID of the resource to link (control, risk, document, or third party)
LinkMeasureOutput:
type: object
@@ -2412,7 +2412,7 @@ components:
description: Measure ID
resource_id:
$ref: "#/components/schemas/GID"
description: ID of the resource to unlink (control, risk, or document)
description: ID of the resource to unlink (control, risk, document, or third party)
UnlinkMeasureOutput:
type: object
@@ -12182,7 +12182,7 @@ tools:
outputSchema:
$ref: "#/components/schemas/ListMeasureEvidencesOutput"
- name: linkMeasure
description: Link a measure to a resource (control, risk, or document). The resource type is determined from the resource_id GID.
description: Link a measure to a resource (control, risk, document, or third party). The resource type is determined from the resource_id GID.
hints:
readonly: false
inputSchema:
@@ -12190,7 +12190,7 @@ tools:
outputSchema:
$ref: "#/components/schemas/LinkMeasureOutput"
- name: unlinkMeasure
description: Unlink a measure from a resource (control, risk, or document). The resource type is determined from the resource_id GID.
description: Unlink a measure from a resource (control, risk, document, or third party). The resource type is determined from the resource_id GID.
hints:
readonly: false
inputSchema: