Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Category dashboard with collapsible filters #934

Merged
merged 36 commits into from
Feb 13, 2025
Merged
Changes from 9 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
d3987bf
custom techreport header
sarahfossheim Aug 28, 2024
b21cc6e
add prototype categories page
sarahfossheim Aug 28, 2024
04628eb
add filters in sidebar and change page based on amount of techs
sarahfossheim Aug 30, 2024
07171f7
add new nav and filter structure (wip
sarahfossheim Sep 4, 2024
c312252
responsive filters prototype
sarahfossheim Sep 17, 2024
706d6f0
wip: add collapsible filter sidebar
sarahfossheim Sep 24, 2024
fa8c689
fix bug with double close button
sarahfossheim Sep 24, 2024
e58f7ce
add aria
sarahfossheim Sep 24, 2024
7f837c1
merge in master
sarahfossheim Sep 25, 2024
6b24d96
add checkboxes on category page, update texts, minor bugfixes
sarahfossheim Oct 18, 2024
cf7c0e8
select correct technology in comparison view, remove category selecto…
sarahfossheim Oct 23, 2024
bf11a13
fix css linting
sarahfossheim Oct 23, 2024
ce8df02
fix linting
sarahfossheim Oct 23, 2024
bc5af01
Merge branch 'main' into cwvtech-category-dashboard
sarahfossheim Oct 30, 2024
b74de3b
fix linting
sarahfossheim Oct 30, 2024
728d42f
Merge remote-tracking branch 'refs/remotes/origin/cwvtech-category-da…
sarahfossheim Oct 30, 2024
0d062cc
fix sorting order multi apps
sarahfossheim Oct 30, 2024
aea33e1
sort table alternative correctly
sarahfossheim Nov 8, 2024
7aaf182
merge main into branch
sarahfossheim Nov 18, 2024
fea35f0
close <nav>
sarahfossheim Nov 19, 2024
c16fe45
merge master into branch
sarahfossheim Dec 3, 2024
3987516
update tests
sarahfossheim Dec 18, 2024
5a45a40
format tests
sarahfossheim Dec 18, 2024
d67c907
Update src/js/techreport/tableLinked.js
sarahfossheim Dec 18, 2024
7820ed5
fix geo/rank bug, long category list bug, adoption bug, add category …
sarahfossheim Jan 7, 2025
cd10b24
add technologies to summary in category page
sarahfossheim Jan 8, 2025
273d19b
add timestamp to category page and format large numbers
sarahfossheim Jan 12, 2025
37dbaeb
remove console.log
sarahfossheim Jan 12, 2025
d861090
select correct technology in comparison view filters
sarahfossheim Jan 15, 2025
3a125ad
make menu responsive on mobile + track aria-expanded status
sarahfossheim Jan 16, 2025
062bf5b
give remove button icon alt that matches the label
sarahfossheim Jan 16, 2025
f664a28
filter out data with empty content
sarahfossheim Jan 16, 2025
7fb726e
fix linting
sarahfossheim Jan 16, 2025
9a905c3
add width and height to close filter button
sarahfossheim Jan 16, 2025
163cdc3
remove placeholder texts
sarahfossheim Jan 16, 2025
962851c
use client filter for origins in category page
sarahfossheim Feb 3, 2025
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
72 changes: 72 additions & 0 deletions config/techreport.json
Original file line number Diff line number Diff line change
@@ -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",
@@ -1118,6 +1123,73 @@
}
},
"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": "technology",
"name": "Tech",
"type": "heading"
},
{
"key": "origins",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In addition to the number of origins, I think a primary use case for this data would be to see the proportion of origins that adopt this category relative to all origins that adopt this category.

For example, there are 3.2M WordPress origins and the /categories endpoint says that there are 8.8M origins in the CMS category, so we could show that "market share" value as 36%.

What do you think would be the best way to show the 36% stat? It could be a new column, combined with the # of origins somehow, or replace the # origin column entirely. Alternatively, would you hate a pie chart at the top of the page?

Aside: I suspect the 8.8M number is wrong as it's double-counting origins that use multiple CMSs. WordPress market share should be more like 75% of the CMS category. This is something we'll need to fix in BigQuery.

cc @max-ostapenko

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think would be the best way to show the 36% stat? It could be a new column, combined with the # of origins somehow, or replace the # origin column entirely.

If a primary use case is to see the market share, I'm inclined to say replace the origins with the market share, so the table doesn't become overcrowded with info. The other numbers in the table (% of good CWV etc) feel more important than the absolute number of origins to provide a summary of the category data as well, so if a column has to be hidden to make space for the market share, I think origins is the safest one to remove.

Alternatively, would you hate a pie chart at the top of the page?

What about just for the top 5/top 10 technologies within a category? I fear anything more than a handful of items will become too busy/hard to read, at which point it's not very useful anymore and the table becomes a better alternative.

Aside: I suspect the 8.8M number is wrong as it's double-counting origins that use multiple CMSs

If some origins can have multiple CMSes, then adding all the market share percentages can end up being more than 100%, and a pie chart doesn't work anymore? So if that's the case, a different way of highlighting the top techs within a category might be better?

→ Sounds like marketshare might be better for a follow-up issue & PR as well, so we can think through the specifics of it a bit more?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SGTM

"name": "Origins",
"breakdown": "subcategory",
"subcategory": "adoption",
"endpoint": "adoption",
"metric": "origins"
},
{
"key": "good_pct",
"name": "Overall good CWV",
"breakdown": "subcategory",
"subcategory": "overall",
"suffix": "%",
"className": "main-cell pct-value",
"endpoint": "vitals",
"metric": "good_pct"
},
{
"key": "good_pct",
"name": "FID",
"breakdown": "subcategory",
"subcategory": "FID",
"suffix": "%",
"endpoint": "vitals",
"metric": "good_pct"
},
{
"key": "medium_score_pct",
"name": "Accessibility",
"breakdown": "subcategory",
"subcategory": "accessibility",
"endpoint": "lighthouse",
"metric": "median_score_pct"
},
{
"key": "client",
"name": "Client",
"className": "client"
}
]
}
}
}
}
},

