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

CoVariants v1 #375

Draft
wants to merge 26 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
67fca88
chore: upgrade deps
ivan-aksamentov Dec 8, 2022
f5a829f
chore: make console less verbose
ivan-aksamentov Dec 8, 2022
000e4e1
chore: make progress bar leafy green to match app style
ivan-aksamentov Dec 8, 2022
b85a0df
fix: move env var to correct place
ivan-aksamentov Dec 8, 2022
2faa6b9
chore: configure typescript checker cache; don't check js
ivan-aksamentov Dec 8, 2022
55bfad2
chore: cut 'debug' package from dependencies
ivan-aksamentov Dec 8, 2022
b123fc1
refactor: enable concurrent react
ivan-aksamentov Dec 8, 2022
cfb3f59
refactor: add suspense and error boundaries
ivan-aksamentov Dec 8, 2022
cd93fa9
chore: remove unused deps and env vars
ivan-aksamentov Dec 8, 2022
4f224e2
refactor: add suspense to every page
ivan-aksamentov Dec 8, 2022
aa83a4f
chore: add dev data server
ivan-aksamentov Dec 8, 2022
6eadd00
refactor: fetch clusters.json for sidebar
ivan-aksamentov Dec 8, 2022
eaf725d
refactor: fetch clusters.json for mutation badge
ivan-aksamentov Dec 8, 2022
d5cf058
feat: delete everything and start over
ivan-aksamentov Dec 13, 2022
f953ce3
feat: setup i18n
ivan-aksamentov Dec 13, 2022
4d29ab6
feat: revamp navbar and sidebar
ivan-aksamentov Dec 13, 2022
c353332
feat: revamp layout; add debug page
ivan-aksamentov Dec 13, 2022
ed917c1
fix: navbar warnings
ivan-aksamentov Dec 13, 2022
304b782
feat: add variant info page
ivan-aksamentov Dec 14, 2022
95f6891
fix: missing component in md
ivan-aksamentov Dec 14, 2022
423ea94
fix: properly configure i18next provider
ivan-aksamentov Dec 14, 2022
3c5b46f
feat: add geography filters
ivan-aksamentov Dec 14, 2022
0e8609b
feat: add geography icons
ivan-aksamentov Dec 14, 2022
3b34ed9
feat: add region switcher
ivan-aksamentov Dec 15, 2022
1e6bf66
fix: ensure regions have separate geography state
ivan-aksamentov Dec 15, 2022
5106090
fix: ensure correct order of countries
ivan-aksamentov Dec 15, 2022
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
10 changes: 1 addition & 9 deletions content/Home.md
Original file line number Diff line number Diff line change
@@ -1,28 +1,20 @@
import { HomeImages } from 'src/components/Home/HomeImages'
import { Link } from 'src/components/Link/Link'
import { LinkExternal } from 'src/components/Link/LinkExternal'
import { NameTable } from 'src/components/Common/NameTable'
import { CladeSchema } from 'src/components/Variants/CladeSchema.tsx'

**Click on a variant button to start exploring!**

CoVariants provides an overview of SARS-CoV-2 variants and mutations that are of interest. Here, you can find out what mutations define a variant, what impact they might have (with links to papers and resources), where variants are found, and see the variants in Nextstrain builds!

Click one of the colored buttons to look at a particular [Variant](/variants) - to read information, see graphs and the protein structure, and link out to focused Nextstrain builds.
To look at many variants at once, check out the [Per Variant](/per-variant) and [Per Country](/per-country) pages, where you can view a lot of data in the same place, and compare variants and countries!

<HomeImages/>

**What do the names mean?**

