From 2a65664a51596a5b08b774c370f917d5e27fa089 Mon Sep 17 00:00:00 2001 From: dc Date: Thu, 24 Sep 2026 20:29:30 +0000 Subject: [PATCH 1/5] docs(troubleshooting): document the browser_info support diagnostics Every service call carries a single-row browser_info input table, readable by any service or hook script as work.browser_info and dumped to the job log when debug is on. Document its columns and how to see it, so a support ticket can be answered from the log without follow-up questions. --- docs/dci-troubleshooting.md | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/docs/dci-troubleshooting.md b/docs/dci-troubleshooting.md index d47cb2b..90e2c46 100644 --- a/docs/dci-troubleshooting.md +++ b/docs/dci-troubleshooting.md @@ -140,4 +140,37 @@ Then recycle any existing compute sessions - hot sessions keep the old setting u The app version is bundled into the frontend during the release, and is visible by clicking your username in the top right. -You can also determine the app version (and SASjs Version, and build time) by opening browser Development Tools and running `appinfo()` in the console. \ No newline at end of file +You can also determine the app version (and SASjs Version, and build time) by opening browser Development Tools and running `appinfo()` in the console. + +## Support Diagnostics + +Every service call the frontend makes carries a single-row `browser_info` input table, available to the service (and to any [hook script](macros.md)) as `work.browser_info`. It records where the request came from, so a support ticket can be diagnosed from the job log without asking the user follow-up questions, and a hook can adapt to its context. + +The table has these columns: + +| Column | Description | +|---|---| +| `url` | The URL of the Data Controller page itself (the iframe), not the document embedding it. When the editor is embedded in a report, any parameters the report author added to the embed URL are visible here. | +| `referrer` | The embedding document, from `document.referrer`. For an embedded report this is the report URL, so the embedding report can be told apart from the editor URL. | +| `timezone` | The browser's IANA timezone name, e.g. `Europe/Berlin`. | +| `tz_offset` | The browser's UTC offset in minutes, as `Date.getTimezoneOffset()` returns it (positive west of UTC, so `Europe/Berlin` in summer is -120). | +| `locale` | The browser locale, e.g. `en-GB`. | +| `dc_version` | The Data Controller build, as shown by `appinfo()`. | +| `adapter_version` | The `@sasjs/adapter` version the client was built with. | +| `browser` | The browser family, parsed from the user agent. | +| `browser_version` | The browser version. | +| `platform` | The operating system, parsed from the user agent. | +| `user_agent` | The raw `navigator.userAgent` string. | + +The values are supplied by the client, so treat them as diagnostics hints rather than a security boundary. + +To see the table, run any request with debug on (for example add `&_debug=131` to the service URL). The session initialisation then writes the row to the job log: + +``` +NOTE: MPEINIT: work.browser_info: +url=... referrer=... timezone=Europe/Berlin tz_offset=-120 locale=en-GB +dc_version=7.15.0 adapter_version=4.19.0 browser=Chrome browser_version=120.0 +platform=Linux user_agent=... +``` + +The table is sent by newer clients only. A service called directly (for instance from a URL, or by a script), or by an older frontend, has no `work.browser_info` - the log then records that fact instead. \ No newline at end of file -- 2.43.0 From 777073796377640a8f945871ded680f2f96fc972 Mon Sep 17 00:00:00 2001 From: dc Date: Fri, 25 Sep 2026 00:21:56 +0000 Subject: [PATCH 2/5] docs(troubleshooting): scope the diagnostics tables to their services, add browser_url_vars The support diagnostics are now sent only where they can be used: the startup service and the services that execute customer-provided code (hook scripts, dynamic cell dropdown programs) - not with every service call. The page's URL parameters arrive as a new browser_url_vars table, one row per parameter, so a SAS developer reads a parameter by name rather than parsing the url string. --- docs/dci-troubleshooting.md | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/docs/dci-troubleshooting.md b/docs/dci-troubleshooting.md index 90e2c46..fc1d7a9 100644 --- a/docs/dci-troubleshooting.md +++ b/docs/dci-troubleshooting.md @@ -144,13 +144,17 @@ You can also determine the app version (and SASjs Version, and build time) by op ## Support Diagnostics -Every service call the frontend makes carries a single-row `browser_info` input table, available to the service (and to any [hook script](macros.md)) as `work.browser_info`. It records where the request came from, so a support ticket can be diagnosed from the job log without asking the user follow-up questions, and a hook can adapt to its context. +The frontend sends two input tables with the startup service and with the services that execute customer-provided code - the [hook scripts](macros.md) (pre/post edit and approve hooks) and the dynamic cell dropdown programs. They are available in those services as `work.browser_info` and `work.browser_url_vars`, so a support ticket can be diagnosed from the job log without asking the user follow-up questions, and a hook can adapt to its context. Other services do not receive them. -The table has these columns: +The services that receive the tables are: `public/startupservice`, `editors/getdata`, `editors/getdynamiccolvals`, `editors/stagedata`, `editors/loadfile`, `editors/restore` and `auditors/postdata`. + +### browser_info + +A single-row table with these columns: | Column | Description | |---|---| -| `url` | The URL of the Data Controller page itself (the iframe), not the document embedding it. When the editor is embedded in a report, any parameters the report author added to the embed URL are visible here. | +| `url` | The URL of the Data Controller page itself (the iframe), not the document embedding it. When the editor is embedded in a report, the embedding URL is visible here. | | `referrer` | The embedding document, from `document.referrer`. For an embedded report this is the report URL, so the embedding report can be told apart from the editor URL. | | `timezone` | The browser's IANA timezone name, e.g. `Europe/Berlin`. | | `tz_offset` | The browser's UTC offset in minutes, as `Date.getTimezoneOffset()` returns it (positive west of UTC, so `Europe/Berlin` in summer is -120). | @@ -162,15 +166,28 @@ The table has these columns: | `platform` | The operating system, parsed from the user agent. | | `user_agent` | The raw `navigator.userAgent` string. | -The values are supplied by the client, so treat them as diagnostics hints rather than a security boundary. +### browser_url_vars -To see the table, run any request with debug on (for example add `&_debug=131` to the service URL). The session initialisation then writes the row to the job log: +The page URL parameters as one row per parameter, so a SAS program can read a parameter by name rather than parse the `url` string: + +| Column | Description | +|---|---| +| `name` | The URL parameter name. | +| `value` | The URL parameter value. | + +Parameters from both the search string and the hash query string (Angular routes carry them after the `#`) are included; on a name collision the hash value wins. The table is sent only when the page URL has at least one parameter. + +The values in both tables are supplied by the client, so treat them as diagnostics hints rather than a security boundary. + +To see the tables, run any of the services above with debug on (for example add `&_debug=131` to the service URL). The session initialisation then writes them to the job log: ``` +NOTE: MPEINIT: work.browser_url_vars: +name=embed value=va NOTE: MPEINIT: work.browser_info: url=... referrer=... timezone=Europe/Berlin tz_offset=-120 locale=en-GB dc_version=7.15.0 adapter_version=4.19.0 browser=Chrome browser_version=120.0 platform=Linux user_agent=... ``` -The table is sent by newer clients only. A service called directly (for instance from a URL, or by a script), or by an older frontend, has no `work.browser_info` - the log then records that fact instead. \ No newline at end of file +A service called directly (for instance from a URL, or by a script), or by an older frontend, has no `work.browser_info` - the log then records that fact instead. \ No newline at end of file -- 2.43.0 From be8d92f43905d938619d835add4e7f083a87d2f5 Mon Sep 17 00:00:00 2001 From: dc Date: Fri, 25 Sep 2026 07:55:49 +0000 Subject: [PATCH 3/5] docs(troubleshooting): note the 128 debug value and the loadfile upload limitation The adapter sends _debug=128 (not 131) on the Viya web JES path when runAsTask is enabled, which is what the frontend uses there. Document that value alongside 131, and note that editors/loadfile is reached through the multipart file upload, which carries no input tables. --- docs/dci-troubleshooting.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/dci-troubleshooting.md b/docs/dci-troubleshooting.md index fc1d7a9..dad4564 100644 --- a/docs/dci-troubleshooting.md +++ b/docs/dci-troubleshooting.md @@ -148,6 +148,8 @@ The frontend sends two input tables with the startup service and with the servic The services that receive the tables are: `public/startupservice`, `editors/getdata`, `editors/getdynamiccolvals`, `editors/stagedata`, `editors/loadfile`, `editors/restore` and `auditors/postdata`. +`editors/loadfile` is the exception in practice: the app reaches it through the adapter's file-upload call (multipart), which carries no input tables, so the service - and the post edit hook it runs - does not receive them in normal use. It is on the list for completeness, and would receive them if the service were ever invoked through the ordinary request path. + ### browser_info A single-row table with these columns: @@ -179,7 +181,7 @@ Parameters from both the search string and the hash query string (Angular routes The values in both tables are supplied by the client, so treat them as diagnostics hints rather than a security boundary. -To see the tables, run any of the services above with debug on (for example add `&_debug=131` to the service URL). The session initialisation then writes them to the job log: +To see the tables, run any of the services above with debug on. The debug value depends on the path: `&_debug=131` on the Compute API and SAS 9, or `&_debug=128` on the Viya web (JES) path with `runAsTask` enabled - which is what the frontend sends there, so turning debug on in the app is enough. Either value makes the session initialisation write the tables to the job log: ``` NOTE: MPEINIT: work.browser_url_vars: -- 2.43.0 From 7f2a76986afde8886315bf0f9c0cfc92798d6cf6 Mon Sep 17 00:00:00 2001 From: dc Date: Sun, 27 Sep 2026 20:20:07 +0000 Subject: [PATCH 4/5] docs(troubleshooting): correct the browser_url_vars collision, add hook read examples --- docs/dci-troubleshooting.md | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/docs/dci-troubleshooting.md b/docs/dci-troubleshooting.md index dad4564..77e65e2 100644 --- a/docs/dci-troubleshooting.md +++ b/docs/dci-troubleshooting.md @@ -177,7 +177,7 @@ The page URL parameters as one row per parameter, so a SAS program can read a pa | `name` | The URL parameter name. | | `value` | The URL parameter value. | -Parameters from both the search string and the hash query string (Angular routes carry them after the `#`) are included; on a name collision the hash value wins. The table is sent only when the page URL has at least one parameter. +Parameters from both the search string and the hash query string (Angular routes carry them after the `#`) are included; on a name collision the search string value wins. The table is sent only when the page URL has at least one parameter. The values in both tables are supplied by the client, so treat them as diagnostics hints rather than a security boundary. @@ -192,4 +192,35 @@ dc_version=7.15.0 adapter_version=4.19.0 browser=Chrome browser_version=120.0 platform=Linux user_agent=... ``` -A service called directly (for instance from a URL, or by a script), or by an older frontend, has no `work.browser_info` - the log then records that fact instead. \ No newline at end of file +A service called directly (for instance from a URL, or by a script), or by an older frontend, has no `work.browser_info` - the log then records that fact instead. + +### Reading the tables from a hook + +A [hook script](macros.md) is `%include`d into the running service, so it shares the service's WORK library and can read the tables directly. To pick up a URL parameter by name: + +```sas +%let labels=N; + +%if %sysfunc(exist(work.browser_url_vars)) %then %do; + proc sql noprint; + select value into :labels + from work.browser_url_vars + where name = 'labels'; + quit; +%end; +``` + +To branch on what the client reports: + +```sas +%if %sysfunc(exist(work.browser_info)) %then %do; + data _null_; + set work.browser_info; + call symputx('dc_timezone', timezone); + call symputx('dc_locale', locale); + run; +%end; +``` + +!!! warning + Guard with `%sysfunc(exist())` before reading. A service called directly, or by a frontend older than this feature, has no `work.browser_info` at all, so an unguarded `set` or `select` fails the job. \ No newline at end of file -- 2.43.0 From 13042876b83a2efa1c3328c8f396f4a62b3cefce Mon Sep 17 00:00:00 2001 From: dc Date: Sun, 27 Sep 2026 20:47:53 +0000 Subject: [PATCH 5/5] docs(troubleshooting): a worked URL-parameter example for the hook read --- docs/dci-troubleshooting.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/docs/dci-troubleshooting.md b/docs/dci-troubleshooting.md index 77e65e2..a2ef798 100644 --- a/docs/dci-troubleshooting.md +++ b/docs/dci-troubleshooting.md @@ -196,16 +196,26 @@ A service called directly (for instance from a URL, or by a script), or by an ol ### Reading the tables from a hook -A [hook script](macros.md) is `%include`d into the running service, so it shares the service's WORK library and can read the tables directly. To pick up a URL parameter by name: +A [hook script](macros.md) is `%include`d into the running service, so it shares the service's WORK library and can read the tables directly. Take an embedded editor opened at: + +``` +https://datacontroller.io/#/editor/SASHELP.CLASS?labels=true&embed=va +``` + +`browser_url_vars` then holds a row per parameter - `labels=true` and `embed=va` - so a hook can pick up either by name: ```sas %let labels=N; +%let embed=N; %if %sysfunc(exist(work.browser_url_vars)) %then %do; proc sql noprint; select value into :labels from work.browser_url_vars where name = 'labels'; + select value into :embed + from work.browser_url_vars + where name = 'embed'; quit; %end; ``` -- 2.43.0