Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
fde9743
Upgrade Dynatrace RUM to 1.329+ with dual-flag version control
redbugz May 26, 2026
605d441
Add Dynatrace RUM automation and update documentation
redbugz May 26, 2026
b44b8f7
Add RUM loading mechanism telemetry for performance comparison
redbugz Jun 1, 2026
212832d
Fix Dynatrace beacon reporting by preserving data-dtconfig attribute
redbugz Jun 1, 2026
0bfcd58
Bump react-scripts to 8.17.0-alpha.2
redbugz Jun 1, 2026
6c51091
Use inline script approach for new RUM with proper JSON config
redbugz Jun 1, 2026
2d6cf85
Update package-lock.json and fetch script permissions for alpha.2 rel…
redbugz Jun 1, 2026
30f35bf
Bump react-scripts to 8.17.0-alpha.3
redbugz Jun 1, 2026
f02d9c4
Fix RUM loading mechanism telemetry timing
redbugz Jun 1, 2026
bc00f0b
Bump react-scripts to 8.17.0-alpha.4
redbugz Jun 1, 2026
29ce2e3
Add async attribute to old RUM asyncCS-script mechanism
redbugz Jun 1, 2026
bac37cf
Revert "Add async attribute to old RUM asyncCS-script mechanism"
redbugz Jun 1, 2026
5b75155
Fix RUM loading mechanisms and fetch inline code correctly
redbugz Jun 1, 2026
341fb90
Call all three Dynatrace RUM API endpoints
redbugz Jun 1, 2026
83ddfa9
Improve fetch script with refactored helpers and CDN config export
redbugz Jun 1, 2026
6b34907
Add locals.dynatrace fallback pattern for progressive enhancement
redbugz Jun 1, 2026
5a922ea
Add S3 publishing via AWS CLI for dynatrace-rum-config.json
redbugz Jun 2, 2026
f81de96
Remove 'New' postfix from property names for cleaner API
redbugz Jun 2, 2026
0efa8f8
Make S3 config file publicly readable with --acl public-read
redbugz Jun 2, 2026
c292c3d
Fix EJS parsing error by using string concatenation in include statem…
redbugz Jun 9, 2026
1f43163
Add hyphen to include() tags for proper EJS output
redbugz Jun 10, 2026
49d8e8e
Use standard EJS include syntax without hyphen
redbugz Jun 10, 2026
89cd853
Compute dynamic paths before include to support variable resolution
redbugz Jun 10, 2026
ee7c0df
Fix EJS parsing error by using string concatenation in include statem…
redbugz Jun 10, 2026
217b706
Add logging to dynatrace.ejs to debug code path execution
redbugz Jun 10, 2026
7161f80
Remove optional chaining syntax unsupported by EJS parser
redbugz Jun 10, 2026
538d7be
Load new Dynatrace RUM via script tags, never inline
redbugz Jul 13, 2026
f8e89ff
Bump @fs/react-scripts to 8.17.0-alpha.13
redbugz Jul 14, 2026
7bc09fe
Merge remote-tracking branch 'origin/frontierMaster' into dynatrace-r…
redbugz Jul 14, 2026
1b901b6
Bump @fs/react-scripts to 8.17.0-alpha.14
redbugz Jul 14, 2026
9f0b2c5
fetch-dynatrace-scripts: AWS preflight, agent-version output, keychai…
redbugz Jul 29, 2026
38369fa
Update Dynatrace RUM to agent 10341260622154106
redbugz Jul 29, 2026
6a58cf0
prepare for PR
redbugz Jul 29, 2026
71c225a
Remove debug console.log from dynatrace.ejs; finalize 8.17.0
redbugz Jul 29, 2026
7468a68
Address Copilot review on PR #425
redbugz Jul 30, 2026
b6f4550
refactor(rum): New RUM baseline, single loading mechanism
redbugz Aug 8, 2026
0cf9e0e
chore(rum): move maintainer tooling out of the published package
redbugz Aug 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions CHANGELOG-FRONTIER.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
## 8.17.0

