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
4 changes: 4 additions & 0 deletions RELEASE_PROCEDURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ Before see How to
1. Update the oeplatform/versions/changelogs/
[`current.md`](https://github.com/OpenEnergyPlatform/oeplatform/blob/develop/versions/changelogs/current.md)
(see the examples of previous releases)
- Write its `## Highlights`: three to five points for users. The platform's
"What's new" page, linked from the homepage, shows them without the version
number. A release without highlights (usually a patch) keeps the previous
release's highlights there.
- Change filename to release version (x_x_x.md)
- Copy template to `current.md`
- Update version with `bumpversion --allow-dirty [minor|patch]`
Expand Down
1 change: 1 addition & 0 deletions REUSE.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3572,6 +3572,7 @@ path = [
"base/templates/base/_instance_stylesheets.html",
"base/templates/base/_feedback.html",
"base/templates/base/_notice.html",
"base/templates/base/whats_new.html",
"theming/stock_tokens.scss",
"theming/literal_scan.mjs",
]
Expand Down
63 changes: 25 additions & 38 deletions base/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,47 +24,34 @@
SITE_ROOT = os.path.dirname(os.path.realpath(__file__))


@lru_cache(maxsize=None)
def read_version_changes() -> dict:
"""read version and changes from changelog markdown
CHANGELOG_DIR = os.path.join(SITE_ROOT, "..", "versions", "changelogs")
RELEASED_CHANGELOG = re.compile(r"(\d+)_(\d+)_(\d+)\.md")
HIGHLIGHTS = re.compile(r"^## Highlights[ \t]*\n(.*?)(?=^## |\Z)", re.M | re.S)
COMMENT = re.compile(r"<!--.*?-->", re.S)

We use cache so it can stay in process memory and we dont have to read files
in every request. this only changes on a new release anyway, in which case
the process is restarted.

Returns:
dict: {"version": (major, minor, patch), "changes": changes}
"""
os.path.dirname(os.path.realpath(__file__))
version_expr = r"^(?P<major>\d+)\.(?P<minor>\d+)+\.(?P<patch>\d+)$"
markdowner = markdown2.Markdown()
@lru_cache(maxsize=None)
def latest_release_highlights(directory=CHANGELOG_DIR) -> str:
"""HTML of the newest released changelog's `## Highlights`, or "".

logger.info("READING VERSION file.")
try:
with open(
os.path.join(SITE_ROOT, "..", "VERSION"), encoding="utf-8"
) as version_file:
text = version_file.read()
match = re.match(version_expr, text)
if not match:
raise ValueError(text)
major, minor, patch = match.groups()
with open(
os.path.join(
SITE_ROOT,
"..",
"versions/changelogs/%s_%s_%s.md" % (major, minor, patch),
),
encoding="utf-8",
) as change_file:
changes = markdowner.convert(
"\n".join(line for line in change_file.readlines())
)
except Exception:
logger.error("READING VERSION file.")
# probably because change_file is missing
major, minor, patch, changes = "", "", "", ""
return {"version": (major, minor, patch), "changes": changes}
A released changelog is named `<major>_<minor>_<patch>.md` (`current.md` is
the next release, not yet out). A release without highlights, as a patch
release usually is, is skipped. The version is deliberately not returned:
the platform's pages do not say which release it runs. Cached for the
process: changelogs only change with a release, which restarts it.
"""
released = []
for name in os.listdir(directory):
match = RELEASED_CHANGELOG.fullmatch(name)
if match:
released.append((tuple(int(part) for part in match.groups()), name))
for _version, name in sorted(released, reverse=True):
with open(os.path.join(directory, name), encoding="utf-8") as file:
section = HIGHLIGHTS.search(file.read())
text = COMMENT.sub("", section.group(1)).strip() if section else ""
if text:
return markdown2.markdown(text)
return ""


def get_json_content(path, json_id=None):
Expand Down
10 changes: 10 additions & 0 deletions base/static/css/index-style.css
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,16 @@
padding: 24px 0;
}

.index-hero-news {
color: var(--oep-on-strong);
}

.index-hero-news:hover,
.index-hero-news:focus-visible {
color: var(--oep-on-strong);
text-decoration: underline;
}

