Fix empty text nodes in generated documents

Helpers feeding ProseMirror JSON templates under pkg/probo/templates
could return empty strings, producing { "type": "text", "text": "" }
nodes that Tiptap rejects with "Empty text nodes are not allowed" when
the document is loaded into the editor. The backend HTML/PDF export
silently tolerates them, so the issue only surfaces in the UI.

Route every enum format helper's default branch through
stringOrNotSpecified (formatClassification, formatAssetType,
formatFinding{Kind,Status,Priority}, formatObligation{Status,Type},
formatVendorCategory, formatDataSensitivity, formatBusinessImpact,
formatProcessingActivityRole, formatLawfulBasis,
formatSpecialOrCriminalData, formatTransferSafeguard, formatDPIANeeded,
formatTIANeeded, formatResidualRisk), wrap asset DataTypesStored, and
treat an empty FullName as a missing profile in every owner/DPO lookup
(lookupProfileName plus the inline profileMap reads in data, asset,
finding, obligation and processing-activity builders).

Also tweak the vendor list template: insert a horizontalRule after each
record so they paginate cleanly, rename the trailing section to
"Definitions", and drop the now-redundant "3.1 Lexicon" subheading.

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
Sacha Al Himdani
2026-04-29 17:59:07 +02:00
parent 3ff7885ad1
commit 8e2227847e
2 changed files with 27 additions and 32 deletions

View File