CoVariants uses the Nextstrain naming system for variants ([read more here](https://nextstrain.org/blog/2021-01-06-updated-SARS-CoV-2-clade-naming/)). However, the fact that there's multiple naming systems is confusing! See the table below to help find the variant you're interested in.

<NameTable/>

<!-- The variants featured are currently slightly biased towards circulation in Europe: this is simply a reflection that the primary maintainer (Emma Hodcroft) works mostly with European data. We hope to add more variants from other regions soon! -->

How are all these variants/clades related to each other? CoVariants follows the Nextstrain Clade schema, where variants can descend from other variants. Here's a chart to show the overall relationships of Nextstrain Clades:

<CladeSchema/>

This project is free and open source. The content, derived data, code used to generate the data, and code that implements this web application can be found on GitHub: [github.com/hodcroftlab/covariants](https://github.com/hodcroftlab/covariants/).

Expand Down
4 changes: 2 additions & 2 deletions content/clusters/22A22B_Spike.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import { LinkExternal } from 'src/components/Link/LinkExternal'

In a <Var name="21L (Omicron)" prefix=""/> background (which does not have the deletion), the <Mut name="S:H69-"/> and <AaMut mut={"S:V70-"}/> deletion allows <Var name="22A (Omicron)" prefix=""/> and <Var name="22B (Omicron)" prefix=""/> to be detected through "S gene drop out" or "S gene target failure (SGTF)" in certain qPCR assays.

- In South Africa, <Var name="22A (Omicron)" prefix=""/> and <Var name="22B (Omicron)" prefix=""/> rapidly replaced <Var name="21L (Omicron)" prefix=""/>, with estimated growth advantages over <Var name="21L (Omicron)" prefix=""/> estimated at 0.08 and 0.12, respectively (similar to the estimated advantage of <Var name="21L (Omicron)" prefix=""/> over <Var name="21K (Omicron)" prefix=""/>). As well as immune evasion (see points below), waning immunity after the initial <WhoBadge name="Omicron" /> may be a factor in the increased transmission advantage ([Tegally et al., medRxiv](https://www.medrxiv.org/content/10.1101/2022.05.01.22274406v1))
- In South Africa, <Var name="22A (Omicron)" prefix=""/> and <Var name="22B (Omicron)" prefix=""/> rapidly replaced <Var name="21L (Omicron)" prefix=""/>, with estimated growth advantages over <Var name="21L (Omicron)" prefix=""/> estimated at 0.08 and 0.12, respectively (similar to the estimated advantage of <Var name="21L (Omicron)" prefix=""/> over <Var name="21K (Omicron)" prefix=""/>). As well as immune evasion (see points below), waning immunity after the initial <Who name="Omicron" /> may be a factor in the increased transmission advantage ([Tegally et al., medRxiv](https://www.medrxiv.org/content/10.1101/2022.05.01.22274406v1))
- Neutralizing immunity against <Var name="22A (Omicron)" prefix=""/> and <Var name="22B (Omicron)" prefix=""/> dropped 7.6- and 7.5-fold, respectively, compared to <Var name="21K (Omicron)" prefix=""/>, in unvaccinated individuals who had been infected with <Var name="21K (Omicron)" prefix=""/>. In vaccinated (Pfizer or J&J) individuals who had been infected with <Var name="21K (Omicron)" prefix=""/>, titers dropped 3.2- and 2.6-fold, respectively ([Khan et al., medRxiv](https://www.medrxiv.org/content/10.1101/2022.04.29.22274477v1))
- Neutralizing titers against <Var name="22A (Omicron)" prefix=""/> and <Var name="22B (Omicron)" prefix=""/> were reduced compared to <Var name="21L (Omicron)" prefix=""/> in vaccinated (CoronaVac) individuals, with a stronger reduction in vaccinated individuals who had been previously infected with <Var name="21K (Omicron)" prefix=""/> ([Cao et al. bioRxiv](https://www.biorxiv.org/content/10.1101/2022.04.30.489997v1))
- ACE2 binding affinity was markedly reduced in <Var name="22A (Omicron)" prefix=""/> and <Var name="22B (Omicron)" prefix=""/>, compared with <Var name="21K (Omicron)" prefix=""/>, possibly due to the <AaMut mut={"S:F486V"}/> mutation and a reversion at <AaMut mut={"S:R493Q"}/> ([Cao et al., bioRxiv](https://www.biorxiv.org/content/10.1101/2022.04.30.489997v1))
- <Var name="22A (Omicron)" prefix=""/> and <Var name="22B (Omicron)" prefix=""/> escape many of the same monoclonal antibodies as <Var name="21L (Omicron)" prefix=""/> (see paper for full chart) (<LinkExternal href="https://www.biorxiv.org/content/10.1101/2022.04.30.489997v1">Cao et al., bioRxiv</LinkExternal>)
- <Var name="22A (Omicron)" prefix=""/> and <Var name="22B (Omicron)" prefix=""/> escape many of the same monoclonal antibodies as <Var name="21L (Omicron)" prefix=""/> (see paper for full chart) (<LinkExternal href="https://www.biorxiv.org/content/10.1101/2022.04.30.489997v1">Cao et al., bioRxiv</LinkExternal>)
4 changes: 2 additions & 2 deletions content/clusters/22C.Omicron.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ As with all <Who name="Omicron" /> variants, <Var name="22C (Omicron)" prefix=""
<br/><br/>

- Neutralizing titers against <Var name="22C (Omicron)" prefix=""/> were reduced compared to <Var name="21L (Omicron)" prefix=""/> in vaccinated (CoronaVac) individuals ([Cao et al., bioRxiv](https://www.biorxiv.org/content/10.1101/2022.04.30.489997v1))
- ACE2 binding affinity was increased in <Var name="22C (Omicron)" prefix=""/>, compared with <Var name="21K (Omicron)" prefix=""/> and other <WhoBadge name="Omicron" /> variants ([Cao et al., bioRxiv](https://www.biorxiv.org/content/10.1101/2022.04.30.489997v1))
- ACE2 binding affinity was increased in <Var name="22C (Omicron)" prefix=""/>, compared with <Var name="21K (Omicron)" prefix=""/> and other <Who name="Omicron" /> variants ([Cao et al., bioRxiv](https://www.biorxiv.org/content/10.1101/2022.04.30.489997v1))

<Var name="22C (Omicron)" prefix=""/> does not have any additional amino-acid mutations outside of spike, compared to <Var name="21L (Omicron)" prefix=""/>.

Expand All @@ -32,4 +32,4 @@ As with all <Who name="Omicron" /> variants, <Var name="22C (Omicron)" prefix=""

_Please help by providing links to further information about this variant if you can!_

_(Thanks to Erik Boehm for his work on curating information relating to 22A-22C, which was used on these pages)_
_(Thanks to Erik Boehm for his work on curating information relating to 22A-22C, which was used on these pages)_
7 changes: 7 additions & 0 deletions docker/docker-dev.dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,13 @@ RUN set -eux >dev/null \
&& npm install -g nodemon@${NODEMON_VERSION} yarn@${YARN_VERSION} >/dev/null \
&& npm config set scripts-prepend-node-path auto

# Calm down the (in)famous chatter from yarn
RUN set -euxo pipefail >/dev/null \
&& sed -i'' "s/this.reporter.warn(this.reporter.lang('incompatibleResolutionVersion', pattern, reqPattern));//g" "${NODE_DIR}/lib/node_modules/yarn/lib/cli.js" \
&& sed -i'' "s/_this2\.reporter.warn(_this2\.reporter.lang('ignoredScripts'));//g" "${NODE_DIR}/lib/node_modules/yarn/lib/cli.js" \
&& sed -i'' 's/_this3\.reporter\.warn(_this3\.reporter\.lang(peerError.*;//g' "/opt/node/lib/node_modules/yarn/lib/cli.js"


# Install Python dependencies
COPY requirements.txt /
RUN set -euxo pipefail >/dev/null \
Expand Down
42 changes: 42 additions & 0 deletions infra/data/lambda-at-edge/OriginRequest.lambda.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/* eslint-disable prefer-destructuring,sonarjs/no-collapsible-if,unicorn/no-lonely-if */
// Implements rewrite of non-compressed to .gz URLs using AWS
// Lambda@Edge. This is useful if you have precompressed your files.
//
// Usage:
// Create an AWS Lambda function and attach it to "Origin Request" event of a
// Cloudfront distribution

const ARCHIVE_EXTS = ['.7z', '.br', '.bz2', '.gz', '.lzma', '.xz', '.zip', '.zst']

function getHeader(headers, headerName) {
const header = headers[headerName.toLowerCase()]
if (!header || !header[0] || !header[0].value) {
return undefined
}
return header[0].value
}

function acceptsEncoding(headers, encoding) {
const ae = getHeader(headers, 'Accept-Encoding')
if (!ae || typeof ae != 'string') {
return false
}
return ae.split(',').some((e) => e.trim().toLowerCase().startsWith(encoding.toLowerCase()))
}

function handler(event, context, callback) {
const request = event.Records[0].cf.request
const headers = request.headers

// If not an archive file (which are not precompressed), rewrite the URL to
// get the corresponding .gz file
if (ARCHIVE_EXTS.every((ext) => !request.uri.endsWith(ext))) {
if (acceptsEncoding(headers, 'gzip')) {
request.uri += '.gz'
}
}

callback(null, request)
}

exports.handler = handler
63 changes: 63 additions & 0 deletions infra/data/lambda-at-edge/ViewerResponse.lambda.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Adds additional headers to the response, including security headers and CORS.
// Suited for serving data and APIs.
//
// See also:
// - https://securityheaders.com/
//
// Usage: Create an AWS Lambda@Edge function and attach it to "Viewer Response"
// event of a Cloudfront distribution

const NEW_HEADERS = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, OPTIONS',
'Content-Security-Policy': `default-src 'none'; frame-ancestors 'none'`,
'Strict-Transport-Security': 'max-age=15768000; includeSubDomains; preload',
'X-Content-Type-Options': 'nosniff',
'X-DNS-Prefetch-Control': 'off',
'X-Download-Options': 'noopen',
'X-Frame-Options': 'DENY',
'X-XSS-Protection': '1; mode=block',
}

function addHeaders(headersObject) {
return Object.fromEntries(
Object.entries(headersObject).map(([header, value]) => [header.toLowerCase(), [{ key: header, value }]]),
)
}

const HEADERS_TO_REMOVE = new Set(['server', 'via'])

function filterHeaders(headers) {
return Object.entries(headers).reduce((result, [key, value]) => {
if (HEADERS_TO_REMOVE.has(key.toLowerCase())) {
return result
}

if (key.toLowerCase().includes('powered-by')) {
return result
}

return { ...result, [key.toLowerCase()]: value }
}, {})
}

function modifyHeaders({ response }) {
let newHeaders = addHeaders(NEW_HEADERS)

newHeaders = {
...response.headers,
...newHeaders,
}

newHeaders = filterHeaders(newHeaders)

return newHeaders
}

exports.handler = (event, context, callback) => {
const { request, response } = event.Records[0].cf
response.headers = modifyHeaders({ request, response })
callback(null, response)
}

exports.modifyHeaders = modifyHeaders
12 changes: 12 additions & 0 deletions infra/find-lambda-at-edge-logs.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/usr/bin/env bash
set -euo pipefail

# Inspired by https://stackoverflow.com/a/54096479/526860

FUNCTION_NAME=$1

for region in $(aws --output text ec2 describe-regions | cut -f 4); do
for loggroup in $(aws --output text logs describe-log-groups --log-group-name "/aws/lambda/us-east-1.$FUNCTION_NAME" --region $region --query 'logGroups[].logGroupName'); do
printf "$region\tconsole.aws.amazon.com/cloudwatch/home?region=$region#logsV2:log-groups/log-group/\$252Faws\$252Flambda\$252Fus-east-1.$FUNCTION_NAME\n"
done
done
15 changes: 15 additions & 0 deletions infra/iam-role-trust-for-lambda-at-edge.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": [
"lambda.amazonaws.com",
"edgelambda.amazonaws.com"
]
},
"Action": "sts:AssumeRole"
}
]
}
9 changes: 9 additions & 0 deletions infra/s3-cors-permissions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[
{
"AllowedHeaders": ["*"],
"AllowedMethods": ["GET", "HEAD"],
"AllowedOrigins": ["*"],
"ExposeHeaders": [],
"MaxAgeSeconds": 3000
}
]
44 changes: 44 additions & 0 deletions infra/web/lambda-at-edge/OriginRequest.lambda.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/* eslint-disable prefer-destructuring */
// Implements rewrite of non-compressed to .gz or .br URLs using AWS
// Lambda@Edge. This is useful if you have precompressed your files.
//
// Usage:
// Create an AWS Lambda function and attach it to "Origin Request" event of a
// Cloudfront distribution

const ARCHIVE_EXTS = ['.7z', '.br', '.bz2', '.gz', '.lzma', '.xz', '.zip', '.zst']

function getHeader(headers, headerName) {
const header = headers[headerName.toLowerCase()]
if (!header || !header[0] || !header[0].value) {
return undefined
}
return header[0].value
}

function acceptsEncoding(headers, encoding) {
const ae = getHeader(headers, 'Accept-Encoding')
if (!ae || typeof ae != 'string') {
return false
}
return ae.split(',').some((e) => e.trim().toLowerCase().startsWith(encoding.toLowerCase()))
}

function handler(event, context, callback) {
const request = event.Records[0].cf.request
const headers = request.headers

// If not an archive file (which are not precompressed), rewrite the URL to
// get the corresponding .gz file
if (ARCHIVE_EXTS.every((ext) => !request.uri.endsWith(ext))) {
if (acceptsEncoding(headers, 'br')) {
request.uri += '.br'
} else if (acceptsEncoding(headers, 'gzip')) {
request.uri += '.gz'
}
}

callback(null, request)
}

exports.handler = handler
Original file line number Diff line number Diff line change
@@ -1,25 +1,21 @@
// Adds some of the security headers to requests using AWS Lambda@Edge
// See https://securityheaders.com/
// Adds additional headers to the response, including security headers.
// Suited for websites.
//
// Usage: Create an AWS Lambda function and attach this to "Viewer Response" event of a Cloudfront distribution
// See also:
// - https://securityheaders.com/
//
// Usage: Create an AWS Lambda@Edge function and attach it to "Viewer Response"
// event of a Cloudfront distribution

const FEATURE_POLICY = {
'accelerometer': `'none'`,
'autoplay': `'none'`,
'camera': `'none'`,
'document-domain': `'none'`,
'encrypted-media': `'none'`,
'fullscreen': `'none'`,
'geolocation': `'none'`,
'gyroscope': `'none'`,
'magnetometer': `'none'`,
'microphone': `'none'`,
'midi': `'none'`,
'payment': `'none'`,
'picture-in-picture': `'none'`,
'sync-xhr': `'none'`,
'usb': `'none'`,
'xr-spatial-tracking': `'none'`,
accelerometer: `'none'`,
camera: `'none'`,
geolocation: `'none'`,
gyroscope: `'none'`,
magnetometer: `'none'`,
microphone: `'none'`,
payment: `'none'`,
usb: `'none'`,
}

function generateFeaturePolicyHeader(featurePolicyObject) {
Expand All @@ -28,9 +24,26 @@ function generateFeaturePolicyHeader(featurePolicyObject) {
.join('; ')
}

const PERMISSIONS_POLICY = {
'accelerometer': '()',
'camera': '()',
'geolocation': '()',
'gyroscope': '()',
'magnetometer': '()',
'microphone': '()',
'payment': '()',
'usb': '()',
'interest-cohort': '()',
}

function generatePermissionsPolicyHeader(permissionsPolicyObject) {
return Object.entries(permissionsPolicyObject)
.map(([policy, value]) => `${policy}=${value}`)
.join(', ')
}

const NEW_HEADERS = {
'Content-Security-Policy':
"default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src *; frame-src https://nextstrain.org",
'Content-Security-Policy': `default-src 'self' *.pangenome.org; script-src 'self' 'unsafe-inline' 'unsafe-eval' blob: *.pangenome.org plausible.io maxcdn.bootstrapcdn.com; style-src 'self' 'unsafe-inline' maxcdn.bootstrapcdn.com fonts.googleapis.com; font-src 'self' maxcdn.bootstrapcdn.com fonts.googleapis.com fonts.gstatic.com;img-src 'self' data:; connect-src *; frame-src 'self' player.vimeo.com`,
'Referrer-Policy': 'no-referrer',
'Strict-Transport-Security': 'max-age=15768000; includeSubDomains; preload',
'X-Content-Type-Options': 'nosniff',
Expand All @@ -39,6 +52,7 @@ const NEW_HEADERS = {
'X-Frame-Options': 'SAMEORIGIN',
'X-XSS-Protection': '1; mode=block',
'Feature-Policy': generateFeaturePolicyHeader(FEATURE_POLICY),
'Permissions-Policy': generatePermissionsPolicyHeader(PERMISSIONS_POLICY),
}

function addHeaders(headersObject) {
Expand All @@ -63,7 +77,7 @@ function filterHeaders(headers) {
}, {})
}

function modifyHeaders({ request, response }) {
function modifyHeaders({ response }) {
let newHeaders = addHeaders(NEW_HEADERS)

newHeaders = {
Expand All @@ -73,18 +87,6 @@ function modifyHeaders({ request, response }) {

newHeaders = filterHeaders(newHeaders)

const url = request.uri || request.url
if (url.startsWith('/_next')) {
const cacheHeaders = addHeaders({
'Cache-Control': 'public,max-age=31536000,immutable',
})

newHeaders = {
...newHeaders,
...cacheHeaders,
}
}

return newHeaders
}

Expand Down
Loading