.index-hero-btn {
display: inline-flex;
align-items: center;
Expand Down
1 change: 1 addition & 0 deletions base/templates/base/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ <h3 class="index-footer-column-title">Resources</h3>
<h3 class="index-footer-column-title">Platform</h3>
<ul class="index-footer-link-list">
<li><a href="{% url 'base:about' %}">About OEP</a></li>
<li><a href="{% url 'base:whats-new' %}">What's new</a></li>
<li><a href="{% url 'base:contact' %}">Contact</a></li>
<li><a href="{% url 'base:legal-privacy-policy' %}">Privacy</a></li>
<li><a href="{% url 'base:legal-tou' %}">Terms</a></li>
Expand Down
19 changes: 19 additions & 0 deletions base/templates/base/whats_new.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{% extends "base/base.html" %}
{% load static %}
{% block site-header %}
{% url 'base:home' as home_url %}
{% include "components/page_header.html" with area="About" icon="icons/area_about.html" crumb_label="Home" crumb_url=home_url title="What's new" description="What changed for you in the platform's latest release." %}
{% endblock site-header %}
{% block after-head %}
<link rel="stylesheet" href="{% static 'css/static_pages.css' %}" />
{% endblock after-head %}
{% block main-content-body %}
<div class="reading-column">
{% if highlights %}
{# The changelogs are written in this repository, so their HTML is trusted. #}
{{ highlights|safe }}
{% else %}
{% include "components/empty_state.html" with text="No release highlights yet." %}
{% endif %}
</div>
{% endblock main-content-body %}
7 changes: 7 additions & 0 deletions base/templates/homepage/hero.html
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,12 @@ <h1 class="index-hero-title">
<a class="index-hero-btn index-hero-btn-outline"
href="{{ EXTERNAL_URLS.mkdocs }}">View Documentation</a>
</div>
{% if highlights %}
<a class="link-with-arrow index-hero-news"
href="{% url 'base:whats-new' %}">
<span>See what's new</span>
{% include "icons/arrow_right.html" %}
</a>
{% endif %}
</div>
</div>
1 change: 1 addition & 0 deletions base/tests/test_page_lint.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ def label(self):
# are #2641's below too)
Page("base:about"),
Page("base:oefamily-sc"),
Page("base:whats-new"),
Page("base:legal-privacy-policy"),
Page("base:legal-tou"),
Page("base:faq"),
Expand Down
1 change: 1 addition & 0 deletions base/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ def test_views(self):
self.get("base:legal-tou")
self.get("base:robots")
self.get("base:tutorials")
self.get("base:whats-new")
self.get("base:project_detail", kwargs={"project_id": "sirop"})

def test_urls_in_templates(self):
Expand Down
87 changes: 87 additions & 0 deletions base/tests/test_whats_new.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
"""The "What's new" page (#2382): the newest released changelog's `## Highlights`.

SPDX-FileCopyrightText: 2026 Jonas Huber <https://github.com/jh-RLI> © Reiner Lemoine Institut
SPDX-License-Identifier: AGPL-3.0-or-later
""" # noqa: 501

import tempfile
from pathlib import Path
from unittest import mock

from django.conf import settings
from django.test import SimpleTestCase, TestCase
from django.urls import reverse

from base.helper import latest_release_highlights

CHANGELOG = """# Changes to the oeplatform code

## Highlights

{}

## Changes

- an internal change users never see
"""

HIGHLIGHTS = "<ul><li>Datasets start as drafts</li></ul>"
VERSION = Path(settings.BASE_DIR, "VERSION").read_text().strip()


class LatestReleaseHighlightsTest(SimpleTestCase):
def highlights(self, **files):
with tempfile.TemporaryDirectory() as directory:
for name, highlights in files.items():
Path(directory, name).write_text(CHANGELOG.format(highlights))
# The cache keys on the directory, and every one here is new.
return latest_release_highlights(directory)

def test_the_newest_release_by_number_wins(self):
html = self.highlights(
**{"1_9_0.md": "- nine", "1_10_0.md": "- ten", "1_2_0.md": "- two"}
)
self.assertEqual(html.strip(), "<ul>\n<li>ten</li>\n</ul>")

def test_only_the_highlights_section_is_shown(self):
html = self.highlights(**{"1_10_0.md": "- **New** list"})
self.assertEqual(
html.strip(), "<ul>\n<li><strong>New</strong> list</li>\n</ul>"
)

def test_a_release_without_highlights_is_skipped(self):
html = self.highlights(
**{
"1_10_0.md": "- ten",
"1_10_1.md": "",
"1_10_2.md": "<!-- the template's comment, never filled -->",
}
)
self.assertEqual(html.strip(), "<ul>\n<li>ten</li>\n</ul>")

def test_the_next_release_is_not_out_yet(self):
self.assertEqual(self.highlights(**{"current.md": "- coming"}), "")


class WhatsNewPagesTest(TestCase):
def get(self, name, highlights):
with mock.patch(
"base.views.latest_release_highlights", return_value=highlights
):
return self.client.get(reverse(name)).content.decode()

def test_the_page_shows_the_highlights(self):
page = self.get("base:whats-new", HIGHLIGHTS)
self.assertIn("<li>Datasets start as drafts</li>", page)

def test_the_page_without_highlights_says_so(self):
self.assertIn("No release highlights yet.", self.get("base:whats-new", ""))

def test_the_homepage_links_the_page_when_there_is_news(self):
self.assertIn("See what's new", self.get("base:home", HIGHLIGHTS))
self.assertNotIn("See what's new", self.get("base:home", ""))

def test_no_page_names_the_running_release(self):
# The pages do not tell a visitor which release production runs.
for name in ("base:home", "base:whats-new"):
self.assertNotIn(VERSION, self.get(name, HIGHLIGHTS), name)
2 changes: 2 additions & 0 deletions base/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
redirect_tutorial_view,
reverse_url_view,
robot_view,
whats_new_view,
)

app_name = "base"
Expand All @@ -40,6 +41,7 @@
name="project_detail",
),
re_path(r"^contact/$", ContactView.as_view(), name="contact"),
path("whats-new/", whats_new_view, name="whats-new"),
re_path(r"^faq/$", redir_view, {"target": "faq"}, name="faq"),
re_path(r"^discussion/$", redir_view, {"target": "discussion"}, name="discussion"),
re_path(
Expand Down
11 changes: 8 additions & 3 deletions base/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
from django.views.generic import View

from base.forms import ContactForm
from base.helper import get_json_content, read_version_changes
from base.helper import get_json_content, latest_release_highlights
from oeplatform.settings import (
CONTACT_ADDRESSES,
DEFAULT_FROM_EMAIL,
Expand All @@ -34,8 +34,13 @@

class WelcomeView(View):
def get(self, request):
context = read_version_changes()
return render(request, "base/index.html", context)
highlights = latest_release_highlights()
return render(request, "base/index.html", {"highlights": highlights})


def whats_new_view(request):
highlights = latest_release_highlights()
return render(request, "base/whats_new.html", {"highlights": highlights})


def redir_view(request, target):
Expand Down
24 changes: 24 additions & 0 deletions versions/changelogs/current.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,25 @@ SPDX-License-Identifier: CC0-1.0

# Changes to the oeplatform code

## Highlights

<!--
Three to five short points for users, shown on the platform's "What's new"
page (base:whats-new) and linked from the homepage, without the version number.
Plain words, no internals. Left empty (as in a patch release), the page keeps
the previous release's highlights.
-->

- **A new homepage and a consistent look:** the start page is redesigned, and
pages across the platform share the same buttons, forms, cards and headers.
- **All your tables in one list:** the tables tab of your dashboard shows drafts
and published tables together, with filters, sorting and actions on many
tables at once.
- **Manage access in place:** see and change who can access a table from a side
drawer in the tables tab.
- **Datasets start as drafts:** a dataset is published when it is ready, and the
new datasets tab manages your datasets and their tables.

## Changes

- The profile is edited in place on Settings: Edit turns the Manage Profile card
Expand Down Expand Up @@ -722,6 +741,11 @@ SPDX-License-Identifier: CC0-1.0
recent problems with the platform to write via the contact form; the band
lives in `base/templates/base/_notice.html`, which is emptied to retire it

- A "What's new" page shows the latest release's highlights for users, linked
from the homepage's hero and footer, without naming the version. A changelog's
new `## Highlights` section is its source
[(#2382)](https://github.com/OpenEnergyPlatform/oeplatform/issues/2382)

## Bugs

- The Edit button in a table page's tag chooser opens the tag manager instead of
Expand Down
9 changes: 9 additions & 0 deletions versions/changelogs/current.template.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@ SPDX-License-Identifier: CC0-1.0

# Changes to the oeplatform code

## Highlights

<!--
Three to five short points for users, shown on the platform's "What's new"
page (base:whats-new) and linked from the homepage, without the version number.
Plain words, no internals. Left empty (as in a patch release), the page keeps
the previous release's highlights.
-->

## Changes

## Features
Expand Down
Loading