Skip to content

Commit 3289b75

Browse files
committed
Implement soft validation in draft mode and update warn messages
Signed-off-by: Raunak Madan <madanraunak24@gmail.com>
1 parent 745ce7f commit 3289b75

7 files changed

Lines changed: 132 additions & 45 deletions

File tree

README.md

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,50 @@ In addition to this core structure, the following content types are available:
2525
final-test)
2626
- **Certification** - Certification programs
2727

28+
### Where Content Should Live
29+
30+
Keep publishable Academy content and any org-specific supporting files inside
31+
the organization directory. Files outside the org directory are not part of
32+
the publishable Academy content tree. Validation catches these cases, and the
33+
files may be skipped during publication or change shared site behavior in
34+
unexpected ways.
35+
36+
Use this general layout:
37+
38+
```text
39+
content/
40+
learning-paths/
41+
<org-id>/
42+
_index.md
43+
course-1/
44+
_index.md
45+
module-1/
46+
_index.md
47+
certifications/
48+
<org-id>/
49+
_index.md
50+
challenges/
51+
<org-id>/
52+
_index.md
53+
layouts/
54+
shortcodes/
55+
<org-id>/
56+
*.html
57+
static/
58+
<org-id>/
59+
...
60+
data/
61+
<org-id>/
62+
...
63+
```
64+
65+
Put org-specific content, shortcodes, static files, and data under the org
66+
directory. If you place them elsewhere, the Academy may not publish them and
67+
the build can emit warnings or break shared assumptions for other content.
68+
69+
Shared theme assets, icons, and reusable partials should stay in
70+
academy-theme itself rather than in a consuming org repository.
71+
2872
## ID Validation
2973

3074
The theme checks publishable root Academy content during Hugo builds and emits
@@ -64,9 +108,9 @@ have not been checked against the registry.
64108

65109
The registry response is cached once per org and registry URL in Hugo's site
66110
store during a build. The remote response uses a cache key scoped to the current
67-
Hugo process, so incremental rebuilds reuse the same response without relying on
68-
Hugo's default permanent `getresource` cache. Restart Hugo to force a fresh
69-
registry lookup during local authoring.
111+
Hugo process, so incremental rebuilds reuse the same response. Rebuild the
112+
site completely to refresh the cached registry API response for fresh ID
113+
lookups.
70114

71115
The Instructor Toolkit surfaces the same build-generated report.
72116

hugo.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ params:
8585
taxonomyPageHeader: [tags, categories]
8686

8787
academy:
88+
domain: cloud.meshery.io
8889
registryURL: https://cloud.meshery.io/api/academy/curricula
8990

9091
privacy_policy: https://policies.google.com/privacy

layouts/partials/academy-validation/collect-content-health.html

Lines changed: 45 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,26 @@
1414
{{- if eq $consoleURL "" -}}
1515
{{- $consoleURL = "/academy/instructors-console" -}}
1616
{{- end -}}
17+
{{- $academyBaseURL := "" -}}
18+
{{- with $site.Params.academy.domain -}}
19+
{{- $academyBaseURL = printf "%v" . | strings.TrimSpace -}}
20+
{{- else -}}
21+
{{- with $site.Params.academy.baseURL -}}
22+
{{- $academyBaseURL = printf "%v" . | strings.TrimSpace -}}
23+
{{- else -}}
24+
{{- with $site.Params.academy.consoleURL -}}
25+
{{- $academyBaseURL = printf "%v" . | strings.TrimSpace -}}
26+
{{- end -}}
27+
{{- end -}}
28+
{{- end -}}
29+
{{- if and (ne $academyBaseURL "") (not (hasPrefix $academyBaseURL "http://")) (not (hasPrefix $academyBaseURL "https://")) -}}
30+
{{- $academyBaseURL = printf "https://%s" $academyBaseURL -}}
31+
{{- end -}}
32+
{{- $academyBaseURL = replaceRE `/academy/.*$` "" $academyBaseURL -}}
33+
{{- $academyBaseURL = strings.TrimSuffix "/" $academyBaseURL -}}
34+
{{- if and (ne $academyBaseURL "") (ne $consoleURL "") (not (hasPrefix $consoleURL "http://")) (not (hasPrefix $consoleURL "https://")) -}}
35+
{{- $consoleURL = printf "%s/academy/instructors-console" $academyBaseURL -}}
36+
{{- end -}}
1737

1838
{{- $validationMode := "warn" -}}
1939
{{- with $site.Params.academy.validationMode -}}
@@ -25,6 +45,7 @@
2545
{{- $good := 0 -}}
2646
{{- $broken := 0 -}}
2747
{{- $draft := 0 -}}
48+
{{- $draftWarning := 0 -}}
2849
{{- $unchecked := 0 -}}
2950

