Skip to content

Commit

Permalink
Usage metrics for v2023.4 (#964)
Browse files Browse the repository at this point in the history
* Stubs for 2023.4 analytics

* Updating analytics queries and tests

* Change part of unprocessed query

* Minor changes to queries

* Check sso for user metrics
  • Loading branch information
ktuite authored Sep 12, 2023
1 parent e50fb0d commit 2be613d
Show file tree
Hide file tree
Showing 4 changed files with 297 additions and 21 deletions.
2 changes: 1 addition & 1 deletion config/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"analytics": {
"url": "https://data.getodk.cloud/v1/key/eOZ7S4bzyUW!g1PF6dIXsnSqktRuewzLTpmc6ipBtRq$LDfIMTUKswCexvE0UwJ9/projects/1/submission",
"formId": "odk-analytics",
"version": "2023.05.22.01"
"version": "v2023.4.0_1"
}
}
},
Expand Down
9 changes: 8 additions & 1 deletion lib/data/analytics.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,14 @@ const metricsTemplate = {
"num_unique_viewers": {},
"num_unique_collectors": {},
"database_size": {},
"uses_external_db": 0
"uses_external_db": 0,
"sso_enabled": 0,
"num_client_audit_attachments": 0,
"num_client_audit_attachments_failures": 0,
"num_client_audit_rows": 0,
"num_audits_failed": 0,
"num_audits_failed5": 0,
"num_audits_unprocessed": 0
},
"projects": [
{
Expand Down
61 changes: 55 additions & 6 deletions lib/model/query/analytics.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const config = require('config');
const { sql } = require('slonik');
const { clone } = require('ramda');
const { metricsTemplate } = require('../../data/analytics');
const oidc = require('../../util/oidc');

const DAY_RANGE = 45;
const _cutoffDate = sql`current_date - cast(${DAY_RANGE} as int)`;
Expand Down Expand Up @@ -38,12 +39,21 @@ const databaseExternal = (dbHost) => () =>

// GENERAL
const auditLogs = () => ({ one }) => one(sql`
select count(*) as total,
SELECT
-- Total count of audit logs
count(*) AS total,
-- Count of recent audit logs only
count(CASE WHEN "loggedAt" >= ${_cutoffDate}
THEN 1
ELSE null
END) AS "recent"
from audits`);
END) AS recent,
-- Any failure, even if ultimately processed
count(*) FILTER (WHERE failures > 0) AS any_failure,
-- Completely failed
count(*) FILTER (WHERE processed IS NULL AND failures >= 5) AS failed5,
-- Unexpectedly unprocessed
count(*) FILTER (WHERE processed IS NULL AND failures < 5 AND "loggedAt" < now() - INTERVAL '1 day') AS unprocessed
FROM audits`);

const countAdmins = () => ({ one }) => one(sql`
select count(u."actorId") as total, count(activeUsers."actorId") as recent
Expand Down Expand Up @@ -106,6 +116,28 @@ where r."system" = 'formfill'`);
const databaseSize = () => ({ one }) => one(sql`
select pg_database_size(current_database()) as database_size`);

const countClientAuditAttachments = () => ({ oneFirst }) => oneFirst(sql`
SELECT count(*)
FROM submission_attachments
WHERE "isClientAudit" AND "blobId" IS NOT NULL`);

const countClientAuditProcessingFailed = () => ({ oneFirst }) => oneFirst(sql`
SELECT count(*)
FROM audits
JOIN submission_attachments ON
submission_attachments."submissionDefId" = (audits.details->'submissionDefId')::INTEGER AND
submission_attachments.name = audits.details->>'name'
WHERE
audits.action = 'submission.attachment.update' AND
audits.processed IS NULL AND
audits.failures >= 5 AND
submission_attachments."isClientAudit"
-- Intentionally not filtering on submission_attachments."blobId"
-- on the off-chance that a failing attachment was cleared.`);

const countClientAuditRows = () => ({ oneFirst }) => oneFirst(sql`
SELECT count(*) FROM client_audits`);

// PER PROJECT
// Users
const countUsersPerRole = () => ({ all }) => all(sql`
Expand Down Expand Up @@ -277,7 +309,7 @@ inner join (
group by (xml_form_id, proj_id)
) as deleted_form_ids
on forms."xmlFormId" = deleted_form_ids."xml_form_id" and
forms."projectId" = deleted_form_ids."proj_id"
forms."projectId" = deleted_form_ids."proj_id"
where forms."deletedAt" is null
group by forms."projectId"`);

Expand Down Expand Up @@ -383,7 +415,7 @@ FROM datasets ds
JOIN forms f ON f.id = fa."formId"
) ON fa."datasetId" = ds.id
LEFT JOIN (
SELECT
SELECT
COUNT (a.details -> 'submissionId'::TEXT) total,
SUM (CASE WHEN a."loggedAt" >= current_date - cast(${DAY_RANGE} as int) THEN 1 ELSE 0 END) recent,
dfd."datasetId"
Expand Down Expand Up @@ -575,9 +607,14 @@ const previewMetrics = () => (({ Analytics }) => Promise.all([
Analytics.countUniqueManagers(),
Analytics.countUniqueViewers(),
Analytics.countUniqueDataCollectors(),
Analytics.countClientAuditAttachments(),
Analytics.countClientAuditProcessingFailed(),
Analytics.countClientAuditRows(),
Analytics.projectMetrics()
]).then(([db, encrypt, bigForm, admins, audits,
archived, managers, viewers, collectors, projMetrics]) => {
archived, managers, viewers, collectors,
caAttachments, caFailures, caRows,
projMetrics]) => {
const metrics = clone(metricsTemplate);
// system
for (const [key, value] of Object.entries(db))
Expand All @@ -600,6 +637,15 @@ const previewMetrics = () => (({ Analytics }) => Promise.all([

metrics.system.uses_external_db = Analytics.databaseExternal(config.get('default.database.host'));

// 2023.4.0 metrics
metrics.system.num_client_audit_attachments = caAttachments;
metrics.system.num_client_audit_attachments_failures = caFailures;
metrics.system.num_client_audit_rows = caRows;
metrics.system.num_audits_failed = audits.any_failure;
metrics.system.num_audits_failed5 = audits.failed5;
metrics.system.num_audits_unprocessed = audits.unprocessed;
metrics.system.sso_enabled = oidc.isEnabled() ? 1 : 0;

return metrics;
}));

Expand All @@ -626,6 +672,9 @@ module.exports = {
countAdmins,
countAppUsers,
countDeviceIds,
countClientAuditAttachments,
countClientAuditProcessingFailed,
countClientAuditRows,
countForms,
countFormsEncrypted,
countFormFieldTypes,
Expand Down
Loading

0 comments on commit 2be613d

Please sign in to comment.