Skip to content

Commit 9f98c13

Browse files
committed
feat: show PII type categories in statusbar instead of field paths
- Add PIIField struct with Path and PIIType to replace [][]string - Add PIIType constants (email, phone, credit_card, ssn, etc.) - Drop redundant Risk column from PII table (always high) - Show deduplicated PII type labels with +N truncation
1 parent 8c12d91 commit 9f98c13

2 files changed

Lines changed: 58 additions & 31 deletions

File tree

‎internal/app/statusbar/pii/pii.go‎

Lines changed: 36 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import (
1313
"github.com/usetero/cli/internal/domain"
1414
"github.com/usetero/cli/internal/sqlite"
1515
"github.com/usetero/cli/internal/styles"
16-
"github.com/usetero/cli/internal/tea/components/status"
1716
"github.com/usetero/cli/internal/tea/components/table"
1817
)
1918

@@ -93,8 +92,11 @@ func (m *Model) stateKey(policies []domain.PIIPolicy) string {
9392
var b strings.Builder
9493
fmt.Fprintf(&b, "%d", len(policies))
9594
for _, p := range policies {
96-
fmt.Fprintf(&b, "|%s:%s:%s:%s:%d",
97-
p.ServiceName, p.LogEventName, p.RiskLevel, p.Status, len(p.Fields))
95+
fmt.Fprintf(&b, "|%s:%s:%s:%d",
96+
p.ServiceName, p.LogEventName, p.Status, len(p.Fields))
97+
for _, f := range p.Fields {
98+
fmt.Fprintf(&b, ":%s", f.PIIType)
99+
}
98100
}
99101
return b.String()
100102
}
@@ -172,53 +174,58 @@ func (m *Model) renderTable(width int) string {
172174
}
173175

174176
tbl := table.New(m.theme, table.WithMaxValueWidth(40))
175-
tbl.Headers("Log Event", "Service", "Risk", "Leaking")
177+
tbl.Headers("Log Event", "Service", "Leaking")
176178
tbl.SetWidth(width)
177179

178180
for _, p := range m.policies {
179181
tbl.Row(
180182
p.LogEventName,
181183
p.ServiceName,
182-
status.Risk(m.theme, p.RiskLevel, true),
183-
formatFields(p.Fields),
184+
formatPIITypes(p.Fields, 3),
184185
)
185186
}
186187

187188
return tbl.View()
188189
}
189190

190-
// formatFields joins PII field paths into a readable string.
191-
// [["attributes","user","email"]] → "user.email"
192-
// Strips the leading "attributes"/"resource_attributes" namespace for readability.
193-
func formatFields(fields [][]string) string {
191+
// formatPIITypes returns deduplicated PII type labels, showing at most maxShow
192+
// before truncating with "+N". For example: "email, phone, +2".
193+
func formatPIITypes(fields []domain.PIIField, maxShow int) string {
194194
if len(fields) == 0 {
195195
return "—"
196196
}
197197

198-
names := make([]string, 0, len(fields))
199-
for _, path := range fields {
200-
names = append(names, formatFieldPath(path))
198+
// Deduplicate and preserve first-seen order.
199+
seen := make(map[string]bool)
200+
var types []string
201+
for _, f := range fields {
202+
label := displayPIIType(f.PIIType)
203+
if !seen[label] {
204+
seen[label] = true
205+
types = append(types, label)
206+
}
201207
}
202-
return strings.Join(names, ", ")
203-
}
204208

205-
// formatFieldPath converts a field path to a dot-separated string,
206-
// stripping common OTel namespace prefixes.
207-
func formatFieldPath(path []string) string {
208-
if len(path) == 0 {
209-
return ""
209+
if len(types) <= maxShow {
210+
return strings.Join(types, ", ")
210211
}
212+
return strings.Join(types[:maxShow], ", ") + fmt.Sprintf(", +%d", len(types)-maxShow)
213+
}
211214

212-
// Strip common OTel namespace prefixes for readability.
213-
start := 0
214-
if len(path) > 1 {
215-
switch path[0] {
216-
case "attributes", "resource_attributes", "scope_attributes":
217-
start = 1
218-
}
215+
// displayPIIType returns a human-readable label for a pii_type value.
216+
func displayPIIType(t string) string {
217+
switch t {
218+
case domain.PIITypeCreditCard:
219+
return "credit card"
220+
case domain.PIITypeIPAddress:
221+
return "IP address"
222+
case domain.PIITypeSSN:
223+
return "SSN"
224+
case domain.PIITypeDateOfBirth:
225+
return "date of birth"
226+
default:
227+
return t
219228
}
220-
221-
return strings.Join(path[start:], ".")
222229
}
223230

224231
func (m *Model) pendingCount() int {

‎internal/domain/policy_pii.go‎

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,32 @@
11
package domain
22

3+
// PIIType constants for categorising PII fields.
4+
const (
5+
PIITypeEmail = "email"
6+
PIITypeName = "name"
7+
PIITypePhone = "phone"
8+
PIITypeAddress = "address"
9+
PIITypeCreditCard = "credit_card"
10+
PIITypeSSN = "ssn"
11+
PIITypePassword = "password"
12+
PIITypeIPAddress = "ip_address"
13+
PIITypeDateOfBirth = "date_of_birth"
14+
PIITypeGeneral = "general"
15+
)
16+
17+
// PIIField identifies an attribute path that contains PII and its type.
18+
type PIIField struct {
19+
Path []string `json:"path"`
20+
PIIType string `json:"pii_type"`
21+
}
22+
323
// PIILeakageAnalysis is the category-specific analysis for PII leakage policies.
424
// Stored as JSON in the log_event_policies.analysis column under the "pii_leakage" key.
525
type PIILeakageAnalysis struct {
626
Rationale string `json:"rationale"`
727
RiskLevel string `json:"risk_level"`
828
Benefits []string `json:"benefits"`
9-
Fields [][]string `json:"fields"` // Attribute paths containing PII, e.g. [["attributes","user","email"]]
29+
Fields []PIIField `json:"fields"`
1030
}
1131

1232
// PIIAnalysisEnvelope wraps the category-keyed analysis JSON from the database.
@@ -20,5 +40,5 @@ type PIIPolicy struct {
2040
ServiceName string
2141
RiskLevel RiskLevel
2242
Status PolicyLogStatus
23-
Fields [][]string // Parsed from analysis JSON
43+
Fields []PIIField // Parsed from analysis JSON
2444
}

0 commit comments

Comments
 (0)