- Migrate to the Dynatrace New RUM Experience (RUM on Grail) and simplify agent loading to a single mechanism
- **Default:** combined `_complete.js` from the Dynatrace global CDN, loaded `async`. It is the only mechanism that tracks tenant configuration without a react-scripts release — two of the previous three treatments had been serving a RUM agent artifact dated 2022-11-04 to production
- **Removed the `frontier_snow_dynatraceNewRUM` flag.** It never controlled New RUM: both its branches emitted the same URL and it only toggled `async`. New RUM enablement is the tenant's `enabledOnGrail` setting, toggled per application in the Dynatrace UI with no deploy
- `frontier_snow_dynatraceRUM` reduced to three states: `off` (kill switch), `asyncCS-inline` (versioned OneAgent tag + SRI, loaded `sync`), and a **fail-open default** for everything else. Retired treatment names, a renamed flag, and a Split outage returning `control` all still load RUM — failing closed would create a silent monitoring gap
- The retained SRI arm covers both fallback cases at once: immutably cached for a year (if Dynatrace never adds a cache validator to `_complete.js`) and synchronous (no blind window if `async` proves to lose early interactions)
- **Removed all old-RUM code paths**: `edgeUrls`, the old `cdnUrls`, and the three `_inline_*.ejs` bootstrap files — about 105 KB out of the published package
- `dtWhenReady(which, fn)` helper added for both RUM APIs; `enableManualPageDetection` and `window.dtinfo.appName` in `layout.ejs` no longer depend on the agent having loaded synchronously, which silently disabled them under async loading
- Snow can supply fresh values at runtime via `locals.dynatrace.*`, published to S3 as `dynatrace-rum-config.json`; publish-time fallbacks remain in `dynatrace.ejs` as a last resort
- **Maintainer tooling and docs moved to `packages/react-scripts/tools/dynatrace/`** and excluded from the published package. `scripts/` is now only scripts consumers run. Published package: 119 files / 392 KB → 104 files / 247 KB

## 8.16.1

