Skip to content

Commit

Permalink
Merge pull request #521 from w3c/support-tables-2.0-feedback
Browse files Browse the repository at this point in the history
Address support tables 2.0 feedback
  • Loading branch information
alflennik authored Mar 27, 2023
2 parents 0f74957 + 6699a39 commit e12e32b
Show file tree
Hide file tree
Showing 16 changed files with 623 additions and 121 deletions.
5 changes: 1 addition & 4 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,6 @@ storybook-static
# Lighthouse
.lighthouseci

#VSCode
.vscode

# Ansible
deploy/ansible-vault-password.txt

Expand All @@ -124,4 +121,4 @@ deploy/provision.retry

# Migration generated files
server/migrations/pg_dump_test_plan_target.sql
server/migrations/test_plan_target_id.csv
server/migrations/test_plan_target_id.csv
19 changes: 19 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"version": "1.0.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Jest Server Debug Current Test File",
//"env": { "NODE_ENV": "test" },
"envFile": "${workspaceFolder}/config/test.env",
"program": "${workspaceFolder}/server/node_modules/.bin/jest",
"args": ["${fileBasenameNoExtension}", "--config", "server/jest.config.js"],
"console": "integratedTerminal",
"disableOptimisticBPs": true,
"windows": {
"program": "${workspaceFolder}/server/node_modules/jest/bin/jest"
}
}
]
}
14 changes: 8 additions & 6 deletions deploy/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,14 @@ To deploy this project to server:
- Run `ssh aria-at-app-sandbox.bocoup.com` and confirm you can connect.
- Confirm that `sudo su` successfully switches you to the root user. You will need to enter the sodoer password you chose during your Bocoup onboarding. This password will be required when deploying to the Sandbox.
3. Obtain a copy of the `ansible-vault-password.txt` file in LastPass and place it in the directory which contains this document.
4. Install [Ansible](https://www.ansible.com/) version 2.8. Instructions for macOS are as follows:
- Install Python 2.7, which is not included by default on recent macOS versions.
- Verify that Pip, Python's package manager, is using Python 2.7 by running `pip --version`.
- Install Ansible at the specific 2.8 version: `pip install ansible==2.8.20`
- Run `ansible --version` to verify your ansible is on version 2.8.
- You may need to run `ansible-galaxy collection install ansible.posix --ignore-certs` as well.
4. Install [Ansible](https://www.ansible.com/) version 2.11. Instructions for macOS are as follows:
- Install Ansible at the specific 2.11 version: `python3 -m pip install --user ansible-core==2.11.1`
- Add the following line to your `~/.zshrc` file, changing the path below to match where Python installs Ansible for you:
```
export PATH=$PATH:/Users/Luigi/Library/Python/3.9/bin
```
- Run `source ~/.zshrc` to refresh your shell.
- Run `ansible --version` to verify your ansible is on version 2.11.
5. Execute the following command from the deploy directory:
- Sandbox:
```
Expand Down
20 changes: 3 additions & 17 deletions server/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,12 @@ const express = require('express');
const bodyParser = require('body-parser');
const cacheMiddleware = require('apicache').middleware;
const proxyMiddleware = require('rawgit/lib/middleware');
const { ApolloServer } = require('apollo-server-express');
const {
ApolloServerPluginLandingPageGraphQLPlayground
} = require('apollo-server-core');
const { session } = require('./middleware/session');
const embedApp = require('./apps/embed');
const authRoutes = require('./routes/auth');
const testRoutes = require('./routes/tests');
const path = require('path');
const graphqlSchema = require('./graphql-schema');
const getGraphQLContext = require('./graphql-context');
const resolvers = require('./resolvers');

const apolloServer = require('./graphql-server');
const app = express();

// test session
Expand All @@ -23,15 +16,8 @@ app.use(bodyParser.json());
app.use('/auth', authRoutes);
app.use('/test', testRoutes);

const server = new ApolloServer({
typeDefs: graphqlSchema,
context: getGraphQLContext,
resolvers,
// The newer IDE does not work because of CORS issues
plugins: [ApolloServerPluginLandingPageGraphQLPlayground()]
});
server.start().then(() => {
server.applyMiddleware({ app });
apolloServer.start().then(() => {
apolloServer.applyMiddleware({ app });
});

const listener = express();
Expand Down
134 changes: 90 additions & 44 deletions server/apps/embed.js
Original file line number Diff line number Diff line change
@@ -1,41 +1,39 @@
const express = require('express');
const { resolve } = require('path');
const { create } = require('express-handlebars');
const {
ApolloClient,
gql,
HttpLink,
InMemoryCache
} = require('@apollo/client');
const fetch = require('cross-fetch');
const { gql } = require('apollo-server-core');
const apolloServer = require('../graphql-server');
const staleWhileRevalidate = require('../util/staleWhileRevalidate');
const hash = require('object-hash');

const app = express();
const handlebarsPath =
process.env.ENVIRONMENT === 'dev' ? 'handlebars' : 'server/handlebars';
process.env.ENVIRONMENT === 'dev' || process.env.ENVIRONMENT === 'test'
? 'handlebars'
: 'server/handlebars';

// handlebars
const hbs = create({
layoutsDir: resolve(`${handlebarsPath}/views/layouts`),
layoutsDir: resolve(handlebarsPath, 'views/layouts'),
extname: 'hbs',
defaultLayout: 'index',
helpers: require(resolve(`${handlebarsPath}/helpers`))
helpers: require(resolve(handlebarsPath, 'helpers'))
});

app.engine('hbs', hbs.engine);
app.set('view engine', 'hbs');
app.set('views', resolve(`${handlebarsPath}/views`));

if (process.env.ENVIRONMENT !== 'dev') {
app.enable('view cache');
}

const client = new ApolloClient({
link: new HttpLink({ uri: 'http://localhost:8000/api/graphql', fetch }),
cache: new InMemoryCache()
});

const getLatestReportsForPattern = async pattern => {
const { data } = await client.query({
app.set('views', resolve(handlebarsPath, 'views'));

// Prevent refreshing cached data for five seconds - using a short time like
// this is possible because the stale-while-revalidate caching strategy works in
// the background and doesn't spin up more than one simultaneous request.
//
// If queries are very slow, anyone trying to get the refreshed data will get
// stale data for however long it takes for the query to complete.
const millisecondsUntilStale = 5000;

const queryReports = async () => {
const { data, errors } = await apolloServer.executeOperation({
query: gql`
query {
testPlanReports(statuses: [CANDIDATE, RECOMMENDED]) {
Expand Down Expand Up @@ -68,9 +66,26 @@ const getLatestReportsForPattern = async pattern => {
`
});

if (errors) {
throw new Error(errors);
}

const reportsHashed = hash(data.testPlanReports);

return { allTestPlanReports: data.testPlanReports, reportsHashed };
};

// As of now, a full query for the complete list of reports is needed to build
// the embed for a single pattern. This caching allows that query to be reused
// between pattern embeds.
const queryReportsCached = staleWhileRevalidate(queryReports, {
millisecondsUntilStale
});

const getLatestReportsForPattern = ({ allTestPlanReports, pattern }) => {
let title;

const testPlanReports = data.testPlanReports.filter(report => {
const testPlanReports = allTestPlanReports.filter(report => {
if (report.testPlanVersion.testPlan.id === pattern) {
title = report.testPlanVersion.title;
return true;
Expand All @@ -80,7 +95,6 @@ const getLatestReportsForPattern = async pattern => {
let allAts = new Set();
let allBrowsers = new Set();
let allAtVersionsByAt = {};
let status = 'RECOMMENDED';
let reportsByAt = {};
let testPlanVersionIds = new Set();
const uniqueReports = [];
Expand All @@ -89,9 +103,6 @@ const getLatestReportsForPattern = async pattern => {
testPlanReports.forEach(report => {
allAts.add(report.at.name);
allBrowsers.add(report.browser.name);
if (report.status === 'CANDIDATE') {
status = report.status;
}

if (!allAtVersionsByAt[report.at.name])
allAtVersionsByAt[report.at.name] =
Expand Down Expand Up @@ -155,6 +166,11 @@ const getLatestReportsForPattern = async pattern => {
.sort((a, b) => a.browser.name.localeCompare(b.browser.name));
});

const hasAnyCandidateReports = Object.values(reportsByAt).find(atReports =>
atReports.find(report => report.status === 'CANDIDATE')
);
let status = hasAnyCandidateReports ? 'CANDIDATE' : 'RECOMMENDED';

return {
title,
allBrowsers,
Expand All @@ -165,25 +181,22 @@ const getLatestReportsForPattern = async pattern => {
};
};

app.get('/reports/:pattern', async (req, res) => {
// In the instance where an editor doesn't want to display a certain title
// as it has defined when importing into the ARIA-AT database for being too
// verbose, etc. eg. `Link Example 1 (span element with text content)`
// Usage: https://aria-at.w3.org/embed/reports/command-button?title=Link+Example+(span+element+with+text+content)
const queryTitle = req.query.title;
const pattern = req.params.pattern;
const protocol = /dev|vagrant/.test(process.env.ENVIRONMENT)
? 'http://'
: 'https://';
const renderEmbed = ({
allTestPlanReports,
queryTitle,
pattern,
protocol,
host
}) => {
const {
title,
allBrowsers,
allAtVersionsByAt,
testPlanVersionIds,
status,
reportsByAt
} = await getLatestReportsForPattern(pattern);
res.render('main', {
} = getLatestReportsForPattern({ pattern, allTestPlanReports });
return hbs.renderView(resolve(handlebarsPath, 'views/main.hbs'), {
layout: 'index',
dataEmpty: Object.keys(reportsByAt).length === 0,
title: queryTitle || title || 'Pattern Not Found',
Expand All @@ -192,11 +205,44 @@ app.get('/reports/:pattern', async (req, res) => {
allBrowsers,
allAtVersionsByAt,
reportsByAt,
completeReportLink: `${protocol}${
req.headers.host
}/report/${testPlanVersionIds.join(',')}`,
embedLink: `${protocol}${req.headers.host}/embed/reports/${pattern}`
completeReportLink: `${protocol}${host}/report/${testPlanVersionIds.join(
','
)}`,
embedLink: `${protocol}${host}/embed/reports/${pattern}`
});
};

// Limit the number of times the template is rendered
const renderEmbedCached = staleWhileRevalidate(renderEmbed, {
getCacheKeyFromArguments: ({ reportsHashed, pattern }) =>
reportsHashed + pattern,
millisecondsUntilStale
});

app.get('/reports/:pattern', async (req, res) => {
// In the instance where an editor doesn't want to display a certain title
// as it has defined when importing into the ARIA-AT database for being too
// verbose, etc. eg. `Link Example 1 (span element with text content)`
// Usage: https://aria-at.w3.org/embed/reports/command-button?title=Link+Example+(span+element+with+text+content)
const queryTitle = req.query.title;
const pattern = req.params.pattern;
const host = req.headers.host;
const protocol = /dev|vagrant/.test(process.env.ENVIRONMENT)
? 'http://'
: 'https://';
const { allTestPlanReports, reportsHashed } = await queryReportsCached();
const embedRendered = await renderEmbedCached({
allTestPlanReports,
reportsHashed,
queryTitle,
pattern,
protocol,
host
});

// Disable browser-based caching which could potentially make the embed
// contents appear stale even after being refreshed
res.set('cache-control', 'must-revalidate').send(embedRendered);
});

app.use(express.static(resolve(`${handlebarsPath}/public`)));
Expand Down
3 changes: 2 additions & 1 deletion server/graphql-context.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const getGraphQLContext = ({ req }) => {
const user = req.session && req.session.user ? req.session.user : null;
const user =
req && req.session && req.session.user ? req.session.user : null;

return { user };
};
Expand Down
17 changes: 17 additions & 0 deletions server/graphql-server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
const { ApolloServer } = require('apollo-server-express');
const {
ApolloServerPluginLandingPageGraphQLPlayground
} = require('apollo-server-core');
const graphqlSchema = require('./graphql-schema');
const getGraphQLContext = require('./graphql-context');
const resolvers = require('./resolvers');

const apolloServer = new ApolloServer({
typeDefs: graphqlSchema,
context: getGraphQLContext,
resolvers,
// The newer IDE does not work because of CORS issues
plugins: [ApolloServerPluginLandingPageGraphQLPlayground()]
});

module.exports = apolloServer;
8 changes: 4 additions & 4 deletions server/handlebars/public/script.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
const iframeClass = `support-levels-${document.currentScript.getAttribute(
'pattern'
)}`;
const iframeClass = `support-levels-${document
.querySelector('script[pattern]')
.getAttribute('pattern')}`;

const iframeCode = link =>
`<iframe
class="${iframeClass}"
src="${link}"
height="500"
height="100"
allow="clipboard-write"
style="border-style: none; width: 100%;">
</iframe>`;
Expand Down
19 changes: 13 additions & 6 deletions server/handlebars/public/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,12 @@ h3#report-title {
display: inline-block;
}

#candidate-title.recommended h3 {
width: 130px;
#candidate-title.recommended {
border: 1.5px solid #7ac498;
background-color: #e9fbe9;
}
#candidate-title.recommended > span {
background-color: #115b11;
}

#candidate-content-container {
Expand All @@ -90,7 +94,7 @@ h3#report-title {
margin-bottom: 3px;
}

.none {
.no-data-cell {
display: block;
color: #72777f;
font-style: italic;
Expand Down Expand Up @@ -187,16 +191,19 @@ table tbody tr td {
background-color: #f4f4f4;
}

#view-report-button, #embed-button {
#view-report-button,
#embed-button {
margin-bottom: 1em;
padding: 0 12px;
line-height: 36px;
}
#view-report-button:focus-visible, #embed-button:focus-visible {
#view-report-button:focus-visible,
#embed-button:focus-visible {
outline-offset: 2px;
outline: 2px solid #3a86d1;
}
#view-report-button svg, #embed-button svg {
#view-report-button svg,
#embed-button svg {
width: 24px;
margin-right: 8px;
float: left;
Expand Down
Loading

0 comments on commit e12e32b

Please sign in to comment.