diff --git a/config/techreport.json b/config/techreport.json index 993d16d7..4a454020 100644 --- a/config/techreport.json +++ b/config/techreport.json @@ -3,6 +3,11 @@ "name": "Technology Report", "summary": "The Core Web Vitals Technology Report is a dashboard combining the powers of real-user experiences in the [Chrome User Experience Report (CrUX)](https://developers.google.com/web/tools/chrome-user-experience-report/) dataset with web technology detections available in HTTP Archive, to allow analysis of the way websites are both built and experienced.", "config": { + "default_apps": { + "drilldown": [ "ALL" ], + "comparison": [ "ALL", "WordPress", "Wix", "Next.js" ] + }, + "default_category": "CMS", "cwv_subcategories": [ "CLS", "LCP", @@ -39,7 +44,7 @@ "id": "landing", "title": "Technology Report", "subtitle": "Report", - "description": "This is placeholder text about how the report works", + "description": "", "data": {}, "filters": { "technologies": ["WordPress", "Squarespace", "Drupal"] @@ -263,7 +268,7 @@ }, "good_cwv_timeseries": { "title": "Good Core Web Vitals over time", - "description": "Placeholder - combination of several metrics", + "description": "The percentage of origins passing all three Core Web Vitals (LCP, INP, CLS) with a good experience. Note that if an origin is missing INP data, it's assessed based on the performance of the remaining metrics.", "id": "good_cwv_timeseries", "endpoint": "vitals", "metric": "good_pct", @@ -425,7 +430,7 @@ }, "lighthouse_timeseries": { "title": "Lighthouse over time", - "description": "Placeholder text", + "description": "", "id": "lighthouse_timeseries", "endpoint": "lighthouse", "metric": "median_score_pct", @@ -557,7 +562,7 @@ }, "weight_timeseries": { "title": "Weight over time", - "description": "Placeholder text", + "description": "", "id": "weight_timeseries", "summary": true, "endpoint": "pageWeight", @@ -695,7 +700,8 @@ "#E24070" ], "overrides": { - "WordPress": "#fff000" + "WordPress": "#3858e9", + "ALL": "#69797e" } }, "default": { @@ -857,7 +863,7 @@ }, "lighthouse_timeseries": { "title": "Lighthouse over time", - "description": "Placeholder text", + "description": "", "id": "lighthouse_timeseries", "endpoint": "lighthouse", "metric": "median_score_pct", @@ -952,7 +958,7 @@ }, "weight_timeseries": { "title": "Page weight over time", - "description": "Placeholder text", + "description": "", "id": "weight_timeseries", "endpoint": "pageWeight", "metric": "median_bytes", @@ -1047,7 +1053,7 @@ }, "adoption_timeseries": { "title": "Origins over time", - "description": "Placeholder text", + "description": "", "id": "adoption_timeseries", "endpoint": "adoption", "metric": "origins", @@ -1118,6 +1124,89 @@ } }, "description": "Comparison placeholder" + }, + "category": { + "id": "category", + "title": "Categories", + "subtitle": "Technology Report", + "config": { + "default": { + "category": "CMS", + "app": ["ALL", "WordPress", "Drupal"], + "series": { + "breakdown": "app" + } + }, + "tech_comparison_summary": { + "id": "tech_comparison_summary", + "table": { + "caption": "Summary", + "columns": [ + { + "key": "selectTech", + "name": "Select technology", + "hiddenName": true, + "type": "checkbox" + }, + { + "key": "technology", + "name": "Tech", + "type": "heading" + }, + { + "key": "origins", + "name": "Origins", + "breakdown": "subcategory", + "subcategory": "adoption", + "endpoint": "adoption", + "metric": "origins" + }, + { + "key": "good_pct", + "name": "Good CWV", + "breakdown": "subcategory", + "subcategory": "overall", + "suffix": "%", + "className": "main-cell pct-value", + "endpoint": "vitals", + "metric": "good_pct" + }, + { + "key": "good_pct", + "name": "LCP", + "breakdown": "subcategory", + "subcategory": "LCP", + "suffix": "%", + "endpoint": "vitals", + "metric": "good_pct" + }, + { + "key": "good_pct", + "name": "INP", + "breakdown": "subcategory", + "subcategory": "INP", + "suffix": "%", + "endpoint": "vitals", + "metric": "good_pct" + }, + { + "key": "good_pct", + "name": "CLS", + "breakdown": "subcategory", + "subcategory": "CLS", + "suffix": "%", + "endpoint": "vitals", + "metric": "good_pct" + }, + { + "key": "client", + "name": "Client", + "className": "client" + } + ] + } + } + } } }, @@ -1145,6 +1234,9 @@ } }, "vitals": { + "general": { + "description": "Each of the Core Web Vitals represents a distinct facet of the user experience, is measurable in the field, and reflects the real-world experience of a critical user-centric outcome. A good threshold to measure is the 75th percentile of page loads, segmented across mobile and desktop devices." + }, "overall": { "label": "Overall Core Web Vitals", "title": "Passes Core Web Vitals", @@ -1178,23 +1270,29 @@ } }, "pageWeight": { + "general": { + "description": "" + }, "images": { "title": "Image Weight", - "description": "todo" + "description": "" }, "js": { "title": "JavaScript Transfer Size", - "description": "todo" + "description": "" }, "total": { "title": "Total Page Weight", - "description": "todo" + "description": "" } }, "adoption": { + "general": { + "description": "" + }, "adoption": { "title": "Adoption", - "description": "Todo" + "description": "The amount of origins using this technology over time." } } }, diff --git a/server/routes.py b/server/routes.py index a97e520d..7c999ee5 100644 --- a/server/routes.py +++ b/server/routes.py @@ -68,7 +68,7 @@ def reports(): @app.route("/reports/techreport/", strict_slashes=False) -def techreport(page_id): +def techreportlanding(page_id): # Needed for the header dropdown all_reports = report_util.get_reports() @@ -85,16 +85,70 @@ def techreport(page_id): "app" ) or ["ALL"] + # Get the filters + requested_geo = request.args.get("geo") or "ALL" + requested_rank = request.args.get("rank") or "ALL" + requested_category = request.args.get("category") or "ALL" + filters = { + "geo": requested_geo, + "rank": requested_rank, + "app": requested_technologies, + "category": requested_category, + } + + active_tech_report["filters"] = filters + + return render_template( + "techreport/%s.html" % page_id, + active_page=page_id, + tech_report_labels=tech_report.get("labels"), + tech_report_config=tech_report.get("config"), + tech_report_page=active_tech_report, + custom_navigation=True, + reports=all_reports, + ) + + +@app.route("/reports/techreport/tech", strict_slashes=False) +def techreport(): + # Needed for the header dropdown + all_reports = report_util.get_reports() + + # Get the configuration for the tech report + tech_report = tech_report_util.get_report() + + # Get the current page_id + requested_technologies = ["ALL"] + if request.args.get("tech"): + requested_technologies = request.args.get("tech").split(",") + + if len(requested_technologies) > 1: + page_id = "comparison" + else: + page_id = "drilldown" + + # Get the settings for the current page + active_tech_report = tech_report.get("pages").get(page_id) + + # Add the technologies requested in the URL to the filters + # Use the default configured techs as fallback + # Use ["ALL"] if there is nothing configured + requested_technologies = active_tech_report.get("config").get("default").get( + "app" + ) or ["ALL"] + if request.args.get("tech"): requested_technologies = request.args.get("tech").split(",") # Get the filters requested_geo = request.args.get("geo") or "ALL" requested_rank = request.args.get("rank") or "ALL" + requested_category = request.args.get("category") or "ALL" filters = { "geo": requested_geo, "rank": requested_rank, "app": requested_technologies, + "category": requested_category, } active_tech_report["filters"] = filters @@ -102,9 +156,11 @@ def techreport(page_id): return render_template( "techreport/%s.html" % page_id, active_page=page_id, + requested_page="technology", tech_report_labels=tech_report.get("labels"), tech_report_config=tech_report.get("config"), tech_report_page=active_tech_report, + custom_navigation=True, reports=all_reports, ) diff --git a/server/tests/routes_test.py b/server/tests/routes_test.py index e86aef80..960286bb 100644 --- a/server/tests/routes_test.py +++ b/server/tests/routes_test.py @@ -232,20 +232,23 @@ def test_render_js_cache_control(client): def test_tech_report_compare(client): response = client.get( - "/reports/techreport/comparison?tech=jQuery%2CWordPress&geo=ALL&rank=ALL" + "/reports/techreport/tech?tech=jQuery%2CWordPress&geo=ALL&rank=ALL" ) assert response.status_code == 200 def test_tech_report_drilldown(client): - response = client.get("/reports/techreport/drilldown?geo=ALL&rank=ALL") + response = client.get("/reports/techreport/tech?geo=ALL&rank=ALL") assert response.status_code == 200 def test_tech_report_drilldown_wordpress(client): - response = client.get( - "/reports/techreport/drilldown?tech=WordPress&geo=ALL&rank=ALL" - ) + response = client.get("/reports/techreport/tech?tech=WordPress&geo=ALL&rank=ALL") + assert response.status_code == 200 + + +def test_tech_report_category(client): + response = client.get("/reports/techreport/category?geo=ALL&rank=ALL&category=CMS") assert response.status_code == 200 diff --git a/src/js/components/drilldownHeader.js b/src/js/components/drilldownHeader.js index 99e784dd..66773163 100644 --- a/src/js/components/drilldownHeader.js +++ b/src/js/components/drilldownHeader.js @@ -1,7 +1,7 @@ import { DataUtils } from "../techreport/utils/data"; function setTitle(title) { - const mainTitle = document.querySelector('h2 span.main-title'); + const mainTitle = document.querySelector('h1 span.main-title'); mainTitle.textContent = title; } diff --git a/src/js/components/filters.js b/src/js/components/filters.js index 260788d8..19b80419 100644 --- a/src/js/components/filters.js +++ b/src/js/components/filters.js @@ -45,6 +45,7 @@ class Filters { /* Get the geo and rank filter */ const geo = document.getElementsByName('geo')[0].value; const rank = document.getElementsByName('rank')[0].value; + const categories = document.getElementsByName('categories')[0]?.value; /* Create a string of technologies */ let technologies = []; @@ -63,8 +64,13 @@ class Filters { url.searchParams.delete('rank'); url.searchParams.append('rank', rank); - /* Scroll to the report content */ - url.hash = '#report-content'; + if(categories) { + url.searchParams.delete('category'); + url.searchParams.append('category', categories); + } + + // /* Scroll to the report content */ + // url.hash = '#report-content'; /* Update the url */ location.href = url; @@ -75,7 +81,7 @@ class Filters { this.technologies = DataUtils.filterDuplicates(this.technologies, 'technology'); /* Get existing tech selectors on the page */ - const allTechSelectors = document.querySelectorAll('select.tech'); + const allTechSelectors = document.querySelectorAll('select[name="tech"]'); const techNames = this.technologies.map(element => element.app); /* Update the options inside all of the selectors */ @@ -95,17 +101,19 @@ class Filters { techs.unshift({ technology: 'ALL' }); /* Add one option per technology */ - techs.forEach((technology) => { - const optionTmpl = document.getElementById('filter-option').content.cloneNode(true); - const option = optionTmpl.querySelector('option'); - const formattedTech = technology.technology; - option.textContent = DataUtils.formatAppName(technology.technology); - option.value = formattedTech; - if(formattedTech === techSelector.getAttribute('data-selected')) { - option.selected = true; - } - techSelector.append(optionTmpl); - }); + if(document.getElementById('filter-option')) { + techs.forEach((technology) => { + const optionTmpl = document.getElementById('filter-option').content.cloneNode(true); + const option = optionTmpl.querySelector('option'); + const formattedTech = technology.technology; + option.textContent = technology.technology; + option.value = formattedTech; + if(formattedTech === techSelector.getAttribute('data-selected')) { + option.selected = true; + } + techSelector.append(optionTmpl); + }); + } }); } @@ -145,7 +153,7 @@ class Filters { /* Update the list with categories */ updateCategories() { - const selects = document.querySelectorAll('select[name="categories"]'); + const selects = document.querySelectorAll('select[name="categories"]') || document.querySelectorAll('select[name="category"]'); if(this.categories) { selects.forEach(select => { @@ -159,6 +167,9 @@ class Filters { const sortedCategories = this.categories.sort((a, b) => a.category !== b.category ? a.category < b.category ? -1 : 1 : 0); sortedCategories.forEach((category) => { const option = document.createElement('option'); + if(category.category === select.getAttribute('data-selected')) { + option.selected = true; + } option.value = category.category; option.innerHTML = category.category; select.append(option); @@ -203,33 +214,35 @@ class Filters { const labelElement = selectorTemplate.querySelector('label.tech'); const removeButton = selectorTemplate.querySelector('.remove-tech'); - const categorySelect = selectorTemplate.querySelector('select.categories-selector'); - const categoryLabel = selectorTemplate.querySelector('label[for="categories-tech-new"]'); - categorySelect.innerHTML = document.querySelector('select.categories-selector').innerHTML; - categorySelect.addEventListener('change', this.updateCategory); - /* Set a unique name on the new element (based on the amount of techs) */ const techId = `tech-${document.querySelectorAll('select.tech[name="tech"]').length + 1}`; selectElement.setAttribute('id', techId); labelElement.setAttribute('for', techId); - removeButton.dataset.tech = techId; - categorySelect.setAttribute('id', `${techId}-category`); - categoryLabel.setAttribute('for', `${techId}-category`); - categorySelect.setAttribute('data-tech', techId); - - removeButton.classList.remove('hidden'); - - /* Bind functionality to the button */ - removeButton.addEventListener('click', this.removeTechnology); + /* Controls for filtering the techs by category */ + // const categorySelect = selectorTemplate.querySelector('select.categories-selector'); + // const categoryLabel = selectorTemplate.querySelector('label[for="categories-tech-new"]'); + // categorySelect.innerHTML = document.querySelector('select.categories-selector').innerHTML; + // categorySelect.addEventListener('change', this.updateCategory); + // categorySelect.setAttribute('id', `${techId}-category`); + // categoryLabel.setAttribute('for', `${techId}-category`); + // categorySelect.setAttribute('data-tech', techId); + /* Set the selected element */ + // categorySelect.innerHTML = document.querySelector('select.categories-selector')?.innerHTML; + // categorySelect.getElementsByTagName('option')[0].selected = true; + + if(removeButton) { + removeButton.dataset.tech = techId; + removeButton.classList.remove('hidden'); + + /* Bind functionality to the button */ + removeButton.addEventListener('click', this.removeTechnology); + } /* Fill in all techs and select the first one */ selectElement.innerHTML = document.querySelector('select.tech').innerHTML; selectElement.getElementsByTagName('option')[0].selected = true; - categorySelect.innerHTML = document.querySelector('select.categories-selector')?.innerHTML; - categorySelect.getElementsByTagName('option')[0].selected = true; - /* Add the new tech to the end of the list */ const techs = document.getElementsByClassName('tech-selector-group'); const last = techs[techs.length - 1]; diff --git a/src/js/techreport/index.js b/src/js/techreport/index.js index 4c3a9717..4ba38082 100644 --- a/src/js/techreport/index.js +++ b/src/js/techreport/index.js @@ -24,9 +24,46 @@ class TechReport { // Load the page this.initializePage(); - this.getAllMetricData(); - this.bindSettingsListeners(); + this.initializeFilters(); this.initializeAccessibility(); + + // Watch for settings updates + this.bindSettingsListeners(); + } + + // Initialize the filter toggle + initializeFilters() { + const closeButton = document.getElementById('close-filters'); + const openButton = document.getElementById('open-filters'); + const filters = document.getElementsByClassName('filters')[0]; + const mobileFilters = document.getElementById('mobile-filter-container'); + const reportFilters = document.getElementById('report-filters'); + const openButtonMobile = document.getElementById('open-filters-mobile'); + + closeButton?.addEventListener('click', () => { + filters.classList.add('hidden'); + openButton.classList.remove('hidden'); + openButton.focus(); + }); + + openButton?.addEventListener('click', () => { + filters.classList.remove('hidden'); + openButton.classList.add('hidden'); + closeButton.focus(); + }); + + openButtonMobile?.addEventListener('click', () => { + if(mobileFilters.classList.contains('hidden')) { + mobileFilters.innerHTML = reportFilters.innerHTML; + mobileFilters.classList.remove('hidden'); + document.getElementById('close-filters').classList.remove('hidden'); + openButtonMobile.setAttribute('aria-expanded', true); + } else { + mobileFilters.innerHTML = ''; + mobileFilters.classList.add('hidden'); + openButtonMobile.setAttribute('aria-expanded', false); + } + }); } // Initialize the sections for the different pages @@ -36,15 +73,24 @@ class TechReport { switch(this.pageId) { case 'landing': this.initializeLanding(); + this.getAllMetricData(); break; case 'drilldown': this.initializeReport(); + this.getAllMetricData(); this.getTechInfo(); break; case 'comparison': this.initializeReport(); + this.getAllMetricData(); + break; + + case 'category': + const category = this.filters.category || 'CMS'; + this.initializeReport(); + this.getCategoryData(category); break; } } @@ -70,10 +116,11 @@ class TechReport { initializeLanding() { } - // TODO + // Initialize the report pages initializeReport() { const sections = document.querySelectorAll('[data-type="section"]'); - // TODO: add general config too + + // Create new class for each of the sections sections.forEach(section => { const reportSection = new Section( section.id, @@ -85,6 +132,7 @@ class TechReport { this.sections[section.id] = reportSection; }); + // Apply settings and watch for updates this.bindClientListener(); } @@ -97,6 +145,7 @@ class TechReport { } } + // Watch for changes in the accessibility/UI settings bindSettingsListeners() { const indicatorSetting = document.querySelector('input[name="indicators-check"]'); if(indicatorSetting) { @@ -129,6 +178,7 @@ class TechReport { } } + // Update which client is selected updateClient(event) { const client = event.target.value; @@ -184,7 +234,7 @@ class TechReport { .replaceAll(" ", "%20"); const geo = this.filters.geo.replaceAll(" ", "%20"); - const rank = this.filters.rank.replaceAll(" ", "%20") + const rank = this.filters.rank.replaceAll(" ", "%20"); let allResults = {}; technologies.forEach(tech => allResults[tech] = []); @@ -224,6 +274,82 @@ class TechReport { }); } + getCategoryData(category) { + const url = `${Constants.apiBase}/categories?category=${category}`; + const apis = [ + { + endpoint: 'cwv', + metric: 'vitals', + parse: DataUtils.parseVitalsData, + }, + { + endpoint: 'lighthouse', + metric: 'lighthouse', + parse: DataUtils.parseLighthouseData, + }, + { + endpoint: 'adoption', + metric: 'adoption', + parse: DataUtils.parseAdoptionData, + }, + { + endpoint: 'page-weight', + metric: 'pageWeight', + parse: DataUtils.parsePageWeightData, + }, + ]; + + fetch(url) + .then(result => result.json()) + .then(result => { + const category = result[0]; + const technologyFormatted = category?.technologies?.join('%2C') + .replaceAll(" ", "%20"); + + const geo = this.filters.geo.replaceAll(" ", "%20"); + const rank = this.filters.rank.replaceAll(" ", "%20"); + const geoFormatted = geo.replaceAll(" ", "%20"); + const rankFormatted = rank.replaceAll(" ", "%20"); + + let allResults = {}; + category.technologies.forEach(tech => allResults[tech] = []); + + Promise.all(apis.map(api => { + const url = `${Constants.apiBase}/${api.endpoint}?technology=${technologyFormatted}&geo=${geoFormatted}&rank=${rankFormatted}`; + + return fetch(url) + .then(techResult => techResult.json()) + .then(techResult => { + techResult.forEach(row => { + const parsedRow = { + ...row, + } + + if(api.parse) { + parsedRow[api.metric] = api.parse(parsedRow[api.metric], parsedRow?.date); + } + + const resIndex = allResults[row.technology].findIndex(res => res.date === row.date); + if(resIndex > -1) { + allResults[row.technology][resIndex] = { + ...allResults[row.technology][resIndex], + ...parsedRow + } + } else { + allResults[row.technology].push(parsedRow); + } + }); + }); + })).then(() => { + category.data = { + technologies: allResults, + summary: 'todo', + }; + this.updateCategoryComponents(category); + }); + }); + } + // Get the information about the selected technology getTechInfo() { const technologies = this.filters.app; @@ -255,6 +381,10 @@ class TechReport { }); } + updateCategoryComponents (category) { + this.updateComponents(category.data.technologies); + } + // Update components and sections that are relevant to the current page updateComponents(data) { switch(this.pageId) { @@ -271,6 +401,11 @@ class TechReport { this.updateComparisonComponents(data); this.getFilterInfo(); break; + + case 'category': + this.updateComparisonComponents(data); + this.getFilterInfo(); + break; } } @@ -309,6 +444,7 @@ class TechReport { }); } + // Update drilldown page components updateDrilldownComponents(data) { DrilldownHeader.update(data, this.filters); @@ -321,6 +457,7 @@ class TechReport { } } + // Update comparison components updateComparisonComponents(data) { if(data && Object.keys(data).length > 0) { UIUtils.updateReportComponents(this.sections, data, data, this.page, this.labels); diff --git a/src/js/techreport/section.js b/src/js/techreport/section.js index 4b774d76..03fd8ce7 100644 --- a/src/js/techreport/section.js +++ b/src/js/techreport/section.js @@ -65,12 +65,15 @@ class Section { ); } - updateSection() { + updateSection(content) { Object.values(this.components).forEach(component => { if(component.data !== this.data) { component.data = this.data; } - component.updateContent(); + if(component.pageFilters !== this.pageFilters) { + component.pageFilters = this.pageFilters; + } + component.updateContent(content); }); } } diff --git a/src/js/techreport/table.js b/src/js/techreport/table.js index 7d0d0b50..de9196f0 100644 --- a/src/js/techreport/table.js +++ b/src/js/techreport/table.js @@ -58,7 +58,18 @@ function formatData(tableConfig, data) { table.push(row); } - return table; + const sorted = sortTableBy(table, 'date'); + + return sorted; +} + +function sortTableBy(dataset, key) { + return dataset.sort((a, b) => { + const selectedA = a.find(obj => obj.key === key); + const selectedB = b.find(obj => obj.key === key); + + return selectedA.value > selectedB.value ? -1 : 1; + }); } function getColumnCell(columnConfig, data, date) { diff --git a/src/js/techreport/tableLinked.js b/src/js/techreport/tableLinked.js index a9bb02f5..f1d229f5 100644 --- a/src/js/techreport/tableLinked.js +++ b/src/js/techreport/tableLinked.js @@ -7,12 +7,16 @@ class TableLinked { this.pageFilters = filters; this.submetric = ''; // TODO: Fetch the default one from somewhere this.data = data; + this.dataArray = Object.values(data) || []; this.updateContent(); } // Update content in the table - updateContent() { + updateContent(content) { + // Temp, move to its own function + this.dataArray = Object.values(this.data); + // Select a table based on the passed in id const component = document.getElementById(`table-${this.id}`); const tbody = component?.querySelector('tbody'); @@ -21,18 +25,55 @@ class TableLinked { // Reset what's in the table before adding new content tbody.innerHTML = ''; + const tableApps = content?.apps || this.pageFilters.app; + // Collect the settings in an object const tableConfig = { - apps: this.pageFilters.app, + apps: tableApps, config: this.pageConfig?.[this.id]?.table, id: this.id, }; - tableConfig.apps.forEach(app => { - const data = this.data[app]; + const filters = new URLSearchParams(window.location.search); + const geo = filters.get('geo') || 'ALL'; + const rank = filters.get('rank') || 'ALL'; + + // sort data + const sortEndpoint = component.dataset.sortEndpoint; + const sortMetric = component.dataset.sortMetric; + const sortKey = component.dataset.sortKey; + const client = component.dataset.client; + + if(sortMetric) { + this.dataArray = this.dataArray.sort((techA, techB) => { + // Sort techs by date to get the latest + const aSortedDate = techA.sort((a, b) => new Date(b.date) - new Date(a.date)); + const bSortedDate = techB.sort((a, b) => new Date(b.date) - new Date(a.date)); + const aLatest = aSortedDate[0]; + const bLatest = bSortedDate[0]; + + // Get the correct endpoint & metric + const aMetric = aLatest[sortEndpoint].find(row => row.name === sortMetric); + const bMetric = bLatest[sortEndpoint].find(row => row.name === sortMetric); + + const aValue = aMetric[client][sortKey]; + const bValue = bMetric[client][sortKey]; + + return bValue - aValue > 0 ? 1 : -1; + }); + } + + this.dataArray.forEach(technology => { + // Set the data and the app name + const data = technology; + const app = technology[0].technology; + const formattedApp = DataUtils.formatAppName(app); + + // Select the latest entry for each technology const sorted = data?.sort((a, b) => new Date(b.date) - new Date(a.date)); const latest = sorted?.[0]; + // If the latest entry exist, add it to the table if(latest) { const tr = document.createElement('tr'); @@ -41,10 +82,41 @@ class TableLinked { if(column.type === 'heading') { cell = document.createElement('th'); const link = document.createElement('a'); - link.setAttribute('href', `?tech=${app}`); - const formattedApp = DataUtils.formatAppName(app); - link.textContent = formattedApp; + link.setAttribute('href', `/reports/techreport/tech?tech=${app}&geo=${geo}&rank=${rank}`); + link.innerHTML = formattedApp; cell.append(link); + } else if (column.type === 'checkbox') { + cell = document.createElement('td'); + const formattedApp = DataUtils.formatAppName(app); + const checkbox = document.createElement('input'); + checkbox.setAttribute('type', 'checkbox'); + checkbox.setAttribute('data-app', formattedApp); + checkbox.setAttribute('data-name', `table-${this.id}`); + checkbox.setAttribute('id', `${app}-table-${this.id}`); + checkbox.setAttribute('name', `${app}-table-${this.id}`); + checkbox.addEventListener('change', (e) => { + const appLinks = document.querySelectorAll('[data-name="selected-apps"]'); + const selectedApps = document.querySelectorAll(`[data-name="table-${this.id}"]:checked`); + + const selectedAppsFormatted = []; + + selectedApps.forEach(selectedApp => { + selectedAppsFormatted.push(selectedApp.dataset.app); + }); + + appLinks.forEach(appLinkEl => { + appLinkEl.setAttribute('href', `/reports/techreport/tech?tech=${selectedAppsFormatted.join(',')}`); + appLinkEl.innerHTML = `Compare ${selectedApps.length} technologies`; + }); + }); + cell.append(checkbox); + + const label = document.createElement('label'); + label.innerHTML = formattedApp; + label.classList.add('sr-only'); + label.setAttribute('for', `${app}-table-${this.id}`); + cell.append(label); + } else if(column.key === 'client') { cell = document.createElement('td'); cell.innerHTML = component.dataset.client; @@ -53,20 +125,23 @@ class TableLinked { const dataset = latest?.[column?.endpoint]; let value = dataset?.find(entry => entry.name === column.subcategory); value = value?.[component.dataset.client]?.[column?.metric]; - cell.innerHTML = `${value}`; + if(column.suffix) { + cell.innerHTML = `${value}${column.suffix}`; + } else { + cell.innerHTML = `${value}`; + } } if(column.className) { cell.className = column.className; } - - tr.append(cell); }); tbody.append(tr); } }); + } } } diff --git a/src/js/techreport/timeseries.js b/src/js/techreport/timeseries.js index 07684a00..c9f0a0ae 100644 --- a/src/js/techreport/timeseries.js +++ b/src/js/techreport/timeseries.js @@ -266,7 +266,8 @@ class Timeseries { // Update the data if(this.data) { - timeseries.series = this.formatSeries(); + const formatted = this.formatSeries(); + timeseries.series = formatted; } timeseries.tooltip = { @@ -293,7 +294,6 @@ class Timeseries { const pointSvg = document.createElement('svg'); let pointSymbol; - switch(point?.point?.graphic?.symbolName) { case 'circle': pointSymbol = document.createElement('circle'); @@ -415,9 +415,11 @@ class Timeseries { }; }); + const sortedData = data.sort((a, b) => new Date(a.x) - new Date(b.x) ? -1 : 1); + series.push({ name: tech, - data: data, + data: sortedData, color: techColor || colors[index] }); }); @@ -433,11 +435,8 @@ class Timeseries { // Get the viz settings const config = this.pageConfig[this.id]?.viz; const app = this.pageFilters.app[0]; - - // TODO: Replace with info from component or config const endpoint = this.pageConfig[this.id]?.endpoint; const metric = this.pageConfig[this.id]?.metric; - const category = this.getCategory(config); // Get color scheme diff --git a/src/js/techreport/utils/data.js b/src/js/techreport/utils/data.js index 46598312..10eefbfd 100644 --- a/src/js/techreport/utils/data.js +++ b/src/js/techreport/utils/data.js @@ -57,7 +57,7 @@ const parseAdoptionData = (submetric, date) => { } const formatBytes = (value) => { - return value > 1048576 ? `${Math.round(value / 1048576)} MB` : value > 1024 ? `${Math.round(value / 1024)} KB` : `${submetric.desktop.median_bytes} bytes`; + return value > 1048576 ? `${Math.round(value / 1048576)} MB` : value > 1024 ? `${Math.round(value / 1024)} KB` : `${value} bytes`; }; const formatAppName = (app) => { @@ -69,14 +69,14 @@ const parsePageWeightData = (metric, date) => { return { ...submetric, desktop: { - ...submetric.desktop, - median_bytes_formatted: formatBytes(submetric.desktop.median_bytes), + ...submetric?.desktop, + median_bytes_formatted: formatBytes(submetric?.desktop?.median_bytes), client: 'desktop', date: date, }, mobile: { - ...submetric.mobile, - median_bytes_formatted: formatBytes(submetric.mobile.median_bytes), + ...submetric?.mobile, + median_bytes_formatted: formatBytes(submetric?.mobile?.median_bytes), client: 'mobile', date: date, }, diff --git a/src/js/techreport/utils/ui.js b/src/js/techreport/utils/ui.js index c7438bb9..0b72785b 100644 --- a/src/js/techreport/utils/ui.js +++ b/src/js/techreport/utils/ui.js @@ -22,10 +22,14 @@ const getAppColor = (tech, technologies, colors) => { // Loop through all the sections in the report // Pass in the new data and config, and re-render -const updateReportComponents = (sections, data, allData, page, labels) => { +const updateReportComponents = (sections, data) => { // Update sections Object.values(sections).forEach(section => { section.data = data; + section.pageFilters = { + ...section.pageFilters, + app: Object.keys(data), + }; section.updateSection(); }); } diff --git a/static/css/techreport/general.css b/static/css/techreport/general.css index 3651401f..cb3fd9c6 100644 --- a/static/css/techreport/general.css +++ b/static/css/techreport/general.css @@ -11,6 +11,7 @@ body { main { color: var(--color-text); + position: relative; } h1 { @@ -19,7 +20,8 @@ h1 { } h2 { - font-size: var(--font-size-large); + font-size: 2rem; + margin-bottom: var(--font-size-regular); } h2 + p { @@ -32,9 +34,18 @@ p { margin: 0; } +strong { + font-weight: 600; +} + :is(a, button, select):focus-visible { outline: 1.5px solid var(--color-teal-dark); outline-offset: 1.5px; + border-radius: 3px; +} + +nav li:hover { + background-color: transparent; } /* Hidden visually, but still accessible through screen readers/other AT */ @@ -57,7 +68,7 @@ p { padding: 1.5rem; background: var(--color-card-background); border-radius: var(--card-radius); - border: 1px solid var(--color-card-border); + border: 1px solid var(--color-card-border-light); box-shadow: var(--card-shadow); transition: padding 0.35s; } @@ -66,6 +77,11 @@ p { margin-top: 0; } +.card :is(h2, h3) { + font-size: 1.5rem; + margin-bottom: 1.5rem; +} + .block-s { width: 50rem; max-width: 90%; @@ -88,14 +104,28 @@ p { } .feedback { - background-image: linear-gradient( - var(--color-page-background), - var(--color-card-background) - ); + background-color: var(--color-card-background); padding: 4rem 0; - margin-top: -4rem; + border-top: 1px solid var(--color-card-border); } .feedback h2 { margin-top: 0; } + +.split-view { + display: flex; + column-gap: 0; +} + +.split-view > .filters { + background-color: var(--color-card-background); + min-width: 20rem; + width: 25rem; + max-width: 100vw; + position: relative; +} + +.split-view:has(.filters.hidden) .page-content { + width: 100%; +} diff --git a/static/css/techreport/landing.css b/static/css/techreport/landing.css index 60bff02a..4c881df8 100644 --- a/static/css/techreport/landing.css +++ b/static/css/techreport/landing.css @@ -1,13 +1,8 @@ -.landing-content { - margin-top: 10rem; -} - .choices { display: grid; grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); gap: 1rem 2rem; margin-bottom: 4rem; - margin-top: -15rem; } .choices .card { diff --git a/static/css/techreport/techreport.css b/static/css/techreport/techreport.css index 0f47b5ea..1980ffa8 100644 --- a/static/css/techreport/techreport.css +++ b/static/css/techreport/techreport.css @@ -2,13 +2,17 @@ :root { /* Main colors to use */ --color-teal-faded: #c0ced0; + --color-teal-light: #ebf6f8; --color-teal-medium: #6a797c; + --color-teal-accent: #4e848b; --color-teal-dark: #3a5c63; --color-teal-darker: #1c4750; --color-blue-dark: #3a7098; --color-blue-100: #eff7ff; --color-blue-light: #e1f1ff; --color-gray-medium: #5c5c5d; + --color-teal-vibrant: #1c818d; + --color-teal-vibrant-darker: #136e78; /* Colors based on function */ --color-link: var(--color-blue-dark); @@ -18,6 +22,7 @@ --color-text-inverted: #fff; --color-card-background: #fff; --color-card-border: #cdd4d6; + --color-card-border-light: #e8e8e8; --color-page-background: #f4f4f4; --color-checkbox: var(--color-blue-dark); --color-checkbox-selected: var(--color-blue-dark); @@ -30,15 +35,25 @@ --color-tooltip-background: var(--color-card-background); --color-tooltip-border: var(--color-card-border); --color-nav: #667a7d; + --color-nav-active: var(--color-teal-accent); + --color-nav-inactive: var(--color-teal-medium); + --color-button-background: var(--color-teal-vibrant); + --color-button-background-hover: var(--color-teal-vibrant-darker); + --color-button-text: #fff; + --color-progress-basic-border: #8e9799; + --color-progress-basic-bg: #eee; + --color-progress-basic-fill: #3a5c63; /* Font sizes */ + --font-size-small: 0.875rem; + --font-size-regular: 1rem; --font-size-medium: 1.75rem; --font-size-large: 2.15rem; - --font-size-xlarge: 2.35rem; + --font-size-xlarge: 2.25rem; /* Components */ - --card-shadow: 0 2px 7px 0px rgba(143, 149, 150, 0.05); - --card-radius: 0.25rem; + --card-shadow: 0 3px 12px 0px rgba(106, 121, 124, 0.2); + --card-radius: 0.625rem; --table-row-hover: var(--color-blue-100); --color-panel-text: #203b40; --color-panel-background: #bfe1e7; @@ -129,6 +144,10 @@ nav { margin-top: 1rem; } +.hidden { + display: none; +} + /* Filter info */ .meta { font-size: 0.75rem; @@ -175,6 +194,9 @@ nav { background-color: var(--color-card-background); border-top: 1px solid var(--color-page-background); border-bottom: 1px solid var(--color-page-background); + display: flex; + justify-content: space-between; + padding: 0 2.5vw; } .report-navigation-content { @@ -185,69 +207,135 @@ nav { .report-navigation ul li { text-transform: none; + margin-right: 1rem; +} + +.report-navigation ul li:last-of-type { + margin-right: 0; } .report-navigation ul li a { - padding: 0.75rem 0.25rem; + padding: 1rem 0.25rem; + font-size: var(--font-size-small); + color: var(--color-nav-inactive); +} + +.report-navigation ul li a[aria-current="page"] span { + padding-bottom: 0.1rem; + border-bottom: 2px solid var(--color-nav-active); + font-weight: 600; + color: var(--color-text); +} + +.report-navigation ul li.all-reports { + position: relative; + padding-right: 1.5rem; + margin-right: 1.5rem; } -.report-navigation ul li a[aria-current="page"] { - border-bottom: 3px solid var(--color-text); +.report-navigation ul li.all-reports::after { + content: ""; + display: block; + height: 1.5rem; + width: 2px; + background-color: var(--color-card-border); + position: absolute; + right: 0; + top: 50%; + transform: translateY(-50%); } -.report-navigation ul li:hover { +.report-navigation ul li:focus-within { background-color: transparent; } +.report-navigation ul li a:focus-visible { + text-decoration: none; + padding: 0.25rem; + outline-color: var(--color-nav-inactive); + border-radius: 3px; + color: var(--color-text); +} + .report-navigation ul li a:hover { color: var(--color-text); background-color: transparent; text-decoration: none; } -.report-navigation ul li a:not([aria-current="page"]):hover { - border-bottom: 3px solid var(--color-teal-faded); +.report-navigation ul li a:not([aria-current="page"]):hover span { + padding-bottom: 0.1rem; + border-bottom: 2px solid var(--color-teal-faded); + color: var(--color-text); +} + +.report-navigation img { + max-height: 2rem; + position: relative; + top: 50%; + transform: translateY(-50%); } /* Page Filters */ #page-filters { - padding: 1.5rem; + padding: 1.5rem 1rem; + padding-right: 0.75rem; + height: 100%; } -#page-filters button:not(.remove-tech) { - background-color: var(--color-teal-darker); - border-radius: 3px; - color: var(--color-blue-100); - font-weight: 600; - border: none; - padding: 0.25rem 0.5rem; +#page-filters button[type="submit"]:is(:hover, :focus-visible) { + background-color: var(--color-button-background-hover); +} + +#page-filters .meta { + padding-top: 1rem; } .remove-tech { background: none; border: none; border-radius: 10rem; - width: 44px; - height: 44px; + width: 1.5rem; + height: 1.5rem; position: relative; - top: -0.15rem; + top: -0.5rem; +} + +.remove-tech:is(:hover, :focus-visible) { + outline: none; } -.remove-tech:is(:hover, :focus) img { +.remove-tech:is(:hover, :focus-visible) img { background: #fcc9c4; border-radius: 3rem; } +.remove-tech:focus-visible img { + box-shadow: 0 0 0 1.5px var(--color-card-background), + 0 0 0 3px #aa4239; +} + +.tech-selector-group:has(.remove-tech:is(:hover, :focus-visible)) select[name="tech"] { + background-color: var(--color-teal-light); +} + .remove-tech img { width: 100%; max-width: 1.75rem; + height: auto; } #page-filters button[type="submit"] { - font-size: 1.25rem; - padding: 0.5rem; + background-color: var(--color-button-background); + border-radius: 4px; + color: var(--color-button-text); + font-weight: 400; + font-size: 1rem; + border: none; + padding: 0.65rem; width: 100%; margin-top: 1rem; + transition: background 0.15s ease-in; } #page-filters legend { @@ -258,26 +346,39 @@ nav { font-weight: 600; } +#page-filters legend.form-title { + font-size: 1.125rem; + font-weight: 400; + margin-bottom: 4.5rem; + color: var(--color-text-darker); +} + /* Page filters: Geo and rank */ #page-filters .lens { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(4rem, 1fr)); - margin: 0; - gap: 0.5rem 0.5rem; - margin-bottom: 0.5rem; + margin-bottom: 1.5rem; margin-right: 0; margin-top: 2rem; + border-bottom: 1px solid var(--color-card-border); + padding-bottom: 2.5rem; } -#page-filters .lens label, -#page-filters .lens select { - color: var(--color-text-lighter); - font-size: 0.8rem; +#page-filters .lens label { + color: var(--color-text); + font-size: 0.875rem; + font-weight: 600; width: 100%; } -#page-filters .lens select { +#page-filters .lens .select-label select { + color: var(--color-text-darker); + font-size: 0.9rem; + width: 100%; border-color: var(--color-text-lighter); + padding: 0.825rem; +} + +#page-filters .lens .select-label:not(:last-of-type) { + margin-bottom: 2rem; } .select-label { @@ -360,27 +461,32 @@ nav { color: var(--color-text) !important; } +.technology-filters { + border-bottom: 1px solid var(--color-card-border); + padding-bottom: 2.5rem; +} + .technology-filters label.tech { - font-size: 1.15rem; - font-weight: normal; margin-bottom: 0.5rem; + color: var(--color-text); + font-size: 0.875rem; + font-weight: 600; width: 100%; } .technology-filters select.tech { - font-size: 1.25rem; width: 100%; margin-bottom: 0; border: 1px solid #959494; - color: var(--color-text-lighter); appearance: none; background-color: transparent; - padding: 0.5rem 0.5rem 0.5rem 0.75rem; + color: var(--color-text-darker); + font-size: 0.9rem; + border-color: var(--color-text-lighter); + padding: 0.5rem; } .technology-filters .tech-selector-group { - border-bottom: 1px solid #eee; - padding-bottom: 2rem; padding-top: 1.5rem; } @@ -388,13 +494,23 @@ nav { margin-top: -1.5rem; } -.tech-selector-group:has(.remove-tech:is(:focus, :hover)) { +.breakdown { + margin-top: 2.5rem; +} + +.breakdown legend { + border-top: 1px solid var(--color-card-border) !important; + padding-top: 1.5rem; + padding-bottom: 0.5rem; +} + +.tech-selector-group:has(.remove-tech:is(:focus-visible, :hover)) { background-color: var(--color-page-background); } .tech-selector-group { display: flex; - column-gap: 1rem; + column-gap: 0.5rem; align-items: end; } @@ -471,12 +587,14 @@ nav { right: 0; top: 0.4rem; text-align: right; + padding-right: 0.15rem; } .categories-selector-group label { font-weight: 400; font-size: 0.825rem; margin-bottom: 0; + margin-top: -0.1rem; } .categories-selector-group select { @@ -488,8 +606,8 @@ nav { font-size: 0.825rem; padding: 0; border-radius: 0; - width: 15ch; - border-bottom: 1px solid #959494; + width: 12ch; + margin-top: 0rem; } select { @@ -498,6 +616,77 @@ select { #add-tech { margin-top: 1.5rem; + border-radius: 3rem; + border: 1px dashed var(--color-nav-inactive); + background-color: transparent; + font-size: 0.75rem; + font-weight: 600; + color: var(--color-nav-inactive); + padding: 0.35rem 1rem; +} + +#add-tech:is(:hover, :focus-visible) { + background-color: var(--color-teal-light); +} + +#add-tech span { + margin-right: 0.25rem; + font-weight: 800; +} + +#close-filters, +#open-filters { + appearance: none; + border: none; + background: transparent; + padding: 0; + margin: 0; + background-color: var(--color-card-background); + width: 2.5rem; + height: 2.5rem; + border-radius: 3rem; + position: absolute; +} + +#close-filters { + right: 1rem; + top: 1rem; +} + +#open-filters { + z-index: 9; + top: 0.75rem; + left: 2.5rem; + border: 1px solid var(--color-text-lighter); +} + +.filter-bar { + background-color: var(--color-card-background); +} + +.filter-bar .wrapper { + display: flex; + justify-content: space-between; + align-items: center; +} + +.filter-bar .open-filters { + appearance: none; + border: none; + background: transparent; + padding: 0; + margin: 0; + width: 2.75rem; + height: 2.75rem; +} + +.filter-bar .open-filters:is(:hover, :focus-visible) { + background-color: var(--color-blue-light); + border: 1px solid var(--color-button-background); +} + +#close-filters:is(:hover, :focus-visible) { + border: 1px solid var(--color-button-background); } /* Results header */ @@ -829,6 +1018,10 @@ select { list-style-type: none; } +.table-ui :is(th, td):is([data-app], .pct-value) { + min-width: 7.5rem; +} + .table-ui .no-data { color: var(--color-text-lighter); } @@ -875,15 +1068,16 @@ select { display: block; height: 0.5rem; width: 100%; - width: calc(100% - 4.5rem); + width: calc(100% - 4.95rem); position: absolute; top: 1.5rem; - right: 0; - border: 1px solid var(--color-teal-dark); + right: 2.5rem; + border: 1px solid var(--color-progress-basic-border); + border-radius: 3px; background-image: linear-gradient( 90deg, - var(--color-teal-dark) 0% var(--cell-value), - transparent var(--cell-value) 100% + var(--color-progress-basic-fill) 0% var(--cell-value), + var(--color-progress-basic-bg) var(--cell-value) 100% ); } @@ -955,7 +1149,7 @@ select { } .intro .categories { - margin: 1rem 0; + margin: -1rem 0 3.5rem; } .intro .categories .cell { @@ -967,16 +1161,23 @@ select { margin-bottom: 0.5rem; } +.page-content { + display: block; + position: relative; + overflow: auto; + width: 100%; +} + .report-content { background-image: linear-gradient(var(--color-bg-gradient), rgba(238, 238, 238, 0)); background-size: 100% 50rem; background-repeat: no-repeat; - padding-top: 3rem; + background-position: 0 25rem; } .report-content .intro { padding: 0; - margin-bottom: 4rem; + margin-top: 3rem; } .report-section { @@ -1256,9 +1457,8 @@ path.highcharts-tick { .accessibility-options { background-color: var(--color-card-background); - border-top: 1px solid var(--color-card-border); border-bottom: 1px solid var(--color-card-border); - padding: 2rem 0; + padding-bottom: 4rem; } .accessibility-options h2 { @@ -1345,6 +1545,17 @@ path.highcharts-tick { border-radius: 3px; } +.mobile-filters { + display: none; +} + +#mobile-filter-container { + background-color: var(--color-card-background); + border-top: 1px solid var(--color-page-background); + border-bottom: 1px solid var(--color-card-border); + position: relative; +} + /* ----------------------- */ /* ---- Media queries ---- */ /* ----------------------- */ @@ -1380,6 +1591,22 @@ path.highcharts-tick { } @media screen and (max-width: 50rem) { + .split-view:has(.filters) { + display: block; + } + + .split-view .filters { + display: none; + } + + .mobile-filters { + display: block; + } + + #open-filters { + display: none; + } + .table-ui td.pct-value[data-value] span { width: 100%; } diff --git a/static/img/close-filters.svg b/static/img/close-filters.svg new file mode 100644 index 00000000..01fab29c --- /dev/null +++ b/static/img/close-filters.svg @@ -0,0 +1,4 @@ + + + + diff --git a/static/img/open-filters.svg b/static/img/open-filters.svg new file mode 100644 index 00000000..d5abda6f --- /dev/null +++ b/static/img/open-filters.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/templates/main.html b/templates/main.html index bdd51acd..8ff47c6a 100644 --- a/templates/main.html +++ b/templates/main.html @@ -89,98 +89,103 @@ {% endif %} -
-
-
- - - -
-
- + + +
- -
- + + + {% endif %} {% block report_navigation %}{% endblock %} diff --git a/templates/techreport/category.html b/templates/techreport/category.html new file mode 100644 index 00000000..ee8c92e2 --- /dev/null +++ b/templates/techreport/category.html @@ -0,0 +1,117 @@ +{% extends "techreport/report.html" %} + +{% block filters %} +
+
+
+ Choose a dataset + +
+ +
+ {% set category_selected = tech_report_page.filters.category or tech_report_config.default_category %} + +
+
+ +
+
+ Lens + + +
+ +
+ + +
+ +
+
+
+ + {% include "techreport/components/filter_meta.html" %} +
+ Breakdown +
+ + +
+
+
+ + + + +{% endblock %} + +{% block section %} + {{ super() }} +
+
+
+