- Convert layout.ejs includes from the removed EJS preprocessor syntax (`<% include x %>`) to the function form (`<%- include('x') %>`) ahead of the company-wide EJS 3 upgrade
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 18 additions & 3 deletions packages/react-scripts/layout/views/layout.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,25 @@
<%- include('partials/experiments') %>
<%- include('partials/sentry') %>
<%- include('partials/clientAppConfig') %>
if (window.dtrum) {
// window.dtinfo is a plain global read by Dynatrace metadata capture rules, so set it
// unconditionally and as early as possible. Both RUM Classic and the New RUM Experience can
// read it via their own (separately configured) capture rules; gating it on the agent being
// loaded meant the async treatments never set it at all.
if (!window.dtinfo) window.dtinfo = {}
window.dtinfo.appName = SERVER_DATA.appName

// enableManualPageDetection is a RUM Classic API with no New RUM equivalent — New RUM models
// navigations / page summaries / view summaries instead. Defer it until dtrum exists rather
// than firing once: under async loading the old `if (window.dtrum)` guard was always false,
// so Classic silently fell back to automatic page detection on those treatments.
if (window.dtWhenReady) {
window.dtWhenReady('classic', function () {
if (typeof window.dtrum.enableManualPageDetection === 'function') {
window.dtrum.enableManualPageDetection()
}
})
} else if (window.dtrum) {
window.dtrum.enableManualPageDetection()
if (!window.dtinfo) window.dtinfo = {}
window.dtinfo.appName = SERVER_DATA.appName
}
</script>
</head>
Expand Down
120 changes: 107 additions & 13 deletions packages/react-scripts/layout/views/partials/dynatrace.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,121 @@
/* SETS up dynatrace for the various environments */

if (typeof getFeatureFlag !== 'undefined') {
/* One flag, three states — see tools/dynatrace/DYNATRACE_RUM_MECHANISMS.md for the full rationale.
*
* 'off' no agent at all (kill switch)
* 'asyncCS-inline' versioned OneAgent JS tag + SRI, loaded SYNCHRONOUSLY
* anything else combined _complete.js from the Dynatrace global CDN, loaded ASYNC
*
* The default is deliberately the fallthrough rather than an explicit 'global-cdn' match: a
* retired treatment name, a renamed flag, or a Split outage returning 'control' must still load
* RUM. Failing closed would produce a silent monitoring gap, which is the one failure mode we
* cannot detect from the data itself.
*
* The SRI arm is the hedge for BOTH documented revisit triggers: it is versioned and immutably
* cached (so it survives Dynatrace never adding an ETag to _complete.js), and it is synchronous
* (so it has no blind window if async proves to lose early interactions or errors). Because its
* URL is cacheable for a year, the sync parse-block is paid only on the first uncached load.
*
* Whether the New RUM Experience is active is NOT a code concern — it is the tenant's
* `enabledOnGrail` setting, toggled per application in the Dynatrace UI with no deploy.
* The old frontier_snow_dynatraceNewRUM flag never controlled it; it only toggled `async`.
*/
const dynatraceFlag = getFeatureFlag('frontier_snow_dynatraceRUM', {appName: process.env.APP_NAME});
const env = envType();
const treatment = dynatraceFlag.treatment;
const rumDisabled = treatment === 'off';
const useSriSync = treatment === 'asyncCS-inline';

const cdnUrls = {
// Snow provides fresh values via locals.dynatrace (published to S3 by
// tools/dynatrace/fetch-dynatrace-scripts.js); fallback to values current at publish time.
// fallback to values current at react-scripts publish time.
const cdnUrlsNew = (locals && locals.dynatrace && locals.dynatrace.cdnUrls) || {
int: 'https://js-cdn.dynatrace.com/jstag/15c157a40ab/sri/ruxitagent_ICA15789MNPQRTUVXfghqrux_10343260715123811.js',
beta: 'https://js-cdn.dynatrace.com/jstag/15c157a40ab/sri/ruxitagent_ICA15789NPRTUVXfghqrux_10343260715123811.js',
prod: 'https://js-cdn.dynatrace.com/jstag/15c157a40ab/sri/ruxitagent_ICA7NQVfghqrux_10341260622154106.js'
}

const cdnIntegrityNew = (locals && locals.dynatrace && locals.dynatrace.cdnIntegrity) || {
int: 'sha256-8SY/pAJr28AjGTRbzyKLcD4kPiJQIJaT2EfcY59FoWE=',
beta: 'sha256-PeyllVOccLjCaea3jYyf0w5YCMFa54GWyc4B3eRQn4g=',
prod: 'sha256-eGB6PLc8ndZISmjwbbi4xZhE9nbarKf48bRKxVdha+8='
}

const cdnConfigNew = (locals && locals.dynatrace && locals.dynatrace.cdnConfig) || {
int: 'app=3faf90e849295814|cors=1|owasp=1|featureHash=ICA15789MNPQRTUVXfghqrux|msl=153600|srsr=10000|nsfnv=1|reportUrl=https://bf99293tkn.bf.dynatrace.com/bf|srvr=%5C%2Fidentity%5C%2Fsettings|rdnt=2|uxrgce=1|cuc=t0rtn87t|srms=2,1,0,0%2Ftextarea%2Cinput%2Cselect%2Coption;0%2Fdatalist;0%2Fform%20button;0%2F%5Bdata-dtrum-input%5D;0%2F.data-dtrum-input;0%2F%5Bcontenteditable%5D%3Anot%28%5Bcontenteditable%3D%22false%22%5D%29;0%2F%5Brole%3D%22textbox%22%5D%2C%5Brole%3D%22checkbox%22%5D%2C%5Brole%3D%22radio%22%5D%2C%5Brole%3D%22option%22%5D%2C%5Brole%3D%22combobox%22%5D%2C%5Brole%3D%22slider%22%5D%2C%5Brole%3D%22searchbox%22%5D%2C%5Brole%3D%22switch%22%5D%2C%5Brole%3D%22spinbutton%22%5D;1%2F%5Edata%28%28%5C-.%2B%24%29%7C%24%29|mel=100000|dpvc=1|md=mdcc1=cfs_anid,mdcc2=bFS.User.profile.cisId,mdcc3=bs.pageName,mdcc4=bFS.attrs.anonId,mdcc5=bFS.attrs.country,mdcc6=bFS.attrs.cisId|lastModification=1786000244457|mdp=mdcc4,mdcc5|tp=500,50,0|srbbv=2|agentUri=https://js-cdn.dynatrace.com/jstag/15c157a40ab/sri/ruxitagent_ICA15789MNPQRTUVXfghqrux_10343260715123811.js',
beta: 'app=c4242bb1eb216374|cors=1|owasp=1|featureHash=ICA15789NPRTUVXfghqrux|srsr=5000|nsfnv=1|reportUrl=https://bf99293tkn.bf.dynatrace.com/bf|srvr=%5C%2Fidentity%5C%2Fsettings|rdnt=2|uxrgce=1|cuc=t0rtn87t|mel=100000|dpvc=1|md=mdcc1=cfs_anid,mdcc2=bFS.attrs.cisId,mdcc3=bFS.User.profile.cisId,mdcc4=fx-ratelimit-remaining,mdcc5=bs.campaign,mdcc6=bs.channel,mdcc7=bs.pageName,mdcc8=bs.pageURL,mdcc9=bs.pageType,mdcc10=bs.visitorID|lastModification=1786000244457|tp=500,50,0|srbbv=2|agentUri=https://js-cdn.dynatrace.com/jstag/15c157a40ab/sri/ruxitagent_ICA15789NPRTUVXfghqrux_10343260715123811.js',
prod: 'app=a8e5edd77f861ace|cors=1|owasp=1|featureHash=ICA7NQVfghqrux|msl=153600|srsr=10000|vcx=1500|nsfnv=1|reportUrl=https://bf99293tkn.bf.dynatrace.com/bf|srvr=%5C%2Fidentity%5C%2Fsettings|rdnt=2|uxrgce=1|vcit=8000|cuc=t0rtn87t|srms=2,2,1,|mdl=mdcc11=20|mel=100000|dpvc=1|md=mdcc1=cfs_anid,mdcc2=bFS.attrs.cisId,mdcc3=bFS.User.profile.cisId,mdcc4=bs.pageName,mdcc5=bs.pageType,mdcc6=bs.pageURL,mdcc7=bs.visitorID,mdcc8=bs.campaign,mdcc9=bs.channel,mdcc10=fx-ratelimit-remaining,mdcc11=bdocument.referrer,mdcc12=dutm_campaign,mdcc13=dutm_source,mdcc14=dutm_medium|lastModification=1786000244457|tp=500,50,0|srbbv=2|agentUri=https://js-cdn.dynatrace.com/jstag/15c157a40ab/sri/ruxitagent_ICA7NQVfghqrux_10341260622154106.js'
}

// New RUM JavaScript tag (combined code+config _complete.js) from the Dynatrace global CDN.
// Snow provides fresh values via locals.dynatrace.cdnCompleteUrls; fallback to publish-time values.
const cdnCompleteUrlsNew = (locals && locals.dynatrace && locals.dynatrace.cdnCompleteUrls) || {
int: 'https://js-cdn.dynatrace.com/jstag/15c157a40ab/bf99293tkn/3faf90e849295814_complete.js',
beta: 'https://js-cdn.dynatrace.com/jstag/15c157a40ab/bf99293tkn/c4242bb1eb216374_complete.js',
prod: 'https://js-cdn.dynatrace.com/jstag/15c157a40ab/bf99293tkn/a8e5edd77f861ace_complete.js'
}

const edgeUrls = {
int: 'https://edge.fscdn.org/assets/components/hf/assets/js/monitoring/dynatrace-20221104-int.min-9aebd229f83ef7c845ae898f451bb617.js',
beta: 'https://edge.fscdn.org/assets/components/hf/assets/js/monitoring/dynatrace-20221104-beta.min-e859eab722487cc71d4f659447d7ed6d.js',
prod: 'https://edge.fscdn.org/assets/components/hf/assets/js/monitoring/dynatrace-20221104-prod.min-44bb9345beec8a984c2fca40385b4f41.js'
}
// The agent is never inlined: EJS compiles included files as templates, and the minified agent
// contains the EJS open-delimiter sequence (a less-than sign immediately followed by a percent
// sign) inside a character-class string, which EJS misreads as an unterminated scriptlet and
// fails with "Could not find matching close tag". It would also re-ship 300-460 KB per response.
if (!rumDisabled) {
if (useSriSync) { %>
<!-- Versioned OneAgent JS tag + SRI, loaded SYNCHRONOUSLY. No blind window, and the versioned
URL is immutable for a year, so the parse-block is paid only on the first uncached load.
Values come from locals.dynatrace when Snow supplies them; the fallbacks below are a
last resort and go stale — refresh with tools/dynatrace/fetch-dynatrace-scripts.js. -->
<script type="text/javascript" src="<%= cdnUrlsNew[env] %>" data-dtconfig="<%= cdnConfigNew[env] %>" integrity="<%= cdnIntegrityNew[env] %>" crossorigin="anonymous"></script>
<% } else { %>
<!-- Default: combined code+config _complete.js from the Dynatrace global CDN, loaded async.
Single request, tracks tenant configuration automatically, and never blocks rendering.
Trade-off: no SRI, and no cache validator, so browsers re-download it every 24 hours. -->
<script type="text/javascript" src="<%= cdnCompleteUrlsNew[env] %>" crossorigin="anonymous" async></script>
<% }
} %>
<script type="text/javascript">
// RUM API readiness helper, exposed as window.dtWhenReady(which, fn).
//
// Two facts drive this shape:
// 1. With async loading neither window.dtrum nor window.dynatrace exists when this inline
// script runs, and the on-page API has no vendor-provided ready callback (only the
// @dynatrace/rum-javascript-sdk npm package waits for availability, and that can't be
// imported from a server-rendered inline script). So we poll.
// 2. The New RUM Experience runs *alongside* RUM Classic and Classic cannot be disabled, so
// both globals can be present at once. We feature-detect each independently and let
// callers register per API rather than branching on which experience is enabled.
(function () {
if (typeof window === 'undefined') return;
var queue = {classic: [], grail: []};
var fired = {classic: false, grail: false};
var attempts = 0;

window.dtWhenReady = function (which, fn) {
if (fired[which]) { try { fn(); } catch (e) {} return; }
if (queue[which]) queue[which].push(fn);
};

function flush(which) {
if (fired[which]) return;
fired[which] = true;
var fns = queue[which];
queue[which] = [];
for (var i = 0; i < fns.length; i++) { try { fns[i](); } catch (e) {} }
}

if (dynatraceFlag.treatment === 'asyncCS-script') { %>
<script type="text/javascript" src="<%= edgeUrls[env] %>" crossorigin="anonymous"></script>
<% } else if (dynatraceFlag.treatment === 'asyncCS-inline') { %>
<%- include(`./partials/dynatrace/_inline_${env}`) %>
<% } else if (dynatraceFlag.treatment === 'global-cdn') { %>
<script type="text/javascript" src="<%= cdnUrls[env] %>" crossorigin="anonymous"></script>
<% } %>
function poll() {
if (window.dtrum) flush('classic');
if (window.dynatrace && typeof window.dynatrace.sendSessionPropertyEvent === 'function') flush('grail');
return fired.classic && fired.grail;
}

poll(); // synchronous treatments have already loaded by now
if (!(fired.classic && fired.grail)) {
var iv = setInterval(function () {
if (poll() || ++attempts > 50) clearInterval(iv); // up to ~10s for an async agent
}, 200);
}
})();
</script>
<% } %>
Loading