@@ -553,7 +553,7 @@ func (s *GeneratedDocumentService) buildDataListDocumentData(
rows := make([]docgen.DataListRow, 0, len(data))
for _, d := range data {
ownerName := "-"
if p, ok := profileMap[d.OwnerID]; ok {
if p, ok := profileMap[d.OwnerID]; ok && p.FullName != "" {
ownerName = p.FullName
}
@@ -600,7 +600,7 @@ func formatClassification(c coredata.DataClassification) string {
case coredata.DataClassificationSecret:
return "Secret"
default:
return string(c)
return stringOrNotSpecified(string(c))
}
}
@@ -843,7 +843,7 @@ func (s *GeneratedDocumentService) buildAssetListDocumentData(
rows := make([]docgen.AssetListRow, 0, len(assets))
for _, a := range assets {
ownerName := "-"
if p, ok := profileMap[a.OwnerID]; ok {
if p, ok := profileMap[a.OwnerID]; ok && p.FullName != "" {
ownerName = p.FullName
}
@@ -866,7 +866,7 @@ func (s *GeneratedDocumentService) buildAssetListDocumentData(
Name: a.Name,
AssetType: formatAssetType(a.AssetType),
Amount: a.Amount,
DataTypesStored: a.DataTypesStored,
DataTypesStored: stringOrNotSpecified(a.DataTypesStored),
Owner: ownerName,
Vendors: vendorStr,
})
@@ -888,7 +888,7 @@ func formatAssetType(t coredata.AssetType) string {
case coredata.AssetTypeVirtual:
return "Virtual"
default:
return string(t)
return stringOrNotSpecified(string(t))
}
}
@@ -1159,7 +1159,7 @@ func (s *GeneratedDocumentService) buildFindingListDocumentData(
for _, f := range findings {
ownerName := "-"
if f.OwnerID != nil {
if p, ok := profileMap[*f.OwnerID]; ok {
if p, ok := profileMap[*f.OwnerID]; ok && p.FullName != "" {
ownerName = p.FullName
}
}
@@ -1235,7 +1235,7 @@ func formatFindingKind(k coredata.FindingKind) string {
case coredata.FindingKindException:
return "Exception"
default:
return string(k)
return stringOrNotSpecified(string(k))
}
}
@@ -1254,7 +1254,7 @@ func formatFindingStatus(s coredata.FindingStatus) string {
case coredata.FindingStatusFalsePositive:
return "False Positive"
default:
return string(s)
return stringOrNotSpecified(string(s))
}
}
@@ -1267,7 +1267,7 @@ func formatFindingPriority(p coredata.FindingPriority) string {
case coredata.FindingPriorityHigh:
return "High"
default:
return string(p)
return stringOrNotSpecified(string(p))
}
}
@@ -1515,7 +1515,7 @@ func (s *GeneratedDocumentService) buildObligationListDocumentData(
rows := make([]docgen.ObligationListRow, 0, len(obligations))
for _, o := range obligations {
ownerName := "-"
if p, ok := profileMap[o.OwnerID]; ok {
if p, ok := profileMap[o.OwnerID]; ok && p.FullName != "" {
ownerName = p.FullName
}
@@ -1580,7 +1580,7 @@ func formatObligationStatus(s coredata.ObligationStatus) string {
case coredata.ObligationStatusCompliant:
return "Compliant"
default:
return string(s)
return stringOrNotSpecified(string(s))
}
}
@@ -1591,7 +1591,7 @@ func formatObligationType(t coredata.ObligationType) string {
case coredata.ObligationTypeContractual:
return "Contractual"
default:
return string(t)
return stringOrNotSpecified(string(t))
}
}
@@ -1845,7 +1845,7 @@ func (s *GeneratedDocumentService) buildProcessingActivityListDocumentData(
for _, pa := range processingActivities {
dpoName := "Not assigned"
if pa.DataProtectionOfficerID != nil {
if p, ok := dpoMap[*pa.DataProtectionOfficerID]; ok {
if p, ok := dpoMap[*pa.DataProtectionOfficerID]; ok && p.FullName != "" {
dpoName = p.FullName
}
}
@@ -1916,7 +1916,7 @@ func formatProcessingActivityRole(role coredata.ProcessingActivityRole) string {
case coredata.ProcessingActivityRoleProcessor:
return "Processor"
default:
return string(role)
return stringOrNotSpecified(string(role))
}
}
@@ -1935,7 +1935,7 @@ func formatLawfulBasis(basis coredata.ProcessingActivityLawfulBasis) string {
case coredata.ProcessingActivityLawfulBasisVitalInterests:
return "Vital Interests"
default:
return string(basis)
return stringOrNotSpecified(string(basis))
}
}
@@ -1948,7 +1948,7 @@ func formatSpecialOrCriminalData(data coredata.ProcessingActivitySpecialOrCrimin
case coredata.ProcessingActivitySpecialOrCriminalDatumPossible:
return "Possible"
default:
return string(data)
return stringOrNotSpecified(string(data))
}
}
@@ -1970,7 +1970,7 @@ func formatTransferSafeguard(safeguard *coredata.ProcessingActivityTransferSafeg
case coredata.ProcessingActivityTransferSafeguardCertificationMechanisms:
return "Certification Mechanisms"
default:
return string(*safeguard)
return stringOrNotSpecified(string(*safeguard))
}
}
@@ -1981,7 +1981,7 @@ func formatDPIANeeded(needed coredata.ProcessingActivityDataProtectionImpactAsse
case coredata.ProcessingActivityDataProtectionImpactAssessmentNotNeeded:
return "No"
default:
return string(needed)
return stringOrNotSpecified(string(needed))
}
}
@@ -1992,7 +1992,7 @@ func formatTIANeeded(needed coredata.ProcessingActivityTransferImpactAssessment)
case coredata.ProcessingActivityTransferImpactAssessmentNotNeeded:
return "No"
default:
return string(needed)
return stringOrNotSpecified(string(needed))
}
}
@@ -2008,7 +2008,7 @@ func formatResidualRisk(risk *coredata.DataProtectionImpactAssessmentResidualRis
case coredata.DataProtectionImpactAssessmentResidualRiskHigh:
return "High"
default:
return string(*risk)
return stringOrNotSpecified(string(*risk))
}
}
@@ -2984,7 +2984,7 @@ func lookupProfileName(profiles map[gid.GID]*coredata.MembershipProfile, id *gid
if id == nil {
return "Not assigned"
}
if p, ok := profiles[*id]; ok {
if p, ok := profiles[*id]; ok && p.FullName != "" {
return p.FullName
}
return "Not assigned"
@@ -3003,7 +3003,7 @@ func formatDataSensitivity(s coredata.DataSensitivity) string {
case coredata.DataSensitivityCritical:
return "Critical"
default:
return string(s)
return stringOrNotSpecified(string(s))
}
}
@@ -3018,7 +3018,7 @@ func formatBusinessImpact(b coredata.BusinessImpact) string {
case coredata.BusinessImpactCritical:
return "Critical"
default:
return string(b)
return stringOrNotSpecified(string(b))
}
}
@@ -3069,7 +3069,7 @@ func formatVendorCategory(c coredata.VendorCategory) string {
case coredata.VendorCategoryVersionControl:
return "Version Control"
default:
return string(c)
return stringOrNotSpecified(string(c))
}
}

View File

@@ -257,17 +257,12 @@
{ "type": "text", "text": "Data Privacy Agreement: ", "marks": [{ "type": "bold" }] },
{ "type": "text", "text": {{if $r.DataPrivacyAgreement}}{{json (printf "Yes — From %s until %s" $r.DataPrivacyAgreement.ValidFrom $r.DataPrivacyAgreement.ValidUntil)}}{{else}}"No"{{end}} }
]
}{{end}},
{ "type": "horizontalRule" },
},
{ "type": "horizontalRule" }{{end}},
{
"type": "heading",
"attrs": { "level": 1 },
"content": [{ "type": "text", "text": "3. Annexes" }]
},
{
"type": "heading",
"attrs": { "level": 2 },
"content": [{ "type": "text", "text": "3.1 Lexicon" }]
"content": [{ "type": "text", "text": "3. Definitions" }]
},
{
"type": "heading",