Beta

+

We're still actively working on the categories page.

+
+
+ +
+
+

+ Category + {{ tech_report_page.filters.category or 'CMS' }} +

+
+
+ +
+

Summary

+ {% set component = tech_report_page.config.tech_comparison_summary %} + {% set id = component.id %} + {% set client = request.args.get('client', '') or 'mobile' %} + {% set sort_endpoint = "adoption" %} + {% set sort_metric = "adoption" %} + {% set sort_key = "origins" %} + {% set sort_order = "desc" %} + + {% set table = component.table %} + {% include "techreport/components/table_linked.html" %} + + Compare technologies +
+
+{% endblock %} diff --git a/templates/techreport/comparison.html b/templates/techreport/comparison.html index 536d18f0..75882501 100644 --- a/templates/techreport/comparison.html +++ b/templates/techreport/comparison.html @@ -1,35 +1,32 @@ {% extends "techreport/report.html" %} +{% block filters %} + {% include "techreport/templates/filters.html" %} +{% endblock %} + {% block section %} {{ super() }} {% set technologies = tech_report_page.filters.app or tech_report_page.config.default.app %} {% set technologies_str = ','.join(technologies) %} -
-
+
+

- Tech Report - Comparison + Technology + Compare {{ technologies | length }} technologies

-
- {% include "techreport/templates/filters.html" %} -
-
- - {% set filter_tech_title = technologies_str or tech_report_page.filters.app[0] or 'All Technologies' %} - {% include "techreport/components/filter_info_header.html" %} -
-

