diff --git a/assets/live-stats.js b/assets/live-stats.js new file mode 100644 index 00000000..573688e5 --- /dev/null +++ b/assets/live-stats.js @@ -0,0 +1,71 @@ +/* + * Latest view counts for the catalog and the resource pages. + * + * The daily traffic workflow keeps a fresh resource-stats.json on the automation/traffic-stats-staging + * branch. Publishing it to master needs a pull request that GitHub Actions cannot open here, so the + * copy on the site is only as fresh as the last merged weekly PR. This script reads the staging copy + * straight from raw.githubusercontent.com, which serves it to any site and caches it for five minutes. + * + * Only the view counts are taken from it. The local file stays the source for everything else + * (upvotes, discussion links), and it is the fallback when the staging copy cannot be read, is + * older than the local file, or was rendered by a different counting method (sources.views). + * Exposes window.FastTrackLiveStats = { url, fetch, pick }. It never throws and never blocks a page. + */ +(function () { + 'use strict'; + + var LIVE_STATS_URL = 'https://raw.githubusercontent.com/microsoft/FastTrack/automation/traffic-stats-staging/resource-stats.json'; + var TIMEOUT_MS = 5000; + + // Resolves with the parsed staging file, or null on any failure or timeout. + function fetchLive() { + return new Promise(function (resolve) { + try { + if (!window.fetch) { resolve(null); return; } + var controller = window.AbortController ? new AbortController() : null; + var timer = setTimeout(function () { + if (controller) controller.abort(); + resolve(null); + }, TIMEOUT_MS); + var options = { credentials: 'omit', referrerPolicy: 'no-referrer' }; + if (controller) options.signal = controller.signal; + window.fetch(LIVE_STATS_URL, options) + .then(function (response) { + if (!response.ok) throw new Error('HTTP ' + response.status); + return response.json(); + }) + .then(function (doc) { clearTimeout(timer); resolve(doc); }) + .catch(function () { clearTimeout(timer); resolve(null); }); + } catch (error) { + resolve(null); + } + }); + } + + // Returns { slug: views } when the staging copy can replace the local view counts, else null. + function pick(local, live) { + try { + if (!local || !live || !live.resources || typeof live.resources !== 'object') return null; + if (!local.sources || !live.sources || !local.sources.views || local.sources.views !== live.sources.views) return null; + var localTime = Date.parse(local.generatedAt); + var liveTime = Date.parse(live.generatedAt); + if (!(liveTime >= localTime)) return null; + + var views = {}; + var found = false; + Object.keys(live.resources).forEach(function (slug) { + var entry = live.resources[slug]; + var count = entry ? Number(entry.views) : NaN; + if (isFinite(count) && count >= 0) { + views[slug] = Math.round(count); + found = true; + } + }); + return found ? views : null; + } catch (error) { + return null; + } + } + + window.FastTrackLiveStats = { url: LIVE_STATS_URL, fetch: fetchLive, pick: pick }; +})(); diff --git a/assets/privacy-notice.js b/assets/privacy-notice.js index 163299e6..25267fa1 100644 --- a/assets/privacy-notice.js +++ b/assets/privacy-notice.js @@ -95,6 +95,7 @@ '', '', diff --git a/assets/resource-page.js b/assets/resource-page.js index 0f51c0ea..fd3554d5 100644 --- a/assets/resource-page.js +++ b/assets/resource-page.js @@ -53,17 +53,25 @@ } // Views come from the published stats, so the page needs no rebuild when the counts change. The row - // stays hidden if the file cannot be read. Keep VIEWS_TIP in step with the catalog (index.html). + // stays hidden if the file cannot be read. The latest counts are taken from the staged stats when + // they can be read in time (assets/live-stats.js). Keep VIEWS_TIP in step with the catalog (index.html). var VIEWS_TIP = "Catalog page views recorded by Microsoft Clarity since Jul 2026, plus views of the resource's GitHub folder from saved snapshots (Apr\u2013Jun and 14\u201327 Oct 2026). Some days were never captured, so this is a minimum."; + var LIVE_STATS_WAIT_MS = 1500; var viewsRow = document.getElementById('viewsRow'); var viewsValue = document.getElementById('viewsValue'); if (viewsRow && viewsValue && slug && script && script.src && window.fetch) { - fetch(new URL('../resource-stats.json', script.src).href, { cache: 'no-cache' }) - .then(function (response) { if (!response.ok) throw new Error('HTTP ' + response.status); return response.json(); }) - .then(function (data) { + var liveStats = window.FastTrackLiveStats ? window.FastTrackLiveStats.fetch() : Promise.resolve(null); + var liveOrTimeout = Promise.race([liveStats, new Promise(function (resolve) { setTimeout(function () { resolve(null); }, LIVE_STATS_WAIT_MS); })]); + var localStats = fetch(new URL('../resource-stats.json', script.src).href, { cache: 'no-cache' }) + .then(function (response) { if (!response.ok) throw new Error('HTTP ' + response.status); return response.json(); }); + Promise.all([localStats, liveOrTimeout]) + .then(function (results) { + var data = results[0]; if (!data || !data.resources || typeof data.resources !== 'object') return; var entry = data.resources[slug]; var views = entry ? Number(entry.views) : 0; + var fresh = window.FastTrackLiveStats ? window.FastTrackLiveStats.pick(data, results[1]) : null; + if (fresh && Object.prototype.hasOwnProperty.call(fresh, slug)) views = fresh[slug]; if (!isFinite(views) || views < 0) views = 0; var short = views >= 10000 ? (views / 1000).toFixed(0) + 'k' : views >= 1000 ? (views / 1000).toFixed(1) + 'k' : String(views); viewsValue.textContent = short; diff --git a/index.html b/index.html index 64c5ddae..6ed1756e 100644 --- a/index.html +++ b/index.html @@ -1641,6 +1641,7 @@

Related resources

+ + diff --git a/r/agent-365-enablement-checklist/index.html b/r/agent-365-enablement-checklist/index.html index 32f33840..91a2cb7c 100644 --- a/r/agent-365-enablement-checklist/index.html +++ b/r/agent-365-enablement-checklist/index.html @@ -103,6 +103,7 @@

Related resources

+ diff --git a/r/agent-365-lifecycle-atlas/index.html b/r/agent-365-lifecycle-atlas/index.html index c1d30f27..58ec8644 100644 --- a/r/agent-365-lifecycle-atlas/index.html +++ b/r/agent-365-lifecycle-atlas/index.html @@ -102,6 +102,7 @@

Related resources

+ diff --git a/r/agent-brainstorming-template/index.html b/r/agent-brainstorming-template/index.html index db3b44a4..202b6967 100644 --- a/r/agent-brainstorming-template/index.html +++ b/r/agent-brainstorming-template/index.html @@ -99,6 +99,7 @@

Related resources

+ diff --git a/r/agents-cost-calculator/index.html b/r/agents-cost-calculator/index.html index f279d5ba..57280fda 100644 --- a/r/agents-cost-calculator/index.html +++ b/r/agents-cost-calculator/index.html @@ -100,6 +100,7 @@

Related resources

+ diff --git a/r/ai-council/index.html b/r/ai-council/index.html index dc348088..7580d8f5 100644 --- a/r/ai-council/index.html +++ b/r/ai-council/index.html @@ -101,6 +101,7 @@

Related resources

+ diff --git a/r/analyze-sharepointrisk/index.html b/r/analyze-sharepointrisk/index.html index edb8d801..73f9f0aa 100644 --- a/r/analyze-sharepointrisk/index.html +++ b/r/analyze-sharepointrisk/index.html @@ -100,6 +100,7 @@

Related resources

+ diff --git a/r/autoreply-agent/index.html b/r/autoreply-agent/index.html index a488a91a..d4bd926f 100644 --- a/r/autoreply-agent/index.html +++ b/r/autoreply-agent/index.html @@ -98,6 +98,7 @@

Related resources

+ diff --git a/r/comparedocs/index.html b/r/comparedocs/index.html index ad0a3162..2cda027d 100644 --- a/r/comparedocs/index.html +++ b/r/comparedocs/index.html @@ -101,6 +101,7 @@

Related resources

+ diff --git a/r/copilot-agents-guide/index.html b/r/copilot-agents-guide/index.html index 8f5a8ae5..10d6cddf 100644 --- a/r/copilot-agents-guide/index.html +++ b/r/copilot-agents-guide/index.html @@ -100,6 +100,7 @@

Related resources

+ diff --git a/r/copilot-analytics-reports-navigator/index.html b/r/copilot-analytics-reports-navigator/index.html index 4a5baa6a..40e6dc30 100644 --- a/r/copilot-analytics-reports-navigator/index.html +++ b/r/copilot-analytics-reports-navigator/index.html @@ -103,6 +103,7 @@

Related resources

+ diff --git a/r/copilot-interaction-report/index.html b/r/copilot-interaction-report/index.html index 2b7c4017..d423da61 100644 --- a/r/copilot-interaction-report/index.html +++ b/r/copilot-interaction-report/index.html @@ -98,6 +98,7 @@

Related resources

+ diff --git a/r/copilot-studio-workflow/index.html b/r/copilot-studio-workflow/index.html index a400f572..ad43ea78 100644 --- a/r/copilot-studio-workflow/index.html +++ b/r/copilot-studio-workflow/index.html @@ -100,6 +100,7 @@

Related resources

+ diff --git a/r/create-engagecommunities/index.html b/r/create-engagecommunities/index.html index 747a9817..9dc5031c 100644 --- a/r/create-engagecommunities/index.html +++ b/r/create-engagecommunities/index.html @@ -100,6 +100,7 @@

Related resources

+ diff --git a/r/deepresearch/index.html b/r/deepresearch/index.html index ef314f19..9d812324 100644 --- a/r/deepresearch/index.html +++ b/r/deepresearch/index.html @@ -101,6 +101,7 @@

Related resources

+ diff --git a/r/delete-allcommunityposts/index.html b/r/delete-allcommunityposts/index.html index 06ed7250..7ac094c6 100644 --- a/r/delete-allcommunityposts/index.html +++ b/r/delete-allcommunityposts/index.html @@ -100,6 +100,7 @@

Related resources

+ diff --git a/r/delete-engagegroups/index.html b/r/delete-engagegroups/index.html index fc1b51a6..ed02036c 100644 --- a/r/delete-engagegroups/index.html +++ b/r/delete-engagegroups/index.html @@ -100,6 +100,7 @@

Related resources

+ diff --git a/r/delete-engageusers/index.html b/r/delete-engageusers/index.html index baa9ec6a..96bb55d2 100644 --- a/r/delete-engageusers/index.html +++ b/r/delete-engageusers/index.html @@ -100,6 +100,7 @@

Related resources

+ diff --git a/r/disable-insightsheadspace/index.html b/r/disable-insightsheadspace/index.html index 78fb0fc1..91f93b95 100644 --- a/r/disable-insightsheadspace/index.html +++ b/r/disable-insightsheadspace/index.html @@ -102,6 +102,7 @@

Related resources

+ diff --git a/r/ess-servicenow-itsm-get-request-details/index.html b/r/ess-servicenow-itsm-get-request-details/index.html index f6bb7933..9a25e5b2 100644 --- a/r/ess-servicenow-itsm-get-request-details/index.html +++ b/r/ess-servicenow-itsm-get-request-details/index.html @@ -101,6 +101,7 @@

Related resources

+ diff --git a/r/ess-servicenow-itsm-get-user-requests/index.html b/r/ess-servicenow-itsm-get-user-requests/index.html index 6f628fa6..55df9af6 100644 --- a/r/ess-servicenow-itsm-get-user-requests/index.html +++ b/r/ess-servicenow-itsm-get-user-requests/index.html @@ -100,6 +100,7 @@

Related resources

+ diff --git a/r/export-copilotinteractions/index.html b/r/export-copilotinteractions/index.html index 6f5aa0c7..e9b72423 100644 --- a/r/export-copilotinteractions/index.html +++ b/r/export-copilotinteractions/index.html @@ -101,6 +101,7 @@

Related resources

+ diff --git a/r/export-engagefiles/index.html b/r/export-engagefiles/index.html index cb4d5049..5c23647b 100644 --- a/r/export-engagefiles/index.html +++ b/r/export-engagefiles/index.html @@ -100,6 +100,7 @@

Related resources

+ diff --git a/r/export-engagenetworkdata/index.html b/r/export-engagenetworkdata/index.html index a6b4680c..b2a20ec4 100644 --- a/r/export-engagenetworkdata/index.html +++ b/r/export-engagenetworkdata/index.html @@ -100,6 +100,7 @@

Related resources

+ diff --git a/r/export-m365copilotreports/index.html b/r/export-m365copilotreports/index.html index 4b26815a..7af7a80e 100644 --- a/r/export-m365copilotreports/index.html +++ b/r/export-m365copilotreports/index.html @@ -99,6 +99,7 @@

Related resources

+ diff --git a/r/export-simpleplacesvisual/index.html b/r/export-simpleplacesvisual/index.html index c4c67419..7964994a 100644 --- a/r/export-simpleplacesvisual/index.html +++ b/r/export-simpleplacesvisual/index.html @@ -100,6 +100,7 @@

Related resources

+ diff --git a/r/find-mailboxdelegates/index.html b/r/find-mailboxdelegates/index.html index 4c7498d0..e2534f37 100644 --- a/r/find-mailboxdelegates/index.html +++ b/r/find-mailboxdelegates/index.html @@ -100,6 +100,7 @@

Related resources

+ diff --git a/r/get-aadadminrolemembers/index.html b/r/get-aadadminrolemembers/index.html index 709b2eed..4b956230 100644 --- a/r/get-aadadminrolemembers/index.html +++ b/r/get-aadadminrolemembers/index.html @@ -102,6 +102,7 @@

Related resources

+ diff --git a/r/get-allinsightslicensedusers/index.html b/r/get-allinsightslicensedusers/index.html index 3cc2b79d..604d20ea 100644 --- a/r/get-allinsightslicensedusers/index.html +++ b/r/get-allinsightslicensedusers/index.html @@ -100,6 +100,7 @@

Related resources

+ diff --git a/r/get-engagegroupinfo/index.html b/r/get-engagegroupinfo/index.html index defb9688..c970c92f 100644 --- a/r/get-engagegroupinfo/index.html +++ b/r/get-engagegroupinfo/index.html @@ -100,6 +100,7 @@

Related resources

+ diff --git a/r/get-groupsmembersmanagers/index.html b/r/get-groupsmembersmanagers/index.html index caf04d62..900a4c55 100644 --- a/r/get-groupsmembersmanagers/index.html +++ b/r/get-groupsmembersmanagers/index.html @@ -100,6 +100,7 @@

Related resources

+ diff --git a/r/get-licenseusage/index.html b/r/get-licenseusage/index.html index 9cc6eb05..0d20d13e 100644 --- a/r/get-licenseusage/index.html +++ b/r/get-licenseusage/index.html @@ -103,6 +103,7 @@

Related resources

+ diff --git a/r/get-m365copilotreadiness/index.html b/r/get-m365copilotreadiness/index.html index 4eba0054..2dcd4f2f 100644 --- a/r/get-m365copilotreadiness/index.html +++ b/r/get-m365copilotreadiness/index.html @@ -100,6 +100,7 @@

Related resources

+ diff --git a/r/get-recurringteamsmeetings/index.html b/r/get-recurringteamsmeetings/index.html index 9a58104a..4a4628be 100644 --- a/r/get-recurringteamsmeetings/index.html +++ b/r/get-recurringteamsmeetings/index.html @@ -99,6 +99,7 @@

Related resources

+ diff --git a/r/get-siteinventory/index.html b/r/get-siteinventory/index.html index 10d3b2a5..92cb1657 100644 --- a/r/get-siteinventory/index.html +++ b/r/get-siteinventory/index.html @@ -102,6 +102,7 @@

Related resources

+ diff --git a/r/get-teamsuseractivityreport/index.html b/r/get-teamsuseractivityreport/index.html index 94c29459..2309cd96 100644 --- a/r/get-teamsuseractivityreport/index.html +++ b/r/get-teamsuseractivityreport/index.html @@ -98,6 +98,7 @@

Related resources

+ diff --git a/r/get-yammersitesize/index.html b/r/get-yammersitesize/index.html index 5c7b9432..4084f7a0 100644 --- a/r/get-yammersitesize/index.html +++ b/r/get-yammersitesize/index.html @@ -100,6 +100,7 @@

Related resources

+ diff --git a/r/managersimulator/index.html b/r/managersimulator/index.html index 92b8a9c0..cc5084e1 100644 --- a/r/managersimulator/index.html +++ b/r/managersimulator/index.html @@ -101,6 +101,7 @@

Related resources

+ diff --git a/r/microsoft-agent-365-customer-implementation-guide/index.html b/r/microsoft-agent-365-customer-implementation-guide/index.html index 3089a56d..856a0c64 100644 --- a/r/microsoft-agent-365-customer-implementation-guide/index.html +++ b/r/microsoft-agent-365-customer-implementation-guide/index.html @@ -103,6 +103,7 @@

Related resources

+ diff --git a/r/microsoft-agent-365-technical-advisory/index.html b/r/microsoft-agent-365-technical-advisory/index.html index 0aa79bcc..1895eebf 100644 --- a/r/microsoft-agent-365-technical-advisory/index.html +++ b/r/microsoft-agent-365-technical-advisory/index.html @@ -103,6 +103,7 @@

Related resources

+ diff --git a/r/migrate-copilotstudioagents/index.html b/r/migrate-copilotstudioagents/index.html index 7d51915f..e6ee68bb 100644 --- a/r/migrate-copilotstudioagents/index.html +++ b/r/migrate-copilotstudioagents/index.html @@ -98,6 +98,7 @@

Related resources

+ diff --git a/r/omniagent/index.html b/r/omniagent/index.html index adf1661e..3b3cda45 100644 --- a/r/omniagent/index.html +++ b/r/omniagent/index.html @@ -101,6 +101,7 @@

Related resources

+ diff --git a/r/powerclaw-agent/index.html b/r/powerclaw-agent/index.html index 6dfa46cb..56a6e7cc 100644 --- a/r/powerclaw-agent/index.html +++ b/r/powerclaw-agent/index.html @@ -99,6 +99,7 @@

Related resources

+ diff --git a/r/preflight-onedrive/index.html b/r/preflight-onedrive/index.html index 79da75c0..48c0201f 100644 --- a/r/preflight-onedrive/index.html +++ b/r/preflight-onedrive/index.html @@ -100,6 +100,7 @@

Related resources

+ diff --git a/r/productquote-agent/index.html b/r/productquote-agent/index.html index 6f37507d..bc4703fd 100644 --- a/r/productquote-agent/index.html +++ b/r/productquote-agent/index.html @@ -98,6 +98,7 @@

Related resources

+ diff --git a/r/purview-audit-copilot-report/index.html b/r/purview-audit-copilot-report/index.html index 41304519..96b7e429 100644 --- a/r/purview-audit-copilot-report/index.html +++ b/r/purview-audit-copilot-report/index.html @@ -99,6 +99,7 @@

Related resources

+ diff --git a/r/reasoningagent/index.html b/r/reasoningagent/index.html index 7e292e84..086d1fc6 100644 --- a/r/reasoningagent/index.html +++ b/r/reasoningagent/index.html @@ -101,6 +101,7 @@

Related resources

+ diff --git a/r/researcher-automation-opportunities/index.html b/r/researcher-automation-opportunities/index.html index 1bb9e2e8..b3fdda6b 100644 --- a/r/researcher-automation-opportunities/index.html +++ b/r/researcher-automation-opportunities/index.html @@ -100,6 +100,7 @@

Related resources

+ diff --git a/r/simplegraph/index.html b/r/simplegraph/index.html index cee0e707..a7e50446 100644 --- a/r/simplegraph/index.html +++ b/r/simplegraph/index.html @@ -101,6 +101,7 @@

Related resources

+ diff --git a/r/teams-audio-conferencing-snippets/index.html b/r/teams-audio-conferencing-snippets/index.html index ec43cdc8..023a86ff 100644 --- a/r/teams-audio-conferencing-snippets/index.html +++ b/r/teams-audio-conferencing-snippets/index.html @@ -107,6 +107,7 @@

Related resources

+ diff --git a/r/teams-phone-system-snippets/index.html b/r/teams-phone-system-snippets/index.html index fc0f9887..08aac948 100644 --- a/r/teams-phone-system-snippets/index.html +++ b/r/teams-phone-system-snippets/index.html @@ -106,6 +106,7 @@

Related resources

+ diff --git a/r/teams-phone-user-migration/index.html b/r/teams-phone-user-migration/index.html index 68258a46..89573c18 100644 --- a/r/teams-phone-user-migration/index.html +++ b/r/teams-phone-user-migration/index.html @@ -98,6 +98,7 @@

Related resources

+ diff --git a/r/teams-rooms-snippets/index.html b/r/teams-rooms-snippets/index.html index 51a74c42..cc1f0f40 100644 --- a/r/teams-rooms-snippets/index.html +++ b/r/teams-rooms-snippets/index.html @@ -104,6 +104,7 @@

Related resources

+ diff --git a/r/teams-upgrade-snippets/index.html b/r/teams-upgrade-snippets/index.html index 20f6c2fc..17a233a0 100644 --- a/r/teams-upgrade-snippets/index.html +++ b/r/teams-upgrade-snippets/index.html @@ -106,6 +106,7 @@

Related resources

+ diff --git a/r/update-vivaengagelicensingtoggle/index.html b/r/update-vivaengagelicensingtoggle/index.html index f62147c3..7cdb682b 100644 --- a/r/update-vivaengagelicensingtoggle/index.html +++ b/r/update-vivaengagelicensingtoggle/index.html @@ -101,6 +101,7 @@

Related resources

+ diff --git a/r/vibewriting-agent/index.html b/r/vibewriting-agent/index.html index 65329293..fc35bf4b 100644 --- a/r/vibewriting-agent/index.html +++ b/r/vibewriting-agent/index.html @@ -98,6 +98,7 @@

Related resources

+ diff --git a/r/viva-engage-community-creation-app/index.html b/r/viva-engage-community-creation-app/index.html index 91c98b6b..e423ab14 100644 --- a/r/viva-engage-community-creation-app/index.html +++ b/r/viva-engage-community-creation-app/index.html @@ -105,6 +105,7 @@

Related resources

+ diff --git a/r/viva-insights-copilot-dashboard/index.html b/r/viva-insights-copilot-dashboard/index.html index 51fd689f..201cfa71 100644 --- a/r/viva-insights-copilot-dashboard/index.html +++ b/r/viva-insights-copilot-dashboard/index.html @@ -98,6 +98,7 @@

Related resources

+ diff --git a/tools/catalog-build/site-pages.js b/tools/catalog-build/site-pages.js index 7436cd5f..f46e7d5f 100644 --- a/tools/catalog-build/site-pages.js +++ b/tools/catalog-build/site-pages.js @@ -323,6 +323,7 @@ ${archived ? '\n

This resource is archived and kept for ref ${siteFooter('../../')} + diff --git a/tools/catalog-build/test-site-output.js b/tools/catalog-build/test-site-output.js index 2d90165f..0bee2dea 100644 --- a/tools/catalog-build/test-site-output.js +++ b/tools/catalog-build/test-site-output.js @@ -1,6 +1,7 @@ import { readFileSync } from 'node:fs'; import { dirname, join } from 'node:path'; import { fileURLToPath } from 'node:url'; +import vm from 'node:vm'; import { renderDownloadPage, renderFeed, renderMarkdown, renderResourcePage, renderSitemap } from './site-pages.js'; // Guards the static resource pages (escaping, status handling, sitemap/feed) and the @@ -94,8 +95,10 @@ const footerPages = { 'the cost calculator': [costToolSource, '../../assets/'] }; for (const [name, [source, assets]] of Object.entries(footerPages)) { - assert(source.includes('https://www.microsoft.com/en-us/legal/terms-of-use'), `${name} must link the Microsoft Terms and Conditions.`); - assert(source.includes('https://privacy.microsoft.com/en-us/privacystatement'), `${name} must link the Microsoft Privacy Statement.`); + // Exact-match lookups: a substring test on a URL says nothing about which host it names. + const hrefs = new Set([...source.matchAll(/href="([^"]*)"/g)].map(match => match[1])); + assert(hrefs.has('https://www.microsoft.com/en-us/legal/terms-of-use'), `${name} must link the Microsoft Terms and Conditions.`); + assert(hrefs.has('https://privacy.microsoft.com/en-us/privacystatement'), `${name} must link the Microsoft Privacy Statement.`); assert(/