Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions printingpress/aggregate_discovery.go
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,7 @@ func (ap *AggregatePrintingPress) buildCatalog(discovered []*aggregateDiscovered
OverviewHref: pppaths.AggregateSpecIndexHTML(group.slug, versionGroup.slug, spec.EntrySlug),
Warnings: append([]string(nil), spec.Warnings...),
Source: spec.Source,
Counts: aggregateLintResultCounts(ap.specLintResults[spec.RelativePath]),
})
versionModel.SpecCount++
serviceModel.SpecCount++
Expand Down Expand Up @@ -368,6 +369,7 @@ func (ap *AggregatePrintingPress) buildCatalog(discovered []*aggregateDiscovered

func (ap *AggregatePrintingPress) finalizeCatalog(catalog *ppmodel.CatalogSite) {
ap.refreshCatalogLatestState(catalog)
ap.refreshCatalogDiagnosticCounts(catalog)
ap.populateHeaderContexts(catalog)
}

Expand Down Expand Up @@ -397,6 +399,67 @@ func (ap *AggregatePrintingPress) refreshCatalogLatestState(catalog *ppmodel.Cat
}
}

func (ap *AggregatePrintingPress) refreshCatalogDiagnosticCounts(catalog *ppmodel.CatalogSite) {
if catalog == nil {
return
}
for _, service := range catalog.Services {
if service == nil {
continue
}
var serviceCounts ppmodel.ViolationCounts
for _, version := range service.Versions {
if version == nil {
continue
}
var versionCounts ppmodel.ViolationCounts
for _, entry := range visibleCatalogEntries(version) {
versionCounts = addViolationCounts(versionCounts, entry.Counts)
}
version.Counts = violationCountsPointer(versionCounts)
serviceCounts = addViolationCounts(serviceCounts, version.Counts)
}
service.Counts = violationCountsPointer(serviceCounts)
}
}

func aggregateLintResultCounts(results []*drV3.RuleFunctionResult) *ppmodel.ViolationCounts {
var counts ppmodel.ViolationCounts
for _, result := range results {
severity, ok := monacoSeverity(resultSeverity(result))
if !ok {
continue
}
switch severity {
case 8:
counts.Errors++
case 4:
counts.Warns++
case 2:
counts.Infos++
}
}
return violationCountsPointer(counts)
}

func addViolationCounts(counts ppmodel.ViolationCounts, next *ppmodel.ViolationCounts) ppmodel.ViolationCounts {
if next == nil {
return counts
}
counts.Errors += next.Errors
counts.Warns += next.Warns
counts.Infos += next.Infos
return counts
}

func violationCountsPointer(counts ppmodel.ViolationCounts) *ppmodel.ViolationCounts {
if counts.Total() == 0 {
return nil
}
cp := counts
return &cp
}

