fix: unnecessary getsubmits call removed
Build / Build-and-ng-test (pull_request) Successful in 5m19s
Lighthouse Checks / lighthouse (pull_request) Successful in 21m40s
Build / Build-and-test-development (pull_request) Failing after 33m35s

This commit is contained in:
hermes
2026-08-28 14:02:52 +01:00
parent a104f78645
commit cc2ff873fa
7 changed files with 136 additions and 34 deletions
@@ -0,0 +1,73 @@
// Marks this file as an ES module (rather than a global script) so its
// top-level consts don't collide, under the TS type-checker, with the same
// names declared in other spec files — see e.g. stage.cy.ts, which gets
// this for free via a real import.
export {}
const hostUrl = Cypress.env('hosturl')
const appLocation = Cypress.env('appLocation')
const longerCommandTimeout = Cypress.env('longerCommandTimeout')
context('submitted-details tests: ', function () {
this.beforeAll(() => {
cy.loginAndUpdateValidKey(true)
})
it('1 | clicking an open submit loads its diffs with a single postdata request', () => {
// Count every STP request the app makes during the test.
// The STP URL is `stp/execute/?_program=...` - glob segments don't match
// past `execute/` with a single `*`, so keep the glob within the path.
cy.intercept('POST', '**/SASjsApi/stp/**').as('stpExecute')
cy.visit(`${hostUrl}${appLocation}/#/review/submitted`)
cy.get('.app-loading', { timeout: longerCommandTimeout }).should(
'not.exist'
)
// the SUBMIT queue list must be rendered with at least one row
cy.get('app-submitter clr-datagrid clr-dg-row', {
timeout: longerCommandTimeout
})
.should('exist')
.and('have.length.greaterThan', 0)
// route-based flow: clicking a row navigates to /review/submitted/:tableId
// and triggers exactly one SHOW_DIFFS (auditors/postdata) request
cy.get('app-submitter clr-datagrid clr-dg-row')
.first()
.click()
.then(() => {
cy.url().should('include', '/review/submitted/')
})
// wait for the diff table to render (the response was applied)
cy.get('app-approve-details .card', { timeout: longerCommandTimeout })
.should('exist')
.should('be.visible')
// give any (incorrect) duplicate request time to surface
cy.wait(3000)
cy.get('@stpExecute.all').then((allRequests: any) => {
// the adapter posts multipart form data, so match on the _program
// query param rather than the request body
const postdataRequests = allRequests.filter((req: any) =>
JSON.stringify(req.request.query || {}).includes('auditors/postdata')
)
expect(
postdataRequests.length,
'exactly one auditors/postdata SHOW_DIFFS request'
).to.equal(1)
// the submit queue was already fetched to render the list - opening a
// row must not fetch it again
const getsubmitsRequests = allRequests.filter((req: any) =>
JSON.stringify(req.request.query || {}).includes('editors/getsubmits')
)
expect(
getsubmitsRequests.length,
'no editors/getsubmits request after opening a submit'
).to.equal(1)
})
})
})
@@ -217,7 +217,9 @@ export class ApproveDetailsComponent implements AfterViewInit, OnDestroy {
}
public calcDiff() {
if (!this.response) return
// params-only responses (e.g. already reviewed submissions) carry no diff
// tables - nothing to calculate, the details header still renders
if (!this.response || !this.response.cols) return
let news = this.response.new
let updates = this.response.updates
@@ -355,26 +357,6 @@ export class ApproveDetailsComponent implements AfterViewInit, OnDestroy {
this.submitArr.push(item)
}
}
let diffs = {
ACTION: 'SHOW_DIFFS',
TABLE: this.tableId,
DIFFTIME: new Date().toUTCString()
}
// show diffs and changes info in a same call
this.sasStoreService
.showDiffs(diffs, 'SASControlTable', 'auditors/postdata')
.then((res: AuditorsPostdataSASResponse) => {
let param = res.params[0]
this.params = param
this.response = res
this.calcDiff()
this.callChangesInfo(this.tableId)
})
.catch((err: any) => err)
.finally(() => {
this.loadingTable = true
})
}
)
if (typeof this.router.snapshot.params['tableId'] === 'undefined') {
@@ -68,6 +68,9 @@ export class SubmitterComponent implements OnInit, AfterViewInit {
}
public goToDetails(table_id: any) {
// the details URL re-creates this component - carry the queue payload
// over so it is not fetched again on mount
this.sasStoreService.handoffSubmits(this.submitData)
this.router.navigateByUrl('/review/submitted/' + table_id)
}
@@ -83,14 +86,19 @@ export class SubmitterComponent implements OnInit, AfterViewInit {
this.itemsNum = 10
try {
let res = await this.sasStoreService.getSubmitts()
// navigating from the submit list to a details URL re-creates this
// component - take the queue payload carried over by that navigation
// instead of fetching it again
let fromsas: any = this.sasStoreService.takeSubmitsHandoff()
this.remained = res.fromsas.length
if (!fromsas) {
fromsas = (await this.sasStoreService.getSubmitts()).fromsas
}
this.remained = fromsas.length
if (this.remained > 0) {
this.submitter = res.fromsas[0].SUBMITTED_BY_NM
let submitterList: SubmitterData[] = res.fromsas.map(function (
item: any
) {
this.submitter = fromsas[0].SUBMITTED_BY_NM
let submitterList: SubmitterData[] = fromsas.map(function (item: any) {
return {
tableId: item.TABLE_ID,
base: item.BASE_TABLE,
@@ -100,7 +108,7 @@ export class SubmitterComponent implements OnInit, AfterViewInit {
}
})
this.submitterList = submitterList
this.submitData = res.fromsas
this.submitData = fromsas
// Details page
if (typeof tableIdParam !== 'undefined') {
@@ -35,6 +35,13 @@ export class SasStoreService {
public setSubmit: Subject<any> = new Subject<any>()
public setSubmitList: Subject<any> = new Subject<any>()
/**
* Submit queue payload carried across the navigation from the submit
* list to a details URL - the re-created list component renders it
* without fetching the queue again.
*/
private submitsHandoff: Array<any> | null = null
constructor(
private sasService: SasService,
private helperService: HelperService,
@@ -155,6 +162,24 @@ export class SasStoreService {
.adapterResponse
}
/**
* Carries the fetched submit queue over to the details URL navigation,
* so the re-created submit list component does not fetch it again.
*/
public handoffSubmits(fromsas: Array<any> | undefined) {
this.submitsHandoff = fromsas || null
}
/**
* Returns the submit queue carried by the current navigation, if any,
* and clears it - it is only valid for the navigation that set it.
*/
public takeSubmitsHandoff(): Array<any> | null {
const fromsas = this.submitsHandoff
this.submitsHandoff = null
return fromsas
}
private libsPromise: Promise<any> | null = null
/**
+9 -3
View File
@@ -327,8 +327,7 @@ function stageSubmission(opts) {
libds,
libref,
dsn,
submitted_by_nm:
typeof _METAUSER !== 'undefined' && _METAUSER ? _METAUSER : 'sasdemo',
submitted_by_nm: getDcUser(),
submitted_on_dttm: Date.now() / 1000,
headers,
rows
@@ -502,6 +501,13 @@ function webOutObj(rows, label, meta) {
* the `_webout` variable that the SASjs Server reads.
* Mirrors mv_webout(CLOSE).
*/
// Current user for MPE_SUBMIT writes/reads - mock equivalent of %mf_getuser().
// SASjs Server predeclares _METAUSER for authenticated requests; the desktop
// mode (no auth) leaves it undefined.
function getDcUser() {
return typeof _METAUSER !== 'undefined' && _METAUSER ? _METAUSER : 'sasdemo'
}
function webOutClose() {
const now = new Date()
const pad = n => String(n).padStart(2, '0')
@@ -510,7 +516,7 @@ function webOutClose() {
'.' + String(now.getMilliseconds()).padStart(3,'0')
// Derive system vars from SASjs Server runtime variables where possible
const mfGetuser = (typeof _METAUSER !== 'undefined') ? _METAUSER : 'sasdemo'
const mfGetuser = getDcUser()
const metaperson = (typeof _METAPERSON !== 'undefined') ? _METAPERSON : 'sasdemo'
const sysprocmode = (typeof SASJSPROCESSMODE !== 'undefined') ? SASJSPROCESSMODE : 'Stored Program'
@@ -8,13 +8,17 @@ eval(fs.readFileSync(nodePath.resolve(driveRoot, "files", appLoc, "services", "d
const dcLibref = 'DC_JSLIB'
const dataDir = nodePath.resolve(driveRoot, 'files', appLoc, 'data', dcLibref)
// Load MPE_SUBMIT
// Load MPE_SUBMIT - open submits of the current user only (mirrors the
// where clause of editors/getsubmits.sas: submitted_by_nm=&mf_getuser and
// submit_status_cd='SUBMITTED')
let fromsas = []
try {
const file = nodePath.resolve(dataDir, 'mpe_submit.json')
const raw = fs.readFileSync(file, {encoding:'utf8'}).toString()
const submitData = JSON.parse(raw)
fromsas = submitData.rows.map(r => ({
fromsas = submitData.rows
.filter(r => r.SUBMIT_STATUS_CD === 'SUBMITTED' && r.SUBMITTED_BY_NM === getDcUser())
.map(r => ({
TABLE_ID: r.TABLE_ID,
BASE_TABLE: r.BASE_LIB + '.' + r.BASE_DS,
INPUT_VARS: r.INPUT_VARS,
@@ -64,7 +64,11 @@ if (dataDir) {
const mpeTablesFile = nodePath.resolve(dataDir, 'mpe_tables.json')
const mpeTablesRaw = fs.readFileSync(mpeTablesFile, {encoding:'utf8'}).toString()
const mpeTables = JSON.parse(mpeTablesRaw)
sasdatasets = mpeTables.rows.map(r => ({ LIBREF: r.libref, DSN: r.dsn }))
// Filter out any malformed rows (e.g. missing libref/dsn) so the
// sasdatasets list never contains empty objects.
sasdatasets = mpeTables.rows
.filter(r => r.libref && r.dsn)
.map(r => ({ LIBREF: r.libref, DSN: r.dsn }))
sasdatasets.sort((a, b) => a.DSN.localeCompare(b.DSN))
} catch(err) {}
}