Skip to content

Instantly share code, notes, and snippets.

@decentlab
Last active November 24, 2023 13:55
Show Gist options
  • Save decentlab/770272a068fc74c55b8bc2216b1b8f02 to your computer and use it in GitHub Desktop.
Save decentlab/770272a068fc74c55b8bc2216b1b8f02 to your computer and use it in GitHub Desktop.
Data query API in Javascript
// MIT License
// Copyright (c) 2023 Decentlab
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
async function query(
domain,
apiKey,
{
timeFilter = undefined,
device = "//",
location = "//",
sensor = "//",
includeNetworkSensors = false,
channel = "//",
aggFunc = undefined,
aggInterval = undefined,
doUnstack = true,
convertTimestamp = true,
withLocation = false,
database = "main",
},
) {
const filter = `
location =~ ${location}
AND node =~ ${device}
AND sensor =~ ${sensor}
AND (channel =~ ${channel} OR channel !~ /.+/)
${includeNetworkSensors ? "" : "AND channel !~ /^link-/"}
`;
const q = `
SELECT ${aggFunc ? aggFunc + '("value") as value' : "value"}
FROM "measurements"
WHERE ${filter} ${timeFilter ? "AND " + timeFilter : ""}
GROUP BY
channel, node, sensor, unit, uqk, title
${withLocation ? ", location" : ""}
${aggFunc ? ", time(" + aggInterval + ")" : ""}
${aggFunc ? "fill(null)" : ""}
`;
const url =
`https://${domain}/api/datasources/proxy/uid/${database}/query?` +
new URLSearchParams({
db: database,
epoch: "ms",
q: q,
});
return fetch(url, {
method: "GET",
mode: "cors",
headers: { Authorization: "Bearer " + apiKey },
})
.then((response) => response.json())
.then((data) => {
if ("message" in data || "error" in data) {
throw data;
}
if (data.results.some((res) => "series" in res)) {
return doUnstack
? unstacked(data, convertTimestamp)
: stacked(data, convertTimestamp);
}
throw { error: "No data" };
});
}
function generateTable(data) {
const titles = data[0];
const ths = "<tr>" + titles.map((c) => `<th>${c}</th>`).join("") + "</tr>";
const tds = data
.slice(1)
.map((r) => {
const ts = `<td>${r.shift().toISOString()}</td>`;
const tds = r
.map(
(v) =>
`<td>${
v ? (v === "number" ? Math.round(v * 100) / 100 : v) : ""
}</td>`,
)
.join("");
return `<tr>${ts}${tds}</tr>`;
})
.join("");
return `<table><thead>${ths}</thead><tbody>${tds}</tbody></table>`;
}
function stacked(data, convertTimestamp) {
const tbl = data.results
.flatMap((res) =>
res.series.flatMap((ser) => {
return ser.values.map((kv) => [
convertTimestamp ? new Date(kv[0]) : kv[0],
ser.tags.uqk,
kv[1],
ser.tags.unit,
]);
}),
)
.sort((a, b) => a[0] - b[0]);
const titles = ["timestamp", "series", "value", "unit"];
tbl.unshift(titles);
return tbl;
}
function unstacked(data, convertTimestamp) {
const rows = {};
const cols = [];
const units = [];
data.results.forEach((res) =>
res.series.forEach((ser) => {
cols.push(ser.tags.uqk);
units.push(ser.tags.unit);
ser.values.forEach((v) => {
const ts = String(v[0]);
const row = ts in rows ? rows[ts] : (rows[ts] = {});
row[ser.tags.uqk] = v[1];
});
}),
);
const tbl = Object.keys(rows)
.sort()
.map((idx) => {
const row = cols.map((c) => rows[idx][c]);
const ts = parseInt(idx);
row.unshift(convertTimestamp ? new Date(ts) : ts);
return row;
});
const titles = cols.map(
(c, i) => `${c}${units[i] ? "&nbsp;[" + units[i] + "]" : ""}`,
);
titles.unshift("timestamp");
tbl.unshift(titles);
return tbl;
}
if (typeof module !== "undefined") {
module.exports = { query: query, generateTable: generateTable };
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment