UNPKG

@cocalc/database

Version:

CoCalc: code for working with our PostgreSQL database

142 lines (138 loc) 6.49 kB
"use strict"; /* * This file is part of CoCalc: Copyright © 2020 Sagemath, Inc. * License: AGPLv3 s.t. "Commons Clause" – see LICENSE.md for details */ Object.defineProperty(exports, "__esModule", { value: true }); exports.number_of_projects_using_site_license = exports.projects_using_site_license = exports.site_license_usage_stats = exports.number_of_projects_with_license_applied = exports.number_of_running_projects_using_license = exports.numberRunningQuery = void 0; const async_utils_1 = require("@cocalc/util/async-utils"); const misc_1 = require("@cocalc/util/misc"); const TIMEOUT_S = 30; function numberRunningQuery(license_id) { if (!(0, misc_1.isValidUUID)(license_id)) { // critical to check to avoid any possible SQL injection attack. throw Error("invalid license_id"); } // "... - 'status'" in the query, because there is always a status field (which is new) // an applied license not providing upgrades is just an empty object. return ` SELECT COUNT(*)::INT FROM projects WHERE state ->> 'state' = 'running' AND ((site_license -> '${license_id}') - 'status') != '{}'::JSONB`; } exports.numberRunningQuery = numberRunningQuery; async function number_of_running_projects_using_license(db, license_id) { /* Do a query to count the number of projects that: (1) are running, (2) have the given license_id has a key in their site_license field with a nontrivial value. (3) we have to ignore the "status" field, which is only information but not providing upgrades. */ const query = numberRunningQuery(license_id); const x = await db.async_query({ query, timeout_s: TIMEOUT_S }); return parseInt(x.rows[0].count); } exports.number_of_running_projects_using_license = number_of_running_projects_using_license; async function number_of_projects_with_license_applied(db, license_id) { /* Do a query to count the number of projects that have the given license_id has a key in their site_license field possibly with a trivial value. Basically, this is the number of projects somebody has set to use this license, whether or not they have successfully actually used it. select project_id, site_license, state from projects where state#>>'{state}' in ('running', 'starting') and site_license#>>'{f3942ea1-ff3f-4d9f-937a-c5007babc693}' IS NOT NULL; */ const query = `SELECT COUNT(*) FROM projects WHERE site_license#>>'{${license_id}}' IS NOT NULL`; const x = await db.async_query({ query, timeout_s: TIMEOUT_S }); return parseInt(x.rows[0].count); } exports.number_of_projects_with_license_applied = number_of_projects_with_license_applied; /* Returns information about how licenses are being used across ALL running projects in the system right now. The following query excludes anything with site_license null or {}, due to how sql works: select site_license from projects where state#>>'{state}' in ('running', 'starting') and site_license!='{}'; We then just process the result in Javascript. It would be possible to make a more complicated query that does all the processing in the database, and returns less data as output, but that would be harder for me, so I leave that to others or later (since this query is not likely to be used too much). */ async function site_license_usage_stats(db) { const query = "select site_license from projects where state#>>'{state}' in ('running', 'starting') and site_license!='{}'"; const result = await db.async_query({ query }); const usage = {}; for (let row of result.rows) { for (const license_id in row.site_license) { if ((0, misc_1.len)(row.site_license[license_id]) > 0) { if (usage[license_id] == null) { usage[license_id] = 1; } else { usage[license_id] += 1; } } } } return usage; } exports.site_license_usage_stats = site_license_usage_stats; function query_projects_using_site_license(license_id, cutoff) { const params = []; let query; if (cutoff) { query = `FROM projects, site_license_usage_log WHERE `; query += "projects.project_id = site_license_usage_log.project_id AND "; query += "site_license_usage_log.license_id = $1 AND"; query += "(site_license_usage_log.start >= $2 OR "; query += " site_license_usage_log.stop >= $2 OR "; query += " site_license_usage_log.stop IS NULL)"; params.push(license_id); params.push(cutoff); } else { // easier -- just directly query the projects table. query = ` FROM projects WHERE state#>>'{state}' IN ('running', 'starting') AND ((site_license -> '${license_id}') - 'status') != '{}'::JSONB`; } return { query, params }; } async function projects_using_site_license(db, opts) { const query_fields = process_fields(opts.fields, opts.truncate); const { query, params } = query_projects_using_site_license(opts.license_id, opts.cutoff); const select = `SELECT ${query_fields.join(",")} `; const x = await (0, async_utils_1.callback2)(db._query.bind(db), { query: select + " " + query, limit: opts.limit, params, }); const v = []; for (const row of x.rows) { v.push((0, misc_1.copy_with)(row, opts.fields)); } return v; } exports.projects_using_site_license = projects_using_site_license; function process_fields(fields, truncate) { const v = []; for (let field of fields) { if (truncate && (field == "title" || field == "description")) { field = `left(projects.${field},${truncate}) as ${field}`; } else if (field == "project_id") { field = `distinct(projects.project_id)`; } else { field = `projects.${field}`; } v.push(field); } return v; } async function number_of_projects_using_site_license(db, opts) { const { query, params } = query_projects_using_site_license(opts.license_id, opts.cutoff); const x = await db.async_query({ query: "SELECT COUNT(DISTINCT(projects.project_id))::INT " + query, params, timeout_s: TIMEOUT_S, }); return parseInt(x.rows[0].count); } exports.number_of_projects_using_site_license = number_of_projects_using_site_license; //# sourceMappingURL=analytics.js.map