Skip to content

feat: sort server list by country name instead of city name #792

Open
valer23 wants to merge 4 commits intolibrespeed:masterfrom
valer23:sort-servers-by-country
Open

feat: sort server list by country name instead of city name #792
valer23 wants to merge 4 commits intolibrespeed:masterfrom
valer23:sort-servers-by-country

Conversation

@valer23
Copy link
Copy Markdown

@valer23 valer23 commented Apr 17, 2026

Sort the server dropdown by country first, then by city within the same country. This makes it easier to find servers for a specific country when the list is long, currently servers are sorted by city, so servers for the same country are scattered across the list.
Applies to both modern and classic UIs. Parses the existing name field format ("City, Country"), no data model changes needed.

Before (sorted by city)

  • Amsterdam, Netherlands
  • Atlanta, United States
  • Chicago, USA
  • Denver, USA
  • Frankfurt, Germany
  • Helsinki, Finland
  • Nuremberg, Germany
  • Washington, USA

After (sorted by country, then city)

  • London, England
  • Helsinki, Finland
  • Frankfurt, Germany
  • Nuremberg, Germany
  • Amsterdam, Netherlands
  • Atlanta, United States
  • Chicago, USA
  • Denver, USA
  • Washington, USA

valer23 added 2 commits April 17, 2026 10:28
Sort the server dropdown by country first, then by city within the same
country. This makes it easier to find servers in a specific country when
the list is long. Applies to both modern and classic UIs.
Address code review findings:
- Sort a shallow copy instead of mutating the caller's array
- Add null guard on server.name to handle malformed entries
- Use original SPEEDTEST_SERVERS index for classic UI option values
@qodo-free-for-open-source-projects
Copy link
Copy Markdown
Contributor

Review Summary by Qodo

Sort server list by country name instead of city name

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Sort server dropdown by country first, then city
• Applies to both modern and classic UI implementations
• Avoids mutating original server array with shallow copy
• Adds null guard for malformed server name entries
Diagram
flowchart LR
  A["Server Array"] -->|"shallow copy + sort"| B["Sorted by Country"]
  B -->|"then by City"| C["Grouped Servers"]
  C -->|"modern UI"| D["index.js Dropdown"]
  C -->|"classic UI"| E["index-classic.html Select"]
Loading

Grey Divider

File Changes

1. frontend/javascript/index.js ✨ Enhancement +14/-1

Add country-then-city sorting to modern UI

• Added sorting logic that creates shallow copy of servers array
• Parses server name field using lastIndexOf(",") to extract country and city
• Sorts by country name first, then by city using localeCompare()
• Includes null guard for malformed server name entries

frontend/javascript/index.js


2. index-classic.html ✨ Enhancement +17/-5

Add country-then-city sorting to classic UI

• Implemented identical sorting logic for classic UI server selection
• Creates shallow copy of SPEEDTEST_SERVERS array before sorting
• Maintains original array indices using indexOf() for option values
• Applies same null guard and country-city parsing as modern UI

index-classic.html


Grey Divider

Qodo Logo

@qodo-free-for-open-source-projects
Copy link
Copy Markdown
Contributor

qodo-free-for-open-source-projects bot commented Apr 17, 2026

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0)

Grey Divider


Action required

1. Wrong country parsing🐞 Bug ≡ Correctness
Description
The new sorting logic extracts the country as the substring after the last comma, which
misclassifies real server names containing multiple commas or suffixes, so servers are not reliably
grouped/sorted by actual country. For example, "Virginia, United States, OVH" is treated as country
"OVH", and names like "Nuremberg, Germany (1) (Hetzner)" get country keys like "Germany (1)
(Hetzner)", preventing true city-within-country ordering.
Code

frontend/javascript/index.js[R233-244]

+  // Sort servers by country, then by city within the same country
+  const sorted = [...servers].sort((a, b) => {
+    const nameA = a.name || "";
+    const nameB = b.name || "";
+    const commaA = nameA.lastIndexOf(",");
+    const commaB = nameB.lastIndexOf(",");
+    const countryA = commaA >= 0 ? nameA.substring(commaA + 1).trim() : nameA;
+    const countryB = commaB >= 0 ? nameB.substring(commaB + 1).trim() : nameB;
+    const cityA = commaA >= 0 ? nameA.substring(0, commaA).trim() : "";
+    const cityB = commaB >= 0 ? nameB.substring(0, commaB).trim() : "";
+    return countryA.localeCompare(countryB) || cityA.localeCompare(cityB);
+  });
Evidence
Both UIs use lastIndexOf(',') to derive the country from the server "name"; however, the default
server list loaded by the modern UI contains names with multiple commas and trailing qualifiers,
which causes the comparator to select the wrong country segment and/or include qualifiers in the
country key. This directly violates the PR goal of sorting by country then city because entries will
be grouped under incorrect countries (e.g., "OVH") or split into multiple pseudo-countries (e.g.,
"Germany" vs "Germany (1) (Hetzner)").

