-
-
Notifications
You must be signed in to change notification settings - Fork 54
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
Enable external data sources to be added and accessed #188
Open
fershad
wants to merge
17
commits into
main
Choose a base branch
from
add-external-data-sources
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
692398e
initial setup
fershad 688b8bf
allow data source to provide own functions
fershad 74648b6
add tests for electricity maps API
fershad daba069
update tests for data sources
fershad c2c063d
initial implementation for current, historic, and zones endpoints
fershad 36a63a8
rename function
fershad f3f94fe
update error wording
fershad 6b440f7
add more tests
fershad e76e2b8
update request URL
fershad 5d8f86e
remove datetime filter function
fershad fadc789
Updated from main
fershad 9c8bcf2
add more tests
fershad 95b7f3d
check for a zone or lat & lon values
fershad ee05f6d
remove console log
fershad 75b9ce1
remove mocks file
fershad f5f5cc8
add name and docs
fershad 8b97e47
add jsdoc comments
fershad File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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
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,7 @@ | ||
const CO2JS_VERSION = require("./package.json").version; | ||
|
||
module.exports = { | ||
define: { | ||
"process.env.CO2JS_VERSION": JSON.stringify(CO2JS_VERSION), | ||
}, | ||
}; |
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
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
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,28 @@ | ||
import { getApiRequestHeaders } from "../src/helpers/index.js"; | ||
const https = jest.createMockFromModule("https"); | ||
import { Stream } from "stream"; | ||
|
||
const stream = new Stream(); | ||
|
||
https.get.mockImplementation((url, options, callback) => { | ||
url, { headers: getApiRequestHeaders("TestRunner") }, callback(stream); | ||
if (url.includes("greencheckmulti")) { | ||
stream.emit( | ||
"data", | ||
Buffer.from( | ||
`{"google.com": {"url":"google.com","hosted_by":"Google Inc.","hosted_by_website":"https://www.google.com","partner":null,"green":true}}` | ||
) | ||
); | ||
} else { | ||
stream.emit( | ||
"data", | ||
Buffer.from( | ||
`{"url":"google.com","hosted_by":"Google Inc.","hosted_by_website":"https://www.google.com","partner":null,"green":true}` | ||
) | ||
); | ||
} | ||
|
||
stream.emit("end"); | ||
}); | ||
|
||
module.exports = https; |
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
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,28 @@ | ||
"use strict"; | ||
import ElectricityMapsApi from "./data/external/electricityMapsApi.js"; | ||
|
||
class DataSources { | ||
constructor() { | ||
/** | ||
* @type {String} - The source of the data. | ||
*/ | ||
this.source = undefined; | ||
} | ||
|
||
/** | ||
* Set the source of the data. | ||
* @param {string} source - The source of the data. | ||
* @throws {Error} Will throw an error if the source is unknown or not provided. | ||
*/ | ||
set(source) { | ||
switch (source) { | ||
case "electricityMapsApi": | ||
this.source = new ElectricityMapsApi(); | ||
break; | ||
default: | ||
throw new Error(`Unknown data source: ${source}`); | ||
} | ||
} | ||
} | ||
export { DataSources }; | ||
export default DataSources; |
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,22 @@ | ||
"use strict"; | ||
|
||
import DataSources from "./data.js"; | ||
|
||
describe("DataSources", () => { | ||
let dataSources; | ||
describe("sets the source", () => { | ||
beforeEach(() => { | ||
dataSources = new DataSources(); | ||
}); | ||
it("throws an error when the data source is not defined", () => { | ||
expect(() => dataSources.set()).toThrow( | ||
new Error("Unknown data source: undefined") | ||
); | ||
}); | ||
it("sets the source correctly", () => { | ||
expect(() => dataSources.set("electricityMapsApi")).not.toThrow( | ||
new Error("Unknown data source: unknown") | ||
); | ||
}); | ||
}); | ||
}); |
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,163 @@ | ||
/** | ||
* Type definition for the options of the ElectricityMapsApi. | ||
* @typedef {Object} ElectricityMapsApiOptions | ||
* @property {string} authToken - The authentication token for the API. | ||
*/ | ||
|
||
/** | ||
* @typedef {Object} LatestData | ||
* @property {string} zone - The zone identifier. | ||
* @property {number} carbonIntensity - The carbon intensity value. | ||
* @property {string} datetime - The date and time of the data. | ||
* @property {string} updatedAt - The date and time the data was last updated. | ||
* @property {string} createdAt - The date and time the data was created. | ||
* @property {string} emissionFactorType - The type of emission factor used. | ||
* @property {boolean} isEstimated - Whether the data is estimated. | ||
* @property {string} estimationMethod - The method used to estimate the data. | ||
*/ | ||
|
||
/** | ||
* @typedef {Object} ZoneData | ||
* @property {string} countryName - The name of the country the zone belongs to. | ||
* @property {string} zoneName - The zone identifier. | ||
* @property {string[]} access - an array of strings listing the API endpoints the zone can be accessed from | ||
*/ | ||
|
||
/** | ||
* @typedef {Object} HistoryData | ||
* @property {Object[]} history - An array of historical data. | ||
* @property {string} history.zone - The zone identifier. | ||
* @property {number} history.carbonIntensity - The carbon intensity value. | ||
* @property {string} history.datetime - The date and time of the data. | ||
* @property {string} history.updatedAt - The date and time the data was last updated. | ||
* @property {string} history.createdAt - The date and time the data was created. | ||
* @property {string} history.emissionFactorType - The type of emission factor used. | ||
* @property {boolean} history.isEstimated - Whether the data is estimated. | ||
* @property {string} history.estimationMethod - The method used to estimate the data. | ||
*/ | ||
|
||
class ElectricityMapsApi { | ||
/** | ||
* Create an instance of ElectricityMapsApi. | ||
* @param {ElectricityMapsApiOptions} options - The options for the ElectricityMapsApi. | ||
*/ | ||
constructor(options) { | ||
/** | ||
* @type {string} The base URL of the API. | ||
*/ | ||
this.baseUrl = "https://api-access.electricitymaps.com/free-tier"; | ||
|
||
/** | ||
* @type {string} The authentication token for the API. | ||
*/ | ||
this.authToken = options?.authToken || undefined; | ||
|
||
/** | ||
* @type {string} The name of the API. | ||
*/ | ||
this.name = "Electricity Maps API - Free Tier"; | ||
|
||
/** | ||
* @type {string} The documentation URL of the API. | ||
*/ | ||
this.docs = "https://static.electricitymaps.com/api/docs/index.html"; | ||
} | ||
|
||
/** | ||
* Fetches the latest grid intensity data from the API. | ||
* @param {string} zone - The zone identifier. | ||
* @param {string} lat - The latitude of the location. | ||
* @param {string} lon - The longitude of the location. | ||
* @returns {Promise<LatestData>} A promise that resolves with the latest grid intensity data. | ||
* @throws {Error} Will throw an error if the authentication token is not provided. | ||
* @throws {Error} Will throw an error if the zone or lat & lon are not provided. | ||
*/ | ||
async getLatest(zone, lat, lon) { | ||
if (!this.authToken || this.authToken === undefined) { | ||
throw new Error( | ||
"An authentication token is required to access this endpoint." | ||
); | ||
} | ||
|
||
if (!zone && (!lat || !lon)) { | ||
throw new Error( | ||
"Either a zone or a latitude and longitude value is required." | ||
); | ||
} | ||
|
||
const query = `${lat ? `lat=${lat}&` : ""}${lon ? `lon=${lon}&` : ""}${ | ||
zone ? `zone=${zone}` : "" | ||
}`; | ||
const url = `${this.baseUrl}/carbon-intensity/latest?${query}`; | ||
const response = await fetch(url, { | ||
method: "GET", | ||
headers: { | ||
"auth-token": this.authToken, | ||
}, | ||
}); | ||
const data = await response.json(); | ||
|
||
if (data.status === "error") { | ||
throw new Error(data.message); | ||
} | ||
|
||
return { data }; | ||
} | ||
|
||
/** | ||
* Fetches the historical grid intensity data from the API. | ||
* @param {string} zone - The zone identifier. | ||
* @param {string} lat - The latitude of the location. | ||
* @param {string} lon - The longitude of the location. | ||
* @returns {Promise<HistoryData>} A promise that resolves with the historical grid intensity data. | ||
* @throws {Error} Will throw an error if the authentication token is not provided. | ||
* @throws {Error} Will throw an error if the zone or lat & lon are not provided. | ||
*/ | ||
|
||
async getHistory(zone, lat, lon) { | ||
if (!this.authToken || this.authToken === undefined) { | ||
throw new Error( | ||
"An authentication token is required to access this endpoint." | ||
); | ||
} | ||
|
||
if (!zone && (!lat || !lon)) { | ||
throw new Error( | ||
"Either a zone or a latitude and longitude value is required." | ||
); | ||
} | ||
|
||
const query = `${lat ? `lat=${lat}&` : ""}${lon ? `lon=${lon}&` : ""}${ | ||
zone ? `zone=${zone}` : "" | ||
}`; | ||
|
||
const url = `${this.baseUrl}/carbon-intensity/history?${query}`; | ||
const response = await fetch(url, { | ||
method: "GET", | ||
headers: { | ||
"auth-token": this.authToken, | ||
}, | ||
}); | ||
const data = await response.json(); | ||
|
||
if (data.status === "error") { | ||
throw new Error(data.message); | ||
} | ||
|
||
return data.history; | ||
} | ||
|
||
/** | ||
* Fetches the zone data from the API. | ||
* @returns {Promise<ZoneData[]>} A promise that resolves with the data for all zones. | ||
*/ | ||
async getZones() { | ||
const url = `${this.baseUrl}/zones`; | ||
const response = await fetch(url); | ||
const data = await response.json(); | ||
return data; | ||
} | ||
} | ||
|
||
export { ElectricityMapsApi }; | ||
export default ElectricityMapsApi; |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure how big of a deal this is, but with this current usage it's possible to call the API with both a zone and coordinates. In this case the coordinates overrule on our end :)