61 changes: 59 additions & 2 deletions server/routes.py
Original file line number Diff line number Diff line change
@@ -66,15 +66,68 @@ def reports():

return render_template("reports.html", reports=all_reports)


@app.route("/reports/techreport/<page_id>", strict_slashes=False)
def techreport(page_id):
def techreportlanding(page_id):
# 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 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

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)

@@ -91,20 +144,24 @@ def techreport(page_id):
# 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,
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,
)

2 changes: 1 addition & 1 deletion src/js/components/drilldownHeader.js
Original file line number Diff line number Diff line change
@@ -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;
}

43 changes: 35 additions & 8 deletions src/js/components/filters.js
Original file line number Diff line number Diff line change
@@ -45,6 +45,7 @@
/* 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 @@
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 @@
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,6 +101,21 @@
techs.unshift({ technology: 'ALL' });

/* Add one option per technology */
<<<<<<< HEAD
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);
});
}
=======
techs.forEach((technology) => {
const optionTmpl = document.getElementById('filter-option').content.cloneNode(true);
const option = optionTmpl.querySelector('option');
@@ -106,6 +127,7 @@
}
techSelector.append(optionTmpl);
});
>>>>>>> main
});
}

@@ -145,7 +167,7 @@

/* 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 +181,9 @@
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);
@@ -212,16 +237,18 @@
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');
if(removeButton) {
removeButton.dataset.tech = techId;
removeButton.classList.remove('hidden');

/* Bind functionality to the button */
removeButton.addEventListener('click', this.removeTechnology);
/* 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;
149 changes: 144 additions & 5 deletions src/js/techreport/index.js
Original file line number Diff line number Diff line change
@@ -24,9 +24,46 @@

// 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 @@
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 @@
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 @@
this.sections[section.id] = reportSection;
});

// Apply settings and watch for updates
this.bindClientListener();
}

@@ -97,6 +145,7 @@
}
}

// Watch for changes in the accessibility/UI settings
bindSettingsListeners() {
const indicatorSetting = document.querySelector('input[name="indicators-check"]');
if(indicatorSetting) {
@@ -129,6 +178,7 @@
}
}

// Update which client is selected
updateClient(event) {
const client = event.target.value;

@@ -157,6 +207,7 @@
getAllMetricData() {
const technologies = this.filters.app;

const base = 'https://prod-gw-2vzgiib6.ue.gateway.dev/v1';
const apis = [
{
endpoint: 'cwv',
@@ -184,7 +235,7 @@
.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 +275,83 @@
});
}

getCategoryData(category) {
const base = 'https://prod-gw-2vzgiib6.ue.gateway.dev/v1';
const url = `${base}/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 = `${base}/${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 +383,10 @@
});
}

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 +403,11 @@
this.updateComparisonComponents(data);
this.getFilterInfo();
break;

case 'category':
this.updateComparisonComponents(data);
this.getFilterInfo();
break;
}
}

@@ -309,6 +446,7 @@
});
}

// Update drilldown page components
updateDrilldownComponents(data) {
DrilldownHeader.update(data, this.filters);

@@ -321,6 +459,7 @@
}
}

// Update comparison components
updateComparisonComponents(data) {
if(data && Object.keys(data).length > 0) {
UIUtils.updateReportComponents(this.sections, data, data, this.page, this.labels);
7 changes: 5 additions & 2 deletions src/js/techreport/section.js
Original file line number Diff line number Diff line change
@@ -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);
});
}
}
14 changes: 10 additions & 4 deletions src/js/techreport/tableLinked.js
Original file line number Diff line number Diff line change
@@ -12,7 +12,7 @@ class TableLinked {
}

// Update content in the table
updateContent() {
updateContent(content) {
// Select a table based on the passed in id
const component = document.getElementById(`table-${this.id}`);
const tbody = component?.querySelector('tbody');
@@ -21,13 +21,19 @@ 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,
};

const filters = new URLSearchParams(window.location.search);
const geo = filters.get('geo') || 'ALL';
const rank = filters.get('rank') || 'ALL';

tableConfig.apps.forEach(app => {
const data = this.data[app];
const sorted = data?.sort((a, b) => new Date(b.date) - new Date(a.date));
@@ -41,9 +47,9 @@ 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.key === 'client') {
cell = document.createElement('td');
10 changes: 5 additions & 5 deletions src/js/techreport/utils/data.js
Original file line number Diff line number Diff line change
@@ -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,
},
6 changes: 5 additions & 1 deletion src/js/techreport/utils/ui.js
Original file line number Diff line number Diff line change
@@ -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();
});
}
43 changes: 36 additions & 7 deletions static/css/techreport/general.css
Original file line number Diff line number Diff line change
@@ -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,27 @@ 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;
max-width: 100vw;
position: relative;
}

.split-view:has(.filters.hidden) .page-content {
width: 100%;
}
338 changes: 290 additions & 48 deletions static/css/techreport/techreport.css

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions static/img/close-filters.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions static/img/open-filters.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
169 changes: 87 additions & 82 deletions templates/main.html
Original file line number Diff line number Diff line change
@@ -89,92 +89,97 @@
</button>
</div>
{% endif %}
<header>
<div class="container">
<div class="pddl0 col-lg-2 col-md-3 col-sm-3 col-xs-10">
<a href="{{ url_for('index') }}">
<img class="logo" src="/static/img/ha.png" width="320" height="160" alt="HTTP Archive"/>
</a>
</div>

<div class="col-lg-10 col-md-9 col-sm-9 col-xs-2 clearfix">
<nav class="hidden-xs pull-right text-right" aria-label="Header Menu">
<ul>
<li>
<span class="dropdown-target" aria-haspopup="true" tabindex="0" role="button" title="Reports menu">
Reports
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18" aria-label="menu"><path d="M5 8l4 4 4-4z"/></svg>
</span>

<ul class="dropdown-content reports">
<li><a href="{{ url_for('reports') }}">All Reports</a></li>
{% for report in reports %}
{% set url = report.url or url_for('report', report_id=report.id) %}
{% if custom_navigation %}
{% block custom_navigation %}{% endblock %}
{% else %}
<header>
<div class="container">
<div class="pddl0 col-lg-2 col-md-3 col-sm-3 col-xs-10">
<a href="{{ url_for('index') }}">
<img class="logo" src="/static/img/ha.png" width="320" height="160" alt="HTTP Archive"/>
</a>
</div>

<div class="col-lg-10 col-md-9 col-sm-9 col-xs-2 clearfix">
<nav class="hidden-xs pull-right text-right" aria-label="Header Menu">
<ul>
<li>
<span class="dropdown-target" aria-haspopup="true" tabindex="0" role="button" title="Reports menu">
Reports
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18" aria-label="menu"><path d="M5 8l4 4 4-4z"/></svg>
</span>

<ul class="dropdown-content reports">
<li><a href="{{ url_for('reports') }}">All Reports</a></li>
{% for report in reports %}
{% set url = report.url or url_for('report', report_id=report.id) %}
<li>
<a href="{{ url }}">
{{ report.name }}
</a>
</li>
{% endfor %}
</ul>
</li>
<li>
<a href="https://discuss.httparchive.org">Discuss</a>
</li>
<li>
<span class="dropdown-target" aria-haspopup="true" tabindex="0" role="button" title="Web Almanac menu">
Web Almanac
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18" aria-label="menu"><path d="M5 8l4 4 4-4z"/></svg>
</span>

<ul class="dropdown-content">
<li>
<a href="{{ url }}">
{{ report.name }}
</a>
<a href="https://almanac.httparchive.org/en/2022/">2022</a>
</li>
{% endfor %}
</ul>
</li>
<li>
<a href="https://discuss.httparchive.org">Discuss</a>
</li>
<li>
<span class="dropdown-target" aria-haspopup="true" tabindex="0" role="button" title="Web Almanac menu">
Web Almanac
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18" aria-label="menu"><path d="M5 8l4 4 4-4z"/></svg>
</span>

<ul class="dropdown-content">
<li>
<a href="https://almanac.httparchive.org/en/2022/">2022</a>
</li>
<li>
<a href="https://almanac.httparchive.org/en/2021/">2021</a>
</li>
<li>
<a href="https://almanac.httparchive.org/en/2020/">2020</a>
</li>
<li>
<a href="https://almanac.httparchive.org/en/2019/">2019</a>
</li>
</ul>
</li>
<li>
<span class="dropdown-target" aria-haspopup="true" tabindex="0" role="button" title="About menu">
About
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18" aria-label="menu"><path d="M5 8l4 4 4-4z"/></svg>
</span>

<ul class="dropdown-content">
<li>
<a href="{{ url_for('about', _anchor='mission') }}">Mission</a>
</li>
<li>
<a href="{{ url_for('about', _anchor='contribute') }}">Contribute</a>
</li>
<li>
<a href="{{ url_for('about', _anchor='contact') }}">Contact</a>
</li>
<li>
<a href="{{ url_for('faq') }}">FAQ</a>
</li>
</ul>
</li>
</ul>
</nav>

<button class="visible-xs hamburger" aria-label="Show navigation">
<svg version="1.1" viewBox="0 0 32 32" height="32px" width="32px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<path d="M4,10h24c1.104,0,2-0.896,2-2s-0.896-2-2-2H4C2.896,6,2,6.896,2,8S2.896,10,4,10z M28,14H4c-1.104,0-2,0.896-2,2 s0.896,2,2,2h24c1.104,0,2-0.896,2-2S29.104,14,28,14z M28,22H4c-1.104,0-2,0.896-2,2s0.896,2,2,2h24c1.104,0,2-0.896,2-2 S29.104,22,28,22z"/>
</svg>
</button>
<li>
<a href="https://almanac.httparchive.org/en/2021/">2021</a>
</li>
<li>
<a href="https://almanac.httparchive.org/en/2020/">2020</a>
</li>
<li>
<a href="https://almanac.httparchive.org/en/2019/">2019</a>
</li>
</ul>
</li>
<li>
<span class="dropdown-target" aria-haspopup="true" tabindex="0" role="button" title="About menu">
About
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18" aria-label="menu"><path d="M5 8l4 4 4-4z"/></svg>
</span>

<ul class="dropdown-content">
<li>
<a href="{{ url_for('about', _anchor='mission') }}">Mission</a>
</li>
<li>
<a href="{{ url_for('about', _anchor='contribute') }}">Contribute</a>
</li>
<li>
<a href="{{ url_for('about', _anchor='contact') }}">Contact</a>
</li>
<li>
<a href="{{ url_for('faq') }}">FAQ</a>
</li>
</ul>
</li>
</ul>
</nav>

<button class="visible-xs hamburger" aria-label="Show navigation">
<svg version="1.1" viewBox="0 0 32 32" height="32px" width="32px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<path d="M4,10h24c1.104,0,2-0.896,2-2s-0.896-2-2-2H4C2.896,6,2,6.896,2,8S2.896,10,4,10z M28,14H4c-1.104,0-2,0.896-2,2 s0.896,2,2,2h24c1.104,0,2-0.896,2-2S29.104,14,28,14z M28,22H4c-1.104,0-2,0.896-2,2s0.896,2,2,2h24c1.104,0,2-0.896,2-2 S29.104,22,28,22z"/>
</svg>
</button>
</div>
</div>
</div>
</header>
<nav id="mobile" class="hidden-sm hidden-md hidden-lg" aria-label="Mobile Menu"></nav>
</header>
<nav id="mobile" class="hidden-sm hidden-md hidden-lg" aria-label="Mobile Menu"></nav>
{% endif %}

{% block report_navigation %}{% endblock %}

91 changes: 91 additions & 0 deletions templates/techreport/category.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
{% extends "techreport/report.html" %}

{% block filters %}
<form id="page-filters">
<div>
<fieldset class="technology-filters">
<legend class="form-title">Choose a dataset</legend>

<div class="tech-selector">
<label for="categories_selector" class="tech">Category</label>
<div class="tech-input-wrapper">
{% set category_selected = tech_report_page.filters.category or tech_report_config.default_category %}
<select name="categories" class="tech" data-selected="{{ category_selected }}" id="categories_selector">
<option value="{{ category_selected }}" selected="true">
{{ category_selected }}
</option>
</select>
</div>
</div>

</fieldset>
<fieldset class="lens">
<legend class="sr-only">Lens</legend>

<label for="geo">Geo</label>
<div class="select-label">
<select name="geo" id="geo">
<option value="ALL" selected="true">
ALL
</option>
</select>
</div>

<label for="rank">Rank</label>
<div class="select-label">
<select name="rank" id="rank">
<option value="ALL" selected="true">
ALL
</option>
</select>
</div>
</fieldset>
</div>
<button type="submit" id="submit-form">Update</button>
{% include "techreport/components/filter_meta.html" %}
</form>


<template id="filter-option">
<option value="Option">
Option
</option>
</template>

<template id="tech-selector">
{% set technology = "ALL" %}
{% set name = "tech-new" %}
{% set key = 2 %}
{% include "techreport/templates/selector_tech.html" %}
</template>

{% endblock %}

{% block section %}
{{ super() }}
<div class="report-content">
<div class="intro block-m">
<div class="info">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When the API supports it, we should also have a placeholder for the category description here.

<h1>
<span class="subtitle">Category</span>
<span class="">{{ tech_report_page.filters.category or 'CMS' }}</span>
</h1>
</div>
</div>

<div
id="comparison-summary"
class="card block-m summary report-section"
data-type="section"
data-api="cwv,lighthouse,page-weight"
>
<h2>Summary</h2>
{% set component = tech_report_page.config.tech_comparison_summary %}
{% set id = component.id %}
{% set client = request.args.get('client', '') or 'mobile' %}

{% set table = component.table %}
{% include "techreport/components/table_linked.html" %}
</div>
</div>
{% endblock %}
25 changes: 11 additions & 14 deletions templates/techreport/comparison.html
Original file line number Diff line number Diff line change
@@ -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) %}

<div class="intro block-m">
<div class="info">
<div id="report-content" class="report-content">
<div class="info intro block-m">
<h1>
<span class="subtitle">Tech Report</span>
<span class="main-title">Comparison</span>
<span class="subtitle">Technology</span>
<span class="main-title">Compare {{ technologies | length }} technologies</span>
</h1>
</div>
<div class="filters">
{% include "techreport/templates/filters.html" %}
</div>
</div>

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

<div id="report-content" class="report-content">
<!-- Section: Summary of the main metrics -->
<div
id="comparison-summary"
class="block-m summary report-section"
class="card block-m summary report-section"
data-type="section"
data-api="cwv,lighthouse,page-weight"
>
<h2>Summary ({{ technologies | length }} technologies)</h2>
<h2>Summary</h2>
<p>Showing the latest data for <strong>{{ technologies | length }} technologies</strong>.</p>
{% set component = tech_report_page.config.tech_comparison_summary %}
{% set id = component.id %}
{% set client = request.args.get('client', '') or 'mobile' %}
25 changes: 7 additions & 18 deletions templates/techreport/drilldown.html
Original file line number Diff line number Diff line change
@@ -1,29 +1,18 @@
{% extends "techreport/report.html" %}

{% block filters %}
{% include "techreport/templates/filters.html" %}
{% endblock %}

{% block section %}
{{ super() }}

<div class="intro block-m">
<div class="info">
<h1>
<span class="subtitle">Tech Report</span>
<span class="">Drilldown</span>
</h1>
</div>
<div class="filters">
{% include "techreport/templates/filters.html" %}
</div>
</div>

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

<div id="report-content" class="report-content">
<div class="info intro block-m">
<h2>
<span class="subtitle">Results for</span>
<h1>
<span class="subtitle">Technology</span>
<span class="main-title">ALL</span>
</h2>
</h1>
<ul class="category-list categories">
<li class="cell">Uncategorized</li>
</ul>
14 changes: 11 additions & 3 deletions templates/techreport/landing.html
Original file line number Diff line number Diff line change
@@ -5,7 +5,7 @@
<link rel="stylesheet" href="{{ get_versioned_filename('/static/css/techreport/landing.css') }}" />
{% endblock %}

{% block section %}
{% block report_content %}
<div class="intro block-m">
<div>
<h1>
@@ -21,13 +21,21 @@ <h1>
<div class="report-content landing-content">
<div class="choices block-m">
<div class="card">
<h2><a href="/reports/techreport/drilldown">Technology Drilldown</a></h2>
{% set techs = tech_report_config.default_apps.drilldown %}
{% set techs = ','.join(techs) %}
<h2><a href="/reports/techreport/tech?tech={{ techs }}">Technology Drilldown</a></h2>
<p>Get detailed information about <strong>one</strong> technology. </p>
</div>
<div class="card">
<h2><a href="/reports/techreport/comparison">Technology Comparison</a></h2>
{% set techs = tech_report_config.default_apps.comparison %}
{% set techs = ','.join(techs) %}
<h2><a href="/reports/techreport/tech?tech={{ techs }}">Technology Comparison</a></h2>
<p>Get detailed information about <strong>two to ten</strong> technologies. </p>
</div>
<div class="card">
<h2><a href="/reports/techreport/category?category=CMS">Category info</a></h2>
<p>Get detailed information about <strong>a category</strong> and its technologies. </p>
</div>
</div>
</div>
{% endblock %}
33 changes: 32 additions & 1 deletion templates/techreport/report.html
Original file line number Diff line number Diff line change
@@ -4,4 +4,35 @@
{{ super() }}
{% endblock %}

{% block section %}{% endblock %}
{% block report_content %}
<button id="open-filters" class="hidden">
<img src="/static/img/open-filters.svg" alt="Expand filters" />
</button>
<div class="mobile-filters">
<div class="filter-bar" id="mobile-filter-bar">
<div class="wrapper block-m">
<p>Filters</p>
<button aria-expanded="false" class="open-filters" id="open-filters-mobile">
<img src="/static/img/open-filters.svg" alt="Expand filters" />
</button>
</div>
</div>
<div class="hidden" id="mobile-filter-container"></div>
</div>


<div class="split-view">
<div class="filters">
<button id="close-filters">
<img src="/static/img/close-filters.svg" alt="Collapse filters" />
</button>
<div id="report-filters">
{% block filters %}{% endblock %}
</div>
</div>

<div class="content page-content">
{% block section %}{% endblock %}
</div>
</div>
{% endblock %}
35 changes: 17 additions & 18 deletions templates/techreport/techreport.html
Original file line number Diff line number Diff line change
@@ -10,56 +10,55 @@
<link rel="stylesheet" href="{{ get_versioned_filename('/static/css/techreport/techreport.css') }}" />
{% endblock %}

{% block report_navigation %}
{% block custom_navigation %}
<nav class="report-navigation">
<a href="{{ url_for('index') }}">
<img class="logo" src="/static/img/ha.png" width="320" height="160" alt="HTTP Archive"/>
</a>
{% set filters = "" %}
{% if tech_report_page.filters %}
{% set filters = "?geo=" + tech_report_page.filters.geo + "&rank=" + tech_report_page.filters.rank %}
{% set filters = "?geo=ALL&rank=ALL" %}
{% endif %}
<ul class="navigation-items block-l">
<ul class="navigation-items">
<li class="all-reports"><a href="/reports/"><span>All reports</span></a></li>
<li>
{% set ariaCurrent = "false" %}
{% if active_page == "landing" %}{% set ariaCurrent = "page" %}{% endif %}
<a
href="/reports/techreport/landing"
aria-current="{{ ariaCurrent }}"
>
Home
<span>Home</span>
</a>
</li>
<li>
{% set ariaCurrent = "false" %}
{% if active_page == "drilldown" %}{% set ariaCurrent = "page" %}{% endif %}
{% if active_page == "technology" or requested_page == "technology" %}{% set ariaCurrent = "page" %}{% endif %}
<a
href="/reports/techreport/drilldown{{ filters }}"
href="/reports/techreport/tech{{ filters }}&tech=ALL"
aria-current="{{ ariaCurrent }}"
>
Drilldown
<span>Technologies</span>
</a>
</li>
<li>
{% set ariaCurrent = "false" %}
{% if active_page == "comparison" %}{% set ariaCurrent = "page" %}{% endif %}
{% if active_page == "category" %}{% set ariaCurrent = "page" %}{% endif %}
<a
href="/reports/techreport/comparison{{ filters }}"
href="/reports/techreport/category{{ filters }}&category=CMS"
aria-current="{{ ariaCurrent }}"
>
Comparison
<span>Categories</span>
</a>
</li>
</ul>
</nav>

<div class="info-panel">
<div class="block-l">
<p class="info-label">Beta version</p>
<p>This dashboard is still under development.</p>
</div>
</div>
{% endblock %}

{% block main %}
{% block section %}{% endblock %}

{% block report_content %}{% endblock %}

<div class="feedback">
<div class="block-m">
<h2>Feedback?</h2>
17 changes: 9 additions & 8 deletions templates/techreport/templates/filters.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<form id="page-filters" class="card">
<form id="page-filters">
<div>
<fieldset class="technology-filters">
<legend class="sr-only">Technologies</legend>
<legend class="form-title">Choose a dataset</legend>

{% set technologies = tech_report_page.filters.app or tech_report_page.config.default.app %}
{% if active_page == "drilldown" %}
@@ -19,22 +19,22 @@
{% include "techreport/templates/selector_tech.html" %}
{% endfor %}

{% if active_page == "comparison" %}
<button id="add-tech">Add another technology</button>
{% endif %}
<button id="add-tech"><span aria-hidden="true">+</span> Add another technology</button>
</fieldset>
<fieldset class="lens">
<legend>Lens</legend>
<legend class="sr-only">Lens</legend>

<label for="geo">Geo</label>
<div class="select-label">
<label for="geo">Geo</label>
<select name="geo" id="geo">
<option value="ALL" selected="true">
ALL
</option>
</select>
</div>

<label for="rank">Rank</label>
<div class="select-label">
<label for="rank">Rank</label>
<select name="rank" id="rank">
<option value="ALL" selected="true">
ALL
@@ -44,6 +44,7 @@
</fieldset>
</div>
<button type="submit" id="submit-form">Update</button>
{% include "techreport/components/filter_meta.html" %}
</form>

<template id="filter-option">
14 changes: 6 additions & 8 deletions templates/techreport/templates/selector_tech.html
Original file line number Diff line number Diff line change
@@ -12,7 +12,7 @@
<div class="tech-selector">
<label for="{{ name }}" class="tech">Technology</label>
<div class="tech-input-wrapper">
<select name="tech" class="tech" data-selected="{{ technology }}" id="{{ name }}">
<select name="tech" class="tech" data-selected="test" id="{{ name }}">
<option value="{{ technology }}" selected="true">
{{ technologyName }}
</option>
@@ -21,11 +21,9 @@
</div>
</div>

{% if active_page == "comparison" %}
<div class="actions">
<button class="remove-tech {% if technologies|length == 1 %}hidden{% endif %}" data-tech="{{ name }}">
<img src="/static/img/icon-delete.svg" alt="Remove" height="28" width="28" data-tech="{{ name }}" />
</button>
</div>
{% endif %}
<div class="actions">
<button class="remove-tech {% if technologies|length == 1 %}hidden{% endif %}" data-tech="{{ name }}">
<img src="/static/img/icon-delete.svg" alt="Remove" data-tech="{{ name }}" />
</button>
</div>
</div>