func (ap *AggregatePrintingPress) populateHeaderContexts(catalog *ppmodel.CatalogSite) {
if catalog == nil {
return
Expand Down
63 changes: 60 additions & 3 deletions printingpress/aggregate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ func TestAggregatePrintingPress_PrintHTML_RendersCatalogAndEntrySites(t *testing
assert.Contains(t, string(rootHTML), "static/printing-press.css")
assert.Contains(t, string(rootHTML), "pp-catalog-card-summary")
assert.Contains(t, string(rootHTML), "pp-catalog-contact-grid")
assert.Contains(t, string(rootHTML), `<dt>Name</dt><dd>API Support</dd>`)
assert.Contains(t, string(rootHTML), `<dt>Contact:</dt><dd>API Support</dd>`)
assert.Contains(t, string(rootHTML), `<dt>Email</dt><dd><a href="mailto:support@example.com">support@example.com</a></dd>`)
assert.Contains(t, string(rootHTML), "pp-model-card")
assert.Contains(t, string(rootHTML), "Current account lifecycle endpoints.")
Expand All @@ -169,6 +169,56 @@ func TestAggregatePrintingPress_PrintHTML_RendersCatalogAndEntrySites(t *testing
assert.NotContains(t, string(entryHTML), `data-pp-versions-href=`)
}

func TestAggregatePrintingPress_PrintHTML_RendersCatalogDiagnosticsCounts(t *testing.T) {
root := t.TempDir()
usersRel := "services/users/src/specs/users.yaml"
writeAggregateSpec(t, root, usersRel, "Users API", "v1")
writeAggregateSpec(t, root, "services/clean/src/specs/clean.yaml", "Clean API", "v1")

outputDir := filepath.Join(root, "site")
ap, err := CreateAggregatePrintingPressFromPath(root, &AggregatePrintingPressConfig{
OutputDir: outputDir,
BuildMode: AggregateBuildModeFull,
StateStore: NewMemorySpecStateStore(),
})
require.NoError(t, err)

_, err = ap.PrintSelectedOutputs(AggregateRenderOptions{
HTML: true,
DeveloperMode: true,
SpecLintResults: map[string][]*drV3.RuleFunctionResult{
usersRel: {
aggregateTestLintResultWithSeverity("error diagnostic", "error"),
aggregateTestLintResultWithSeverity("warning diagnostic", "warn"),
aggregateTestLintResultWithSeverity("info diagnostic", "info"),
},
},
})
require.NoError(t, err)

catalog, err := ap.PressModel()
require.NoError(t, err)
users := findCatalogService(t, catalog, "users")
require.NotNil(t, users.Counts)
assert.Equal(t, &ppmodel.ViolationCounts{Errors: 1, Warns: 1, Infos: 1}, users.Counts)
require.NotNil(t, users.LatestVersion.Counts)
assert.Equal(t, &ppmodel.ViolationCounts{Errors: 1, Warns: 1, Infos: 1}, users.LatestVersion.Counts)
require.NotNil(t, users.LatestVersion.Entries[0].Counts)
assert.Equal(t, &ppmodel.ViolationCounts{Errors: 1, Warns: 1, Infos: 1}, users.LatestVersion.Entries[0].Counts)
clean := findCatalogService(t, catalog, "clean")
assert.Nil(t, clean.Counts)

rootHTML, err := os.ReadFile(filepath.Join(outputDir, "index.html"))
require.NoError(t, err)
rootRendered := string(rootHTML)
assert.Equal(t, 1, strings.Count(rootRendered, `class="pp-catalog-diagnostics"`))
assert.Contains(t, rootRendered, `href="services/users/versions/v1/specs/users-api/diagnostics.html"`)
assert.Contains(t, rootRendered, `aria-label="Diagnostics: 1 error, 1 warning, 1 info"`)
assert.Contains(t, rootRendered, `<sl-icon name="exclamation-square" aria-hidden="true"></sl-icon><span class="pp-catalog-diagnostic-number">1</span>`)
assert.Contains(t, rootRendered, `<sl-icon name="exclamation-triangle" aria-hidden="true"></sl-icon><span class="pp-catalog-diagnostic-number">1</span>`)
assert.Contains(t, rootRendered, `<sl-icon name="info-square" aria-hidden="true"></sl-icon><span class="pp-catalog-diagnostic-number">1</span>`)
}

func TestAggregateEntrySharedAssetBaseURL(t *testing.T) {
assert.Empty(t, (*AggregatePrintingPress)(nil).entrySharedAssetBaseURL(&aggregateDiscoveredSpec{}))
assert.Empty(t, (&AggregatePrintingPress{}).entrySharedAssetBaseURL(&aggregateDiscoveredSpec{}))
Expand Down Expand Up @@ -1097,6 +1147,9 @@ func TestCatalogStylesheet_UsesSharedBackgroundSurface(t *testing.T) {
assert.Contains(t, css, `background: var(--background-color);`)
assert.Contains(t, css, `.pp-catalog-shell pb33f-footer`)
assert.Contains(t, css, `margin-top: calc(var(--global-padding-double) * 2);`)
assert.Contains(t, css, `.pp-catalog-diagnostics`)
assert.Contains(t, css, `border-top: 1px dotted color-mix(in srgb, var(--tertiary-color) 38%, transparent);`)
assert.Contains(t, css, `background: color-mix(in srgb, var(--tertiary-color) 8%, transparent);`)
assert.NotContains(t, css, `radial-gradient(`)
assert.NotContains(t, css, `var(--terminal-background)`)
}
Expand All @@ -1112,12 +1165,16 @@ func normalizedAggregateEntryConfigHash(t *testing.T, config *AggregatePrintingP
}

func aggregateTestLintResult(message string) *drV3.RuleFunctionResult {
return aggregateTestLintResultWithSeverity(message, "warn")
}

func aggregateTestLintResultWithSeverity(message, severity string) *drV3.RuleFunctionResult {
return &drV3.RuleFunctionResult{
Message: message,
Path: "$.paths['/health'].get.operationId",
RuleId: "test-operation-id",
RuleSeverity: "warn",
Rule: &drV3.Rule{Id: "test-operation-id", Severity: "warn"},
RuleSeverity: severity,
Rule: &drV3.Rule{Id: "test-operation-id", Severity: severity},
}
}

Expand Down
119 changes: 117 additions & 2 deletions printingpress/aggregate_writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -816,6 +816,9 @@ func catalogRootContent(catalog *ppmodel.CatalogSite, disableSkippedRendering bo
return err
}
}
if _, err := io.WriteString(w, catalogDiagnosticsHTML(service.Counts, catalogServiceDiagnosticsHref(".", service))); err != nil {
return err
}
if _, err := io.WriteString(w, `</article>`); err != nil {
return err
}
Expand All @@ -838,8 +841,10 @@ func catalogVersionContent(service *ppmodel.CatalogService, version *ppmodel.Cat
if version == nil || len(visibleCatalogEntries(version)) == 0 {
return nil
}
if _, err := io.WriteString(w, `<section class="pp-catalog-section"><article class="pp-catalog-card pp-model-card"><h2 class="pp-catalog-card-title"><a href="`+templ.EscapeString(relativeCatalogHref(path.Dir(version.OverviewHref), catalogVersionPrimaryHref(version)))+`">`+templ.EscapeString(version.Label)+`</a></h2>`+
fromDir := path.Dir(version.OverviewHref)
if _, err := io.WriteString(w, `<section class="pp-catalog-section"><article class="pp-catalog-card pp-model-card"><h2 class="pp-catalog-card-title"><a href="`+templ.EscapeString(relativeCatalogHref(fromDir, catalogVersionPrimaryHref(version)))+`">`+templ.EscapeString(version.Label)+`</a></h2>`+
catalogSummaryHTML(version.Summary)+
catalogDiagnosticsHTML(version.Counts, catalogVersionDiagnosticsHref(fromDir, version))+
`</article></section>`); err != nil {
return err
}
Expand Down Expand Up @@ -874,6 +879,9 @@ func catalogVersionEntriesContent(service *ppmodel.CatalogService, version *ppmo
if _, err := io.WriteString(w, catalogContactHTML(entry.Contact)); err != nil {
return err
}
if _, err := io.WriteString(w, catalogDiagnosticsHTML(entry.Counts, catalogEntryDiagnosticsHref(path.Dir(version.OverviewHref), entry))); err != nil {
return err
}
if _, err := io.WriteString(w, `</article>`); err != nil {
return err
}
Expand Down Expand Up @@ -1053,6 +1061,17 @@ func catalogServiceContact(service *ppmodel.CatalogService) *ppmodel.ContactInfo
return entries[0].Contact
}

func visibleCatalogServiceEntries(service *ppmodel.CatalogService) []*ppmodel.CatalogSpecEntry {
if service == nil {
return nil
}
var entries []*ppmodel.CatalogSpecEntry
for _, version := range visibleCatalogVersions(service) {
entries = append(entries, visibleCatalogEntries(version)...)
}
return entries
}

func catalogSummaryHTML(value string) string {
value = strings.TrimSpace(value)
if value == "" {
Expand All @@ -1067,6 +1086,102 @@ func catalogSummaryHTML(value string) string {
return builder.String()
}

func catalogServiceDiagnosticsHref(fromDir string, service *ppmodel.CatalogService) string {
entries := visibleCatalogServiceEntries(service)
if len(entries) != 1 {
return ""
}
return catalogEntryDiagnosticsHref(fromDir, entries[0])
}

func catalogVersionDiagnosticsHref(fromDir string, version *ppmodel.CatalogVersion) string {
entries := visibleCatalogEntries(version)
if len(entries) != 1 {
return ""
}
return catalogEntryDiagnosticsHref(fromDir, entries[0])
}

func catalogEntryDiagnosticsHref(fromDir string, entry *ppmodel.CatalogSpecEntry) string {
if entry == nil || strings.TrimSpace(entry.OverviewHref) == "" {
return ""
}
diagnosticsPath := path.Join(path.Dir(entry.OverviewHref), pppaths.FileDiagnosticsHTML)
return relativeCatalogHref(fromDir, diagnosticsPath)
}

func catalogDiagnosticsHTML(counts *ppmodel.ViolationCounts, href string) string {
if counts == nil || counts.Total() == 0 {
return ""
}
var builder strings.Builder
tag := "div"
if strings.TrimSpace(href) != "" {
tag = "a"
}
builder.WriteString(`<`)
builder.WriteString(tag)
builder.WriteString(` class="pp-catalog-diagnostics"`)
if tag == "a" {
builder.WriteString(` href="`)
builder.WriteString(templ.EscapeString(href))
builder.WriteString(`"`)
}
builder.WriteString(` aria-label="`)
builder.WriteString(templ.EscapeString(catalogDiagnosticsLabel(counts)))
builder.WriteString(`">`)
builder.WriteString(`<span class="pp-catalog-diagnostics-title">Diagnostics</span>`)
builder.WriteString(catalogDiagnosticCountHTML("err", "exclamation-square", counts.Errors, "error", "errors"))
builder.WriteString(catalogDiagnosticCountHTML("warn", "exclamation-triangle", counts.Warns, "warning", "warnings"))
builder.WriteString(catalogDiagnosticCountHTML("info", "info-square", counts.Infos, "info", "infos"))
builder.WriteString(`</`)
builder.WriteString(tag)
builder.WriteString(`>`)
return builder.String()
}

func catalogDiagnosticCountHTML(className, icon string, count int, singular, plural string) string {
if count <= 0 {
return ""
}
noun := plural
if count == 1 {
noun = singular
}
var builder strings.Builder
builder.WriteString(`<span class="pp-catalog-diagnostic-count `)
builder.WriteString(templ.EscapeString(className))
builder.WriteString(`"><sl-icon name="`)
builder.WriteString(templ.EscapeString(icon))
builder.WriteString(`" aria-hidden="true"></sl-icon><span class="pp-catalog-diagnostic-number">`)
builder.WriteString(templ.EscapeString(fmt.Sprintf("%d", count)))
builder.WriteString(`</span><span class="pp-catalog-sr-only"> `)
builder.WriteString(templ.EscapeString(noun))
builder.WriteString(`</span></span>`)
return builder.String()
}

func catalogDiagnosticsLabel(counts *ppmodel.ViolationCounts) string {
if counts == nil || counts.Total() == 0 {
return "Diagnostics"
}
parts := make([]string, 0, 3)
appendPart := func(count int, singular, plural string) {
if count <= 0 {
return
}
noun := plural
if count == 1 {
noun = singular
}
parts = append(parts, fmt.Sprintf("%d %s", count, noun))
}
appendPart(counts.Errors, "error", "errors")
appendPart(counts.Warns, "warning", "warnings")
appendPart(counts.Infos, "info", "infos")
return "Diagnostics: " + strings.Join(parts, ", ")
}

func catalogContactHTML(contact *ppmodel.ContactInfo) string {
name := catalogContactName(contact)
email := catalogContactEmail(contact)
Expand All @@ -1076,7 +1191,7 @@ func catalogContactHTML(contact *ppmodel.ContactInfo) string {
var builder strings.Builder
builder.WriteString(`<dl class="pp-catalog-contact-grid">`)
if name != "" {
builder.WriteString(`<dt>Name</dt><dd>`)
builder.WriteString(`<dt>Contact:</dt><dd>`)
builder.WriteString(templ.EscapeString(name))
builder.WriteString(`</dd>`)
}
Expand Down
3 changes: 3 additions & 0 deletions printingpress/model/catalog.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ type CatalogService struct {
LatestVersion *CatalogVersion `json:"latestVersion,omitempty"`
Versions []*CatalogVersion `json:"versions,omitempty"`
CollisionGroups []string `json:"collisionGroups,omitempty"`
Counts *ViolationCounts `json:"counts,omitempty"`
}

// CatalogVersion groups one or more spec entries under the same service version.
Expand All @@ -57,6 +58,7 @@ type CatalogVersion struct {
SpecCount int `json:"specCount"`
IsLatest bool `json:"isLatest,omitempty"`
Entries []*CatalogSpecEntry `json:"entries,omitempty"`
Counts *ViolationCounts `json:"counts,omitempty"`
}

// CatalogSpecEntry is one discovered root specification rendered within the catalog.
Expand All @@ -78,4 +80,5 @@ type CatalogSpecEntry struct {
Warnings []string `json:"warnings,omitempty"`
Source *SourceRef `json:"source,omitempty"`
HeaderContext *SiteHeaderContext `json:"headerContext,omitempty"`
Counts *ViolationCounts `json:"counts,omitempty"`
}
Loading
Loading