The logs modal parsed the log itself, with two regular expressions and a substring, and the lists it drew disagreed with the log beside them.
An ODS HTML line was cut from the first capital E anywhere in it and lost its last character: ERROR: File WORK.X.DATA does not exist.</font>.
A message SAS wrapped over several lines was recorded once, truncated at the break. ERROR 22-322: Syntax error, expecting one of the following: ;, CANCEL, never showed RUN, QUIT., and the tail was not reported anywhere.
The colon-less form SAS writes (ERROR Libref X is not assigned.) was missed on any line carrying markup.
The notable notes - uninitialised variable, invalid data, a format too small for its value - were not shown at all.
Clicking an issue searched the rendered text for its message with includes, so a log that repeats a message always jumped to the first copy. Two identical ERROR 22-322: lines in one step is ordinary.
The trees rendered at zero: @if (logErrors) is truthy for [], so a clean log drew Errors (0) and Warnings (0).
What it is now
parseLog from @sasjs/utils 4.3.1 replaces all of it - the same function the VS Code extension and the SASjs server Studio use, so one log reads identically in every host.
piece
before
after
lists
errors, warnings
errors, warnings, notes
entry
the whole line, or a substring of it
the message, with SAS's message code where there is one
line
index found by searching the text
issue.line, plus endLine for a wrapped message
mark
<font class="error-line"> on the marker line
on every line the issue occupies
scroll
search for the message text
line * lineHeight
zero lists
drawn
not drawn
Notes are always shown, as sasjs/server#500 does by default, and are left unmarked in the log text and coloured blue when clicked - a notable note is not a defect. They are not capped: a step that reports one note per rejected record will list them all, which is worth revisiting if a real load makes the tree unwieldy.
The modal is SAS Logs now: the menu entry, the deploy screen's button, the abort modal's Open logs modal, the modal title, the component (SasLogsModalComponent, app-sas-logs-modal), the event service method and the stylesheet selectors. The tabs underneath (SAS Log, SAS Source Code, SAS Generated Code, SAS Work Tables) are unchanged.
Two smaller things on the way past: the warning flash was yellow where the warning text is ochre (both ochre now, notes blue), and formatTimestamp decided on moment().format(), which is the truthy string 'Invalid date' for anything it cannot parse, so its fallback could never fire - it asks isValid() now.
Verification
client, scoped: ng test --include='src/app/shared/sas-logs-modal/**/*.spec.ts' - 20 specs, all pass: grouping by severity, codes, a wrapped endLine, a log with nothing to report, a request with no log at all, line marking for wrapped messages, notes left unmarked, the scroll arithmetic (including two identical messages at different lines and a wrap covering two lines), the opened input, timestamps and the three downloads.
client, full karma suite: 724 of 724 pass.
client, coverage of sas-logs-modal.component.ts (--code-coverage, scoped to those specs): statements 89.41%, branches 92.3%, functions 86.36%, lines 88.31%. What is left is the empty ngOnInit, the layout measurement goToLogLine stubs out, and the three-second overlay removal.
client: npm run build (production) exits 0 and emits dist; SAS Logs and the parser's keyword table are both in main.js, so the dependency is bundled rather than tree-shaken away.
client: npm run lint:check and prettier --check src/styles.scss clean.
CI on this branch: build.yaml run 2810 success - install (npm ci), lint, SAS lint, licence check, Angular tests in both timezones, production build, and the Cypress suite against a mocked SASjs server; lighthouse.yaml run 2811 success.
Not run by me: an estate deploy, so the modal has not been clicked in a real SAS 9 or Viya session - CI exercises it against the mocked server only.
The dependency bump
@sasjs/utils goes 4.0.0 -> 4.3.1, which is where the parser lives.
The lock diff is much larger than one version, and it has to be: the lock is regenerated with the repository's own client/.npmrc (ignore-scripts, save-exact), so npm resolves peer dependencies the same way CI does. Regenerating it with legacy-peer-deps produces a lock that omits 44 peer packages, and npm ci then refuses to install at all - which is exactly how the first push failed here.
Verified the way CI runs it: a clean copy of package.json + package-lock.json + .npmrc + the decrypted sheet-crypto.tgz, then npm ci --ignore-scripts - 1818 packages installed, exit 0. The previous lock under the same command fails with npm ci can only install packages when your package.json and package-lock.json ... are in sync.
@sasjs/adapter 5.1.2 pins @sasjs/utils 4.0.1, so its own copy stays nested and untouched - DC imports the parser from the top-level package.
One follow-up, in utils
A wrapped line of an ODS HTML log is not folded: isWrappedLine tested the raw line for indentation, and SAS indents inside the <font> tags, so the second half of a wrapped message never folded on SAS 9. That is fixed in sasjs/utils#294 (fix(logs): fold a wrapped line that carries ODS HTML markup) and reaches DC with the next utils release. This PR does not depend on it: the wrap it exercises is plain text.
## What was wrong
The logs modal parsed the log itself, with two regular expressions and a `substring`, and the lists it drew disagreed with the log beside them.
- An ODS HTML line was cut from the first capital `E` anywhere in it and lost its last character: `ERROR: File WORK.X.DATA does not exist.</font>`.
- A message SAS wrapped over several lines was recorded once, truncated at the break. `ERROR 22-322: Syntax error, expecting one of the following: ;, CANCEL,` never showed `RUN, QUIT.`, and the tail was not reported anywhere.
- The colon-less form SAS writes (`ERROR Libref X is not assigned.`) was missed on any line carrying markup.
- The notable notes - uninitialised variable, invalid data, a format too small for its value - were not shown at all.
- Clicking an issue searched the rendered text for its message with `includes`, so a log that repeats a message always jumped to the first copy. Two identical `ERROR 22-322:` lines in one step is ordinary.
- The trees rendered at zero: `@if (logErrors)` is truthy for `[]`, so a clean log drew `Errors (0)` and `Warnings (0)`.
## What it is now
`parseLog` from `@sasjs/utils` 4.3.1 replaces all of it - the same function the VS Code extension and the SASjs server Studio use, so one log reads identically in every host.
| piece | before | after |
| --- | --- | --- |
| lists | errors, warnings | errors, warnings, **notes** |
| entry | the whole line, or a substring of it | the message, with SAS's message code where there is one |
| line | index found by searching the text | `issue.line`, plus `endLine` for a wrapped message |
| mark | `<font class="error-line">` on the marker line | on every line the issue occupies |
| scroll | search for the message text | `line * lineHeight` |
| zero lists | drawn | not drawn |
Notes are always shown, as sasjs/server#500 does by default, and are left unmarked in the log text and coloured blue when clicked - a notable note is not a defect. They are not capped: a step that reports one note per rejected record will list them all, which is worth revisiting if a real load makes the tree unwieldy.
The modal is **SAS Logs** now: the menu entry, the deploy screen's button, the abort modal's `Open logs modal`, the modal title, the component (`SasLogsModalComponent`, `app-sas-logs-modal`), the event service method and the stylesheet selectors. The tabs underneath (SAS Log, SAS Source Code, SAS Generated Code, SAS Work Tables) are unchanged.
Two smaller things on the way past: the warning flash was yellow where the warning text is ochre (both ochre now, notes blue), and `formatTimestamp` decided on `moment().format()`, which is the truthy string `'Invalid date'` for anything it cannot parse, so its fallback could never fire - it asks `isValid()` now.
## Verification
- `client`, scoped: `ng test --include='src/app/shared/sas-logs-modal/**/*.spec.ts'` - **20 specs, all pass**: grouping by severity, codes, a wrapped `endLine`, a log with nothing to report, a request with no log at all, line marking for wrapped messages, notes left unmarked, the scroll arithmetic (including two identical messages at different lines and a wrap covering two lines), the `opened` input, timestamps and the three downloads.
- `client`, full karma suite: **724 of 724 pass**.
- `client`, coverage of `sas-logs-modal.component.ts` (`--code-coverage`, scoped to those specs): statements **89.41%**, branches **92.3%**, functions **86.36%**, lines **88.31%**. What is left is the empty `ngOnInit`, the layout measurement `goToLogLine` stubs out, and the three-second overlay removal.
- `client`: `npm run build` (production) exits 0 and emits `dist`; `SAS Logs` and the parser's keyword table are both in `main.js`, so the dependency is bundled rather than tree-shaken away.
- `client`: `npm run lint:check` and `prettier --check src/styles.scss` clean.
- CI on this branch: `build.yaml` run 2810 **success** - install (`npm ci`), lint, SAS lint, licence check, Angular tests in both timezones, production build, and the Cypress suite against a mocked SASjs server; `lighthouse.yaml` run 2811 **success**.
- Not run by me: an estate deploy, so the modal has not been clicked in a real SAS 9 or Viya session - CI exercises it against the mocked server only.
## The dependency bump
`@sasjs/utils` goes `4.0.0` -> `4.3.1`, which is where the parser lives.
The lock diff is much larger than one version, and it has to be: the lock is regenerated with the repository's own `client/.npmrc` (`ignore-scripts`, `save-exact`), so npm resolves peer dependencies the same way CI does. Regenerating it with `legacy-peer-deps` produces a lock that omits 44 peer packages, and `npm ci` then refuses to install at all - which is exactly how the first push failed here.
Verified the way CI runs it: a clean copy of `package.json` + `package-lock.json` + `.npmrc` + the decrypted `sheet-crypto.tgz`, then `npm ci --ignore-scripts` - **1818 packages installed, exit 0**. The previous lock under the same command fails with `npm ci can only install packages when your package.json and package-lock.json ... are in sync`.
`@sasjs/adapter` 5.1.2 pins `@sasjs/utils` 4.0.1, so its own copy stays nested and untouched - DC imports the parser from the top-level package.
## One follow-up, in utils
A wrapped line of an ODS HTML log is not folded: `isWrappedLine` tested the raw line for indentation, and SAS indents inside the `<font>` tags, so the second half of a wrapped message never folded on SAS 9. That is fixed in sasjs/utils#294 (`fix(logs): fold a wrapped line that carries ODS HTML markup`) and reaches DC with the next utils release. This PR does not depend on it: the wrap it exercises is plain text.
The modal decided what was wrong with two regular expressions of its own, and the answer disagreed with the log it was showing:
- an ODS HTML line was cut from the first capital `E` in it and lost its last character, so an error read `ERROR: File WORK.X.DATA does not exist.</font>`;
- a message SAS wrapped over several lines was reported once, truncated at the break, and the rest of it was not reported at all;
- the colon-less form SAS writes (`ERROR Libref X is not assigned.`) was missed on any line carrying markup;
- the notable notes - uninitialised variable, invalid data, a format too small - were not shown anywhere;
- clicking an issue searched the rendered text for its message, so a log that repeats a message (two `ERROR 22-322:` lines is ordinary) always jumped to the first of them.
`parseLog` from `@sasjs/utils` replaces all of it, the same function the VS Code extension and the SASjs server Studio use, so one log now reads identically in every host. Each request keeps the issues the parser reported, grouped by severity, and the tree gains a Notes list beside Errors and Warnings. The click scrolls to `issue.line` and flashes from that line to `issue.endLine`, and the lists no longer render at zero.
The modal is SAS Logs now, as are the menu entry, the deploy button and the abort modal's button; the request history is unchanged underneath.
`formatTimestamp` decided on `moment().format()`, which is 'Invalid date' - truthy - for anything it cannot parse, so its fallback could never fire. It asks `isValid()` now.
The warning flash was yellow where the warning text is ochre; both are ochre now, and the notes flash blue.
`@sasjs/utils` goes to 4.3.1, which is where the parser lives.
CI on the first push caught a real problem in this PR, now fixed.
lighthouse.yaml failed at Install npm dependencies: npm ci refused the tree with can only install packages when your package.json and package-lock.json ... are in sync. The lock had been regenerated while a local .npmrc carrying legacy-peer-deps=true was in place, so npm resolved the tree differently from CI and left out 44 peer packages.
The lock is regenerated with the repository's own client/.npmrc and the PR body now says so. Verified locally against the CI command, in a clean directory holding only package.json, package-lock.json, .npmrc and the decrypted sheet-crypto.tgz:
npm ci --ignore-scripts with the previous lock: fails, the same out-of-sync error as CI;
npm ci --ignore-scripts with the lock in this branch: 1818 packages added, exit 0.
The only dependency change is @sasjs/utils 4.0.0 -> 4.3.1 plus one patch drift it pulled in (@typescript-eslint/types 8.72.0 -> 8.72.1, inside its existing range).
CI on the first push caught a real problem in this PR, now fixed.
`lighthouse.yaml` failed at **Install npm dependencies**: `npm ci` refused the tree with `can only install packages when your package.json and package-lock.json ... are in sync`. The lock had been regenerated while a local `.npmrc` carrying `legacy-peer-deps=true` was in place, so npm resolved the tree differently from CI and left out 44 peer packages.
The lock is regenerated with the repository's own `client/.npmrc` and the PR body now says so. Verified locally against the CI command, in a clean directory holding only `package.json`, `package-lock.json`, `.npmrc` and the decrypted `sheet-crypto.tgz`:
- `npm ci --ignore-scripts` with the previous lock: fails, the same out-of-sync error as CI;
- `npm ci --ignore-scripts` with the lock in this branch: **1818 packages added, exit 0**.
The only dependency change is `@sasjs/utils` 4.0.0 -> 4.3.1 plus one patch drift it pulled in (`@typescript-eslint/types` 8.72.0 -> 8.72.1, inside its existing range).
FYI while triaging a red run nearby: the merge commit for this branch (43e7274) fails Build / Build-and-ng-test at npm ci --ignore-scripts, before any spec runs:
npm error `npm ci` can only install packages when your package.json and package-lock.json or npm-shrinkwrap.json are in sync. Please update your lock file with `npm install` before continuing.
npm error Missing: @typescript-eslint/utils@8.72.1 from lock file
npm error Missing: @webcomponents/custom-elements@1.6.0 from lock file
npm error Missing: @babel/preset-env@7.29.7 from lock file
npm error Missing: babel-loader@9.2.1 from lock file
...
This branch changes client/package.json (+1/-1) and client/package-lock.json (46/46), so the lock looks only partly regenerated for the bumped package's transitive set. cd client && npm install (or npm install --package-lock-only with the registry config in place) and committing the resulting lock should clear it. It reproduced identically on a re-run, so it is not runner flakiness.
FYI while triaging a red run nearby: the merge commit for this branch (`43e7274`) fails `Build / Build-and-ng-test` at `npm ci --ignore-scripts`, before any spec runs:
```
npm error `npm ci` can only install packages when your package.json and package-lock.json or npm-shrinkwrap.json are in sync. Please update your lock file with `npm install` before continuing.
npm error Missing: @typescript-eslint/utils@8.72.1 from lock file
npm error Missing: @webcomponents/custom-elements@1.6.0 from lock file
npm error Missing: @babel/preset-env@7.29.7 from lock file
npm error Missing: babel-loader@9.2.1 from lock file
...
```
This branch changes `client/package.json` (+1/-1) and `client/package-lock.json` (46/46), so the lock looks only partly regenerated for the bumped package's transitive set. `cd client && npm install` (or `npm install --package-lock-only` with the registry config in place) and committing the resulting lock should clear it. It reproduced identically on a re-run, so it is not runner flakiness.
Reviewed the full diff: the renamed modal component and template, the parseLog integration in markIssueLines and goToLogLine, the event/service renames, styles.scss, and the @sasjs/utils bump.
client/src/app/shared/sas-logs-modal/sas-logs-modal.component.ts (markIssueLines) - log lines are interpolated into <font class="..."> markup that the template binds through [innerHTML]. Angular's sanitiser stops scripts, but a log line containing < or & is parsed as markup and the display is mangled: a SAS log echoing data or a < in a path is enough. Escape the line text (and the attribute value) before wrapping it, and only add the severity wrapper yourself.
client/package-lock.json - besides @sasjs/utils 4.0.0 -> 4.3.1 the lock also moves the @typescript-eslint/* packages 8.72.0 -> 8.72.1. That is unrelated dev-tooling churn from a plain install, and it will be read later as a deliberate upgrade. Regenerate so the lock diff carries only the package this PR changes.
No blocking issues.
REVIEWED ON 2026-11-01 22:11 UTC
Reviewed the full diff: the renamed modal component and template, the parseLog integration in markIssueLines and goToLogLine, the event/service renames, styles.scss, and the @sasjs/utils bump.
- client/src/app/shared/sas-logs-modal/sas-logs-modal.component.ts (markIssueLines) - log lines are interpolated into `<font class="...">` markup that the template binds through [innerHTML]. Angular's sanitiser stops scripts, but a log line containing `<` or `&` is parsed as markup and the display is mangled: a SAS log echoing data or a `<` in a path is enough. Escape the line text (and the attribute value) before wrapping it, and only add the severity wrapper yourself.
- client/package-lock.json - besides @sasjs/utils 4.0.0 -> 4.3.1 the lock also moves the @typescript-eslint/* packages 8.72.0 -> 8.72.1. That is unrelated dev-tooling churn from a plain install, and it will be read later as a deliberate upgrade. Regenerate so the lock diff carries only the package this PR changes.
No blocking issues.
allan
merged commit 0d8fa59e81 into release/7.222026-11-01 22:18:15 +00:00
allan
deleted branch feat/sas-logs-modal2026-11-01 22:18:15 +00:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
What was wrong
The logs modal parsed the log itself, with two regular expressions and a
substring, and the lists it drew disagreed with the log beside them.Eanywhere in it and lost its last character:ERROR: File WORK.X.DATA does not exist.</font>.ERROR 22-322: Syntax error, expecting one of the following: ;, CANCEL,never showedRUN, QUIT., and the tail was not reported anywhere.ERROR Libref X is not assigned.) was missed on any line carrying markup.includes, so a log that repeats a message always jumped to the first copy. Two identicalERROR 22-322:lines in one step is ordinary.@if (logErrors)is truthy for[], so a clean log drewErrors (0)andWarnings (0).What it is now
parseLogfrom@sasjs/utils4.3.1 replaces all of it - the same function the VS Code extension and the SASjs server Studio use, so one log reads identically in every host.issue.line, plusendLinefor a wrapped message<font class="error-line">on the marker lineline * lineHeightNotes are always shown, as sasjs/server#500 does by default, and are left unmarked in the log text and coloured blue when clicked - a notable note is not a defect. They are not capped: a step that reports one note per rejected record will list them all, which is worth revisiting if a real load makes the tree unwieldy.
The modal is SAS Logs now: the menu entry, the deploy screen's button, the abort modal's
Open logs modal, the modal title, the component (SasLogsModalComponent,app-sas-logs-modal), the event service method and the stylesheet selectors. The tabs underneath (SAS Log, SAS Source Code, SAS Generated Code, SAS Work Tables) are unchanged.Two smaller things on the way past: the warning flash was yellow where the warning text is ochre (both ochre now, notes blue), and
formatTimestampdecided onmoment().format(), which is the truthy string'Invalid date'for anything it cannot parse, so its fallback could never fire - it asksisValid()now.Verification
client, scoped:ng test --include='src/app/shared/sas-logs-modal/**/*.spec.ts'- 20 specs, all pass: grouping by severity, codes, a wrappedendLine, a log with nothing to report, a request with no log at all, line marking for wrapped messages, notes left unmarked, the scroll arithmetic (including two identical messages at different lines and a wrap covering two lines), theopenedinput, timestamps and the three downloads.client, full karma suite: 724 of 724 pass.client, coverage ofsas-logs-modal.component.ts(--code-coverage, scoped to those specs): statements 89.41%, branches 92.3%, functions 86.36%, lines 88.31%. What is left is the emptyngOnInit, the layout measurementgoToLogLinestubs out, and the three-second overlay removal.client:npm run build(production) exits 0 and emitsdist;SAS Logsand the parser's keyword table are both inmain.js, so the dependency is bundled rather than tree-shaken away.client:npm run lint:checkandprettier --check src/styles.scssclean.build.yamlrun 2810 success - install (npm ci), lint, SAS lint, licence check, Angular tests in both timezones, production build, and the Cypress suite against a mocked SASjs server;lighthouse.yamlrun 2811 success.The dependency bump
@sasjs/utilsgoes4.0.0->4.3.1, which is where the parser lives.The lock diff is much larger than one version, and it has to be: the lock is regenerated with the repository's own
client/.npmrc(ignore-scripts,save-exact), so npm resolves peer dependencies the same way CI does. Regenerating it withlegacy-peer-depsproduces a lock that omits 44 peer packages, andnpm cithen refuses to install at all - which is exactly how the first push failed here.Verified the way CI runs it: a clean copy of
package.json+package-lock.json+.npmrc+ the decryptedsheet-crypto.tgz, thennpm ci --ignore-scripts- 1818 packages installed, exit 0. The previous lock under the same command fails withnpm ci can only install packages when your package.json and package-lock.json ... are in sync.@sasjs/adapter5.1.2 pins@sasjs/utils4.0.1, so its own copy stays nested and untouched - DC imports the parser from the top-level package.One follow-up, in utils
A wrapped line of an ODS HTML log is not folded:
isWrappedLinetested the raw line for indentation, and SAS indents inside the<font>tags, so the second half of a wrapped message never folded on SAS 9. That is fixed in sasjs/utils#294 (fix(logs): fold a wrapped line that carries ODS HTML markup) and reaches DC with the next utils release. This PR does not depend on it: the wrap it exercises is plain text.43e7274dc2tof5d431eaa9f5d431eaa9to3583781d773583781d77toaabc578d5eCI on the first push caught a real problem in this PR, now fixed.
lighthouse.yamlfailed at Install npm dependencies:npm cirefused the tree withcan only install packages when your package.json and package-lock.json ... are in sync. The lock had been regenerated while a local.npmrccarryinglegacy-peer-deps=truewas in place, so npm resolved the tree differently from CI and left out 44 peer packages.The lock is regenerated with the repository's own
client/.npmrcand the PR body now says so. Verified locally against the CI command, in a clean directory holding onlypackage.json,package-lock.json,.npmrcand the decryptedsheet-crypto.tgz:npm ci --ignore-scriptswith the previous lock: fails, the same out-of-sync error as CI;npm ci --ignore-scriptswith the lock in this branch: 1818 packages added, exit 0.The only dependency change is
@sasjs/utils4.0.0 -> 4.3.1 plus one patch drift it pulled in (@typescript-eslint/types8.72.0 -> 8.72.1, inside its existing range).FYI while triaging a red run nearby: the merge commit for this branch (
43e7274) failsBuild / Build-and-ng-testatnpm ci --ignore-scripts, before any spec runs:This branch changes
client/package.json(+1/-1) andclient/package-lock.json(46/46), so the lock looks only partly regenerated for the bumped package's transitive set.cd client && npm install(ornpm install --package-lock-onlywith the registry config in place) and committing the resulting lock should clear it. It reproduced identically on a re-run, so it is not runner flakiness.REVIEWED ON 2026-11-01 22:11 UTC
Reviewed the full diff: the renamed modal component and template, the parseLog integration in markIssueLines and goToLogLine, the event/service renames, styles.scss, and the @sasjs/utils bump.
<font class="...">markup that the template binds through [innerHTML]. Angular's sanitiser stops scripts, but a log line containing<or&is parsed as markup and the display is mangled: a SAS log echoing data or a<in a path is enough. Escape the line text (and the attribute value) before wrapping it, and only add the severity wrapper yourself.No blocking issues.