Summary ({{ technologies | length }} technologies)

+

Summary

+

Showing the latest data for {{ technologies | length }} technologies.

{% set component = tech_report_page.config.tech_comparison_summary %} {% set id = component.id %} {% set client = request.args.get('client', '') or 'mobile' %} @@ -48,7 +45,7 @@

Summary ({{ technologies | length }} technologies)

data-api="cwv" >

Core Web Vitals

-

Description

+

{{ tech_report_labels.metrics.vitals.general.description }}

@@ -68,7 +65,7 @@

Core Web Vitals

data-api="lighthouse" >

Lighthouse

-

Description

+

{{ tech_report_labels.metrics.lighthouse.general.description }}

@@ -88,7 +85,7 @@

Lighthouse

data-api="page-weight" >

Page weight

-

Description

+

{{ tech_report_labels.metrics.pageWeight.general.description }}

@@ -108,7 +105,7 @@

Page weight

data-api="adopiton" >

Adoption

-

Description

+

{{ tech_report_labels.metrics.adoption.general.description }}

diff --git a/templates/techreport/components/table_linked.html b/templates/techreport/components/table_linked.html index ffc95406..81e589c9 100644 --- a/templates/techreport/components/table_linked.html +++ b/templates/techreport/components/table_linked.html @@ -4,11 +4,15 @@ @@ -21,11 +25,15 @@ data-endpoint="{{ column.endpoint }}" class="{{ column.className }}" > - {{ column.name }} - {% if column.hiddenSuffix %} - + {% if column.hiddenName %} + {{ column.name }} + {% else %} + {{ column.name }} + {% if column.hiddenSuffix %} + + {% endif %} {% endif %} {% endfor %} diff --git a/templates/techreport/drilldown.html b/templates/techreport/drilldown.html index 1a8df86a..0f914fa1 100644 --- a/templates/techreport/drilldown.html +++ b/templates/techreport/drilldown.html @@ -1,29 +1,18 @@ {% extends "techreport/report.html" %} +{% block filters %} + {% include "techreport/templates/filters.html" %} +{% endblock %} + {% block section %} {{ super() }} -
-
-

