Skip to content
Open
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
7 changes: 7 additions & 0 deletions .github/workflows/deploy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,13 @@ jobs:
uses: actions/checkout@v3
with:
ref: ${{ github.event.inputs.branch || github.ref }}
# Full history, not the default shallow clone: the lastmod
# manifest step below needs to walk git log for every template
# file, and the rock image itself ships no .git at all.
fetch-depth: 0

- name: Pre-generate sitemap lastmod manifest
run: python3 webapp/lastmod_manifest.py generate-lastmod
Comment on lines +85 to +86

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Were you able to test it on staging? I think you would have to make changes to rockcraft.yaml so that it actually copies the generated manifest inside the OCI-image.


- name: Use Node.js
uses: actions/setup-node@v4
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ static/js/dist/
*.swp
templates/sitemap_tree.xml
templates/llms-full.txt
templates/lastmod-manifest.json

# Playwright
/test-results/
Expand Down
38 changes: 7 additions & 31 deletions templates/careers/sitemap.xml
Original file line number Diff line number Diff line change
@@ -1,41 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml">
{% for page in pages %}
<url>
<loc>https://canonical.com/careers</loc>
<changefreq>monthly</changefreq>
</url>
<url>
<loc>https://canonical.com/careers/career-explorer</loc>
<changefreq>monthly</changefreq>
</url>
<url>
<loc>https://canonical.com/careers/all</loc>
<changefreq>weekly</changefreq>
</url>
<url>
<loc>https://canonical.com/careers/hiring-process</loc>
<changefreq>weekly</changefreq>
</url>
<url>
<loc>https://canonical.com/careers/company-culture/remote-work</loc>
<changefreq>monthly</changefreq>
</url>
<url>
<loc>https://canonical.com/careers/company-culture/progression</loc>
<changefreq>monthly</changefreq>
</url>
<url>
<loc>https://canonical.com/careers/company-culture/diversity</loc>
<changefreq>monthly</changefreq>
</url>
<url>
<loc>https://canonical.com/careers/company-culture/sustainability</loc>
<changefreq>monthly</changefreq>
<loc>{{ page.url }}</loc>
<lastmod>{{ page.last_modified }}</lastmod>
<changefreq>{{ page.changefreq }}</changefreq>
</url>
{% endfor %}

{% for department in departments %}
<url>
<loc>https://canonical.com/careers/{{ department }}</loc>
<loc>https://canonical.com/careers/{{ department.slug }}</loc>
<lastmod>{{ department.last_modified }}</lastmod>
<changefreq>monthly</changefreq>
</url>
{% endfor %}
Expand Down
5 changes: 4 additions & 1 deletion templates/knowledge/sitemap.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,22 @@
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml">
<url>
<loc>https://canonical.com/knowledge</loc>
<lastmod>{{ last_modified }}</lastmod>
<changefreq>monthly</changefreq>
</url>

{% for section in sections %}
{% if section.articles|length > 0 %}
<url>
<loc>https://canonical.com/knowledge/{{ section.slug }}</loc>
<lastmod>{{ section.last_modified }}</lastmod>
<changefreq>monthly</changefreq>
</url>

