Skip to content

Commit

Permalink
Add support for "data_provenance" metadata
Browse files Browse the repository at this point in the history
In the early stages of COVID-19, we added support for acknowledging
GISAID as the source of data in the Byline. This was inferred based
on domain / dataset name heuristics.

We now support data provenance to be defined in the dataset JSON
(see nextstrain/augur#705) and all core
nCoV builds have been updated to include this here. This commit
parses and renders such information.

Note that a previous commit removed the "Build info" from the byline
for datasets displaying GISAID (see [0]), which I believe was an
oversight. This commit reinstates it.

[0] 18d5d21
  • Loading branch information
jameshadfield committed Mar 29, 2021
1 parent 8d1bce2 commit 4506442
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 33 deletions.
3 changes: 3 additions & 0 deletions src/actions/recomputeReduxState.js
Original file line number Diff line number Diff line change
Expand Up @@ -648,6 +648,9 @@ const createMetadataStateFromJSON = (json) => {
if (json.meta.genome_annotations) {
metadata.genomeAnnotations = json.meta.genome_annotations;
}
if (json.meta.data_provenance) {
metadata.dataProvenance = json.meta.data_provenance;
}
if (json.meta.filters) {
metadata.filters = json.meta.filters;
}
Expand Down
100 changes: 67 additions & 33 deletions src/components/info/byline.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,49 +4,25 @@ import { withTranslation } from 'react-i18next';
import styled from 'styled-components';
import { headerFont } from "../../globalStyles";

/**
* React component for the byline of the current dataset.
* This details (non-dynamic) information about the dataset, such as the
* maintainers, source, data provenance etc.
*/
@connect((state) => {
return {
metadata: state.metadata
};
})
class Byline extends React.Component {
constructor(props) {
super(props);
}

render() {
const { t } = this.props;

/** Render a special byline for nexstrain's nCoV (SARS-CoV-2) builds.
* This is somewhat temporary and may be switched to a nextstrain.org
* auspice customisation in the future.
*/
if (
// comment out the next line for testing on localhost
(window.location.hostname === "nextstrain.org" || window.location.hostname === "dev.nextstrain.org") &&
window.location.pathname.startsWith("/ncov")
) {
return (
<>
{renderAvatar(t, this.props.metadata)}
{renderMaintainers(t, this.props.metadata)}
{
this.props.metadata.buildUrl &&
<span>
{" Enabled by data from "}
<img src="https://www.gisaid.org/fileadmin/gisaid/img/schild.png" alt="gisaid-logo" width="65"/>
</span>
}
</>
);
}
/* End nextstrain-specific ncov / SARS-CoV-2 code */

return (
<>
{renderAvatar(t, this.props.metadata)}
{renderBuildInfo(t, this.props.metadata)}
{renderMaintainers(t, this.props.metadata)}
{renderDataProvenance(t, this.props.metadata)}
</>
);
}
Expand All @@ -57,6 +33,10 @@ const AvatarImg = styled.img`
margin-bottom: 2px;
`;

/**
* Renders the GitHub avatar of the current dataset for datasets with a `buildUrl`
* which is a GitHub repo. The avatar image is fetched from GitHub (by the client).
*/
function renderAvatar(t, metadata) {
const repo = metadata.buildUrl;
if (typeof repo === 'string') {
Expand All @@ -71,7 +51,8 @@ function renderAvatar(t, metadata) {
}

/**
* Render the byline of the page to indicate the source of the build (often a GitHub repo)
* Returns a React component detailing the source of the build (pipeline).
* Renders a <span> containing "Built with X", where X derives from `metadata.buildUrl`
*/
function renderBuildInfo(t, metadata) {
if (Object.prototype.hasOwnProperty.call(metadata, "buildUrl")) {
Expand All @@ -94,7 +75,10 @@ function renderBuildInfo(t, metadata) {
return null;
}


/**
* Returns a React component detailing the maintainers of the build (pipeline).
* Renders a <span> containing "Maintained by X", where X derives from `metadata.maintainers`
*/
function renderMaintainers(t, metadata) {
let maintainersArray;
if (Object.prototype.hasOwnProperty.call(metadata, "maintainers")) {
Expand All @@ -109,14 +93,64 @@ function renderMaintainers(t, metadata) {
{i === maintainersArray.length-1 ? "" : i === maintainersArray.length-2 ? " and " : ", "}
</React.Fragment>
))}
{"."}
{". "}
</span>
);
}
}
return null;
}


/**
* Returns a React component detailing the data provenance of the build (pipeline).
* Renders a <span> containing "Enabled by data from X", where X derives from `metadata.dataProvenance`
* Note that this function includes logic to special-case certain values which may appear there.
*/
function renderDataProvenance(t, metadata) {
if (!Array.isArray(metadata.dataProvenance)) return null;
const sources = metadata.dataProvenance
.filter((source) => typeof source === "object")
.filter((source) => Object.prototype.hasOwnProperty.call(source, "name"))
.map((source) => {
if (source.name === "GISAID") { // SPECIAL CASE
return <img key={source.name} src="https://www.gisaid.org/fileadmin/gisaid/img/schild.png" alt="gisaid-logo" width="65"/>;
}
const url = parseUrl(source.url);
if (url) {
return <Link url={url} key={source.name}>{source.name}</Link>;
}
return source.name;
});
if (!sources.length) return null;
return (
<span>
{t("Enabled by data from") + " "}
{makePrettyList(sources)}
{"."}
</span>
);
}

function makePrettyList(els) {
if (els.length<2) return els;
return Array.from({length: els.length*2-1})
.map((_, idx) => idx%2===0 ? els[idx/2] : idx===els.length*2-3 ? " and " : ", ");
}


/**
* Attempts to parse a url. Returns a valid-looking URL string or `false`.
*/
function parseUrl(potentialUrl) {
try {
const urlObj = new URL(potentialUrl);
return urlObj.href;
} catch (err) {
return false;
}
}

const BylineLink = styled.a`
font-family: ${headerFont};
font-size: 15;
Expand Down

0 comments on commit 4506442

Please sign in to comment.