- Tech Report - Drilldown -

-
-
- {% include "techreport/templates/filters.html" %} -
-
- - {% set filter_tech_title = tech_report_page.filters.app[0] or 'All technologies' %} - {% include "techreport/components/filter_info_header.html" %} -
-

- Results for +

+ Technology ALL -

+
  • Uncategorized
@@ -53,7 +42,7 @@

Summary

data-api="cwv" >

Core Web Vitals

-

Each of the Core Web Vitals represents a distinct facet of the user experience, is measurable in the field, and reflects the real-world experience of a critical user-centric outcome. A good threshold to measure is the 75th percentile of page loads, segmented across mobile and desktop devices.

+

{{ tech_report_labels.metrics.vitals.general.description }}

{% if tech_report_page.config.good_cwv_summary %}

{{ tech_report_page.config.good_cwv_summary.title }}

@@ -107,7 +96,7 @@

{{ tech_report_page.config.lighthouse_summary.title }}

data-api="page-weight" >

Page weight

-

Description

+

{{ tech_report_labels.metrics.pageWeight.general.description }}

{% if tech_report_page.config.weight_summary %} {% set section_prefix = "weight" %} @@ -134,7 +123,7 @@

{{ tech_report_page.config.weight_summary.title }}

data-api="adoption" >

