From ed0cb367439e5440f739530104012c7a9bdf5c4f Mon Sep 17 00:00:00 2001 From: Barry Pollard Date: Tue, 10 Mar 2026 12:53:22 +0000 Subject: [PATCH 1/2] Add date filters to tech report --- package-lock.json | 12 +++++++- server/routes.py | 32 +++++++++++++++++++++ server/tests/routes_test.py | 23 +++++++++++++++ src/js/components/filters.js | 12 ++++++++ src/js/techreport/index.js | 10 ++++++- src/js/techreport/tableLinked.js | 4 ++- static/css/techreport/techreport.css | 9 +++--- templates/report/report.html | 2 +- templates/techreport/templates/filters.html | 32 +++++++++++++++++++++ tools/test/test_status_codes.js | 2 ++ 10 files changed, 129 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index a0741c12..20707c42 100644 --- a/package-lock.json +++ b/package-lock.json @@ -324,6 +324,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -348,6 +349,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -438,6 +440,7 @@ "url": "https://github.com/sponsors/ai" } ], + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -1470,6 +1473,7 @@ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.4.tgz", "integrity": "sha512-jTywjboN9aHxFlToqb0K0Zs9SbBoW4zRUlGzI2tYNxVYcEi/IPpn+Xi4ye5jTLvX2YeLuic/IvxNot+Q1jMoOw==", "dev": true, + "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", @@ -1518,6 +1522,7 @@ "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-6.0.1.tgz", "integrity": "sha512-MfwFQ6SfwinsUVi0rNJm7rHZ31GyTcpVE5pgVA3hwFRb7COD4TzjUUwhGWKfO50+xdc2MQPuEBBJoqIMGt3JDw==", "dev": true, + "peer": true, "dependencies": { "@discoveryjs/json-ext": "^0.6.1", "@webpack-cli/configtest": "^3.0.1", @@ -1900,7 +1905,8 @@ "version": "8.16.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", - "dev": true + "dev": true, + "peer": true }, "acorn-import-phases": { "version": "1.0.3", @@ -1914,6 +1920,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, + "peer": true, "requires": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -1972,6 +1979,7 @@ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", "dev": true, + "peer": true, "requires": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -2691,6 +2699,7 @@ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.4.tgz", "integrity": "sha512-jTywjboN9aHxFlToqb0K0Zs9SbBoW4zRUlGzI2tYNxVYcEi/IPpn+Xi4ye5jTLvX2YeLuic/IvxNot+Q1jMoOw==", "dev": true, + "peer": true, "requires": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", @@ -2724,6 +2733,7 @@ "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-6.0.1.tgz", "integrity": "sha512-MfwFQ6SfwinsUVi0rNJm7rHZ31GyTcpVE5pgVA3hwFRb7COD4TzjUUwhGWKfO50+xdc2MQPuEBBJoqIMGt3JDw==", "dev": true, + "peer": true, "requires": { "@discoveryjs/json-ext": "^0.6.1", "@webpack-cli/configtest": "^3.0.1", diff --git a/server/routes.py b/server/routes.py index 62d5f878..1f2a30bb 100644 --- a/server/routes.py +++ b/server/routes.py @@ -1,4 +1,6 @@ import re +from datetime import datetime + from flask import abort, jsonify, redirect, request, send_from_directory from urllib.parse import urlsplit, urlunsplit, parse_qsl, urlencode @@ -10,6 +12,7 @@ from . import talisman from . import techreport as tech_report_util from . import url_for +from .dates import get_dates def safe_int(value, default=1): @@ -145,6 +148,17 @@ def techreportlanding(page_id): last_page = request.args.get("last_page") or False + all_dates = [ + { + "value": d.replace("_", "-"), + "display": datetime.strptime(d, "%Y_%m_%d").strftime("%b %Y"), + } + for d in get_dates() + ] + + requested_start = request.args.get("start") or "" + requested_end = request.args.get("end") or "" + filters = { "geo": requested_geo, "rank": requested_rank, @@ -154,6 +168,8 @@ def techreportlanding(page_id): "last_page": last_page, "selected": selected_techs, "rows": selected_rows, + "start": requested_start, + "end": requested_end, } params = { "geo": requested_geo.replace(" ", "+"), @@ -171,6 +187,7 @@ def techreportlanding(page_id): tech_report_page=active_tech_report, custom_navigation=True, reports=all_reports, + all_dates=all_dates, ) @@ -209,11 +226,25 @@ def techreport(): requested_geo = request.args.get("geo") or "ALL" requested_rank = request.args.get("rank") or "ALL" requested_category = request.args.get("category") or "ALL" + + all_dates = [ + { + "value": d.replace("_", "-"), + "display": datetime.strptime(d, "%Y_%m_%d").strftime("%b %Y"), + } + for d in get_dates() + ] + + requested_start = request.args.get("start") or "" + requested_end = request.args.get("end") or "" + filters = { "geo": requested_geo, "rank": requested_rank, "app": requested_technologies, "category": requested_category, + "start": requested_start, + "end": requested_end, } params = { "geo": requested_geo.replace(" ", "+"), @@ -242,6 +273,7 @@ def techreport(): tech_report_page=active_tech_report, custom_navigation=True, reports=all_reports, + all_dates=all_dates, ) diff --git a/server/tests/routes_test.py b/server/tests/routes_test.py index 0136e241..409a3279 100644 --- a/server/tests/routes_test.py +++ b/server/tests/routes_test.py @@ -278,6 +278,29 @@ def test_tech_report_invalid_page(client): assert response.status_code == 404 +def test_tech_report_drilldown_with_dates(client): + response = client.get( + "/reports/techreport/tech?tech=WordPress&geo=ALL&rank=ALL&start=2024-01-01&end=2024-03-01" + ) + data = response.get_data(as_text=True) + assert response.status_code == 200 + assert 'value="2024-01-01" selected' in data + assert 'value="2024-03-01" selected' in data + assert 'value="2025-01-01" selected' not in data + + +def test_tech_report_comparison_with_dates(client): + response = client.get( + "/reports/techreport/tech?tech=jQuery%2CWordPress&geo=ALL&rank=ALL&start=2024-01-01&end=2024-03-01" + ) + assert response.status_code == 200 + data = response.get_data(as_text=True) + assert response.status_code == 200 + assert 'value="2024-01-01" selected' in data + assert 'value="2024-03-01" selected' in data + assert 'value="2025-01-01" selected' not in data + + def test_well_known_atproto_did(client): response = client.get("/.well-known/atproto-did") assert response.status_code == 200 diff --git a/src/js/components/filters.js b/src/js/components/filters.js index be4fe952..3652191f 100644 --- a/src/js/components/filters.js +++ b/src/js/components/filters.js @@ -48,6 +48,8 @@ class Filters { const geo = document.getElementsByName('geo')[0].value; const rank = document.getElementsByName('rank')[0].value; const categories = document.getElementsByName('categories')[0]?.value; + const startDate = document.getElementsByName('startDate')[0]?.value; + const endDate = document.getElementsByName('endDate')[0]?.value; /* Create a string of technologies */ let technologies = []; @@ -75,6 +77,16 @@ class Filters { url.searchParams.append('category', categories); } + url.searchParams.delete('start'); + if (startDate) { + url.searchParams.append('start', startDate); + } + + url.searchParams.delete('end'); + if (endDate) { + url.searchParams.append('end', endDate); + } + // Reset to page 1 when filters change url.searchParams.delete('page'); url.searchParams.append('page', '1'); diff --git a/src/js/techreport/index.js b/src/js/techreport/index.js index 7ca29342..af64cbcd 100644 --- a/src/js/techreport/index.js +++ b/src/js/techreport/index.js @@ -241,13 +241,21 @@ class TechReport { const geo = this.filters.geo.replaceAll(" ", "%20"); const rank = this.filters.rank.replaceAll(" ", "%20"); + const start = this.filters.start; + const end = this.filters.end; let allResults = {}; let techInfo = {}; technologies.forEach(tech => allResults[tech] = []); Promise.all(apis.map(api => { - const url = `${Constants.apiBase}/${api.endpoint}?technology=${technology}&geo=${geo}&rank=${rank}`; + let url = `${Constants.apiBase}/${api.endpoint}?technology=${technology}&geo=${geo}&rank=${rank}`; + if (start) { + url += `&start=${start}`; + } + if (end) { + url += `&end=${end}`; + } return fetch(url) .then(result => result.json()) diff --git a/src/js/techreport/tableLinked.js b/src/js/techreport/tableLinked.js index 09d69c75..3ed4846a 100644 --- a/src/js/techreport/tableLinked.js +++ b/src/js/techreport/tableLinked.js @@ -54,6 +54,8 @@ class TableLinked { const filters = new URLSearchParams(window.location.search); const geo = filters.get('geo') || 'ALL'; const rank = filters.get('rank') || 'ALL'; + const start = filters.get('start') || ''; + const end = filters.get('end') || ''; // sort data const sortEndpoint = component.dataset.sortEndpoint; @@ -117,7 +119,7 @@ class TableLinked { const formattedApp = DataUtils.formatAppName(app); const link = document.createElement('a'); - link.setAttribute('href', `/reports/techreport/tech?tech=${app}&geo=${geo}&rank=${rank}`); + link.setAttribute('href', `/reports/techreport/tech?tech=${app}&geo=${geo}&rank=${rank}${start ? '&start=' + start : ''}${end ? '&end=' + end : ''}`); link.innerText = formattedApp; wrapper.append(link); cell.append(wrapper); diff --git a/static/css/techreport/techreport.css b/static/css/techreport/techreport.css index 13f6e8c2..2c75c977 100644 --- a/static/css/techreport/techreport.css +++ b/static/css/techreport/techreport.css @@ -613,8 +613,7 @@ nav { color: var(--color-text-darker); } -/* Page filters: Geo and rank */ -#page-filters .lens { +#page-filters :is(.lens, .date-range) { margin-bottom: 1.5rem; margin-right: 0; margin-top: 1.5rem; @@ -622,7 +621,7 @@ nav { padding-bottom: 2rem; } -#page-filters .lens label, +#page-filters :is(.lens, .date-range) label, .breakdown label { color: var(--color-text); font-size: 0.875rem; @@ -630,7 +629,7 @@ nav { width: 100%; } -#page-filters .lens .select-label select { +#page-filters :is(.lens, .date-range) .select-label select { color: var(--color-text-darker); font-size: 0.9rem; width: 100%; @@ -645,7 +644,7 @@ nav { border: none; } -#page-filters .lens .select-label:not(:last-of-type) { +#page-filters :is(.lens, .date-range) .select-label:not(:last-of-type) { margin-bottom: 2rem; } diff --git a/templates/report/report.html b/templates/report/report.html index a7b7ffea..ae90a7fe 100644 --- a/templates/report/report.html +++ b/templates/report/report.html @@ -92,7 +92,7 @@

-
+
+ Date range +
+ +
+ +
+
+ +
+ +
+ +
+
+
{% include "techreport/components/filter_meta.html" %} diff --git a/tools/test/test_status_codes.js b/tools/test/test_status_codes.js index 708e2aae..e0175080 100644 --- a/tools/test/test_status_codes.js +++ b/tools/test/test_status_codes.js @@ -67,6 +67,8 @@ const test_status_codes = async () => { await test_status_code('/reports/techreport/landing', 200); await test_status_code('/reports/techreport/drilldown', 200); await test_status_code('/reports/techreport/comparison', 200); + await test_status_code('/reports/techreport/tech?tech=WordPress&geo=ALL&rank=ALL&start=2024-01-01&end=2024-03-01', 200); + await test_status_code('/reports/techreport/tech?tech=jQuery%2CWordPress&geo=ALL&rank=ALL&start=2024-01-01&end=2024-03-01', 200); // Test non-sitemap pages await test_status_code('/sitemap.xml', 200); From 5d219ae4cc4f8012f7b862e13b54f0164d98e63c Mon Sep 17 00:00:00 2001 From: Barry Pollard Date: Tue, 10 Mar 2026 13:20:58 +0000 Subject: [PATCH 2/2] Linting --- templates/techreport/templates/filters.html | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/templates/techreport/templates/filters.html b/templates/techreport/templates/filters.html index 2400ca43..1e6f7e63 100644 --- a/templates/techreport/templates/filters.html +++ b/templates/techreport/templates/filters.html @@ -58,10 +58,15 @@ @@ -73,10 +78,15 @@