{% for article in section.articles %}
<url>
<loc>https://canonical.com{{ article.url }}</loc>
<lastmod>{{ article.last_modified }}</lastmod>
<changefreq>monthly</changefreq>
</url>
{% endfor %}
Expand Down
51 changes: 5 additions & 46 deletions templates/partners/sitemap.xml
Original file line number Diff line number Diff line change
@@ -1,51 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml">
{% for page in pages %}
<url>
<loc>https://canonical.com/partners</loc>
<loc>{{ page.url }}</loc>
<lastmod>{{ page.last_modified }}</lastmod>
<changefreq>monthly</changefreq>
</url>
<url>
<loc>https://canonical.com/partners/find-a-partner</loc>
<changefreq>monthly</changefreq>
</url>
<url>
<loc>https://canonical.com/partners/become-a-partner</loc>
<changefreq>monthly</changefreq>
</url>
<url>
<loc>https://canonical.com/partners/channel-and-reseller</loc>
<changefreq>monthly</changefreq>
</url>
<url>
<loc>https://canonical.com/partners/desktop</loc>
<changefreq>monthly</changefreq>
</url>
<url>
<loc>https://canonical.com/partners/gsi</loc>
<changefreq>monthly</changefreq>
</url>
<url>
<loc>https://canonical.com/partners/ihv-and-oem</loc>
<changefreq>monthly</changefreq>
</url>
<url>
<loc>https://canonical.com/partners/public-cloud</loc>
<changefreq>monthly</changefreq>
</url>
<url>
<loc>https://canonical.com/partners/iot-device</loc>
<changefreq>monthly</changefreq>
</url>
<url>
<loc>https://canonical.com/partners/silicon</loc>
<changefreq>monthly</changefreq>
</url>
<url>
<loc>https://canonical.com/partners/silicon/intel</loc>
<changefreq>monthly</changefreq>
</url>
<url>
<loc>https://canonical.com/partners/executive-summit</loc>
<changefreq>monthly</changefreq>
</url>
</urlset>
{% endfor %}
</urlset>
41 changes: 4 additions & 37 deletions templates/sitemap-links.xml
Original file line number Diff line number Diff line change
@@ -1,43 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml">
{% for page in pages %}
<url>
<loc>https://canonical.com/</loc>
<changefreq>monthly</changefreq>
</url>
<url>
<loc>https://canonical.com/contact-us</loc>
<changefreq>monthly</changefreq>
</url>
<url>
<loc>https://canonical.com/projects</loc>
<changefreq>monthly</changefreq>
</url>
<url>
<loc>https://canonical.com/documentation</loc>
<changefreq>monthly</changefreq>
</url>
<url>
<loc>https://canonical.com/press-center</loc>
<changefreq>monthly</changefreq>
</url>
<url>
<loc>https://canonical.com/data</loc>
<changefreq>monthly</changefreq>
</url>
<url>
<loc>https://canonical.com/solutions/telco/5g-edge</loc>
<changefreq>monthly</changefreq>
</url>
<url>
<loc>https://canonical.com/solutions/telco/5g-core</loc>
<changefreq>monthly</changefreq>
</url>
<url>
<loc>https://canonical.com/company</loc>
<changefreq>monthly</changefreq>
</url>
<url>
<loc>https://canonical.com/knowledge</loc>
<loc>{{ page.url }}</loc>
<lastmod>{{ page.last_modified }}</lastmod>
<changefreq>monthly</changefreq>
</url>
{% endfor %}
</urlset>
67 changes: 65 additions & 2 deletions tests/test_sitemap.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import unittest
from unittest.mock import patch
from unittest.mock import patch, MagicMock
import logging
import re
import os
import xml.etree.ElementTree as ET
from webapp.app import app, build_sitemap_tree, knowledge_sitemap
from webapp.app import (
app,
build_sitemap_tree,
knowledge_sitemap,
careers_sitemap,
)

logging.getLogger("talisker.context").disabled = True

Expand Down Expand Up @@ -207,12 +212,14 @@ def test_knowledge_sitemap_returns_xml(self, mock_get_sections):
"slug": "ubuntu-and-linux",
"title": "Ubuntu and Linux",
"description": "Learn about Ubuntu and Linux",
"last_modified": "2026-01-15",
"articles": [
{
"hero_title": "Getting Started",
"description": "A beginner's guide",
"url": "/knowledge/ubuntu-and-linux/getting-started",
"tag": "beginner",
"last_modified": "2026-01-15",
}
],
}
Expand All @@ -237,12 +244,14 @@ def test_knowledge_sitemap_contains_urls(self, mock_get_sections):
"slug": "ubuntu-and-linux",
"title": "Ubuntu and Linux",
"description": "Learn about Ubuntu and Linux",
"last_modified": "2026-01-15",
"articles": [
{
"hero_title": "Getting Started",
"description": "A beginner's guide",
"url": "/knowledge/ubuntu-and-linux/getting-started",
"tag": "beginner",
"last_modified": "2026-01-20",
}
],
}
Expand All @@ -268,6 +277,13 @@ def test_knowledge_sitemap_contains_urls(self, mock_get_sections):
xml_content,
)