Adoption

-

Description

+

{{ tech_report_labels.metrics.adoption.general.description }}

{% set timeseries = tech_report_page.config.adoption_timeseries %} diff --git a/templates/techreport/landing.html b/templates/techreport/landing.html index d57c1518..0c03a79e 100644 --- a/templates/techreport/landing.html +++ b/templates/techreport/landing.html @@ -5,7 +5,7 @@ {% endblock %} -{% block section %} +{% block report_content %}

@@ -21,13 +21,21 @@

-

Technology Drilldown

+ {% set techs = tech_report_config.default_apps.drilldown %} + {% set techs = ','.join(techs) %} +

Technology Drilldown

Get detailed information about one technology.

-

Technology Comparison

+ {% set techs = tech_report_config.default_apps.comparison %} + {% set techs = ','.join(techs) %} +

Technology Comparison

Get detailed information about two to ten technologies.

+
+

Category info

+

Get detailed information about a category and its technologies.

+
{% endblock %} diff --git a/templates/techreport/report.html b/templates/techreport/report.html index d2f24c61..ba41ec8c 100644 --- a/templates/techreport/report.html +++ b/templates/techreport/report.html @@ -4,4 +4,35 @@ {{ super() }} {% endblock %} -{% block section %}{% endblock %} +{% block report_content %} + +
+
+
+

Filters

+ +
+
+ +
+ + +
+
+ +
+ {% block filters %}{% endblock %} +
+
+ +
+ {% block section %}{% endblock %} +
+
+{% endblock %} diff --git a/templates/techreport/techreport.html b/templates/techreport/techreport.html index 6ffef20b..38cafc70 100644 --- a/templates/techreport/techreport.html +++ b/templates/techreport/techreport.html @@ -10,13 +10,17 @@ {% endblock %} -{% block report_navigation %} +{% block custom_navigation %} - -
-
-

Beta version

-

This dashboard is still under development.

-
-
{% endblock %} {% block main %} - {% block section %}{% endblock %} + + {% block report_content %}{% endblock %} +

{{ table.caption }}