frontend/javascript/index.js[233-244]
index-classic.html[53-73]
index-modern.html[11-15]
frontend/server-list.json[101-147]
frontend/server-list.json[398-408]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The dropdown sort extracts `country` using `lastIndexOf(',')` and compares that raw substring. This breaks for real entries in `frontend/server-list.json` that include extra comma-separated suffixes (e.g. `"Virginia, United States, OVH"`) and for entries that append qualifiers to the country (e.g. `"Nuremberg, Germany (1) (Hetzner)"`), causing incorrect grouping and preventing true "country then city" ordering.
### Issue Context
- Modern UI loads `server-list.json` (and therefore hits these problematic names) and uses this comparator.
- Classic UI duplicates the same parsing logic.
### Fix Focus Areas
- Implement a small shared parsing routine (or duplicated but identical logic) that:
- splits on commas into trimmed `parts`
- chooses the country segment more robustly when there are 3+ parts (e.g., treat patterns like `City, Country, Provider` as `country = parts[-2]` when `parts[-1]` looks like a provider token and `parts[-2]` looks like a country), otherwise fallback to `country = lastPart`
- derives `city = parts.slice(0, -1Or-2).join(', ')`
- for sorting only, strip trailing parenthetical qualifiers from the chosen country string (e.g. remove ` (1) (Hetzner)` / `(FRA01)` via a regex like `/\s*(\([^)]*\)\s*)+$/`)
- Apply the same corrected parsing in both UIs.
#### Files/lines to change
- frontend/javascript/index.js[233-244]
- index-classic.html[53-64]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Index lookup inside loop🐞 Bug ➹ Performance
Description
Classic UI sets each <option> value using SPEEDTEST_SERVERS.indexOf(sortedServers[i]) inside the
population loop, doing an extra linear scan per server. This is avoidable work during
initialization, especially when SPEEDTEST_SERVERS is loaded from a URL and may be large.
Code

index-classic.html[R66-71]

+							for (var i = 0; i < sortedServers.length; i++) {
+								if (sortedServers[i].pingT == -1) continue;
  						var option = document.createElement("option");
-								option.value = i;
-								option.textContent = SPEEDTEST_SERVERS[i].name;
-								if (SPEEDTEST_SERVERS[i] === server) option.selected = true;
+								option.value = SPEEDTEST_SERVERS.indexOf(sortedServers[i]);
+								option.textContent = sortedServers[i].name;
+								if (sortedServers[i] === server) option.selected = true;
Evidence
The new code calls indexOf(...) for every option created, which is O(n) per iteration and
therefore O(n²) overall for dropdown population. The selected server logic still expects option
values to be indices into the original SPEEDTEST_SERVERS array, so the index should be carried
through sorting rather than searched repeatedly.

index-classic.html[53-73]
index-classic.html[82-96]
index-classic.html[512-514]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`option.value = SPEEDTEST_SERVERS.indexOf(sortedServers[i])` performs a linear search for each option during dropdown creation.
### Issue Context
The onchange handler uses `SPEEDTEST_SERVERS[this.value]`, so `option.value` should remain the original index into `SPEEDTEST_SERVERS`.
### Fix Focus Areas
- Build an array of `{ idx, server }` pairs once, sort by `server.name` using the existing comparator, and then populate options using `pair.idx`.
- Avoid calling `indexOf` inside the loop.
#### Files/lines to change
- index-classic.html[53-73]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

Comment thread frontend/javascript/index.js Outdated
valer23 added 2 commits April 17, 2026 10:39
Parse server names more robustly for sorting:
- "City, Country, Provider" → use second part as country
- "City, Country (1) (Hetzner)" → strip parentheticals from country
- "Frankfurt, Germany (FRA01)" → country is "Germany" not "Germany (FRA01)"
Build an array of {idx, server} pairs before sorting so the original
SPEEDTEST_SERVERS index is carried through, eliminating the per-option
indexOf call.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant