From 4f5ae35e15da937322d31b123bbbc18986f63ea8 Mon Sep 17 00:00:00 2001 From: Tony Breeds Date: Wed, 5 Jul 2023 21:43:25 +1000 Subject: [PATCH] Add a Sorting function. Currently we're reverse sorting with a simple sort, this sadly puts newer released (antalope, bobcat) at the end of the list. Add a key function that has context around our release names and release ids to sort "2024.1/cantaloupe"[1] at the top. [1] As of now the c name isn't selected cantaloupe is my placeholder Change-Id: I4b021d99bd4ecc232838a3639dd8d5ef075e1a24 --- doc/source/_exts/deliverables.py | 4 +- openstack_releases/series_sorting.py | 55 ++++++++++++++++++ openstack_releases/tests/test_sorting.py | 71 ++++++++++++++++++++++++ 3 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 openstack_releases/series_sorting.py create mode 100644 openstack_releases/tests/test_sorting.py diff --git a/doc/source/_exts/deliverables.py b/doc/source/_exts/deliverables.py index 53c214ff1f..e009871bf8 100644 --- a/doc/source/_exts/deliverables.py +++ b/doc/source/_exts/deliverables.py @@ -27,6 +27,7 @@ from sphinx.util.nodes import nested_parse_with_titles from openstack_releases import deliverable from openstack_releases import links +from openstack_releases import series_sorting from openstack_releases import series_status from openstack_releases._redirections import generate_constraints_redirections @@ -384,7 +385,8 @@ class TeamDirective(rst.Directive): ) all_series = reversed(sorted( - _deliverables.get_team_series(self.team_name) + _deliverables.get_team_series(self.team_name), + key=series_sorting.keyfunc )) # If independent is in the list, it should be sorted last all_series = sorted(all_series, key='independent'.__eq__) diff --git a/openstack_releases/series_sorting.py b/openstack_releases/series_sorting.py new file mode 100644 index 0000000000..444cf4dc8a --- /dev/null +++ b/openstack_releases/series_sorting.py @@ -0,0 +1,55 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +_series_data = [ + ["austin", "bexar", "cactus", "diablo", "essex", "folsom", "grizzly", + "havana", "icehouse", "juno", "kilo", "liberty", "mitaka", "newton", + "ocata", "pike", "queens", "rocky", "stein", "train", "ussuri", + "victoria", "wallaby", "xena", "yoga", "zed"], + # ["antelope", bobcat, etc etc etc],` +] + + +def keyfunc(series_name): + assert series_name.isascii() + + # NOTE(tonyb): Create a private copy to avoid mutating input variable + _series_name = series_name.lower() + # This for/else statement looks for a series_name in series_data. If it + # is found stop looking (via break), because we have all the information + # we need. If the series name isn't found, i.e a run through the entire + # series_data list-of-lists, the 'else' clause will be executed to do our + # best to deduce the sort key from there. + for series_nr, series_names in enumerate(_series_data): + if _series_name in series_names: + series_idx = series_names.index(_series_name) + break + else: + if _series_name[0].isalpha(): + series_nr += 1 + series_idx = ord(_series_name[0]) - ord("a") + elif _series_name[0].isdigit(): + (year, release) = map(int, _series_name.split(".")) + # This arithmetic comes from the fact that we started using + # year.release naming scheme, after we completed a full list + # of the alphabet. + # This happened with the 2023.1 release. To date it's two + # releases per year. If that changes this code will need to + # be updated. + # Releases "austin" -> "zed" are 0 -> 25 so 2023.1 is the 26th + # OpenStack release + (series_nr, series_idx) = \ + divmod(26 + ((year - 2023) * 2 + (release - 1)), 26) + else: + assert False + # TODO(tonyb): Do we want to switch this to aa_austin, ba_2023.1 to force + # a stable sort order + return series_nr * 26 + series_idx diff --git a/openstack_releases/tests/test_sorting.py b/openstack_releases/tests/test_sorting.py new file mode 100644 index 0000000000..e6165281f3 --- /dev/null +++ b/openstack_releases/tests/test_sorting.py @@ -0,0 +1,71 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +from unittest import mock + +from oslotest import base + +from openstack_releases import series_sorting + + +class TestSeries_Sorting(base.BaseTestCase): + FAKE_SERIES_DATA = [ + series_sorting._series_data[0], + ["antelope", "bobcat", "camel", "duck", "elephant", "fox", "gorilla", + "hamster", "ibex", "jellyfish", "kudu", "lion", "meerkat", "narwhal", + "otter", "pony", "quail", "raccoon", "salmon", "termite", "uakari", + "viperfish", "whale", "xerus", "yak", "zebra"] + ] + + def test_assert_nonascii(self): + self.assertRaises(AssertionError, series_sorting.keyfunc, "ハロー") + + def test_assert_nonaplphanumeric(self): + self.assertRaises(AssertionError, series_sorting.keyfunc, "__austin") + + def test_simple(self): + test_series_list = ["antelope", "victoria", "2023.1", "zed", + "c-release-not-cactus"] + sorted_series_list = ["victoria", "zed", "antelope", "2023.1", + "c-release-not-cactus"] + self.assertEqual(sorted(test_series_list, key=series_sorting.keyfunc), + sorted_series_list) + + def test_simple_with_case(self): + test_series_list = ["Antelope", "victoria", "2023.1", "zed", + "C-release-not-cactus"] + sorted_series_list = ["victoria", "zed", "Antelope", "2023.1", + "C-release-not-cactus"] + self.assertEqual(sorted(test_series_list, key=series_sorting.keyfunc), + sorted_series_list) + + @mock.patch.object(series_sorting, "_series_data", FAKE_SERIES_DATA) + def test_with_series_2(self): + test_series_list = ["antelope", "austin", "aardvark"] + sorted_series_list = ["austin", "antelope", "aardvark"] + self.assertEqual(sorted(test_series_list, key=series_sorting.keyfunc), + sorted_series_list) + + @mock.patch.object(series_sorting, "_series_data", FAKE_SERIES_DATA) + def test_with_series_2_rollover(self): + test_series_list = ["antelope", "austin", "zebra", "yak", "aardvark"] + sorted_series_list = ["austin", "antelope", "yak", "zebra", "aardvark"] + self.assertEqual(sorted(test_series_list, key=series_sorting.keyfunc), + sorted_series_list) + + @mock.patch.object(series_sorting, "_series_data", FAKE_SERIES_DATA) + def test_with_series_2_mixed_styles(self): + test_series_list = ["elephant", "2023.1", "duck", "2024.2", + "aardvark", "2024.1"] + sorted_series_list = ["2023.1", "2024.1", "duck", "2024.2", + "elephant", "aardvark"] + self.assertEqual(sorted(test_series_list, key=series_sorting.keyfunc), + sorted_series_list)