3051
{{- range $site.Pages -}}
@@ -60,25 +81,21 @@
6081
{{- end -}}
6182

6283
{{- if $isDraft -}}
63-
{{- $status = "draft" -}}
64-
{{- $issue = "Draft content is not checked." -}}
65-
{{- $fix = "Publish or remove draft mode when this content is ready for validation." -}}
6684
{{- $draft = add $draft 1 -}}
67-
{{- else if not (partial "academy-validation/validate-academy-org-id.html" $orgID) -}}
85+
{{- end -}}
86+
87+
{{- if not (partial "academy-validation/validate-academy-org-id.html" $orgID) -}}
6888
{{- $status = "broken" -}}
6989
{{- $issue = "Content is outside a valid organization directory." -}}
7090
{{- $fix = "Move this root content under content/<type>/<org-id>/<slug>/." -}}
71-
{{- $broken = add $broken 1 -}}
7291
{{- else if eq $contentID "" -}}
7392
{{- $status = "broken" -}}
7493
{{- $issue = "Missing Content ID." -}}
7594
{{- $fix = "Create or register this content in the Instructor Console, then add the returned ID to front matter." -}}
76-
{{- $broken = add $broken 1 -}}
7795
{{- else if not $registryValidationEnabled -}}
7896
{{- $status = "unchecked" -}}
7997
{{- $issue = "Content health validation is turned off." -}}
8098
{{- $fix = "Enable Academy content health validation before publishing." -}}
81-
{{- $unchecked = add $unchecked 1 -}}
8299
{{- else -}}
83100
{{- $registryCacheKey := printf "academy-theme-registry-ids-%s-%s" $orgID (md5 $registryURL) -}}
84101
{{- partial "academy-validation/validate-academy-registry-ids.html" (dict "site" $site "orgID" $orgID "registryURL" $registryURL) -}}
@@ -88,22 +105,37 @@
88105
{{- $status = "unchecked" -}}
89106
{{- $issue = $registry.message -}}
90107
{{- $fix = "Configure params.academy.registryURL to check this ID against the Academy registry." -}}
91-
{{- $unchecked = add $unchecked 1 -}}
92108
{{- else if not (in $registry.ids $contentID) -}}
93109
{{- $status = "broken" -}}
94110
{{- $issue = printf "Content ID '%s' does not exist in the Academy registry for org '%s'." $contentID $orgID -}}
95111
{{- $fix = "Use the registered ID from the Instructor Console, or create/register this content before publishing." -}}
96-
{{- $broken = add $broken 1 -}}
97-
{{- else -}}
98-
{{- $good = add $good 1 -}}
99112
{{- end -}}
100113
{{- end -}}
101114

