Render mermaid diagram per risk assessment scope

Each scope card now shows a flowchart of its nodes, processes, and
threats, with a distinct shape per type: stadium for entities,
hexagon for boundaries, rectangle for assets, cylinder for data, and
a red hexagon for threats attached via dashed edges to their process
target. The Mermaid source is built on the backend and exposed as a
new `mermaid` field on RiskAssessmentScope; the frontend just renders
it via @probo/ui's MermaidDiagram and shows a copy button + legend.

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
Sacha Al Himdani
2026-05-12 17:31:35 +02:00
parent b9262b5150
commit eedfdcecc8
11 changed files with 714 additions and 5 deletions

View File

@@ -106,6 +106,45 @@ WHERE
return nil
}
func (ns *RiskAssessmentNodes) LoadAllByRiskAssessmentScopeID(
ctx context.Context,
conn pg.Querier,
scope Scoper,
riskAssessmentScopeID gid.GID,
) error {
q := `
SELECT
id,
organization_id,
risk_assessment_scope_id,
node_type,
name,
created_at,
updated_at
FROM
risk_assessment_nodes
WHERE
%s
AND risk_assessment_scope_id = @risk_assessment_scope_id
ORDER BY
created_at ASC, id ASC
`
q = fmt.Sprintf(q, scope.SQLFragment())
args := pgx.NamedArgs{"risk_assessment_scope_id": riskAssessmentScopeID}
maps.Copy(args, scope.SQLArguments())
rows, err := conn.Query(ctx, q, args)
if err != nil {
return fmt.Errorf("cannot query risk assessment nodes: %w", err)
}
results, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[RiskAssessmentNode])
if err != nil {
return fmt.Errorf("cannot collect risk assessment nodes: %w", err)
}
*ns = results
return nil
}
func (ns *RiskAssessmentNodes) CountByRiskAssessmentScopeID(
ctx context.Context,
conn pg.Querier,

View File

@@ -108,6 +108,46 @@ WHERE
return nil
}
func (ps *RiskAssessmentProcesses) LoadAllByRiskAssessmentScopeID(
ctx context.Context,
conn pg.Querier,
scope Scoper,
riskAssessmentScopeID gid.GID,
) error {
q := `
SELECT
id,
organization_id,
risk_assessment_scope_id,
source_node_id,
target_node_id,
name,
created_at,
updated_at
FROM
risk_assessment_processes
WHERE
%s
AND risk_assessment_scope_id = @risk_assessment_scope_id
ORDER BY
created_at ASC, id ASC
`
q = fmt.Sprintf(q, scope.SQLFragment())
args := pgx.NamedArgs{"risk_assessment_scope_id": riskAssessmentScopeID}
maps.Copy(args, scope.SQLArguments())
rows, err := conn.Query(ctx, q, args)
if err != nil {
return fmt.Errorf("cannot query risk assessment processes: %w", err)
}
results, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[RiskAssessmentProcess])
if err != nil {
return fmt.Errorf("cannot collect risk assessment processes: %w", err)
}
*ps = results
return nil
}
func (ps *RiskAssessmentProcesses) CountByRiskAssessmentScopeID(
ctx context.Context,
conn pg.Querier,

View File

@@ -108,6 +108,46 @@ WHERE
return nil
}
func (ts *RiskAssessmentThreats) LoadAllByRiskAssessmentScopeID(
ctx context.Context,
conn pg.Querier,
scope Scoper,
riskAssessmentScopeID gid.GID,
) error {
q := `
SELECT
id,
organization_id,
risk_assessment_scope_id,
process_id,
name,
category,
created_at,
updated_at
FROM
risk_assessment_threats
WHERE
%s
AND risk_assessment_scope_id = @risk_assessment_scope_id
ORDER BY
created_at ASC, id ASC
`
q = fmt.Sprintf(q, scope.SQLFragment())
args := pgx.NamedArgs{"risk_assessment_scope_id": riskAssessmentScopeID}
maps.Copy(args, scope.SQLArguments())
rows, err := conn.Query(ctx, q, args)
if err != nil {
return fmt.Errorf("cannot query risk threats: %w", err)
}
results, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[RiskAssessmentThreat])
if err != nil {
return fmt.Errorf("cannot collect risk threats: %w", err)
}
*ts = results
return nil
}
func (ts *RiskAssessmentThreats) CountByRiskAssessmentScopeID(
ctx context.Context,
conn pg.Querier,