Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@ test('can fetch and parse system information', async () =>
isa_number: '2014427',
schema_version: '2.9',
specify6_version: '6.8.03',
stats_url: 'https://stats.specifycloud.org/capture',
stats_url: 'https://sp7-stats.specifycloud.org/capture',
version: '(debug)',
}));
89 changes: 79 additions & 10 deletions specifyweb/frontend/js_src/lib/components/InitialContext/stats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,72 @@ type StatsCounts = {
readonly Specifyuser: number;
};

const stats2RequestIntervalMs = 24 * 60 * 60 * 1000;
const stats2RequestKeyPrefix = 'specify7-stats2-last-request';
const stats2RequestTimeoutMs = 5000;
const stats2LambdaFunctionUrl = 'https://stats-2.specifycloud.org';

function buildStatsLambdaUrl(base: string | null | undefined): string | null {
if (!base) return null;
let u = base.trim();

if (!/^https?:\/\//i.test(u)) u = `https://${u}`;
return u;
}

function buildStats2RequestKey(lambdaUrl: string, collectionGuid: string): string {
return `${stats2RequestKeyPrefix}:${collectionGuid}:${lambdaUrl}`;
}

function shouldSendStats2Request(storageKey: string, now = Date.now()): boolean {
if (globalThis.localStorage === undefined) return true;

const hasRoute = /\/(prod|default)\/[^\s/]+/.test(u);
if (!hasRoute) {
const stage = 'prod';
const route = 'AggrgatedSp7Stats';
u = `${u.replace(/\/$/, '')}/${stage}/${route}`;
try {
const previousRequestAt = globalThis.localStorage.getItem(storageKey);
if (previousRequestAt === null) return true;
const parsed = Number(previousRequestAt);
if (!Number.isFinite(parsed)) return true;
if (parsed > now) return true;
return now - parsed >= stats2RequestIntervalMs;
} catch {
return true;
}
return u;
}

function recordStats2Request(storageKey: string, now = Date.now()): void {
if (globalThis.localStorage === undefined) return;

try {
globalThis.localStorage.setItem(storageKey, `${now}`);
} catch {}
}

function shouldSkipLambdaStatsRequest(hostname: string): boolean {
const normalizedHostname = hostname.toLowerCase();
return (
normalizedHostname === 'localhost' ||
normalizedHostname.endsWith('.test.specifysystems.org')
);
}

function pingInBackground(url: string): void {
const controller =
typeof globalThis.AbortController === 'function'
? new globalThis.AbortController()
: undefined;
const timeoutId =
controller === undefined
? undefined
: globalThis.setTimeout(() => controller.abort(), stats2RequestTimeoutMs);

void ping(url, {
errorMode: 'silent',
...(controller === undefined ? {} : { signal: controller.signal }),
})
.catch(softFail)
.finally(() => {
if (timeoutId !== undefined) globalThis.clearTimeout(timeoutId);
});
}

export const fetchContext = fetchSystemInfo.then(async (systemInfo) => {
Expand Down Expand Up @@ -72,10 +125,26 @@ export const fetchContext = fetchSystemInfo.then(async (systemInfo) => {
{ errorMode: 'silent' }
).catch(softFail);

const lambdaUrl = buildStatsLambdaUrl(systemInfo.stats_2_url);
const lambdaUrl = buildStatsLambdaUrl(stats2LambdaFunctionUrl);
if (lambdaUrl) {
await ping(formatUrl(lambdaUrl, parameters, false), {
errorMode: 'silent',
}).catch(softFail);
if (shouldSkipLambdaStatsRequest(globalThis.location.hostname)) {
return;
}
const storageKey = buildStats2RequestKey(
lambdaUrl,
`${systemInfo.collection_guid}`
);
if (!shouldSendStats2Request(storageKey)) {
return;
}
recordStats2Request(storageKey);
pingInBackground(formatUrl(lambdaUrl, parameters, false));
}
});

export const exportsForTests = {
buildStats2RequestKey,
shouldSendStats2Request,
recordStats2Request,
shouldSkipLambdaStatsRequest,
};
2 changes: 1 addition & 1 deletion specifyweb/frontend/js_src/lib/tests/ajax/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ export async function ajaxMock<RESPONSE_TYPE>(
expectedErrors = [],
}: Parameters<typeof ajax>[1]
): Promise<AjaxResponseObject<RESPONSE_TYPE>> {
if (url.startsWith('https://stats.specifycloud.org/capture'))
if (url.startsWith('https://sp7-stats.specifycloud.org/capture'))
return formatResponse('', accept, expectedErrors, undefined);

const parsedUrl = new URL(url, globalThis?.location.origin);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"specify6_version": "6.8.03",
"database_version": "6.8.03",
"schema_version": "2.9",
"stats_url": "https://stats.specifycloud.org/capture",
"stats_url": "https://sp7-stats.specifycloud.org/capture",
"database": "specify",
"institution": "University of Kansas Biodiversity Institute",
"institution_guid": "77ff1bff-af23-4647-b5d1-9d3c414fd003",
Expand Down
5 changes: 2 additions & 3 deletions specifyweb/settings/specify_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,9 +122,8 @@

# Usage stats are transmitted to the following address.
# Set to None to disable.
STATS_URL = "https://stats.specifycloud.org/capture"
# STATS_2_URL = "https://stats-2.specifycloud.org/prod/AggrgatedSp7Stats"
STATS_2_URL = "pj9lpoo1pc.execute-api.us-east-1.amazonaws.com"
STATS_URL = "https://sp7-stats.specifycloud.org/capture"
STATS_2_URL = "https://stats-2.specifycloud.org"

# Workbench uploader log directory.
# Must exist and be writeable by the web server process.
Expand Down