115+
{{- if and $isDraft (ne $status "good") -}}
116+
{{- $status = "draft_warning" -}}
117+
{{- if eq $issue "" -}}
118+
{{- $issue = "Draft content could not be fully validated." -}}
119+
{{- end -}}
120+
{{- $fix = "This draft will not block publishing, but fix the ID before removing draft mode." -}}
121+
{{- end -}}
122+
123+
{{- if eq $status "good" -}}
124+
{{- $good = add $good 1 -}}
125+
{{- else if eq $status "broken" -}}
126+
{{- $broken = add $broken 1 -}}
127+
{{- else if eq $status "unchecked" -}}
128+
{{- $unchecked = add $unchecked 1 -}}
129+
{{- else if eq $status "draft_warning" -}}
130+
{{- $draftWarning = add $draftWarning 1 -}}
131+
{{- end -}}
132+
102133
{{- $items = $items | append (dict
103134
"title" $title
104135
"contentType" $contentType
105136
"sourcePath" $filePath
106137
"relPermalink" .RelPermalink
138+
"productionPermalink" (cond (ne $academyBaseURL "") (printf "%s/academy%s" $academyBaseURL .RelPermalink) "")
107139
"orgID" $orgID
108140
"contentID" $contentID
109141
"status" $status
@@ -125,10 +157,11 @@
125157

126158
{{- $report := dict
127159
"items" $items
128-
"counts" (dict "good" $good "broken" $broken "draft" $draft "unchecked" $unchecked "total" (len $items))
160+
"counts" (dict "good" $good "broken" $broken "draft" $draft "draftWarning" $draftWarning "unchecked" $unchecked "total" (len $items))
129161
"summary" $summary
130162
"registryURLConfigured" (ne $registryURL "")
131163
"consoleURL" $consoleURL
164+
"academyBaseURL" $academyBaseURL
132165
-}}
133166
{{- $site.Store.Set $cacheKey $report -}}
134167
{{- end -}}

layouts/partials/academy-validation/validate-academy-build-paths-walk.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{{- $dir := .dir -}}
22
{{- $base := .base -}}
33
{{- $label := .label -}}
4+
{{- $contentStructureURL := "https://github.com/layer5io/academy-theme#academy-theme" -}}
45
{{- with try (os.ReadDir $dir) -}}
56
{{- if not .Err -}}
67
{{- range .Value -}}
@@ -12,7 +13,7 @@
1213
{{- $parts := split $relative "/" -}}
1314
{{- $scope := index $parts 0 -}}
1415
{{- if and $scope (not (partial "academy-validation/validate-academy-org-id.html" $scope)) -}}
15-
{{- warnf "%s found outside the organization directory: %s." $label $path -}}
16+
{{- warnf "%s found outside the organization directory: %s. For more info checkout the content structure at %s" $label $path $contentStructureURL -}}
1617
{{- end -}}
1718
{{- end -}}
1819
{{- end -}}

layouts/partials/academy-validation/validate-academy-build-paths.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{{- $site := .site -}}
22
{{- $state := newScratch -}}
33
{{- $state.Set "hasOrgScopedAcademyContent" false -}}
4+
{{- $contentStructureURL := "https://github.com/layer5io/academy-theme#academy-theme" -}}
45

56
{{- range $site.Pages -}}
67
{{- if and .File (not .Draft) -}}
@@ -10,7 +11,7 @@
1011
{{- if partial "academy-validation/validate-academy-org-id.html" (index $parts 1) -}}
1112
{{- $state.Set "hasOrgScopedAcademyContent" true -}}
1213
{{- else -}}
13-
{{- warnf "content found outside the organization directory: %s." $filePath -}}
14+
{{- warnf "content found outside the organization directory: %s. For more info checkout the content structure at %s" $filePath $contentStructureURL -}}
1415
{{- end -}}
1516
{{- end -}}
1617
{{- end -}}

layouts/partials/academy-validation/validate-academy-build.html

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,18 @@
1414
{{- $warnedRegistryNotConfigured := false -}}
1515
{{- $warnedRegistryUnavailable := false -}}
1616
{{- range $report.items -}}
17-
{{- if eq .status "broken" -}}
17+
{{- if or (eq .status "broken") (eq .status "draft_warning") -}}
1818
{{- $message := "" -}}
1919
{{- if eq .contentID "" -}}
2020
{{- $message = printf "Missing Content ID at %s" .sourcePath -}}
2121
{{- else -}}
22-
{{- $message = printf "Invalid Content ID '%s' at %s" .contentID .sourcePath -}}
22+
{{- $message = printf "Invalid Content ID '%s' at %s. Rebuild the site completely to refresh the cached registry response." .contentID .sourcePath -}}
2323
{{- end -}}
24-
{{- if eq $validationMode "error" -}}
24+
{{- if eq .status "draft_warning" -}}
25+
{{- if ne $validationMode "off" -}}
26+
{{- warnf "Draft warning: %s" $message -}}
27+
{{- end -}}
28+
{{- else if eq $validationMode "error" -}}
2529
{{- errorf "%s" $message -}}
2630
{{- else if ne $validationMode "off" -}}
2731
{{- warnf "%s" $message -}}

layouts/shortcodes/instructor-toolkit.html

Lines changed: 28 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,33 +2,37 @@
22
{{- $report := .Page.Site.Store.Get "academy-theme-content-health-report" -}}
33
{{- $consoleURL := $report.consoleURL -}}
44
{{- $items := $report.items -}}
5-
{{- $draftItems := where $items "status" "draft" -}}
6-
{{- $checkedItems := where $items "status" "ne" "draft" -}}
7-
{{- $problemItems := where $checkedItems "status" "ne" "good" -}}
5+
{{- $blockingItems := where $items "draft" false -}}
6+
{{- $problemItems := where $blockingItems "status" "ne" "good" -}}
87
{{- $problemCount := len $problemItems -}}
98
{{- $invalidItems := where $problemItems "status" "broken" -}}
109
{{- $uncheckedItems := where $problemItems "status" "unchecked" -}}
10+
{{- $draftWarningItems := where $items "status" "draft_warning" -}}
1111
{{- $missingItems := where $invalidItems "contentID" "" -}}
1212
{{- $missingCount := len $missingItems -}}
1313
{{- $uncheckedCount := len $uncheckedItems -}}
1414
{{- $unregisteredCount := sub (len $invalidItems) $missingCount -}}
15-
{{- $draftCount := len $draftItems -}}
16-
{{- $publishable := and (gt (len $checkedItems) 0) (eq $problemCount 0) -}}
15+
{{- $draftWarningCount := len $draftWarningItems -}}
16+
{{- $publishable := and (gt (len $items) 0) (eq $problemCount 0) -}}
1717

1818
<section class="academy-toolkit academy-toolkit--{{ $report.summary }}">
1919
<header class="academy-toolkit__header">
2020
<h2>ID Validation</h2>
2121
</header>
2222

23+
<p class="academy-toolkit__verdict {{ cond $publishable "academy-toolkit__verdict--good" "academy-toolkit__verdict--bad" }}">
24+
{{- if $publishable -}}
25+
<em>Ready to publish to the Academy.</em>
26+
{{- else -}}
27+
<em>Not ready to publish to the Academy.</em>
28+
{{- end -}}
29+
</p>
30+
2331
{{- if eq (len $items) 0 -}}
2432
<p class="academy-toolkit__empty">No content found.</p>
25-
{{- else if and (gt (len $checkedItems) 0) (eq $problemCount 0) -}}
33+
{{- else if eq $problemCount 0 -}}
2634
<p class="academy-toolkit__summary academy-toolkit__summary--good">
27-
<strong>All content IDs are registered.</strong>
28-
</p>
29-
{{- else if eq (len $checkedItems) 0 -}}
30-
<p class="academy-toolkit__summary academy-toolkit__summary--good">
31-
<strong>Draft pages are not validated until they are published.</strong>
35+
<strong>All publishable content IDs are registered.</strong>
3236
</p>
3337
{{- else -}}
3438
<div class="academy-toolkit__summary-row">
@@ -54,8 +58,11 @@ <h2>ID Validation</h2>
5458
Fix the invalid IDs in the <a href="{{ $consoleURL }}" target="_blank" rel="noopener">Instructor Console</a> using the <a href="https://docs.layer5.io/cloud/academy/creating-content/instructor-console-guide/#using-the-content-creation-tool" target="_blank" rel="noopener">Content Creation Tool</a>.
5559
</p>
5660
{{- end -}}
61+
{{- end -}}
5762

63+
{{- if ne (len $items) 0 -}}
5864
<p class="academy-toolkit__legend">
65+
<span>⚠️ Draft page: Content ID is not registered or missing.</span>
5966
<span>❌ Content ID is not registered or missing.</span>
6067
<span>✅ Content ID is registered.</span>
6168
</p>
@@ -79,13 +86,17 @@ <h2>ID Validation</h2>
7986
</thead>
8087
<tbody>
8188
{{- range $items -}}
89+
{{- $pageURL := .relPermalink -}}
90+
{{- if and (eq .status "good") (ne .productionPermalink "") -}}
91+
{{- $pageURL = .productionPermalink -}}
92+
{{- end -}}
8293
<tr>
83-
<th scope="row"><a href="{{ .relPermalink }}">{{ .title }}</a></th>
84-
<td>{{ cond (eq .status "draft") "True" "False" }}</td>
94+
<th scope="row"><a href="{{ $pageURL }}">{{ .title }}</a></th>
95+
<td>{{ cond .draft "True" "False" }}</td>
8596
<td>{{ cond (ne .contentID "") .contentID "Missing ID" }}</td>
8697
<td>
87-
{{- if eq .status "draft" -}}
88-
N/A
98+
{{- if eq .status "draft_warning" -}}
99+
⚠️
89100
{{- else if eq .status "good" -}}
90101
91102
{{- else if eq .status "unchecked" -}}
@@ -100,19 +111,11 @@ <h2>ID Validation</h2>
100111
</table>
101112
{{- end -}}
102113

103-
{{- if and (gt $draftCount 0) (gt (len $checkedItems) 0) -}}
114+
{{- if gt $draftWarningCount 0 -}}
104115
<p class="academy-toolkit__draft-note">
105-
Draft pages are not validated until they are published.
116+
Draft ID warnings do not block publishing, but fix missing or invalid IDs before publishing those draft pages.
106117
</p>
107118
{{- end -}}
108-
109-
<p class="academy-toolkit__verdict {{ cond $publishable "academy-toolkit__verdict--good" "academy-toolkit__verdict--bad" }}">
110-
{{- if $publishable -}}
111-
<em>Ready to publish to the Academy.</em>
112-
{{- else -}}
113-
<em>Not ready to publish to the Academy.</em>
114-
{{- end -}}
115-
</p>
116119
</section>
117120

118121
<style>

0 commit comments

Comments
 (0)