# Verify every URL has a lastmod tag with a real date
self.assertEqual(
xml_content.count("<loc>"), xml_content.count("<lastmod>")
)
self.assertIn("<lastmod>2026-01-15</lastmod>", xml_content)
self.assertIn("<lastmod>2026-01-20</lastmod>", xml_content)

@patch("webapp.app.get_knowledge_sections")
def test_knowledge_sitemap_empty_sections(self, mock_get_sections):
"""Test that knowledge_sitemap handles empty sections gracefully"""
Expand All @@ -291,25 +307,29 @@ def test_knowledge_sitemap_multiple_sections(self, mock_get_sections):
"slug": "cloud",
"title": "Cloud",
"description": "Cloud computing",
"last_modified": "2026-02-01",
"articles": [
{
"hero_title": "Cloud Basics",
"description": "Cloud basics",
"url": "/knowledge/cloud/basics",
"tag": "cloud",
"last_modified": "2026-02-01",
}
],
},
{
"slug": "security",
"title": "Security",
"description": "Security topics",
"last_modified": "2026-03-01",
"articles": [
{
"hero_title": "Security Best Practices",
"description": "Security practices",
"url": "/knowledge/security/best-practices",
"tag": "security",
"last_modified": "2026-03-01",
}
],
},
Expand All @@ -332,5 +352,48 @@ def test_knowledge_sitemap_multiple_sections(self, mock_get_sections):
)


class TestStaticSitemapsLastmod(unittest.TestCase):
def setUp(self):
app.testing = True
self.client = app.test_client()

def assert_every_url_has_lastmod(self, xml_content):
self.assertEqual(
xml_content.count("<loc>"), xml_content.count("<lastmod>")
)
# A lastmod tag should never be left empty/unresolved
self.assertNotIn("<lastmod></lastmod>", xml_content)

def test_partners_sitemap_has_lastmod(self):
response = self.client.get("/partners/sitemap.xml")

self.assertEqual(response.status_code, 200)
xml_content = response.get_data(as_text=True)
self.assertIn(
"https://canonical.com/partners/find-a-partner", xml_content
)
self.assert_every_url_has_lastmod(xml_content)

def test_home_sitemap_has_lastmod(self):
response = self.client.get("/sitemap-links.xml")

self.assertEqual(response.status_code, 200)
xml_content = response.get_data(as_text=True)
self.assertIn("https://canonical.com/knowledge", xml_content)
self.assert_every_url_has_lastmod(xml_content)

def test_careers_sitemap_static_pages_have_lastmod(self):
greenhouse = MagicMock()
greenhouse.get_vacancies.return_value = []

with app.app_context():
response = careers_sitemap(greenhouse)

self.assertEqual(response.status_code, 200)
xml_content = response.get_data(as_text=True)
self.assertIn("https://canonical.com/careers/all", xml_content)
self.assert_every_url_has_lastmod(xml_content)


if __name__ == "__main__":
unittest.main()
8 changes: 7 additions & 1 deletion tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -760,7 +760,13 @@ def test_all_four_fields_together(self):
self.assertEqual(len(sections[0]["articles"]), 2)

# Verify structure of returned dict
expected_keys = {"slug", "title", "description", "articles"}
expected_keys = {
"slug",
"title",
"description",
"articles",
"last_modified",
}
self.assertEqual(set(sections[0].keys()), expected_keys)

def test_multiple_sections(self):
Expand Down
Loading
Loading