forked from nextstrain/nextstrain.org
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
sources: Reorganize from one long file into several shorter files
I found it was starting to get harder to find your way around and ignore adjacent things that weren't relevant. No functional code changes; only added lines which aren't identical copies of removed lines are for require() path adjustments.
- Loading branch information
Showing
8 changed files
with
895 additions
and
835 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
/* eslint no-use-before-define: ["error", {"functions": false, "classes": false}] */ | ||
const {fetch} = require("../fetch"); | ||
const queryString = require("query-string"); | ||
const {NotFound} = require('http-errors'); | ||
const utils = require("../utils"); | ||
const {Source, Dataset, Narrative} = require("./models"); | ||
|
||
class CommunitySource extends Source { | ||
constructor(owner, repoName) { | ||
super(); | ||
|
||
// The GitHub owner and repo names are required. | ||
if (!owner) throw new Error(`Cannot construct a ${this.constructor.name} without an owner`); | ||
if (!repoName) throw new Error(`Cannot construct a ${this.constructor.name} without a repoName`); | ||
|
||
this.owner = owner; | ||
[this.repoName, this.branch] = repoName.split(/@/, 2); | ||
this.branchExplicitlyDefined = !!this.branch; | ||
|
||
if (!this.repoName) throw new Error(`Cannot construct a ${this.constructor.name} without a repoName after splitting on /@/`); | ||
|
||
this.defaultBranch = fetch(`https://api.github.com/repos/${this.owner}/${this.repoName}`) | ||
.then((res) => res.json()) | ||
.then((data) => data.default_branch) | ||
.catch(() => { | ||
console.log(`Error interpreting the default branch of ${this.constructor.name} for ${this.owner}/${this.repoName}`); | ||
return "master"; | ||
}); | ||
if (!this.branch) { | ||
this.branch = this.defaultBranch; | ||
} | ||
} | ||
|
||
static get _name() { return "community"; } | ||
get repo() { return `${this.owner}/${this.repoName}`; } | ||
async baseUrl() { | ||
return `https://github.com/${this.repo}/raw/${await this.branch}/`; | ||
} | ||
|
||
async repoNameWithBranch() { | ||
const branch = await this.branch; | ||
const defaultBranch = await this.defaultBranch; | ||
if (branch === defaultBranch && !this.branchExplicitlyDefined) { | ||
return this.repoName; | ||
} | ||
return `${this.repoName}@${branch}`; | ||
} | ||
|
||
dataset(pathParts) { | ||
return new CommunityDataset(this, pathParts); | ||
} | ||
narrative(pathParts) { | ||
return new CommunityNarrative(this, pathParts); | ||
} | ||
|
||
async availableDatasets() { | ||
const qs = queryString.stringify({ref: await this.branch}); | ||
const response = await fetch(`https://api.github.com/repos/${this.repo}/contents/auspice?${qs}`); | ||
|
||
if (response.status === 404) throw new NotFound(); | ||
else if (response.status !== 200 && response.status !== 304) { | ||
utils.warn(`Error fetching available datasets from GitHub for source ${this.name}`, await utils.responseDetails(response)); | ||
return []; | ||
} | ||
|
||
const filenames = (await response.json()) | ||
.filter((file) => file.type === "file") | ||
// remove anything which doesn't start with the repo name, which is required of community datasets | ||
.filter((file) => file.name.startsWith(this.repoName)) | ||
.map((file) => file.name); | ||
const pathnames = utils.getDatasetsFromListOfFilenames(filenames) | ||
// strip out the repo name from the start of the pathnames | ||
// as CommunityDataset().baseParts will add this in | ||
.map((pathname) => pathname.replace(`${this.repoName}/`, "")); | ||
return pathnames; | ||
} | ||
|
||
async availableNarratives() { | ||
const qs = queryString.stringify({ref: await this.branch}); | ||
const response = await fetch(`https://api.github.com/repos/${this.repo}/contents/narratives?${qs}`); | ||
|
||
if (response.status !== 200 && response.status !== 304) { | ||
if (response.status !== 404) { | ||
// not found doesn't warrant an error print, it means there are no narratives for this repo | ||
utils.warn(`Error fetching available narratives from GitHub for source ${this.name}`, await utils.responseDetails(response)); | ||
} | ||
return []; | ||
} | ||
|
||
const files = await response.json(); | ||
return files | ||
.filter((file) => file.type === "file") | ||
.filter((file) => file.name !== "README.md") | ||
.filter((file) => file.name.endsWith(".md")) | ||
.filter((file) => file.name.startsWith(this.repoName)) | ||
.map((file) => file.name | ||
.replace(this.repoName, "") | ||
.replace(/^_/, "") | ||
.replace(/[.]md$/, "") | ||
.split("_") | ||
.join("/")); | ||
} | ||
async getInfo() { | ||
/* could attempt to fetch a certain file from the repository if we want to implement | ||
this functionality in the future */ | ||
const branch = await this.branch; | ||
return { | ||
title: `${this.owner}'s "${this.repoName}" community builds`, | ||
byline: ` | ||
Nextstrain community builds for GitHub → ${this.owner}/${this.repoName} (${branch} branch). | ||
The available datasets and narratives in this repository are listed below. | ||
`, | ||
website: null, | ||
showDatasets: true, | ||
showNarratives: true, | ||
/* avatar could be fetched here & sent in base64 or similar, or a link sent. The former (or similar) has the advantage | ||
of private S3 buckets working, else the client will have to make (a) an authenticated request (too much work) | ||
or (b) a subsequent request to nextstrain.org/charon (why not do it at once?) */ | ||
avatar: `https://github.com/${this.owner}.png?size=200` | ||
}; | ||
} | ||
} | ||
|
||
class CommunityDataset extends Dataset { | ||
get baseParts() { | ||
// We require datasets are in the auspice/ directory and include the repo | ||
// name in the file basename. | ||
return [`auspice/${this.source.repoName}`, ...this.pathParts]; | ||
} | ||
get isRequestValidWithoutDataset() { | ||
if (!this.pathParts.length) { | ||
return true; | ||
} | ||
return false; | ||
} | ||
} | ||
|
||
class CommunityNarrative extends Narrative { | ||
get baseParts() { | ||
// We require narratives are in the narratives/ directory and include the | ||
// repo name in the file basename. | ||
return [`narratives/${this.source.repoName}`, ...this.pathParts]; | ||
} | ||
} | ||
|
||
module.exports = { | ||
CommunitySource, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
const {fetch} = require("../fetch"); | ||
const queryString = require("query-string"); | ||
const {NotFound} = require('http-errors'); | ||
const utils = require("../utils"); | ||
const {Source} = require("./models"); | ||
|
||
class CoreSource extends Source { | ||
static get _name() { return "core"; } | ||
async baseUrl() { return "http://data.nextstrain.org/"; } | ||
get repo() { return "nextstrain/narratives"; } | ||
get branch() { return "master"; } | ||
|
||
async urlFor(path, method = 'GET') { // eslint-disable-line no-unused-vars | ||
const baseUrl = path.endsWith(".md") | ||
? `https://raw.githubusercontent.com/${this.repo}/${await this.branch}/` | ||
: await this.baseUrl(); | ||
|
||
const url = new URL(path, baseUrl); | ||
return url.toString(); | ||
} | ||
|
||
// The computation of these globals should move here. | ||
secondTreeOptions(path) { | ||
return (global.availableDatasets.secondTreeOptions[this.name] || {})[path] || []; | ||
} | ||
|
||
availableDatasets() { | ||
return global.availableDatasets[this.name] || []; | ||
} | ||
|
||
async availableNarratives() { | ||
const qs = queryString.stringify({ref: this.branch}); | ||
const response = await fetch(`https://api.github.com/repos/${this.repo}/contents?${qs}`); | ||
|
||
if (response.status === 404) throw new NotFound(); | ||
else if (response.status !== 200 && response.status !== 304) { | ||
utils.warn(`Error fetching available narratives from GitHub for source ${this.name}`, await utils.responseDetails(response)); | ||
return []; | ||
} | ||
|
||
const files = await response.json(); | ||
return files | ||
.filter((file) => file.type === "file") | ||
.filter((file) => file.name !== "README.md") | ||
.filter((file) => file.name.endsWith(".md")) | ||
.map((file) => file.name | ||
.replace(/[.]md$/, "") | ||
.split("_") | ||
.join("/")); | ||
} | ||
|
||
async getInfo() { | ||
return { | ||
title: `Nextstrain ${this.name} datasets & narratives`, | ||
showDatasets: true, | ||
showNarratives: true, | ||
}; | ||
} | ||
} | ||
|
||
class CoreStagingSource extends CoreSource { | ||
static get _name() { return "staging"; } | ||
async baseUrl() { return "http://staging.nextstrain.org/"; } | ||
get repo() { return "nextstrain/narratives"; } | ||
get branch() { return "staging"; } | ||
} | ||
|
||
module.exports = { | ||
CoreSource, | ||
CoreStagingSource, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
/* eslint no-use-before-define: ["error", {"functions": false, "classes": false}] */ | ||
const {Source, Dataset, DatasetSubresource, Narrative, NarrativeSubresource} = require("./models"); | ||
|
||
class UrlDefinedSource extends Source { | ||
static get _name() { return "fetch"; } | ||
|
||
constructor(authority) { | ||
super(); | ||
|
||
if (!authority) throw new Error(`Cannot construct a ${this.constructor.name} without a URL authority`); | ||
|
||
this.authority = authority; | ||
} | ||
|
||
async baseUrl() { | ||
return `https://${this.authority}`; | ||
} | ||
dataset(pathParts) { | ||
return new UrlDefinedDataset(this, pathParts); | ||
} | ||
narrative(pathParts) { | ||
return new UrlDefinedNarrative(this, pathParts); | ||
} | ||
|
||
// available datasets & narratives are unknown when the dataset is specified by the URL | ||
async availableDatasets() { return []; } | ||
async availableNarratives() { return []; } | ||
async getInfo() { return {}; } | ||
} | ||
|
||
class UrlDefinedDataset extends Dataset { | ||
get baseName() { | ||
return this.baseParts.join("/"); | ||
} | ||
subresource(type) { | ||
return new UrlDefinedDatasetSubresource(this, type); | ||
} | ||
} | ||
|
||
class UrlDefinedDatasetSubresource extends DatasetSubresource { | ||
get baseName() { | ||
const type = this.type; | ||
const baseName = this.resource.baseName; | ||
|
||
if (type === "main") { | ||
return baseName; | ||
} | ||
|
||
return baseName.endsWith(".json") | ||
? `${baseName.replace(/\.json$/, '')}_${type}.json` | ||
: `${baseName}_${type}`; | ||
} | ||
} | ||
|
||
class UrlDefinedNarrative extends Narrative { | ||
get baseName() { | ||
return this.baseParts.join("/"); | ||
} | ||
subresource(type) { | ||
return new UrlDefinedNarrativeSubresource(this, type); | ||
} | ||
} | ||
|
||
class UrlDefinedNarrativeSubresource extends NarrativeSubresource { | ||
get baseName() { | ||
return this.resource.baseName; | ||
} | ||
} | ||
|
||
module.exports = { | ||
UrlDefinedSource, | ||
}; |
Oops, something went wrong.